background image

 
 
 
 
prof. Jan Bielecki 

 

 
 
 
 
 
 

Visual C++ 6.0 
Podstawy programowania 

 
 
 

1. 

Pierwsze kroki 

2. 

Ś

rodowisko Visual C++ 

3. 

Wskaźniki i odnośniki 

4. 

Przetwarzanie łańcuchów 

5. 

Posługiwanie się funkcjami 

6. 

Zarządzanie pamięcią 

7. 

Widoczność deklaracji 

8. 

Studia programowe 

 
 

Dodatki 

 

 

Priorytety operatorów 

 

Opracowywanie wyraŜeń 

 

Konwersje standardowe 

 

Operatory bitowe i warunkowe 

 

Operacje wejścia-wyjścia 

 

 

 

background image

 

Pierwsze kroki 

 
 
 
 
Program  jest  zbiorem  modułów  źródłowych.  KaŜdy  moduł  składa  się  z  deklaracji  typów,  zmiennych  funkcji
Napis  od  znaków  //  do  końca  wiersza  jest  komentarzem.  Jako  taki  nie  ma  wpływu  na  przebieg  wykonania 
programu. 
 
Dokładnie jeden moduł, nazywany głównym, zawiera deklarację funkcji main. Wykonanie programu polega na 
opracowaniu  wszystkich  jego  globalnych  deklaracji,  a  następnie  przystąpieniu  do  wykonywania  instrukcji 
zawartych  w  funkcji  main.  Zakończenie  wykonywania  programu  następuje  po  wykonaniu  w  funkcji  głównej 
instrukcji return, albo tuŜ po powrocie z funkcji exit. MoŜe to nastąpić jeszcze przed podjęciem wykonywania 
funkcji głównej. 
 

int main(void)      // deklaracja funkcji głównej 

    return 0;       // instrukcja return 

 
void exit(int);     // deklaracja funkcji exit 
 
struct Empty {      // deklaracja typu Empty 
    Empty(void) 
    { 
        exit(0);    // wywołanie funkcji exit 
    } 
}; 
 
Empty obj;          // deklaracja zmiennej 

 
 
Program  napisano  w  taki  sposób,  aby  jego  wykonanie  zakończyło  się  przed  podjęciem  wykonywania  funkcji 
głównej. 
 
 

Komunikacja z otoczeniem 

 
W  najprostszym  przypadku,  program  pobiera  dane  z  klawiatury  i  wyprowadza  je  na  monitor.  Operacje 
wprowadzania  danych  odbywają  się  za  pomocą  operatora  >>,  a  operacje  wyprowadzania  danych  za  pomocą 
operatora <<. Klawiatura jest reprezentowana przez zmienną cin, a monitor przez zmienną cout. PosłuŜenie się 
nimi wymaga uŜycia dyrektywy #include wyszczególniającej nazwę iostream.h
 
Daną  wprowadzoną  z  klawiatury  kończy  odstęp,  uzyskany  przez  naciśnięcie  klawisza  Space,  Tab  albo  Enter
Analiza  danych  wejściowych  następuje  wierszami,  to  jest  dopiero  po  naciśnięciu  klawisza  Enter.  W 
szczególności,  jeśli  program  oczekuje  3  danych,  to  kaŜdą  moŜna  podać  w  osobnym  wierszu,  albo  wszystkie 
podać w jednym wierszu. Przed wprowadzeniem kolejnej danej pomija się poprzedzające ją odstępy. 
 
Uwaga: Wygodnym sposobem wyprowadzenia odstępu Enter jest uŜycie symbolu endl, a wygodnym sposobem 
wyprowadzenia znaku o kodzie 0 jest uŜycie symbolu ends
 
PoniewaŜ operacja wejścia-wyjścia dostarcza w miejscu jej uŜycia jej lewy argument, więc zapis pary instrukcji 
 
cin >> one; 
cin >> two; 
 
moŜna uprościć do 
 

background image

 

cin >> one >> two; 
 
Właściwość tę, nazywaną łączeniem operacji, moŜna zastosować takŜe do wyprowadzania danych. 
 
 

#include <iostream.h> 
 
int main(void) 

    int one, two; 
    cout << "Enter 2 numbers:" << endl; 
    cin >> one >> two; 
    cout << "Sum = " << one + two << endl; 
 
    return 0; 

 
Program wyprowadza zachętę do wprowadzenia 2 liczb, a następnie wyznacza i wyprowadza ich sumę
 
 

Wykonywanie operacji 

 
Wykonanie programu sprowadza się do wykonania operacji na danych. W Dodatku A zamieszczono kompletny 
wykaz operacji, a w Dodatku B omówiono zasady opracowywania wyraŜeń. Podane tam opisy stanowią istotny 
element niniejszego opracowania. 
 
 

Operacje przypisania 

 
Prosta operacja przypisania ma postać  
 
 

a = b 

 
w której a i b są wyraŜeniami, ale ponadto a jest l-nazwą zmiennej (por. Dodatek B). 
 
Wykonanie operacji polega na przypisaniu zmiennej a wartości wyraŜenia b
 
 
ZłoŜona operacja przypisania ma postać 
 

 

a 

@

b  

 
w której @= jest jednym z operatorów wymienionych w Dodatku A (np.  +=-=*= , /=). 
 
Operację a @= b (np. a += b) wykonuje tak, jak operację 
 

 

a = a + b 

 
ale wymaga się, aby opracowanie a i b było jednokrotne
 
 

Operacja połączenia 

 
Operacja połączenia ma postać 
 

 

a , b 

 
Jej wykonanie składa się z opracowania wyraŜenia a (wyłącznie dla jego skutków ubocznych) oraz z niejawnego 
zastąpienia całej operacji nazwą zmiennej reprezentowanej przez wyraŜenie b

background image

 

 
Uwaga: Nie jest operatorem połączenia przecinek oddzielający parametry i argumenty funkcji. 
 
W szczególności wykonanie instrukcji 
 

return a = 10, cout <<  a, b = 20; 

 
jest równowaŜne wykonaniu instrukcji 
 

a = 10; 
cout << a; 
b = 20; 
return b; 

 
 

Operacje arytmetyczne 

 
Operacje arytmetyczne wykonuje się za pomocą operatorów wymienionych w tabeli Operacje arytmetyczne
 
Tabela Operacje arytmetyczne 

### 

 

+ 

(dodawanie)   

- 

(odejmowanie

 

 

* 

(mnoŜenie

/ 

(dzielenie

(reszta

 
 

++  (zwiększenie o 1

-- 

(zmniejszenie o 1

 
 

+=  (dodanie

-= 

(odjęcie

 

*=  (pomnoŜenie

/= 

(podzielenie

### 

 
Sposób  wykonania  podstawowych  działań  arytmetycznych  nie  wymaga  opisu.  NaleŜy  jedynie  zauwaŜyć,  Ŝe 
rezultat  dzielenia  całkowitego  jest  całkowity,  a argumenty wyznaczania reszty muszą być całkowite (np. 11 / 4 
ma wartość 2, a 11 % 4 ma wartość 3). 
 
 

Operacje przedrostkowe 

 
Wykonanie operacji ++num powoduje zwiększenie wartości zmiennej num 1. W miejsce wykonania operacji 
jest dostarczana nowa wartość num
 
Wykonanie operacji --num powoduje zmniejszenie wartości zmiennej num o 1. W miejsce wykonania operacji 
jest dostarczana nowa wartość num
 

int fix = 10; 
cout << ++fix;    // 11 
cout << fix;      // 11 

 
 

Operacje przyrostkowe 

 
Wykonanie operacji num++ powoduje zwiększenie wartości zmiennej num o 1. W miejsce wykonania operacji 
jest dostarczana pierwotna wartość num
 
Wykonanie  operacji  num--  powoduje  zmniejszenie wartości zmiennej num o 1 W miejsce wykonania operacji 
jest dostarczana pierwotną wartość num
 

int fix = 10; 
cout << fix--;    // 10 
cout << fix;      // 9 

 

background image

 

 

 

Operacje porównania 

 
Operacje porównania wykonuje się za pomocą operatorów wymienionych w tabeli Operacje porównania
 
Tabela Operacje porównania 

### 

 

== 

(równe

!= 

(nie równe), 

 

< 

(mniejsze

> 

(większe), 

 

<= 

(mniejsze lub równe

>= 

(większe lub równe

### 

 
Jeśli porównanie wyraŜa orzeczenie prawdziwe, to jego rezultat ma wartość true (prawda). W przeciwnym razie 
ma wartość false (fałsz). 
 
Uwaga: Porównanie na równość wykonuje się za pomocą operacji ==, a nie za pomocą operacji =. Zaniedbanie 
tego faktu jest źródłem trudnych do wykrycia błędów semantycznych. 
 

#include <iostream.h> 
 
int main(void) 
{     
    int num = 0; 
    while(num == 0) 
        cin >> num; 
    cout << num << endl; 
 
    return 0; 

 
Program  wyprowadza  liczbę  0  albo  pierwszą  niezerową  liczbę  wprowadzoną  z  klawiatury.  Gdyby  operator 
porównania zast
ąpiono operatorem przypisania, to zawsze wyprowadzałby liczbę 0
 
 

Operacje orzecznikowe 

 
Operacje orzecznikowe wykonuje się za pomocą operatorów wymienionych w tabeli Operacje orzecznikowe
 
Tabela Operacje orzecznikowe 

### 

 

!

  (zaprzeczenie

&&

  (koniunkcja

||

  (dysjunkcja

 
### 

 
Argumenty i rezultaty operacji orzecznikowych są typu bool i mają wartości true albo false
 
Rezultat zaprzeczenia ma wartość true tylko wówczas, gdy argument ma wartość false
 
Rezultat koniunkcji ma wartość true tylko wówczas, gdy oba argumenty mają wartość true
 
Rezultat dysjunkcji ma wartość false tylko wówczas, gdy oba argumenty mają wartość false
 
Uwaga:  Operacja koniunkcji i dysjunkcji jest wykonywana w taki sposób, Ŝe jeśli po opracowaniu pierwszego 
argumentu  jest  znana  wartość  rezultatu całej operacji (bo dla koniunkcji ma wartość false, a dla dysjunkcji ma 
wartość true), to rezygnuje się z opracowania drugiego argumentu. 
 

#include <iostream.h> 
 
int vec[] = { 10, 20, 30, 40, 50 }; 
 
int main(void)  

background image

 

 
    int pos; 
    cin >> pos; 
 

pos >= 0 && pos < 5 && (cout << vec[pos]); 

 
    return 0; 

 
Program wyprowadza wartość tego elementu tablicy, którego indeks wprowadzono z klawiatury.  
 
Je
śli wprowadzi się indeks, który nie ma wartości z przedziału [0 ; 4], to program nie wyprowadzi nic. 
 
Dzi
ęki uŜyciu operatora &&, nigdy nie dojdzie do opracowania wyraŜenia vec[pos] z niedozwolonym indeksem. 
 
 

Operacje konwersji 

 
Wykonanie konwersji ma na celu przekształcenie zmiennej pewnego typu w zmienną typu docelowego.  
 
Operacja konwersji wyraŜenia e do typu Type ma postać 
 

 

(Type)e 

 
Jeśli  nazwę  typu  docelowego  Type  moŜna  wyrazić  za  pomocą  identyfikatora  (np. int),  to  operację  konwersji 
moŜna zapisać jako 
 

 

Type(e)         

 
 
W szczególności, jeśli w programie występuje instrukcja 
 

int num = 4.8; 

 
w której zmienna num jest typu int, a wyraŜenie 4.8 jest typu double, to poniewaŜ danej typu double (zazwyczaj 
8-bajtowej)  nie  moŜna  pomieścić  w  zmiennej  typu  int  (zazwyczaj  4-bajtowej),  więc  najprościej  byłoby  taką 
instrukcje uznać za błędną. 
 
PoniewaŜ  w  C++  przekształcenie  zmiennej  typu  double  w  zmienną  typu  int  zdefiniowano  jako  konwersję 
standardową  (polega  ona  na  odrzuceniu  części  ułamkowej),  więc  rozpatrywana  instrukcja  zostanie  niejawnie 
zmieniona w poprawną instrukcję 
 

int num = int(4.8); 

 
równowaŜną  
 

int num = 4; 

 
w której wyraŜenie inicjujące jest juŜ typu int
 
Uwaga: WaŜne informacje na temat konwersji zamieszczono w Dodatku C
 
 

Operacje warunkowe 

 
Operacje warunkowe wykonuje się za pomocą trójargumentowego operatora ?: (pytajnikdwukropek). 
 
Rezultatem operacji 
 

 

e ? eT : eL 

 

background image

 

jest  zmienna  o  wartości  eT    jeśli  orzeczenie  wyraŜone  przez  e  jest  prawdziwe,  albo  zmienna  o  wartości  eF  w 
przeciwnym razie..  
 
Uwaga: Po opracowaniu wyraŜenia e, opracowuje się tylko jedno z wyraŜeń eT i eF
 

num =  fix > 0 ? fix1 : fix2;   
num < 0 ? fix1 : fix2 = 30;    

 
 

Operatory :: i Name:: 

 
Jeśli  id  jest  identyfikatorem,  to  ::id  jest  nazwą  globalną,  a  Name::id  jest  nazwą  składnika  typu  strukturowego 
Name
 

int num = 0; 
 
struct Fix { 
    int num;                   
    void set(int num =::num)   
    { 
        Fix::num = num; 
    } 
}; 

 
Napis ::num jest nazwą zmiennej globalnej, a napis Fix::num jest nazwą składnika num
 
Prawy argument przypisania Fix::num = num jest nazw
ą parametru. 
 
 

Wykonywanie instrukcji 

 
Do  napisania  dowolnego  programu  wystarczy  zaledwie  kilka  instrukcji.  NajwaŜniejszymi  z  nich  są:  instrukcja 
pusta,  grupująca,  warunkowa  (if),  iteracyjna  (while)  i  powrotu  (return).  Opis  pozostałych  ograniczono  do 
przykładów. 
 
 

Instrukcja pusta 

 
Instrukcja pusta składa się ze średnika.  
 

 

 
Jej wykonanie nie ma Ŝadnych skutków.  
 
 

Instrukcja grupująca 

 
Instrukcja grupująca składa się z nawiasów klamrowych zawierających dowolną sekwencję instrukcji.  
 
Jeśli w miejscu, w którym składnia wymaga uŜycia dokładnie jednej instrukcji, chce się umieścić ich więcej, to 
wystarczy ująć je w nawiasy klamrowe i powstanie jedna instrukcja. 
 

{ int a; cin >> a; a++; cout << a; } 

 
 

Instrukcja warunkowa 

 
Instrukcja warunkowa ma postać 

background image

 

 

 

if(c

 

    s   

albo 
 

 

if(c

 

    s1 

 

else 

 

    s2 

 
w  której  c  jest  wyraŜeniem  orzecznikowym  o  wartości  true  albo  false,  a  s1  oraz  s2  jest  pojedynczą  instrukcją 
(np. instrukcją grupującą). 
 
Wykonanie instrukcji warunkowej zaczyna się od opracowania wyraŜenia c (np. a > 2). Jeśli wyraŜone przez nie 
orzeczenie jest prawdziwe, to w pierwszym przypadku jest wykonywana instrukcja s, a w drugim instrukcja s1
W przeciwnym razie, w pierwszym przypadku nie robi się nic, a w drugim wykonuje instrukcję s2
 

if(a > 2)  
    { a++; cout << a; } 
else 
    { cout << a; a-- } 

 
albo równowaŜnie 
 

if(a > 2) { 
    a++; 
    cout << a; 
} else { 
    cout << a; 
    a--; 

 
Jeśli  podczas  opracowywania  instrukcji  warunkowej  napotka  się  słowo  kluczowe  else,  to  przyjmuje  się,  Ŝe 
dotyczy ono najbliŜszego z lewej słowa if, nie połączonego jeszcze z else
 
W szczególności instrukcja 
 

if(fix1 > fix2) if(fix1) fix1++; else fix2++; 

 
jest wykonywana jak instrukcja 
 

if(fix1 > fix2) { 
    if(fix1)  
        fix1++;  
    else fix2++; 

 
a nie jak instrukcja 
 

if(fix1 > fix2) { 
    if(fix1)  
        fix1++;  
} else  
    fix2++; 

 
 

Instrukcje iteracyjne 

 
Instrukcja iteracyjna while ma postać 
 

 

while(c

 

    s 

 

background image

 

w której c jest wyraŜeniem orzecznikowym, a jest pojedynczą instrukcją. 
 
Wykonanie instrukcji iteracyjnej while polega na cyklicznym badaniu orzeczenia wyraŜonego przez wyraŜenie c 
i  wykonywaniu instrukcji s
 
Iteracja kończy się w chwili stwierdzenia, Ŝe orzeczenie jest nieprawdziwe. Jeśli okaŜe się to juŜ na wstępie, to 
instrukcja  s nie będzie wykonana wcale
 

int i = 3; 
while(i > 0) { 
    int t = i * i; 
    cout << t << endl;    //  9 4 1 
    i--; 

 
Często uŜywa się instrukcji iteracyjnej for 
 

 

for(d c ; e) { 

 

    s s ... s 

 

 
w której d jest instrukcją deklaracyjną, a c i e są wyraŜeniami. 
 
Tak zapisana instrukcja for jest równowaŜna instrukcji 
 

 

d 

 

while(c) { 

 

    s s ... s 

 

    e

 

 

 

Instrukcja for dobrze opisuje czynności o znanej liczbie powtórzeń. 
 

int tab[5] = { 10, 20, 30, 40, 50 }, sum = 0; 
for(int i = 0; i < 5 ; i++) 
    sum += tab[i]; 
cout << "Sum = " << sum << endl; 

 
 

Instrukcja zaniechania 

 
Instrukcja zaniechania ma postać 
 

 

 

break; 

 
Wykonanie  instrukcji  zaniechania  powoduje  zakończenie  wykonywania  najwęŜszej  obejmującej  ją  instrukcji 
iteracyjnej albo decyzyjnej. 
 

int sum = 0; 
while(true) { 
    int tmp = 0; 
    cin >> tmp;         // wprowad

ź

 dan

ą

 

    if(tmp == 0)        // zbadaj czy 0 
        break;          // zako

ń

cz iteracj

ę

 

    sum += tmp;         // dosumuj 

cout << "Sum = " << sum << endl; 

 
albo 
 

int tmp = 0, sum = 0; 
while(cin >> tmp, tmp)  // wprowad

ź

 i zbadaj 

    sum += tmp;         // dosumuj           
cout << "Sum = " << sum << endl; 

background image

 

 
lub 
 

for(int tmp = 0, sum = 0; cin >> tmp, tmp ; sum += tmp); 
cout << "Sum = " << sum << endl; 

 
 

Instrukcja powrotu 

 
Instrukcja powrotu ma postać 
 

 

return e

 
w której e jest wyraŜeniem. 
 
Wykonanie  instrukcji  powrotu  powoduje  zakończenie  wykonywania  funkcji  i  dostarczenie  rezultatu  o  wartości 
określonej przez e.  
 

int sum(int one, int two) 

    return one + two; 

 
Jeśli  typem  funkcji  jest  void,  to  uŜyta  w  niej  instrukcja  powrotu  nie  moŜe  zawierać  wyraŜenia.  UŜycie  takiej 
instrukcji jest zazwyczaj zbyteczne, poniewaŜ domniemywa się ją tuŜ przed klamrą zamykajacą ciało funkcji. 
 

void sum(int one, int two) 

    cout << one + two << endl; 
 
    return;      // zb

ę

dne 

 
 

Instrukcja decyzyjna 

 
Instrukcja  decyzyjna  uogólnia  instrukcję  warunkową  i  jest  przydatna  wówczas,  gdy  w  programie  występują 
więcej niŜ dwie gałęzie decyzyjne. W szczególności instrukcję warunkową 
 

if(a == 2) 
    b = 3; 
else if(a == 1) 
    b = 5; 
else if(a == 4) 
    b = -1; 
else 
    b = 0; 

 
moŜna zapisać w postaci 
 

switch(a) { 
    case 2:         // je

ś

li a == 2 

        b = 3; 
        break; 
    case 1:         // je

ś

li a == 1 

        b = 5; 
        break; 
    case 4:         // je

ś

li a == 4 

        b = -1; 
        break; 
    default:        // w pozostałych przypadkach 
        b = 0; 

 

background image

 

10 

 

Deklarowanie zmiennych i typów 

 
KaŜdy moduł programu jest kompilowany niezaleŜnie od pozostałych. Analiza składniowa modułu odbywa się 
od-góry-do-dołu  i  od-lewej-do-prawej  i  polega  na  rozpoznawaniu  jednostek  leksykalnych:  identyfikatorów 
(np. exit), literałów (np. 0), operatorów (np. +) i ograniczników (np. ;). 
 
 

Identyfikatory  

 
Identyfikatorem jest spójna sekwencja liter i cyfr, zaczynająca się od litery. Identyfikator nie moŜe mieć postaci 
słowa kluczowego (np. return). Za jego literę uznaje się równieŜ znak podkreślenia (_). 
 
Litery  małe  uznaje  się  za  róŜne  od  duŜych.  Zaleca  się,  aby  w  wielosłowowych  nazwach  zmiennych  i  funkcji, 
wszystkie słowa, z wyjątkiem pierwszego, były zapisane za pomocą duŜych liter. 
 
np. 

 

forSale  speedLimit  veryLongName 

 
 

Literały 

 
Literałami  są  liczby  (np. 12,  0xff  i  2.e-3),  znaki  (np. 'a')  i  łańcuchy  (np.  "Hello").  KaŜdy  literał  jest  nazwą 
zmiennej ustalonej. Jej typ wynika z zapisu literału. 
 
Uwaga: Jeśli łańcuch ma zawierać znak \ (ukośnik), to naleŜy go zapisać jako \\ (np. "C:\\Data.txt). 
 
np. 

 

'a' 

'\n' 

'\0'    // nazwy zmiennych typu char 

 

12 

-12 

  // nazwy zmiennych typu int 

 

-2.4 

2.e4 

.2     // nazwy zmiennych typu double 

 

"a" 

"N" 

"\n"   // nazwy zmiennych typu char [2] 

 
 

Deklaracje 

 
KaŜde uŜycie identyfikatora musi być poprzedzone jego deklaracją. Deklaracja kompletnie opisująca zmienną 
(określająca  jej  wartość  początkową),  typ  (wyszczególniająca  strukturę  jego  obiektów)  i  funkcję  (podająca  jej 
ciało) jest nazywana definicją.  
 
W skład deklaracji wchodzą specyfikatorydeklaratory inicjatory
 
np. 

 

const int tab[3] = { -1, 0, +1 }; 

 
Specyfikatorami są const int, deklaratorem jest tab[3], a inicjatorem jest = { -1, 0, +1 }
 
 

Nagłówki 

 
Deklaracje  typów  i  funkcji  są  ujmowane  w  nagłówki.  KaŜdy  nagłówek  jest  zapisany  w  odrębnym  pliku. 
Włączenie nagłówka odbywa się w miejscu wystąpienia wyszczególniającej go dyrektywy #include
 
Do  najczęściej  uŜywanych  nagłówków  naleŜą:  iostream.h  i  iomanip.h,  math.h,  string.h  i  stdlib.h.  Dwa 
pierwsze  włączają  do  modułu  deklaracje  zmiennych  i  operatorów  wejścia-wyjścia  (cin,  cout,  >>,  <<),  dwa 
następne  włączają  deklaracje  funkcji  matematycznych  (sqrt,  sin,  cos)  i  łańcuchowych  (strlen,  strcpy,  strcat
strcmp), a ostatni włącza m.in. deklarację funkcji exit
 

background image

 

11 

#include <iostream.h> 
#include <math.h> 
 
int main(void) 

    double number;          // deklaracja zmiennej 
    cin >> number;          // wprowadzenie liczby 
    cout << sqrt(number);   // wyprowadzenie pierwiastka 
 
    return 0;               // zako

ń

czenie wykonywania 

 
 

Zmienne 

 
Zmienną  jest  obszar  pamięci  do  przechowywania  danych  określonego  typu:  skalarnych,  tablicowych  
strukturowych. KaŜde odwołanie do zmiennej musi być poprzedzone deklaracją jej typu. 
 

int fix;           // zmienna całkowita 
char chr;          // zmienna znakowa 
double num;        // zmienna rzeczywista 

 
Zmienna fix jest typu int, zmienna chr jest typu char, zmienna num jest typu double
 
 

Rozmiar zmiennej 

 
Rozmiar  zmiennej  w  bajtach  określa  się  za  pomocą  operatora  sizeof.  Argumentem  operatora  sizeof  moŜe  być 
nazwa zmiennej albo nazwa typu. 
 
Uwaga: Rozmiar zmiennej zaleŜy od implementacji. W Visual C++ zmienne typu char są 1-bajtowe, zmienne 
typu int są 2-bajtowe, a zmienne typu double są 8-bajtowe. 
 

int age = 24;         
cout << sizeof(age);    // 4 
cout << sizeof(int);    // 4 
int tab[3];              
cout << sizeof(tab);    // 12 

 
 

Zmienne ustalone 

 
Zmienna  zadeklarowana  ze  specyfikatorem  const  jest  zmienną  ustaloną.  Zmienna  ustalona  musi  być 
zainicjowana, ale nadana jej wartość nie moŜe ulec zmianie. 
 
Uwaga:  Zmiennymi  ustalonymi  są  takŜe  zmienne  reprezentowane  przez  literały.  W  szczególności  liczba  12e2 
jest nazwą zmiennej ustalonej o wartości 1200
 

const int size = 100; 
const double width = -2e-7, height = 2e2; 
const int tab[2] = { 10, 20 }; 

 
 

Zmienne skalarne 

 
Deklaracja zmiennej skalarnej określa jej identyfikator oraz wyszczególnia typ danych jakie moŜna przypisywać 
zmiennej (np. intdoublechar). 
 

int number; 
double speedLimit; 
char separator; 

 

background image

 

12 

Wartość  początkową  zmiennej  określa  się  za  pomocą  inicjatora.  Jeśli  deklaracja  zmiennej  zawiera  jawny  albo 
domniemany inicjator, to jest jej definicją. 
 

int minValue = 10, maxValue = 90; 
double width = 2.4, height = 4.5e+2, area; 
char lastChar = '.'; 

 
 

Składnia inicjatora 

 
Inicjatory  dzielą  się  na  wyraŜeniowe,  klamrowe  i  nawiasowe.  Inicjator  zmiennej  ustalonej  musi  mieć  postać 
wyraŜenia  stałego.  W  jego  skład  wchodzą  odwołania  do  literałów  i  zmiennych  ustalonych,  ale  nie  mogą 
wchodzić odwołania do zmiennych nie-ustalonych. 
 

int base = 100;             // inicjator wyra

Ŝ

eniowy 

int min =  { base + 20 };   // inicjator klamrowy 
int max(base + 40);         // inicjator nawiasowy 
 
const int size = max - min; // bł

ą

 
 

Punkt zadeklarowania 

 
Identyfikator  zmiennej  uwaŜa  się  za  zadeklarowany  w  punkcie  tuŜ  przed  inicjatorem  wyraŜeniowym  i 
klamrowym, ale tuŜ po inicjatorze nawiasowym. Ta subtelna róŜnica ma niekiedy wpływ na poprawność i skutek 
wykonania programu. 
 

#include <iostream.h> 
 
const int val = 10;  // definicja zmiennej globalnej 
 
int main(void) 

    int val(val);    // definicja zmiennej lokalnej 
    cout << val;     // 10 
 
    return 0; 

 
Punkt  zadeklarowania  zmiennej  lokalnej  występuje  tuŜ  po  inicjatorze  (val).  Gdyby  inicjator  nawiasowy 
zast
ąpiono  jednym  z  pozostałych  inicjatorów,  to  program  stałby  się  błędny,  poniewaŜ  zmienna  lokalna  byłaby 
wówczas inicjowana nie warto
ścią zmiennej globalnej, ale nieokreśloną jeszcze wartością zmiennej lokalnej. 
 
 

Operacje wejścia-wyjścia 

 
Zmienne  typu  int,  double  i  char  są  zmiennymi  arytmetycznymi,  przystosowanymi  odpowiednio  do 
przechowywania liczb całkowitych, zmiennopozycyjnych i kodów znaków.  
 
Podczas  wykonywania  operacji  wejścia,  do  zmiennych  typu  int  i  double  wprowadza  się  dane  liczbowe,  a  do 
zmiennych typu char wprowadza się kody znaków. A zatem, jeśli z klawiatury wprowadzi się na przykład napis 
20e3,  to  liczba  pobranych  znaków  i  otrzymana  wartość  będzie  zaleŜeć  od  typu  zmiennej,  zgodnie  z  tabelą 
Wprowadzanie danych
 
Tabela Wprowadzanie danych 
 
 

Typ zmiennej 

Pobrano znaków 

Wprowadzono wartość   

 
 

int 

20 

 

double 

20000 

 

char 

49 

 

background image

 

13 

Podczas  wykonywania  operacji  wyjścia,  wyprowadza  się  liczby  o  wartości  zmiennych  typu  int  i  double  oraz 
znaki o kodach określonych przez wartości zmiennych typu char
 

#include <iostream.h> 
 
int main(void) 

    int mant, exp; 
    char sep; 
    cin >> mant >> sep >> exp; 
    int value = mant; 
    while(exp > 0) { 
        value = value * 10; 
        exp--; 
    } 
    cout << mant << sep << exp << 
         " == " << value << endl; 
 
    return 0; 

 
Jeśli wprowadzi się napis 2e3, to program wyprowadzi ten napis oraz liczbę 2000
 
 

Zmienne tablicowe 

 
Zmienną tablicową, w skrócie tablicą, jest zestaw sąsiadujących ze sobą elementów tablicy. KaŜdy element jest 
zmienną takiego samego typu: skalarną, tablicową, strukturową. 
 

int tab[20]; 

 
Zmienna tab jest tablicą o 20-elementach typu int
 
Z kaŜdym elementem tablicy jest związany indeks, określający połoŜenie elementu w obrębie tablicy. Elementy 
tablicy  są  indeksowane  od  0.  W  deklaracji  tablicy  podaje  się  liczbę  jej  elementów,  a  nie  indeks  jej  ostatniego 
elementu. Jeśli deklarator nie podaje liczby elementów, ale deklaracja zawiera inicjator, to za liczbę elementów 
uznaje się liczbę fraz inicjujących. 
 
Uwaga: Liczba fraz inicjujących nie moŜe przekraczać liczby elementów tablicy. Jesli jest od niej mniejsza, to 
jest niejawnie dopełniana frazami 0
 

int tab[100] = { 4, 4 }; 

 
Zerowy i pierwszy element tablicy tab ma wartość 4. Wszystkie pozostałe mają wartość 0
 
 
Liczba  elementów  tablicy  musi  być  wyraŜona  za  pomocą  wyraŜenia  stałego.  WyraŜenie  stałe  moŜe  zawierać 
literały i identyfikatory zmiennych ustalonych, ale nie moŜe zawierać operatora połączenia. 
 

const int Count = 3; 
double sizes[Count] = { 2.4, 3.8, 5.2 }; 
int values[] = { 10, 20, 30, 40, 50 }; 
int Size = 4; 
double reals[Size];      // bł

ą

 
Tablica sizes składa się z 3 zmiennych, z których kaŜda jest typu double
 
Tablica values składa si
ę z 5 zmiennych, z których kaŜda jest typu int.  
 
 

Identyfikowanie elementów tablicy 

 

background image

 

14 

Jeśli nazwą tablicy jest vec, to nazwą jej elementu o indeksie ind jest vec[ind]. Jest to prawdziwe tylko wówczas, 
gdy wyraŜenie ind ma wartość większą-lub-równą 0 i jednocześnie mniejszą od liczby elementów tablicy. 
 
Uwaga: Jeśli tablica vec ma n elementów, to zezwala się, aby wyraŜenie ind miało wartość -1 oraz n, ale tylko 
wówczas,  gdy  opracowanie  wyraŜenia  vec[ind]  nie  ma  na  celu  dokonania  zmiany  albo  dostarczenia  wartości 
elementu. 
 

#include <iostream.h> 
 
int values[5] = { 10, 20, 30, 40, 50 }; 
 
int main(void) 

    int index; 
    cin >> index; 
    if(index >= 0 && index < 5) 
        cout << values[index] << endl; 
    else 
        cout << "Wrong index" << endl; 
 
    return 0; 

 
Program  wyprowadza  wartość  elementu  o  podanym  indeksie.  Jeśli  indeks  nie  mieści  się  w  domkniętym 
przedziale [0 ; 4], to program wyprowadza napis Wrong index
 
 

Tablice znakowe 

 
Tablicą znakową jest tablica o elementach typu char. Przechowuje się w niej zazwyczaj małe liczby oraz kody 
znaków.  
 
PoniewaŜ Visual C++ uŜywa kodu ASCII, w którym kodem cyfry jest 48, więc zainicjowanie 4-elementowej 
tablicy znakowej kodami cyfr 02 oraz kodem znaku '\0' moŜna wykonać na wiele sposobów, w tym m.in. 
 

char digits[] = { '0', '1', '2', '\0' }; 
char digits[] = { 48, 49, 50, 0 }; 
char digits[4] = { '0', '0'+1, '3'-1 }; 
char digits[4] = "012"; 

 
Z klawiatury moŜna wprowadzać tylko spójne ciągi znaków. Za ostatnim wprowadzonym znakiem umieszcza się 
wówczas specjalny znak o kodzie 0.   
 
Jeśli  chce  się  wyprowadzić  ciąg  znaków  utworzony  w  tablicy  programowo,  to  naleŜy  zakończyć  go  znakiem  o 
kodzie 0 (jego rozpoznanie spowoduje zakończenie wyprowadzania znaków). 
 

#include <iostream.h> 
 
char name[100]; 
 
int main(void) 

    cin >> name; 
    name[1] = 0; 
    cout << "Your initial is: " << name << endl; 
 
    return 0; 

 
Program wprowadza imię, a następnie wyprowadza jego inicjał. 
 
 

Literały łańcuchowe 

 

background image

 

15 

Literał łańcuchowy, na przykład "Hello", ma postać ciągu znaków ujętego w cudzysłowy. Znaki specjalne są w 
tym ciągu reprezentowane przez nastepujące symbole 
 
 

\\ (ukośnik

\n (nowy wiersz

\t (tabulator),  

 

\' (apostrof

\" (cudzysłów

\0 (znak o kodzie 0). 

 
KaŜdy literał łańcuchowy, jest nazwą tablicy o elementach typu char, zainicjowanych kodami kolejnych znaków 
literału oraz kodem znaku \0. W szczególności (w kodzie ASCII) literał "No" jest nazwą 3-elementowej tablicy 
zainicjowanej liczbami 78111 i 0
 

#include <iostream.h> 
 
int main(void) 

    int i = 0; 
    while("Hello"[i] != 0) { 
        cout << "Hello"[i] << ' '; 
        i++; 
    } 
    cout << endl; 
 
    return 0; 

 
Program wyprowadza kolejne znaki napisu Hello, po kaŜdym znaku dodająspację. Zakończenie wykonywania 
nast
ępuje po rozpoznaniu elementu zainicjowanego liczbą 0
 
Literały  łańcuchowe  mogą  być  uŜyte  do  inicjowania  tablic  znakowych.  Tak  zainicjowana  tablica  musi  mieć  co 
najmniej  
tyle  elementów  ile  ma  tablica  reprezentowana  przez  literał.  Jeśli  jest  dłuŜsza,  to  jej  nadmiarowe 
elementy są inicjowane liczbami 0
 

char name1[10] = { 'I', 's', 'a', 0 };  
char name2[10] = "Isa";   
char name3[]   = "Isa"; 
char name4[3]  = "Isa";        // bł

ą

 
 

Operacje wejścia-wyjścia 

 
Tablice  znakowe  mogą  być  wykorzystane  do  wprowadzania  z  klawiatury  spójnych  ciągów  znaków.  W  takim 
przypadku argumentem operacji jest zazwyczaj nazwa tablicy, a wykonanie operacji powoduje umieszczenie w 
tablicy kodów znaków łańcucha oraz kodu o wartości 0
 
PoniewaŜ moŜe wówczas dojść do przepełnienia tablicy, zaleca się uŜycie manipulatora setw, zadeklarowanego 
w nagłówku iomanip.h, ograniczającego liczbę wprowadzonych znaków. 
 
Uwaga: Manipulator setw moŜe być uŜyty takŜe podczas wyprowadzania danych. W takim wypadku określa on 
szerokość pola zewnętrznego, w którym umieszcza się dane wyjściowe. 
 

#include <iostream.h> 
#include <iomanip.h> 
 
char name[20]; 
 
int main(void) 

    cin >> setw(20) >> name; 
    name[1] = 0; 
    cout << "Your initial is: " << name << endl; 
 
    return 0; 

 

background image

 

16 

Program  wprowadza  imię,  a  następnie  wyprowadza  jego  inicjał.  Aby  zabezpieczyć  się  przed  wpisaniem  do 
tablicy name wi
ęcej niŜ 20 znaków, uŜyto manipulatora setw(20) zadeklarowanego w nagłówku iomanip.h. 
 
 

Zmienne strukturowe 

 
Zmienną  strukturową,  w  skrócie  strukturą,  jest  zestaw  sąsiadujących  ze  sobą  elementów  struktury.  KaŜdy 
element struktury moŜe być zmienną innego typu: skalarną, tablicową, strukturową. 
 
Przed zadeklarowaniem zmiennej strukturowej naleŜy zdefiniować jej typ. Deklaracja typu strukturowego składa 
się z deklaracji pól struktury. Deklaracja pola struktury ma postać deklaracji zmiennej. 
 

struct Child { 
    char name[20]; 
    int age; 
}; 
 
Child isa = { "Isabel", 15 }; 

 
Struktura  isa  składa  się  z  2  zmiennych,  opisanych  przez  pola  name  i  age.  Wartości  początkowe  elementów 
struktury okre
ślono za pomocą inicjatora klamrowego. UŜycie innych inicjatorów jest zabronione. 
 
 

Identyfikowanie elementów 

 
Jeśli  nazwą  struktury  jest  str,  a  w  opisie  jej  typu  występuje  pole  fld,  to  nazwą  zmiennej  odpowiadającej  temu 
polu jest str.fld
 

#include <iostream.h> 
#include <iomanip.h> 
 
struct Child { 
    char name[20]; 
    int age; 
}; 
 
Child child; 
 
int main(void) 

    cin >> setw(20) >> child.name >> child.age; 
    cout << child.name << " is "  
         << child.age << " now" << endl; 
 
    return 0; 

 
Zmienna  child  składa  się  z  tablicy  o elementach typu char i zmiennej skalarnej typu int. Program wprowadza 
imi
ę i wiek dziecka, a następnie wyprowadza je.  
 
 

Kopiowanie struktur 

 
W odróŜnieniu od tablic, które moŜna kopiować tylko element-po-elemencie, kopiowanie struktur moŜe dotyczyć 
pełnego zestawu jej elementów i to nawet wówczas, gdy struktura zawiera tablice. 
 

#include <iostream.h> 
 
struct Child { 
    char name[20]; 
    int age; 
}; 
 
Child girl; 

background image

 

17 

 
int main(void) 

    Child isa = { "Isabel", 15 }; 
    girl = isa; 
    cout << girl.name << " is " << girl.age << endl; 
 
    return 0; 

 
Program wyprowadza te same dane, którymi zainicjowano strukturę isa
 
 

Unia elementów 

 
Struktura, której elementy są rozmieszczone w pamięci nie jeden-za-drugim, ale zawsze od tego samego miejsca, 
jest nazywana unią. W celu zadeklarowania unii naleŜy zamiast słowa kluczowego struct uŜyć słowa union
 
Definicja  unii,  w  której  pominięto  nazwę  typu,  jest  definicją  unii  anonimowej.  Pola  unii  anonimowej  są 
zadeklarowane w miejscu zdefiniowania unii. 
 

struct Number { 
    bool isFixed; 
    union {            // unia anonimowa 
        int fixed; 
        double real; 
    }; 
}; 
 
Number num = { true, 12 }; 
if(num.isFixed) 
    cout << num.fixed << endl;   // 12 
else 
    cout << num.real << endl; 
cout << num.real << endl;        // bł

ą

 
W kaŜdej chwili struktura num składa się ze zmiennych typu bool int, albo ze zmiennych typu bool double
 
ąd polega na tym, Ŝe w chwili gdy zmienna num składa się ze zmiennych typu bool i int, następuje odwołanie 
do zmiennej typu double
  
 

Przetwarzanie plików 

 
Przetwarzanie  plików  odbywa  się  za  pośrednictwem  zmiennych  strumieniowych  klas  ifstream  i  ofstream
zadeklarowanych w nagłówku fstream.h. Po utworzeniu zmiennej strumieniowej naleŜy otworzyć skojarzony z 
nią plik, a następnie upewnić się, Ŝe otwarcie było pomyślne.  
 
Po pomyślnym otwarciu pliku, pochodzący z niego strumień danych moŜna przetwarzać w taki sam sposób, jak 
strumień danych związany z klawiaturą albo z monitorem. 
 
 

Stany strumienia 

 
Początkowo  strumień  znajduje  się  w  stanie  dobrym,  ale  na  skutek  błędu  operacji  wejścia-wyjścia  albo  próby 
wprowadzenia nieistniejącej danej, moŜe znaleźć się w stanie nie-dobrym (fail).  
 
W  stanie  nie-dobrym  wszystkie  operacje  na  strumieniu  są  ignorowane.  Jeśli  dane  przygotowano  właściwie,  a 
jakość pamięci zewnętrznej jest zadowalająca, to stan nie-dobry oznacza, Ŝe napotkano koniec strumienia.  
 

background image

 

18 

Uwaga:  W  programach  przykładowych  nie  będzie  rozpatrywany  przypadek  wystąpienia  błędu  przesyłania 
danych
.  
 
Szczególnym przypadkiem stanu nie-dobrego jest stan zły (bad). Powstaje on w przypadku rozpoznania danych o 
złym formacie. Niestety, na skutek niefortunnych domniemań, wprowadzenie takiej "danej" jak 3e, zamiast 3e0 
(w kontekście 3ex) nie zmienia stanu strumienia na zły. 
 
Uwaga: Do sprawdzenia czy stan strumienia jest zły, słuŜy funkcja bad, a do sprawdzenia, czy strumień znajduje 
się w pozycji za końcem pliku, słuŜy funkcja eof. Funkcji tych uŜywa się bardzo rzadko. 
 
 

Zmienna plikowa 

 
Jeśli  w  miejscu  wystąpienia  operacji  wejścia-wyjścia  odbywa  się  takie  badanie  zmiennej  plikowej,  jakby 
dotyczyło wyraŜenia o wartości orzecznikowej, na przykład 
 

 

while(cin >> num) ... 

albo 

 

if(cin) ... 

 
to w stanie dobrym jest dostarczana wartość true, a w stanie nie-dobrym wartość false
 
 

Wprowadzanie danych 

 
Zmienna strumieniowa uŜyta do wprowadzania danych z pliku jest typu ifstream. Otwarcie pliku odbywa się za 
pomocą  funkcji  open,  której  pierwszym  argumentem  jest  nazwa,  a  drugim  tryb  otwarcia  pliku:  ios::in.  Jeśli 
otwierany plik nie istnieje, to zostanie utworzony jako pusty. Aby tego uniknąć, plik naleŜy otworzyć w trybie 
ios::in | ios::nocreate
 
Do  zbadania,  czy  otwarcie  pliku  się  powiodło,  słuŜy  funkcja  is_open.  Jej  rezultat  ma  wartość  nie-zero  tylko 
wówczas, gdy otwarcie było pomyślne. 
 

#include <iostream.h> 
#include <fstream.h> 
#include <assert.h> 
 
int sum = 0; 
 
int main(void) 

    ifstream inp;         // zmienna plikowa 
    inp.open("Data.txt", ios::in | ios::nocreate); 
    if(!inp.is_open()) { 
        cout << "File does not exist" << endl; 
        return -1; 
    } 
    int val; 
    while(inp >> val)     // wprowad

ź

 i sprawd

ź

 stan 

        sum += val;       // dosumuj 
    assert(!inp.bad());   // raczej zb

ę

dne 

    cout << "Sum = " << sum << endl; 
 
    return 0; 

 
Wykonanie programu powoduje wyprowadzenie sumy liczb całkowitych zawartych w pliku Data.txt. 
 
Wywołanie funkcji assert ma na celu upewnienie si
ęŜe strumień nie znajduje się w złym stanie. Gdyby tak było, 
to wykonanie programu zostałoby zaniechane. 
 
 

background image

 

19 

Wyprowadzanie danych 

 
Zmienna strumieniowa uŜyta do wyprowadzania danych do pliku jest typu ofstream. Otwarcie pliku odbywa się 
za pomocą funkcji open, której pierwszym argumentem jest nazwa, a drugim tryb otwarcia pliku: ios::out.  
 
Jeśli  otwierany  plik  nie  istnieje,  to  zostanie  utworzony  i  otworzony  jako  pusty.  Jeśli  juŜ  istnieje,  to  zostanie 
otworzony jako pusty. 
 
Do  zbadania,  czy  otwarcie  pliku  się  powiodło,  słuŜy  funkcja  is_open.  Jej  rezultat  ma  wartość  nie-zero  tylko 
wówczas, gdy otwarcie było pomyślne. 
 
 

#include <iostream.h> 
#include <fstream.h> 
 
int main(void) 

    ifstream inp; 
    inp.open("Data.txt", ios::in | ios::nocreate); 
    if(!inp.is_open()) { 
        cout << "Source does not exist" << endl; 
        return -1; 
    } 
 
    ofstream out; 
    out.open("Data2.txt", ios::out); 
    if(!out.is_open()) { 
        cout << "Target not opened" << endl; 
        return -1; 
    } 
 
    int val; 
    while(inp >> val) 
        out << val << endl; 
    cout << "Done!" << endl; 
 
    return 0; 

 
Program  kopiuje  liczby  całkowite  z  pliku  Data.txt  do  pliku  Data2.txt.  KaŜdą  kopiowaną  liczbę  umieszcza  w 
nowym wierszu.
 
 
 

UŜycie klawiatury 

 
Jeśli  dane  wprowadza  się  z  klawiatury,  to  koniec  strumienia  określa  się  za  pomocą  znaku  końca:  Ctrl-Z  (na 
polskiej klawiaturze Ctrl-Y). W Visual C++ nastąpi wówczas pominięcie pierwszego znaku wyprowadzonego na 
konsolę. 
 
Uwaga: Zaleca się, aby znak końca został wprowadzony na początku nowego wiersza (po Enter). 
 

#include <iostream.h> 
 
int main(void) 
{     
    int count = 0; 
    double tmp; 
    while(cin >> tmp) 
        count++; 
 
    cout << endl;     // na po

Ŝ

arcie 

    cout << "Count = " << count << endl; 
 
    return 0; 

background image

 

20 

 
Program zlicza dane liczbowe wprowadzone z klawiatury. 
 

background image

 

21 

 

Ś

rodowisko Visual C++  

 
 
 
 
Program  źródłowy  składa  się  z  modułów  źródłowych.  KaŜdy  moduł  jest  umieszczony  w  odrębnym  pliku  z 
rozszerzeniem .cpp. Dodatkowo, w skład programu mogą wchodzić moduły skompilowane (*.obj) i biblioteczne 
(*.lib). 
 
W  celu  przekształcenia  zestawu  modułów  w  program  wykonalny,  naleŜy  utworzyć  projekt,  umieścić  go  w 
przestrzeni roboczej, włączyć do projektu nazwy plików z rozszerzeniami .cpp.obj i .lib, a następnie zbudować 
program. Zostanie on umieszczony w pliku z rozszerzeniem .exe
 
 

Katalog 

 
Zaleca  się,  aby  pliki  programu  znajdowały  się  we  własnym  katalogu.  Jeśli  dysponuje  się  wolnym  miejscem  na 
przykład w katalogu głównym dysku D:, to naleŜy wywołać Eksplorator Windows, kliknąć na nazwie katalogu 
głównego  i  wydać  polecenie  Plik  /  Nowy  obiekt  /  Folder,  a  następnie  określić  nazwę  swojego  katalogu,  na 
przykład jbVisual
 
 

Przestrzeń 

 
W  celu  utworzenia  przestrzeni  roboczej  naleŜy  wydać  polecenie  File  /  New,  a  następnie  (w  zakładce 
Workspaces)  podać  nazwę  przestrzeni,  np.  Workspace:  jbSpace  oraz  określić  jej  połoŜenie,  np.  Location: 
D:\jbVisual\jbSpace, po czym nacisnąć przycisk OK
 
Jeśli  przestrzeń  juŜ  istnieje,  to  aby  ją  otworzyć,  naleŜy  wydać  polecenie  File  /  Open  Workspace,  wejść  do 
katalogu przestrzeni (np. jbSpace), a następnie dwu-kliknąć na nazwie jbSpace.dsw
 
 

Projekt 

 
W celu utworzenia projektu naleŜy wydać polecenie File / New, a w zakładce Projects podać typ projektu: Win 
32  Console  Application
  i  jego  nazwę,  np.  Project  name:  jbTests.  Po  upewnieniu  się,  Ŝe  projekt  zostanie 
włączony 

do 

bieŜącej 

przestrzeni 

(Add 

to 

current 

workspace

czym 

zaświadczy 

Location: D:\jbVisual\jbSpace\jbTests, naleŜy nacisnąć przycisk OK
 
 

Pliki 

 
W celu utworzenia plików projektu naleŜy wydać polecenie File / New, a następnie (w zakładce Files), określić 
rodzaj pliku 
 
 

C/C++ Source File 

dla pliku z rozszerzeniem .cpp 

 

C++ Header File 

dla pliku z rozszerzeniem .h 

 

Text File 

dla pliku z rozszerzeniem .txt 

 
nie zapominając o podaniu jego nazwy (bez rozszerzenia), np. File name: Sum
 
Po  wykonaniu  tych  czynności,  w  katalogu  D:\jbVisual\jbSpace\jbTests  powstanie  plik  Sum.cpp,  a  jego 
(początkowo pusta) zawartość ujawni się odrębnym oknie edycyjnym. 

background image

 

22 

 
Jeśli  program  wymaga  utworzenia  plików  z  danymi,  to  zaleca  się  je  umieścić  w  tym  samym  katalogu  co  pliki 
ź

ródłowe. Dla wygody moŜna je dołączyć do plików projektu. 

 
 

Budowanie projektu 

 
W celu zbudowania projektu, to jest skompilowania jego wszystkich plików *.cpp, oraz ewentualnie jego plików 
*.obj i *.lib, naleŜy kliknąć ikonę Build. Spowoduje to niezaleŜne kompilacje wszystkich modułów źródłowych 
oraz połączenie ich w program wykonalny. 
 
Przebieg  budowania  projektu  jest  diagnozowany  w  oknie  Output.  Jeśli  okno  nie  jest  widoczne,  to  moŜna  je 
wyświetlić wydając polecenie View / Output
 
Błędy  modułu  wyszczególnia  się  w  oknie  Output.  Po  rozpoznaniu  kaŜdego  z  nich  podaje  się  krótki  opis 
przyczyny  błędu  i  numer  wiersza  programu.  Dwu-kliknięcie  w  obrębie  opisu  błędu  powoduje  przeniesienie 
kursora w pobliŜe miejsca, w którym wykryto błąd.  
 
W  rzadkich  przypadkach,  gdy  poprawność  diagnozy  budzi  wątpliwości,  zaleca  się  zastąpienie  polecenia  Build 
poleceniem Build / Rebuild All
 
 

Wykonanie programu 

 
Program wykonalny, pod nazwą jbTests.exe jest umieszczany w podkatalogu jbTests\Debug. Jeśli wykonuje się 
bezbłędnie i jest naleŜycie wytestowany, to moŜe zostać zoptymalizowany. 
 
W  celu  zoptymalizowania  programu  naleŜy  wydać  polecenie  Build  /  Set  Active  Configuration,  a  następnie 
zamiast konfiguracji Win 32 Debug, wybrać konfigurację Win 32 Release. Po ponownym zbudowaniu projektu, 
w katalogu  jbTests\Release, powstanie program znacznie krótszy, ale juŜ bez informacji uruchomieniowych. 
 
 

Zarządzanie projektami 

 
Przestrzeń  robocza  moŜe  zawierać  więcej  niŜ  jeden  projekt,  a  projekt  moŜe  składać  się  z  więcej  niŜ  jednego 
pliku.  
 
Jeśli przestrzeń zawiera więcej niŜ jeden projekt, to tylko jeden z nich moŜe być aktywny, to jest taki, którego 
dotyczą polecenia Build. Uaktywnienie projektu odbywa się przez p-kliknięcie jego nazwy i wydanie polecenia 
Set Active Project
 
W celu umieszczenia w przestrzeni dodatkowego projektu naleŜy p-kliknąć nazwę przestrzeni, wydać polecenie 
Add New Project to Workspace, a dalej postępować tak, jak podczas tworzenia pierwszego projektu. 
 
W celu włączenia do projektu dodatkowego pliku naleŜy p-kliknąć nazwę projektu, a następnie wydać polecenie 
Add Files to Project i wybrać skopiowany plik. 
 
Jeśli  włączany  do  projektu  plik  źródłowy  juŜ  istnieje,  to  naleŜy  skopiować  go  do  katalogu  projektowego 
(posługując  się  np.  Eksploratorem  Windows),  a  następnie  postąpić  tak,  jak  podczas  dodawania  pliku  do 
projektu.  
 
 

Dopasowanie oblicza 

 
Oblicze  środowiska  uruchomieniowego  składa  się  z  menu  oraz  z  pasków,  które  moŜna  konfigurować.  Odbywa 
się to za pomocą polecenia Tools / Customize umoŜliwiającego zarządzanie wyświetlaniem pasków edycyjnych, 
uruchomieniowych i innych. 

background image

 

23 

 
 

Uruchamianie programu 

 
Systematyczne wyszukiwanie błędów w programie odbywa się za pomocą uruchamiacza. W celu wyświetlenia 
paska zawierającego jego narzędzia naleŜy wydać polecenie Tools / Customize / Toolbars, a następnie odhaczyć 
nastawę Debug
 
Wykonanie programu nadzorowanego przez uruchamiacz zaczyna się w konfiguracji Win32 Debug po wydaniu 
polecenia Build / Start Debug / Step into (F10). Program zatrzyma się tuŜ przed przystąpieniem do wykonania 
pierwszej funkcji (zazwyczaj funkcji main). 
 
Począwszy od tego momentu moŜna  
 
 

Określać argumenty funkcji głównej 

 

 

Project / Settings // DebugProgram arguments 

 
 

Zastawiać / usuwać pułapki 

 

 

ikona Hand (F9

 
 

Usuwać pułapki 

 

 

Edit / Breakpoints / Remove All (Alt-F9) 

 
 

Wykonywać program krokowo 

 

 

ikona Go (po zastawieniu pułapki) 

 

 

ikona Step over (F10

 

 

ikona Step into (F11

 

 

ikona Step out (Shift-F11

 
 

Obserwować zmienne 

 

 

ikona Quick Watch (Shift-F9

 
 

Kompilacja warunkowa 

 
Podczas  uruchamiania  programu  przydaje  się  ignorowanie  jego  wybranych  fragmentów.  Odbywa  się  to  za 
pomocą dyrektyw kompilacji warunkowej: #if#else#endif
 
Zinterpretowanie dyrektywy 
 

 

#if c 

 

    kod-źródłowy 

 

#else 

 

    kod-alternatywny 

 

#endif 

 
zaczyna się od wyznaczenia wartości wyraŜenia c (najczęściej liczby 1 albo 0). Jeśli wyraŜenie ma wartość róŜną 
od 0, to całą dyrektywę zastępuje się napisem kod-źródłowy. W przeciwnym razie zastępuje się ją napisem kod-
alternatywny

 
Uwaga: Jeśli napis kod-źródłowy jest pusty, to dyrektywę moŜna zapisać bez frazy #else
 

#include <iostream.h> 
 
int main(void) 

    int one, two; 
    cin >> one >> two; 
#if 1 

background image

 

24 

    cout << "Sum = "; 
#endif 
    cout << one + two << endl; 
 
    return 0; 

 
Program  wyprowadza  sumę  pary  danych  wejściowych  poprzedzając  ją napisem Sum =. Jeśli w dyrektywie #if 
zmieni si
ę 1, na 0, to powstanie program, który takiego napisu nie wyprowadzi. 

background image

 

25 

Wskaźniki i odnośniki 

 
 
 
 
Wskaźniki  i  odnośniki  są  zmiennymi,  które  słuŜą  do  identyfikowania  innych  zmiennych.  Wskaźnik  moŜe 
identyfikować  wiele  zmiennych  pokrewnego  mu  typu,  natomiast  odnośnik  moŜe  identyfikować  tylko  jedną 
zmienną.  
 
Wskaźnikom  przypisuje  się  wskazania,  a  odnośnikom  odniesienia.  Mimo  iŜ  w  typowych  implementacjach 
zarówno  wskazania  jak  i  odniesienia  są  reprezentowane  przez  adresy,  posługiwanie  się  pojęciem  adres  jest 
całkowicie  zbyteczne  i  dowodzi  myślenia  o  C++  nie  w  kategoriach  języka  wysokiego  poziomu,  ale  w 
kategoriach implementacji. Dlatego o adresach nie będzie juŜ mowy. 
 
 

Zmienne wskaźnikowe 

 
Wskaźnikiem jest zmienna, której moŜna przypisywać wskazania. Deklarację wskaźnika moŜna poznać po tym, 
Ŝ

e jej identyfikator jest poprzedzony symbolem (gwiazdka). 

 
Jeśli w pewnym miejscu programu jest wymagane uŜycie wskazania zmiennej, to otrzymuje się je poprzedzając 
nazwę zmiennej operatorem wskazywania & (ampersand). 
 
Po  przypisaniu  wskaźnikowi  ptr  wskazania  zmiennej,  napis  *ptr  staje  się  chwilową  nazwą  tej  zmiennej.  Po 
przypisaniu wskaźnikowi wskazania pustego (reprezentowanego przez liczbę 0), uŜycie nazwy *ptr albo nazwy 
jej równowaŜnej (np. ptr[0]) jest zabronione. 
 

int fix1 = 10,  
    fix2 = 20; 
 
int *ptr = &fix1; 
cout << *ptr;         // 10 
*ptr = 11; 
cout << *ptr << fix;  // 11 11 
ptr = &fix2; 
cout << *ptr;         // 20 
*ptr = 22; 
cout << *ptr << fix;  // 22 22 
ptr = 0; 
cout << *ptr;         // bł

ą

 
Wskaźnik  ptr  jest  przystosowany  do  wskazywania  zmiennych  typu  int.  Przypisano  mu  kolejno:  wskazanie 
zmiennej fix1, wskazanie zmiennej fix2 i wskazanie puste. 
 
Po  przypisaniu  wska
źnikowi  ptr  wskazania  zmiennej  fix1,  napis  *ptr  jest  chwilową  nazwą  zmiennej  fix1,  a  po 
przypisaniu mu wskazania zmiennej fix2, jest chwilow
ą nazwą zmiennej fix2
 
Po  przypisaniu  wska
źnikowi  ptr  wskazania  pustego,  aŜ  do  chwili  przypisania  mu  wskazania  zmiennej,  uŜycie 
nazwy *ptr jest zabronione. 
 
 

Dla dociekliwych 

 
Typ wyraŜenia inicjującego wskaźnik musi być zgodny z typem wskaźnika. Przyjmuje się z definicji, Ŝe zgodne 
ze  wskaźnikiem  typu  Type  jest  kaŜde  wyraŜenie  typu  Type  oraz  kaŜde  wyraŜenie,  które  moŜe  być  poddane 
niejawnej konwersji do typu Type (por. Dodatek C). 
 

background image

 

26 

char *ptr1 = "0\0\0\0"           // niejawna konwersja 
int *ptr2 = "0\0\0\0";           // bł

ą

int *ptr = (int *)"0\0\0\0";     // jawna konwersja 
cout << *ptr;                    // 48 (kod cyfry 0) 

 
Skutek  uŜytej  tu jawnej konwersji zaleŜy od implementacji. W Visual C++ powoduje to potraktowanie obszaru 
pami
ęci zajętego przez pierwsze 4 bajty literału jako zmiennej całkowitej. 
 
 

Wskaźniki i tablice 

 
Związki między wskaźnikami i tablicami są bardzo bliskie. KaŜda nazwa tablicy jest niejawnie przekształcana na 
wskaźnik jej zerowego elementu, a kaŜda nazwa wskaźnika moŜe być indeksowana tak, jak nazwa tablicy. 
 
Jeśli  wskaźnik  ptr  wskazuje  pewien  element  tablicy,  to  zarówno  *ptr  jak  i  ptr[0]  jest  nazwą  tego  elementu. 
Elementy połoŜone z lewej strony elementu wskazywanego mają nazwy ptr[-1]ptr[-2], itd., a elementy połoŜone 
z prawej mają nazwy ptr[1]ptr[2], itd. 
 
Jeśli  i  jest  wyraŜeniem  całkowitym,  to wyraŜenie ptr+i jest wskaźnikiem elementu odległego o i elementów od 
wskazywanego (dla i ujemnego - w lewo, a dla i dodatniego - w prawo). 
 
Jeśli wskaźniki ptr1 ptr2 wskazują odpowiednio elementy o indeksach  oraz tej samej n-elementowej tablicy 
(a takŜe gdy wskazują nie istniejące "elementy" o indeksach -1 i n), to wyraŜenie ptr1-ptr2 ma wartość i-j
 

int vec[3] = { 10, 20, 30 }; 
int *ptr = vec + 2; 
cout << ptr++[-1];             // 20 
cout << *(vec + 2);            // 30 
cout << vec - ptr;             // -3 

 
Nazwa vec zostaje niejawnie przekształcona na wskazanie elementu vec[0], to jest na &vec[0]
 
Wyra
Ŝenie vec + 2  wskazuje element o wartości 30
 
Wyra
Ŝenie ptr++[-1] jest nazwą elementu o wartości 20
 
W  wyra
Ŝeniu  vec - ptr  pierwszy  argument  wskazuje  element  zerowy,  a  drugi  argument  wskazuje  nie  istniejący 
element vec[3]
 
 

Wskaźniki i struktury 

 
Jeśli wskaźnik ptr wskazuje strukturę o polu f, to nazwą zmiennej odpowiadającej temu polu jest (*ptr).f, albo 
krócej ptr->f
 

#include <iostream.h> 
 
struct Child { 
    char name[20]; 
    int age; 
    Child *pNext; 
}; 
 
Child bob = { "Robert", 20 }, 
      tom = { "Thomas", 30, 0 }; 
 
Child *pBob = &bob, 
      *pTom = &tom; 
 
int main(void)  

    cout << pBob->name << endl;         // Robert 
 

background image

 

27 

    pBob->pNext = pTom; 
     
    cout << pBob->pNext->age << endl;   // 30 
 
    return 0; 

 
Zmienna  pBob  jest  wskaźnikiem  przystosowanym  do  wskazywania  zmiennych  typu  Child.  Przypisano  jej 
wskazanie struktury bob
 
Napis pBob->name jest chwilow
ą nazwą tego elementu struktury bob, który jest opisany przez pole name
 
Napis  pBob->pNext  jest  nazw
ą  wskaźnika  opisanego  przez  pole  pNext.  PoniewaŜ  wskazuje  on  strukturę  tom
wi
ępBob->pNext->age jest nazwą tego elementu struktury tom, który jest opisany przez pole age
 
 

Tablice wskaźników 

 
Tablicą  wskaźników  jest  tablica,  której  elementami  są  wskaźniki.  W  deklaracji  tablicy  wskaźników  jej 
identyfikator jest poprzedzony znakiem * (gwiazdka). 
 
W deklaracji wskaźnika, który słuŜy do wskazywania-wskaźników, jego identyfikator jest poprzedzony dwiema 
znakami * (gwiazdka). 
 

#include <iostream.h> 
 
const int Count = 3; 
 
struct Child { 
    char name[20]; 
    int age; 
}; 
 
Child john = { "John Smith",  30 }, 
      tom  = { "Thomas Mill", 10 }, 
      bill = { "Robert Dole", 20 }; 
       
Child *pBoys[Count] = { &john, &tom, &bill }; 
 
int main(void) 
{    
    for(int i = 0; i < Count-1 ; i++) { 
        int minAge = pBoys[i]->age; 
        for(int j = i+1; j < Count ; j++) { 
            if(pBoys[j]->age < minAge) { 
                minAge = pBoys[j]->age; 
                Child *ptr = pBoys[i]; 
                pBoys[i] = pBoys[j]; 
                pBoys[j] = ptr; 
            } 
        } 
    } 
 
    Child **ptr = pBoys; 
    for(i = 0; i < Count ; i++) 
        cout << (*ptr++)->name << endl; 
 
    return 0; 

 
Program  wyprowadza  nazwiska  chłopców,  w  kolejności  ich  rosnącego  wieku.  Sortowanie  dotyczy  tylko 
elementów tablicy wska
źników i nie powoduje kopiowania struktur typu Child
 
 

Wskaźniki a ustalenia 

background image

 

28 

 
Podobnie jak zwykła zmienna, tak i wskaźnik moŜe być ustalony albo nie-ustalony. Ponadto wskaźnik moŜe być 
przystosowany do wskazywania zmiennych ustalonych albo nie-ustalonych. Daje to cztery moŜliwości. 
 
Uwaga: Zabrania się, aby wskaźnikowi przystosowanemu do wskazywania zmiennych nie-ustalonych przypisano 
wskazanie zmiennej ustalonej. 
 

#include <iostream.h> 
 
int main(void) 
{     
 
    int mod = 10; 
    const int fix = 20; 
 
    int *ptr1 = &mod; 
    int *const ptr2 = &mod;         
    const int *ptr3 = &mod; 
    const int *const ptr4 = &fix; 
 
    cout << *ptr1 << endl;  // 10 
    cout << *ptr2 << endl;  // 10 
    cout << *ptr3 << endl;  // 10 
    cout << *ptr4 << endl;  // 20 
 
    ptr1 = &fix;            // bł

ą

    ++ptr2;                 // bł

ą

    ++*ptr3;                // bł

ą

 
    ptr1 = &(int &)fix;     // dozwolone 
    cout << *ptr1 << endl;  // 20 
 
    return 0; 

 
Wskaźnik ptr1 słuŜy do wskazywania zmiennych nie-ustalonych. Wskaźnik ptr2 jest wskaźnikiem ustalonym, który 
słu
Ŝy do wskazywania zmiennych nie-ustalonych. Wskaźnik ptr3 jest wskaźnikiem nie-ustalonym, który słuŜy do 
wskazywania  zmiennych  ustalonych.  Wska
źnik  ptr4  jest  wskaźnikiem  ustalonym,  który  słuŜy  do  wskazywania 
zmiennych ustalonych. 
 
 

Zmienne odnośnikowe 

 
Odnośnikiem jest zmienna, którą moŜna zainicjować odniesieniem. Deklarację odnośnika moŜna poznać po tym, 
Ŝ

e jej identyfikator jest poprzedzony symbolem (ampersand). Istnieją odnośniki do zmiennych, ale nie istnieją 

tablice odnośników. KaŜdy odnośnik musi być zainicjowany. 
 
Uwaga:  Jeśli  w  pewnym  miejscu  programu  występuje  nazwa  zmiennej,  a  program  byłby  poprawny  tylko 
wówczas, gdyby występowała tam nazwa odnośnika do zmiennej, to nazwę zmiennej niejawnie przekształca się 
w odnośnik. 
 

int fix = 10; 
int &ref = fix;    // int &ref = (int &)fix; 

 
PoniewaŜ odnośnik ref jest typu int &, więc nie moŜe być zainicjowany wartością zmiennej fix, która jest typu 
int.  Dlatego,  za  pomoc
ą  niejawnej  konwersji  (int  &)fix,  nazwę  zmiennej  fix  niejawnie  przekształca  się  w 
odno
śnik. 
 
Po zainicjowaniu odnośnika ref odniesieniem do zmiennej, napis ref staje się trwałą nazwą tej zmiennej. A więc 
odnośnik moŜna zainicjować, ale nie moŜna mu przypisać odniesienia. 
 

#include <iostream.h> 
 

background image

 

29 

int main(void)  

    int fix = 10; 
    int &ref = fix; 
    ref = 10; 
    cout << fix << ref << endl; // 10 10 
 
    return 0; 

 
Po zainicjowaniu odnośnika, napis ref staje się trwałą nazwą zmiennej fix. Dlatego przypisanie ref = 10 zmienia 
warto
ść zmiennej fix, ale nie zmienia wartości odnośnika ref. 
 
 

Dla dociekliwych 

 
Typ  wyraŜenia  inicjującego  odnośnik  musi  być  zgodny  z  typem  odnośnika.  Przyjmuje  się  z  definicji,  Ŝe  typ 
"odnośnik do zmiennej typu Type" (np. int &) jest zgodny z typem Type (np. int). Jeśli wyraŜenie inicjujące jest 
innego typu, to moŜe być poddane niejawnej konwersji do typu zgodnego, ale tylko wówczas, gdy typ odnośnika 
jest ustalony (const). W takim wypadku odnośnik zostanie zainicjowany odniesieniem do zmiennej pomocniczej 
typu z nim zgodnego, zainicjowanej wartością wyraŜenia po konwersji. 
 

int &ref1 = 2.4;           // bł

ą

const int &ref = 2.4; 

 
Identyfikator ref2 jest trwałą nazwą zmiennej pomocniczej o wartości (int)2.4
 
 

Wskaźniki i odnośniki 

 
Podejmując  decyzję  o  uŜyciu  wskaźnika,  czy  odnośnika,  naleŜy  kierować  się  wytyczną,  Ŝe wszędzie tam gdzie 
jest to moŜliwe, naleŜy stosować odnośniki, gdyŜ zwiększa to czytelność programu.  
 
W  rzadkich  przypadkach  stosuje  się  odnośniki  do  wskaźników.  Jest  to  niezbędne  wówczas,  gdy  poprzez 
odnośnik naleŜy zmodyfikować wskaźnik. 
 

#include <iostream.h> 
 
int main(void) 

    int vec[3] = { 10, 20, 30 }; 
    int *ptr = vec; 
    int *&ref = ptr; 
 
    ++ref; 
 
    cout << *ptr << endl;            // 20 
 
    return 0; 

 
Po zadeklarowaniu odnośnika, napis ref jest trwałą nazwą wskaźnika ptr. Dlatego po wykonaniu operacji ++ref 
wska
źnik ptr wskazuje element vec[1] o wartości 20
 
Gdyby  z  deklaracji  odno
śnika  usunięto  znak  &,  to  napis  ref  stałby  się  nazwą  wskaźnika  zainicjowanego 
wskazaniem elementu vec[0], a wykonanie operacji ++ref nie miałoby 
Ŝadnego wpływu na wskaźnik ptr. W takim 
wypadku nast
ąpiłoby wyprowadzenie liczby 10

background image

 

30 

Przetwarzanie łańcuchów 

 
 
 
 
Łańcuchem jest dowolna sekwencja elementów tablicy znakowej, zakończona elementem o wartości 0. PoniewaŜ 
kaŜdy literał łańcuchowy jest nazwą takiej właśnie sekwencji elementów, więc jest nazwą łańcucha. 
 
W  szczególności,  literał  "Hello"  jest  nazwą  6-elementowej  tablicy  znakowej,    której  element  "Hello"[0]  ma 
wartość 'H', a element "Hello"[5] ma wartość 0
 
Do  typowych  operacji  wykonywanych  na  łańcuchach  naleŜą:  wprowadzenie  i  wyprowadzenie  łańcucha, 
wyznaczenie  długości  łańcucha  (strlen),    skopiowanie  łańcucha  (strcpy),  połączenie  łańcuchów  (strcat)  i 
porównanie  łańcuchów  (strcmp).  Operacje  te  moŜna  wykonać  za  pomocą  funkcji  bibliotecznych, 
zadeklarowanych w nagłówku string.h
 
Uwaga: Jeśli wskaźnik wskazuje pierwszy element łańcucha, to mówi się w skrócie, Ŝe wskazuje łańcuch.. 
 

int strlen(char *pStr) 

Dostarcza liczbę znaków łańcucha wskazanego przez argument. 
np. 

cout << strlen("Hello");              // 5 

 

char *strcpy(char *pTrg, const char *pSrc) 

Dostarcza  pierwszy  argument.  Ponadto  kopiuje,  począwszy  od  miejsca  wskazanego  przez  pierwszy  argument, 
łańcuch wskazany przez drugi argument. 
np. 

char buf[100] = "Hello "; 
cout << strcpy(buf + 6, "World")- 6;  // Hello World 

 

char *strcat(char *pTrg, const char *pSrc) 

Dostarcza  pierwszy  argument.  Ponadto  kopiuje,  poczawszy  od  miejsca,  w  którym  znajduje  się  znak  końca 
łańcucha wskazanego przez pierwszy argument, łańcuch wskazany przez drugi argument. 
np. 

char buf[100] = "Hello "; 
cout << strcat(buf, "World");         // Hello World 

 

int strcmp(const char *pOne, const char *pTwo) 

Dostarcza wartość +1 jeśli łańcuch wskazany przez pierwszy argument jest większy niŜ łańcuch wskazany przez 
drugi argument, dostarcza wartość 0 jeśli są równe, albo wartość -1 jeśli pierwszy jest mniejszy.  
Uwaga: Porównanie  łańcuchów  zastępuje  się  porównaniem  pierwszej  pary  znaków  róŜnych.  Jeśli  jeden  z 
łańcuchów jest podłańcuchem drugiego, to za większy uznaje się dłuŜszy.  
np. 

cout << strcmp("abc", "abaaaaa");     // -1 
cout << strcmp("abcde", "ab");        // 1 
cout << strcmp("ab", "ab");           // 0 

 
 

Wprowadzanie i wyprowadzanie łańcuchów 

 
Operacja  wprowadzenia  łańcucha  ma  postać  cin >> ptr,  w  której  ptr  jest  wskaźnikiem  elementu  tablicy 
znakowej.  Jej  wykonanie  powoduje  umieszczenie  w  tablicy,  począwszy  od  jej  elementu  *ptr,  kodów  spójnego 
ciągu znaków wejściowych oraz kodu znaku '\0' (o wartości 0).  
 
Przed  wprowadzeniem  znaków  zostaną  pominięte  odstępy  wiodące.  W  celu  zabezpieczenia  się  przed 
przepełnieniem tablicy moŜna uŜyć manipulatora setw.  
 

background image

 

31 

Operacja  wyprowadzenia  łańcucha  ma  postać  cout << ptr,  w której ptr jest wskaźnikiem. Zabrania się, aby ptr 
było wskaźnikiem elementu tablicy, który nie jest zerowym elementem łańcucha. 
 

#include <iostream.h> 
#include <string.h> 
 
const int Size = 100; 
 
char buffer[Size] = "prof. "; 
 
int main(void)  

    int len1 = strlen(buffer); 
    cin >> buffer + len1; 
    int len2 = strlen(buffer); 
    buffer[len2] = ' '; 
    buffer[len2+1] = 0; 
    cin >> buffer + len2 + 1; 
    cout << buffer << endl; 
    cout << "dr " << buffer + len1 << endl; 
 
    return 0; 

 
Jeśli z klawiatury wprowadzi się imię i nazwisko (np. Jan Bielecki), to program wyprowadzi to imię i to nazwisko 
poprzedzone napisem prof. (np. prof. Jan Bielecki), a ponadto tylko to imi
ę i to nazwisko.  
 
 

Wyznaczenie długości 

 

#include <iostream.h> 
#include <string.h> 
 
char str[6] = "Hello"; 
 
int main(void)  

    cout << strlen("Hello") << endl;  // 5 
 
    char *ptr = str; 
    int len = 0; 
    while(*ptr != 0) { 
        len++; 
        ptr++; 
    } 
    cout << len << endl;              // 5 
 
    ptr = str; 
    len = 0; 
    while(*ptr++) 
        len++; 
    cout << len << endl;              // 5 
 
    return 0; 

 
Pokazano trzy sposoby wyznaczenia długości łańcucha zapisanego w tablicy znakowej. 
 
Wyra
Ŝenie  *ptr++  jest  nazwą  zmiennej,  wskazywanej  przez  wskaźnik  ptr,  przed  wykonaniem  na  nim  operacji 
zwi
ększenia.  
 
 

Kopiowanie 

 

#include <iostream.h> 
#include <string.h> 
 
char src[7] = "Hello "; 

background image

 

32 

char trg[100]; 
 
int main(void)  

    char *pSrc = src, 
         *pTrg = trg; 
     
    strcpy(pTrg, pSrc); 
    cout << trg << endl;              // Hello 
 
    while(*pSrc != 0) { 
        *pTrg = *pSrc; 
        pSrc++; 
        pTrg++; 
    } 
    pTrg = 0; 
    cout << trg << endl;              // Hello 
 
    pSrc = src; 
    pTrg  = trg; 
    while(*pTrg++ = *pSrc++) 
        ; 
    cout << trg << endl;              // Hello 
 
    return 0; 

 
Pokazano trzy sposoby kopiowania łańcucha znaków. 
 
 

Łączenie 

 

#include <iostream.h> 
#include <string.h> 
 
char *pSrc = "Hello", 
     buf[100]; 
 
int main(void)  

    strcat(strcpy(buf, pSrc), "!"); 
    cout << buf << endl;              // Hello! 
 
    char *pBuf = buf; 
    strcpy(pBuf, pSrc); 
    while(*pBuf++) 
        ; 
    char *pSrc = "!"; 
    while(pBuf++[-1] = *pSrc++) 
        ; 
    cout << buf << endl;              // Hello! 
 
    return 0; 

 
Pokazano dwa sposoby łączenia łańcuchów. 
 
 

Porównanie 

 

#include <iostream.h> 
#include <string.h> 
 
char one[100], 
     two[100]; 
 
int main(void)  

    cin >> one >> two; 

background image

 

33 

 
    cout << one; 
    switch(strcmp(one, two)) { 
        case +1: 
            cout << " > "; 
            break; 
        case -1: 
            cout << " < "; 
            break; 
        default: 
            cout << " == "; 
    } 
    cout << two << endl; 
     
    char *pOne = one, 
         *pTwo = two; 
    cout << one; 
    while(*pOne == *pTwo && *pOne != 0) { 
        pOne++; 
        pTwo++; 
    } 
    if(*pOne == 0 && *pTwo == 0) 
        cout << " == "; 
    else if(*pOne > *pTwo) 
        cout << " > "; 
    else 
        cout << " < "; 
    cout << two << endl; 
 
    pOne = one; 
    pTwo = two; 
    cout << one; 
    while(*pOne || *pTwo) { 
        if(*pOne++ != *pTwo++) { 
            if(pOne[-1] > pTwo[-1]) 
                cout << " > "; 
            else  
                cout << " < "; 
            cout << two << endl; 
            return 0; 
        } 
    } 
    cout << " == "; 
    cout << two << endl; 
 
    return 0; 

 
Pokazano trzy sposoby porównywania łańcuchów wprowadzonych z klawiatury. 
 

background image

 

34 

Posługiwanie się funkcjami 

 
 
 
 
Funkcja  jest  sparametryzowanym  opisem  czynności.  W  miejscu  wywołania  funkcji  musi  być  znana  jej 
deklaracja albo definicja. W szczególności oznacza to, Ŝe wywołanie 
 
sum(10, 20)  
 
funkcji sumującej argumenty, musi być poprzedzone 
 
albo jej definicją 
 

int sum(int one, int two) 

    return one + two; 

 
albo jej deklaracją 
 

int sum(int one, int two); 

 
albo włączeniem nagłówka zawierającego deklarację. 
 
 
Uwaga: W deklaracji funkcji moŜna pominąć dowolny zestaw identyfikatorów parametrów. Jeśli uczyni się to w 
definicji, to uniemoŜliwi to odwoływanie się do argumentów. 
 
 

Parametry i argumenty 

 
Wywołanie  funkcji  zaczyna  się  od  skojarzenia  jej  parametrów  z  argumentami.  Skojarzenie  parametru  z 
argumentem odbywa się przez-wartość, co oznacza, Ŝe parametr jest traktowany tak, jak lokalna zmienna funkcji, 
zadeklarowana tuŜ przed jej pierwszą instrukcją i zainicjowana wartością argumentu. 
 
A zatem, jeśli definicją funkcji jest 
 

int sum(int one, int two) 

    return one + two; 

 
to dla wywołania  
 

sum(10, 20) 

 
funkcja jest traktowana tak, jakby miała postać 
 

int sum() 

    int one = 10; 
    int two = 20; 
 
    return one + two; 

 
 

background image

 

35 

Parametry zwykłe 

 
Parametr  funkcji  jest  "zwykły",  jeśli  nie  jest  wskaźnikiem  ani  odnośnikiem.  Z  parametrem  zwykłym  moŜna 
skojarzyć argument, który jest takiego samego typu jak parametr, albo który moŜna poddać niejawnej konwersji 
do typu parametru.  
 
Zainicjowanie  parametru  polega  na  skopiowaniu  argumentu.  Jeśli  argument  jest  strukturą,  to  kopiuje  się 
wszystkie jej elementy (co w przypadku duŜych struktur ma oczywiste wady!). 
 
Po  dokonaniu  skojarzenia,  wszelkie  operacje  wykonywane  na  parametrze  dotyczą  lokalnej  zmiennej 
zainicjowanej argumentem i nie powodują zmiany wartości skojarzonego z nim argumentu. 
 

#include <iostream.h> 
 
int main(void)  

    void inc(int par); 
    int fix = 10; 
    cout << fix << endl;      // 10 
    inc(fix); 
    cout << fix << endl;      // 10     
 
    return 0; 

 
void inc(int par) 

    ++par; 

 
Program  potwierdza,  Ŝe  wykonanie  operacji  na  parametrze  "zwykłym"  nie  powoduje  zmiany  wartości 
skojarzonego z nim argumentu. 
 
 

Parametry wskaźnikowe 

 
Z  parametrem  wskaźnikowym  moŜna  skojarzyć  argument,  który  jest  takiego  samego  typu  jak  parametr,  albo 
który moŜna poddać niejawnej konwersji do typu parametru.  
 
Zainicjowanie  parametru  polega  na  skopiowania  wskaźnika.  Nie  pociąga  to  za  sobą  kopiowania  zmiennej 
identyfikowanej przez argument (co moŜna wykorzystać w przypadku duŜych struktur!). 
 
Po  dokonaniu  skojarzenia,  wszelkie  operacje  wykonywane  na  parametrze  dotyczą  lokalnej  zmiennej 
zainicjowanej argumentem, ale operacje wykonywane za pośrednictwem parametru (np. *parpar[i] albo par->f
dotyczą zmiennej wskazywanej przez argument. MoŜe to mieć wpływ na wartość argumentu. 
 

#include <iostream.h> 
 
int main(void)  

    void inc(int *ptr); 
    int fix = 10; 
    cout << fix << endl;      // 10 
    inc(&fix); 
    cout << fix << endl;      // 11     
 
    return 0; 

 
void inc(int *ptr) 

    ++*ptr; 

 

background image

 

36 

Program  potwierdza,  Ŝe  wykonanie  operacji  za  pośrednictwem  parametru  wskaźnikowego  moŜe  powodować 
zmian
ę wartości zmiennej wskazywanej przez skojarzony z nim argument. 
 
 

Parametry tablicowe 

 
KaŜdy  parametr  tablicowy  jest  niejawnie  zastępowany  parametrem  wskaźnikowym,  który  powstaje  po 
zastąpieniu deklaratora vec[i] deklaratorem (* const vec)
 
W szczególności funkcja 
 

int sum(int tab[20]) 

    int sum = 0; 
    for(int i = 0; i < 3 ; i++) 
        sum += tab[i]; 
    return sum; 

 
jest niejawnie przekształcana w funkcję 
 

int sum(int *const tab) 

    int sum = 0; 
    for(int i = 0; i < 3 ; i++) 
        sum += tab[i]; 
    return sum; 

 
Powoduje  to,  Ŝe  jeśli  chce  się  zdefiniować  funkcję  do  sumowania  tablic,  nie  odwołującą  się  do  zmiennych 
globalnych, to jeden z jej argumentów musi określać liczbę elementów tablicy. 
 

#include <iostream.h> 
 
int sum(int tab[], int count) 

    int sum = 0; 
    for(int i = 0; i < count ; i++) 
        sum += tab[i]; 
    return sum; 

 
int main(void) 

    int small[] = { 10 }, 
        large[] = { 10, 20, 30 }; 
 
    cout << sum(small, 1) << endl; 
    cout << sum(large, 3) << endl; 
 
    return 0; 

 
 

Parametry odnośnikowe 

 
Z  parametrem  odnośnikowym  moŜna  skojarzyć  argument,  który  jest  takiego  samego  typu  jak  parametr,  albo 
który moŜna poddać niejawnej konwersji do typu parametru.  
 
Zainicjowanie  parametru  polega  na  skopiowania  odnośnika.  Podobnie  jak  dla  parametru  wskaźnikowego,  nie 
pociąga to za sobą kopiowania zmiennej identyfikowanej przez argument. 
 
Po  dokonaniu  skojarzenia,  wszelkie  operacje  wykonywane  na  parametrze  dotyczą  zmiennej  identyfikowanej 
przez argument. MoŜe to powodować zmianę wartości skojarzonego z nim argumentu. 

background image

 

37 

 

#include <iostream.h> 
 
int main(void)  

    void inc(int &ref); 
    int fix = 10; 
    cout << fix << endl;      // 10 
    inc(fix); 
    cout << fix << endl;      // 11     
 
    return 0; 

 
void inc(int &ref) 

    ++ref; 

 
Program  potwierdza,  Ŝe  wykonanie  operacji  za  pośrednictwem  parametru  odnośnikowego  moŜe  powodować 
zmian
ę wartości skojarzonego z nim argumentu. 
 
 

Parametry funkcji main 

 
Funkcja  główna  moŜe  być  zadeklarowana  jako  bezparametrowa  albo  dwu-parametrowa.  Jeśli  jest 
dwuparametrowa,  to  jej  pierwszy  parametr  jest  typu  int  i  ma  wartość  równą  liczbie  argumentów  programu 
zwiększonej  o  1,  a  drugi  jest  typu  char  *[]  i  jest  tablicą  odnośników  do  łańcuchów  zainicjowanych  nazwą 
programu oraz nazwami jego argumentów. 
 

#include <iostream.h> 
 
int main(int argc, char *argv[]) 

    cout << "My name is: " << argv[0] << endl; 
    cout << "My arguments are: " << endl; 
    for(int i = 1; i < argc ; i++) 
        cout << argv[i] << endl; 
 
    return 0; 

 
Program wyprowadza swoją nazwę i  argumenty; kaŜde w osobnym wierszu. 
 
 

Skojarzenia powrotne 

 
W  chwili  zakończenia  wykonywania  funkcji  rezultatowej  (o  typie  róŜnym  od  void)  następuje  skojarzenie  jej 
rezultatu  z  wyraŜeniem  występującym  w  instrukcji  powrotu.  Odbywa  się  to  według  tych  samych  zasad  co 
skojarzenie  parametru  z  argumentem  i  polega  na  zainicjowaniu  rezultatu  funkcji  wyraŜeniem  występującym  w 
instrukcji powrotu. 
 
Uwaga:  Rezultat  funkcji  jest  zmienną.  Typ  rezultatu  jest  identyczny  z  typem  funkcji.  Nazwą  rezultatu  jest 
wywołanie  funkcji.  Z  punktu  widzenia  łączenia  operacji  (np.  ++*fun(1,2)+3),  nazwa  funkcji  jest  zastępowana 
nazwą rezultatu. 
 

#include <iostream.h> 
#include <math.h> 
 
double sqr(double val) 

    return val * val; 

 

background image

 

38 

int main(void) 
{    
    double a, b; 
    cin >> a >> b; 
    cout << sqrt(sqr(a) + sqr(b)) << endl; 
 
    return 0; 

 
Wywołanie sqr(a) jest nazwą rezultatu o wartości "kwadrat a", wywołanie sqr(b) jest nazwą rezultatu o wartości 
"kwadrat b",  a wyra
Ŝenie sqrt(sqr(a) + sqr(b)) jest nazwą rezultatu o wartości "pierwiastek z sumy kwadratów 
b". 
 
 

Typ nie-odnośnikowy 

 
Jeśli typ funkcji jest nie-odnośnikowy, to zainicjowanie rezultatu polega na skopiowaniu zmiennej, której nazwą 
jest wyraŜenie występujące w instrukcji powrotu. 
 
Jeśli typ wyraŜenia nie jest identyczny z typem funkcji, to wyraŜenie jest poddawane konwersji do typu rezultatu. 
Zezwala się na niejawne wykonanie co najwyŜej jednej konwersji standardowej i jednej definiowanej. 
 

#include <iostream.h> 
 
double getSqr(int par); 
 
int main(void)  

    cout << getSqr(3) << endl;  // 9 
 
    return 0; 

 
double getSqr(int par) 

    return par * par;     // return double(par * par); 

 
WyraŜenie par * par jest nazwą zmiennej typu int zainicjowanej daną o wartości 9
 
Poniewa
Ŝ  typ  rezultatu  jest  róŜny  od  typu  tej  zmiennej,  więc  zostanie  zastosowana  niejawna  konwersja 
standardowa z typu int do double
 
Po zainicjowaniu rezultatu zmienn
ą double(par * par), wywołanie getSqr(3) moŜna traktować nazwę rezultatu. 
 
 

dla dociekliwych 

 

#include <iostream.h> 
 
struct Child { 
    char name[20]; 
    int age; 
}; 
 
Child isa = { "Isabel", 15 }; 
 
Child getOlder(Child child, int val); 
void show(Child &child); 
 
int main(void)  

    show(isa);                // Isabel is 15 
    show(getOlder(isa, 2));   // Isabel is 17 
    show(isa);                // Isabel is 15 
 

background image

 

39 

    return 0; 

 
Child getOlder(Child child, int val) 

    child.age += val; 
 
    return child; 

 
void show(Child &child) 

    cout << child.name << " is " <<  
            child.age << endl; 

 
Wywołanie funkcji getOlder(isa, 2) powoduje skopiowanie struktury isa do lokalnej zmiennej funkcji getOlder
 
Operacja child.age += val jest wykonywana na tej zmiennej lokalnej. 
 
Wywołanie getOlder(isa, 2) jest nazw
ą zmiennej, do której skopiowano tę zmienną lokalną
 
 

Typ odnośnikowy 

 
Jeśli typ funkcji jest odnośnikowy, to zainicjowanie rezultatu polega na skopiowaniu odnośnika do tej zmiennej, 
której  nazwą  jest  wyraŜenie  występujące  w  instrukcji  powrotu.  A  zatem  wywołanie  funkcji  jest  nazwą  tej 
zmiennej. 
 

#include <iostream.h> 
 
int &refVal(void) 

    static int val = -1; 
 
    return ++val; 

 
int main(void) 

    cout << refVal() << endl;   // 0 
    cout << refVal() << endl;   // 1 
    refVal() = 5; 
    cout << refVal() << endl;   // 6 
    ++refVal(); 
    cout << refVal() << endl;   // 9 
 
    return 0; 

 
Wywołanie refVal() jest nazwą statycznej zmiennej val. A zatem kaŜda operacja wykonana na refVal() dotyczy 
tej wła
śnie zmiennej. 
 
 

dla dociekliwych 

 
Jeśli  typ  wyraŜenia  w  instrukcji  powrotu  nie  jest  zgodny  z  typem  funkcji,  to  typ  funkcji  musi  być  ustalony 
(const), a ponadto musi istnieć niejawna konwersja z typu wyraŜenia do typu zgodnego z typem funkcji. 
 

#include <iostream.h> 
 
const int &refVal(double par) 

    return par * par;         // return double(par * par); 

 

background image

 

40 

int main(void) 

    cout << refVal(3) << endl; 
 
    return 0; 

 
Uwaga: WyraŜenie zawarte w instrukcji powrotu moŜe tylko wówczas identyfikować zmienną lokalną funkcji, 
gdy typ funkcji jest ustalony
 

#include <iostream.h> 
 
int &getInc(int par); 
 
int main(void)  

    cout << getInc(3) << endl;   // bł

ą

 
    return 0; 

 
int &getInc(int par) 

    return ++par; 

 
Wywołanie getInc(3) jest nazwą lokalnej zmiennej par. PoniewaŜ po powrocie z funkcji getInc zmienna par juŜ 
nie istnieje, wi
ęc odwołanie się do niej jest zabronione. W Visual C++ program wyprowadza liczbę 4
 
Program mo
Ŝna poprawić, nadając mu postać 

 
#include <iostream.h> 
 
const int &getInc(int par); 
 
int main(void)  

    cout << getInc(3) << endl;   // 4 
 
    return 0; 

 
const int &getInc(int par) 

    return ++par; 
}  

 
 

Deklarowanie funkcji 

 
Deklaracja  funkcji  podaje  jej  identyfikator  oraz  określa  typ  funkcji  oraz  typy  jej  parametrów.  Jeśli  ponadto 
podaje ciało funkcji, to jest jej definicją. 
 
 

Funkcje bezrezultatowe 

 
Funkcja,  której  typem  jest  void,  jest  funkcją  bezrezultatową.  Jej  wywołanie  kończy  się  w  chwili  wykonania 
instrukcji powrotu nie zawierającej wyraŜenia, albo w chwili zakończenia wykonywania jej ciała. 
 

void outDiv(int a, int b) 

    if(b == 0) 
        return; 
    cout << a / b; 

background image

 

41 

 
 

Funkcje otwarte i zamknięte 

 
Funkcja  zadeklarowana  ze  specyfikatorem  inline  jest  realizowana  jako  otwarta.  W  odróŜnieniu  od  funkcji 
zamkniętej,  ciało  funkcji  otwartej  wstawia  się  w  kaŜdym  miejscu  jej  wywołania.  Powoduje  to  przyspieszenie 
wykonania programu, ale niekiedy wydłuŜa jego kod wynikowy. 
 
Uwaga: Wystąpienie specyfikatora inline nie ma wpływu na skutek wykonania programu. Jeśli funkcja otwarta 
zostanie uznana za zbyt skomplikowaną, to moŜe być zrealizowana jako zamknięta. 
 

inline in sum(int a int b) 

    return a + b; 

 
 

Funkcje przeciąŜone 

 
Jeśli  w  pewnym  zakresie  są  widoczne  deklaracje  dwóch  lub  większej  liczby  funkcji  o  takiej  samej  nazwie,  ale 
róŜniących się typami parametrów, to ogół takich funkcji stanowi wieloaspektową funkcję przeciąŜoną. 
 
W  miejscu  wywołania  funkcji  przeciąŜonej  wywołuje  się  ten  z  jej  aspektów,  do  którego  parametrów  najlepiej 
pasują  podane  argumenty.  Ma  to  miejsce  wówczas,  gdy  istnieje  taki  aspekt,  Ŝe  do  kaŜdego  z  jego  parametrów 
podany argument pasuje nie gorzej niŜ do pozostałych, ale istnieje taki parametr, do którego jeden z argumentów 
pasuje lepiej niŜ do pozostałych. 
 
Uwaga: Jeśli argument nie pasuje do parametru dokładnie, to moŜe być poddany konwersji dopasowującej, ale 
im konwersja ta jest bardziej złoŜona, tym dopasowanie pierwotnego argumentu uznaje się za gorsze. 
 

#include <iostream.h> 
 
void out(char par); 
void out(int par); 
 
int main(void)  

    out('a'); 
    out(2); 
    out(2.0);    // bł

ą

d (niejednoznaczno

ść

 
    return 0; 

 
void out(char par) 

    cout << par << endl; 

 
void out(int par) 

    cout << par << endl; 

 
Argument  'a'  typu  char  najlepiej  pasuje  do  parametru  typu  char,  a  argument  2  typu  int  najlepiej  pasuje  do 
parametru typu int
 
Argument 2.0 typu double pasuje równie dobrze do parametru typu char jak i do parametru typu int. Poniewa
Ŝ 
do 
Ŝadnego z nich nie pasuje najlepiej, więc wywołanie out(2.0) jest błędne. 
 
Gdyby z programu usuni
ęto dowolną z funkcji out, to wszystkie odwołania do out byłyby poprawne. 
 
 

background image

 

42 

Argumenty domniemane 

 
W  deklaracji  parametru  funkcji  moŜe  wystąpić  inicjator  wyraŜeniowy  określający  domniemaną  wartość 
argumentu kojarzonego z tym parametrem. 
 

int sum(int a, int b =0, int c =0); 

 
Jeśli pewien parametr wyposaŜono w argument domniemany, to kaŜdy z następnych parametrów takŜe musi być 
wyposaŜony w argument domniemany. 
 

int sum(int a, int b =0, int c);   // bł

ą

 
Z  kaŜdym  parametrem  nie  wyposaŜonym  w  argument  domniemany  musi  być  skojarzony  jawny  argument. 
Końcowy zestaw argumentów, dla których podano domniemania, moŜna pominąć. W ich miejscu zostaną uŜyte 
argumenty domniemane. 
 

#include <iostream.h> 
 
int sum(int a, int b, int c =0, int d =0); 
 
int main(void)  

    cout << sum(1, 2, 3) << endl;   // 6 
    cout << sum(1, 2) << endl;      // 3 
    cout << sum(1) << endl;         // bł

ą

 
    return 0; 

 
int sum(int a, int b, int c, int d) 

    return a + b + c + d; 

 
 

dla dociekliwych 

 
WyraŜenie  określające  wartość  argumentu  domniemanego  nie  musi być wyraŜeniem stałym. W takim wypadku 
jest opracowywane w kontekście jego deklaracji, a nie w kontekście jego uŜycia. 
 

#include <iostream.h> 
 
int p = 20; 
 
int sub(int a =p*p) 

    return a; 

 
int main(void)  

    int p = 10; 
    cout << sub() << endl;   // 400 
    ::p = 10; 
    cout << sub() << endl;   // 100 
 
    return 0; 

 
 

Wywołania rekurencyjne 

 

background image

 

43 

Wywołanie  funkcji  jest  rekurencyjne,  jeśli  nastąpi  przed  powrotem  z  jej  poprzedniego  wywołania.  UŜycie 
rekurencji  moŜe  uczynić  program  czytelniejszym,  ale  w  wielu  wypadkach  powoduje  zwiększenie  rozmiaru 
pamięci operacyjnej niezbędnej do jego wykonania. 
 

#include <iostream.h> 
#include <limits.h> 
#include <stdlib.h> 
 
int sqrt(int par, int min =0, int max =INT_MAX) 

    int mid = (min + max) / 2; 
    if(mid == min) 
        return mid; 
    if(par < double(mid) * mid) 
        return sqrt(par, min, mid); 
    else 
        return sqrt(par, mid, max); 

 
int main(void) 
{     
    int val; 
    cin >> val; 
    val = abs(val); 
    cout << "sqrt(" << val << ") = " <<  
            sqrt(val) << endl; 
 
    return 0; 

 
Funkcja  sqrt  dostarcza  pierwiastek  z  jej  nieujemnego  argumentu.  Nieobowiązkowe  argumenty  dodatkowe 
okre
ślają przedział, w którym znajduje się pierwiastek. 
 
 

Definiowanie funkcji 

 
Zdefiniowanie  funkcji  polega  na  podaniu  jej  ciała.  Dobry  styl  programowania  poznaje  się  po  uŜyciu  wielu 
krótkich, a nie małej liczby długich funkcji. 
 
Tak  dalece  jak  jest  to  moŜliwe,  naleŜy  posługiwać  się  funkcjami  bibliotecznymi.  Ilustruje  to  następujący 
program, który napisano w dwóch wersjach: z uŜyciem i bez uŜycia funkcji bibliotecznych. 
 

#include <iostream.h> 
#include <iomanip.h> 
#include <string.h> 
 
const int Size = 100; 
 
int main(void)  

    char srcOne[Size], 
         srcTwo[Size]; 
 
    cin >> setw(Size) >> srcOne >> 
           setw(Size) >> srcTwo; 
 
    char trg[2*Size-1]; 
 
    strcat(strcpy(trg, srcOne), " "); 
    int len = strlen(strcat(trg, srcTwo)); 
 
    cout << trg << endl << len << endl; 
 
    return 0; 

 

background image

 

44 

Program  wprowadza  dwa  łańcuchy,  łączy  je  oddzielając  spacją,  a  następnie  wyprowadza:  łańuch  docelowy, 
długo
ść łańcucha docelowego i wynik porównania łańcuchów źródłowych. 
 

#include <iostream.h> 
 
const int Size = 100; 
 
int strLen(char *ptr); 
char *strCpy(char *pTrg, char *pSrc); 
char *strCat(char *pTrg, char *pSrc); 
int strCmp(char *pOne, char *pTwo); 
 
int main(void)  

    char srcOne[Size], 
         srcTwo[Size]; 
 
    cin >> setw(Size) >> srcOne >> 
           setw(Size) >> srcTwo; 
 
    char trg[2*Size-1]; 
 
    strCat(strCpy(trg, srcOne), " "); 
    int len = strLen(strCat(trg, srcTwo)); 
 
    cout << trg << endl << len << endl; 
 
    cout << srcOne << ' '; 
    char chr = '='; 
    switch(strCmp(srcOne, srcTwo)) { 
        case +1: 
            chr = '>'; 
            break; 
        case -1: 
            chr = '<'; 
            break; 
    } 
    cout << chr << ' ' << srcTwo << endl;     
 
    return 0; 

 
int strLen(char *ptr) 

    int len = 0; 
    while(*ptr++) 
        len++; 
    return len; 

 
char *strCpy(char *pTrg, char *pSrc) 

    char *pTrg2 = pTrg; 
    while(*pTrg++ = *pSrc++); 
    return pTrg2; 

 
char *strCat(char *pTrg, char *pSrc) 

    char *pTrg2 = pTrg; 
    strCpy(pTrg += strLen(pTrg), pSrc); 
    return pTrg2; 

 
int strCmp(char *pOne, char *pTwo) 

    while(*pOne || *pTwo) 
        if(*pOne++ != *pTwo++) 
            if(pOne[-1] > pTwo[-1]) 
                return +1; 
            else  
                return -1; 

background image

 

45 

    return 0; 

 

background image

 

46 

Zarządzanie pamięcią 

 
 
 
 
Wykonanie programu polega na przepływie sterowania przez jego deklaracjedefinicje instrukcje
 
W pierwszej kolejności sterowanie przepływa przez wszystkie deklaracje globalne (takie, które nie wchodzą w 
skład  innych  deklaracji).  Następnie  jest  wyszukiwana  funkcja  główna  i  sterowanie  przepływa  przez  zawarte  w 
niej  instrukcje.  Przepływ  sterowania  kończy  się  po  powrocie  z  wywołania  funkcji  exit  albo  po  wykonaniu 
instrukcji powrotu z funkcji głównej. 
 

#include <iostream.h> 
#include <stdlib.h> 
 
int main(void) 

    int num; 
    cin >> num; 
    if(num != 0) { 
        cout << num << endl; 
        exit(num); 
    } 
 
    return 0; 

 
W  zaleŜności  od  tego,  jaką  wartość  ma  wprowadzona  liczba,  program  kończy  się  po  napotkaniu  instrukcji 
powrotu albo po wywołaniu funkcji exit
 
 

Zmienne statyczne 

 
Jeśli  sterowanie  przepłynie  przez  definicję  zmiennej  globalnej,  albo  przez  definicję  zmiennej  lokalnej 
zadeklarowanej  ze  specyfikatorem  static,  to  zostanie  utworzona  zmienna  statyczna.  TuŜ  przed  zakończeniem 
wykonywania  programu  wszystkie  zmienne  statyczne  zostaną  zniszczone.  Odbędzie  się  to  w  kolejności 
odwrotnej do ich tworzenia. 
 
Uwaga:  Zmienna  statyczna  jest  tworzona  w  obszarze  statycznym.  Inicjator  zmiennej  statycznej  jest  brany  pod 
uwagę tylko podczas pierwszego opracowania jej deklaracji. 
 

#include <iostream.h> 
 
int main(void)  

    void fun(int par); 
 
    fun(10); 
    static int one = 1; 
    fun(20); 
 
    return 0; 

 
int two = 2; 
 
void fun(int par) 

    static int loc = par; 
    cout << loc << ' ' << par << endl; 
    loc++; 

background image

 

47 

 
Zmienne  statyczne  one,  two,  loc  zostaną  utworzone  w  kolejności:  two,  loc,  one,  a  zostaną  zniszczone  w 
kolejno
ści: oneloctwo
 
Program wyprowadzi dwie pary liczb: 10 10 11 20
 
 

Zmienne automatyczne 

 
Jeśli sterowanie przepłynie przez definicję zmiennej lokalnej, nie zadeklarowanej ze specyfikatorem static albo 
extern,  to  zostanie  utworzona  zmienna  automatyczna.  Jawny  albo  niejawny  inicjator  zmiennej  automatycznej 
będzie brany pod uwagę podczas kaŜdego opracowania tej definicji. 
 
Uwaga:  Zmienne  automatyczne  tworzy  się  na  stosie.  Stos  jest  obszarem  pamięci,  w  którym  moŜna  tworzyć 
zmienne, ale takim, Ŝe moŜna je niszczyć tylko w kolejności odwrotnej do ich tworzenia. 
 

void sub(void) 

    int num;         // int num = int(); 
    cout << num;     // bł

ą

    // ... 

 
Zmienną automatyczną num wyposaŜono w niejawny inicjator = int() dostarczający wartość nieokreśloną
 
 
Zmienna  automatyczna  zostanie  zniszczona  tuŜ  przed  zakończeniem  wykonywania  bloku  (wnętrza  instrukcji 
grupującej), w którym ją zadeklarowano. Jeśli w bloku zadeklarowano więcej niŜ jedną zmienną automatyczną, 
to ich niszczenie odbędzie się w kolejności odwrotnej do ich tworzenia, ale przed przystąpieniem do niszczenia 
zmiennych statycznych. 
 
 

#include <iostream.h> 
 
int main(void)  

    int cnt = 2; 
    while(cnt > 0) { 
        int val = cnt--; 
        cout << val << endl; 
    } 
 
    return 0; 

 
int one = 10; 

 
Najpierw  zostanie  utworzona  zmienna  statyczna  one,  a  po  niej  zmienna  automatyczna  cnt.  Następnie  zostanie 
utworzona  i  zniszczona  zmienna  automatyczna  val  zainicjowana  warto
ścią  2,  a  po  tym  zostanie  utworzona  i 
zniszczona  zmienna  automatyczna  val  zainicjowana  warto
ścią  1.  TuŜ  przed  wykonaniem  instrukcji  powrotu 
zostanie zniszczona zmienna cnt, a po niej zmienna one
 
 

Zmienne kontrolowane 

 
Zmienna  kontrolowana  powstaje  na  skutek  wykonania  operacji  new,  a  jest  niszczona  po  jawnym  wykonaniu 
operacji delete. Zmienne kontrolowane są tworzone na stercie. Sterta jest obszarem pamięci, do którego moŜna 
dokładać zmienne, a następnie usuwać je w dowolnej kolejności. 
 

background image

 

48 

Jeśli  wykonanie  operacji  new  jest  niemoŜliwe,  poniewaŜ  wyczerpano  obszar  sterty,  to  rezultatem  operacji 
przydzielenia pamięci jest wskaźnik pusty (o wartości reprezentowanej przez 0). 
 
Uwaga: Programiści rzadko badają rezultat operacji new, bo są z natury optymistami. 
 

int *ptr = new char [10000000]; 
if(ptr == 0) { 
    cout << "No memory" << endl; 
    exit(-1); 

 
 

Zmienne skalarne  

 
Wykonanie operacji 
 

 

new Type 

 
w której Type jest opisem typu skalarnego (tj. nie-tablicowego!), powoduje utworzenie na stercie zmiennej typu 
Type. Rezultatem operacji jest wskaźnik zainicjowany wskazaniem utworzonej zmiennej. 
 
Wykonanie operacji 
 

 

delete ptr 

 
w której ptr wskazuje zmienną utworzoną na stercie, powoduje zniszczenie tej zmiennej. 
 

#include <iostream.h> 
 
int main(void)  

    int *pOne = new int; 
    double &two = *new double; 
    two = 2.8; 
    *pOne = (int)two; 
    cout << *pOne << endl;   // 2 
    delete pOne; 
    delete &two; 
 
    return 0; 

 
Najpierw zostanie utworzona zmienna typu int, a następnie zmienna typu double. Najpierw zostanie zniszczona 
zmienna typu int, a nast
ępnie zmienna typu double
 
 

Zmienne tablicowe 

 
Wykonanie operacji 
 

 

new Type 

 
w której Type jest opisem typu tablicowego (np. int [12]), powoduje utworzenie na stercie zmiennej typu Type
Rezultatem operacji jest wskaźnik zainicjowany wskazaniem zerowego elementu utworzonej tablicy. 
 
Jeśli elementami tablicy są obiekty, to do ich zainicjowania jest niejawnie stosowany konstruktor domyślny. 
 
Uwaga: WyraŜenie określające liczbę elementów tablicy nie musi być wyraŜeniem stałym. 
 
Wykonanie operacji 
 

 

delete [] ptr 

background image

 

49 

 
w której ptr wskazuje zerowy element tablicy utworzonej na stercie, powoduje zniszczenie tej tablicy. 
 

#include <iostream.h> 
#include <string.h> 
 
int main(void)  

    char *ptr = new char [100]; 
    cin >> ptr; 
    char &vec = *new char [strlen(ptr) + 1]; 
    cout << strcpy(&vec, ptr) << endl; 
    delete [] ptr; 
    delete [] &vec; 
 
    return 0; 

 
Program tworzy na stercie 100-elementową tablicę znakową i wprowadza do niej ciąg znaków. Następnie tworzy 
na  stercie  najmniejsz
ą  tablicę,  w  której  moŜna  pomieścić  wprowadzony  ciąg  znaków  oraz  tworzy  na  stosie 
odno
śnik vec identyfikujący zerowy element tej tablicy. 
 
Przed zako
ńczeniem wykonywania program niszczy obie tablice, w kolejności ich utworzenia. 
 
 

OstrzeŜenie 

 
W Ŝadnym wypadku nie wolno zmiennej utworzonej za pomocą operacji new dla zmiennych skalarnych niszczyć 
za  pomocą  operacji  delete  dla  zmiennych  tablicowych,  a  zmiennej  utworzonej  za  pomocą  operacji  new  dla 
zmiennych tablicowych niszczyć za pomocą operacji delete dla zmiennych skalarnych. 
 
Nie  wolno  takŜe  uŜywać  operacji  delete  ze  wskaźnikiem  ptr  identyfikującym  co  innego  niŜ  zmienna  skalarna 
albo  zerowy  element  tablicy  utworzonej  za  pomocą  operacji  new,  ani  przyjmować,  Ŝe  po  wykonaniu  operacji 
delete wskaźnik ptr ma wartość określoną. 
 
Uwaga: W celu uniknięcia trudnych do wykrycia błędów, zaleca się (o ile to moŜliwe) zerowanie wskaźnika ptr 
bezpośrednio po uŜyciu go w operacji delete
 

#include <iostream.h> 
 
int main(void)  

    int *ptr = new int [5]; 
    delete [] (ptr + 2);        // bł

ą

 
    int &vec = *new int [5]; 
    delete &vec;                // bł

ą

 
    int &ref = (*new int) = 3; 
    delete &ref; 
    cout << ref << endl;        // bł

ą

 
    return 0; 

 
Mimo  iŜ  program  jest  poprawny  składniowo,  zawiera  3  powaŜne  błędy  logiczne.  Wykonany  w  środowisku 
Visual C++, program ten załamuje system zarz
ądzania stertą

background image

 

50 

Widoczność deklaracji 

 
 
 
 
Identyfikatorem zmiennejfunkcji typu moŜna posługiwać się tylko w miejscu, w którym jest widoczna jego 
deklaracja.  
 
Zaleca  się,  aby  w  tym  samym  zakresie,  identyfikator  uŜyty  do  zadeklarowania  zmiennej,  funkcji  albo  typu  nie 
został uŜyty do zadeklarowania innej zmiennej, funkcji albo typu. 
 
Uwaga: Podano zalecenie, a nie zakaz, poniewaŜ w tym samym zakresie mogą wystąpić, nie kolidujące za sobą, 
deklaracje funkcji i typu. 
 

void id(int id) 
{     
    struct id { 
    }; 
    extern void id(id id); 
    int id = 10;    // bł

ą

 
Z  kaŜdą  deklaracja  jest  związany  jej  zakres  i  zasięg.  Jeśli  w  pewnym  module  zdefiniowano  identyfikator  o 
zasięgu globalnym, a w innym zadeklarowano go ze specyfikatorem extern, to oba dotyczą tej samej zmiennej, 
funkcji albo typu. 
 
plik Main.cpp 
 

#include <iostream.h> 
 
int fix = 10;                // definicja 
 
int main(void) 

    extern void fun(void);   // deklaracja 
    fun(); 
 
    return 0; 

 
 
plik One.cpp 
 

#include <iostream.h> 
 
void fun()                   // definicja 

    extern int fix;          // deklaracja 
    cout << fix << endl;     // 10 

 
Gdyby  pominięto  wszystkie  specyfikatory  extern,  to  program  stałby  się  statycznie  poprawny,  ale  dynamicznie 
ędny. Błąd polegałby na uŜyciu wartości zmiennej, której nie zainicjowano. 
 
 

Deklaracje lokalne 

 
Zakresem  deklaracji  identyfikatora  zadeklarowanego  w  bloku  jest  obszar  programu  od  punktu  zadeklarowania 
do  końca  bloku.  Zasięgiem  deklaracji  jest  ta  część  zakresu,  która  nie  jest  zakresem  innej  deklaracji  takiego 
samego identyfikatora. 

background image

 

51 

 
 

#include <iostream.h> 
 
int main(void)  

    int num = 10; 
    cout << num << endl;       // 10 
    { 
        cout << num << endl;   // 10 
        int num = 20; 
        cout << num << endl;   // 20 
    } 
    cout << num << endl;       // 10 
 
    return 0; 

 
Zakresem  deklaracji  pierwszej  zmiennej  num  jest  obszar  zaczynający  się  od  = 10  i  kończący  na  klamrze 
zamykaj
ącej funkcję main
 
Zakresem  deklaracji  drugiej  zmiennej  num  jest  obszar  zaczynaj
ący  się  od  = 20  i  kończący  na  klamrze 
zamykaj
ącej blok wewnętrzny. 
 
Zasi
ęgiem  deklaracji  pierwszej  zmiennej  num  jest  zakres  deklaracji  pierwszej  zmiennej  num,  pomniejszony  o 
zakres deklaracji drugiej zmiennej num
 
 

Deklaracje globalne 

 
Zakresem  deklaracji  identyfikatora  zadeklarowanego  w  module  (tj.  poza  blokiem),  jest  obszar  programu  od 
punktu  zadeklarowania  do  końca  modułu.  Zasięgiem  deklaracji  jest  ta  część  zakresu,  która  nie  jest  zakresem 
innej deklaracji takiego samego identyfikatora. 
 
Uwaga: Modułem jest zawartość pliku *.cpp projektu, po zastosowaniu uŜytych w nim dyrektyw (#include#if
#endif, itp.). 
 

#include <iostream.h> 
 
int num = 10; 
 
int main(void)  

    cout << num << endl;       // 10 
    { 
        cout << num << endl;   // 10 
        int num = 20; 
        cout << num << endl;   // 20 
    } 
    cout << num << endl;       // 10 
 
    return 0; 

 
int num2 = num; 

 
Zasięg deklaracji pierwszego identyfikatora num obejmuje m.in. deklarację występującą po funkcji main. 
 
 

Deklaracje i definicje 

 

background image

 

52 

Jeśli  deklaracja  globalna  zawiera  specyfikator  static,  to  jest  widoczna  tylko  w  jej  module.  Jeśli  deklaracja 
globalna  jest  definicją,  ale  nie  zawiera  specyfikatora  static,  to  jest  widoczna  w  tych  obszarach  pozostałych 
modułów  programu,  w  których  jest  widoczna  zgodna  z  nią  deklaracja ze specyfikatorem extern bez inicjatora, 
nie dotycząca deklaracji globalnej ze specyfikatorem static
 
Uwaga:  Globalne  zmienne  ustalone  są  domyślnie  wyposaŜone  w  specyfikator  static.  Specyfikator  extern 
występujący w deklaracji funkcji moŜna pominąć. 
 
plik Main.cpp 
 

#include <iostream.h> 
 
int main(void)  

    int fun(void);           // pomini

ę

to extern 

    cout << fun() << endl;   // 10 
 
    extern int num;  
    cout << num << endl;     // 20 
 
    return 0; 

 
 
plik One.cpp 
 

static int num = 10; 
 
int fun(void) 

    extern int num;          // zb

ę

dne 

    return num; 

 
 
plik Two.cpp 
 

int num = 20; 

 
 

Deklaracje typów 

 
Globalna deklaracja typu, na przykład  
 

 

struct Child; 

 
nie wystarczy do tego, aby moŜna było nawiązać do definicji tego typu podanej w innym module. 
 
W  odróŜnieniu  od  definicji  zmiennej  i  funkcji,  która  w  zbiorze  modułów  programu  moŜe  wystąpić tylko jeden 
raz, definicja struktury musi być powtórzona w kaŜdym z odwołujących się do niej modułów. 
 
plik Main.cpp 
 

#include <iostream.h> 
 
struct Child { 
    char name[20]; 
    int age; 
}; 
 
int main(void)  

    Child getIsa(void); 
    Child isa = getIsa(); 

background image

 

53 

    cout << isa.name << " is " << 
            isa.age << endl; 
 
    return 0; 

 
 
plik Isa.cpp 
 

struct Child { 
    char name[20]; 
    int age; 
}; 
 
Child isa = { "Isabel", 15 }; 
 
Child getIsa(void) 

    return isa; 

 
albo lepiej i bezpieczniej 
 
plik child.h 
 

struct Child { 
    char name[20]; 
    int age; 
}; 

 
 
plik Main.cpp 
 

#include <iostream.h> 
#include "child.h" 
 
int main(void)  

    Child getIsa(void); 
    Child isa = getIsa(); 
    cout << isa.name << " is " << 
            isa.age << endl; 
 
    return 0; 

 
 
plik Isa.cpp 
 

#include "child.h" 
 
Child isa = { "Isabel", 15 }; 
 
Child getIsa(void) 

    return isa; 

 

background image

 

54 

Studia programowe 

 
Przedstawiono dwa rozwiązania następującego problemu 
 
 

Napisać program, który wprowadza z pliku sekwencję danych arytmetycznych, a następnie wyprowadza 
ich  
średnie  odchylenie  standardowe:  pierwiastek  z  sumy  kwadratów  róŜnic  dana-średnia,  podzielony 
liczb
ę danych. 

 
W  szczególności,  jeśli  w  pliku  Data.txt  umieści  się  liczby  6  9  12,  a  jako  argument  programu  poda  Data.txt 
(polecenie Project / Settings // Debug), to nastąpi wyprowadzenie liczby 1.41421
 
 

Struktura tablicowa 

 

#include <iostream.h> 
#include <fstream.h> 
#include <math.h> 
 
int readData(char *fileName, double *&pData); 
double getAverage(double *pData, int count); 
double getResult(double *pData, int count, double average); 
void freeMemory(double *pData); 
 
int main(int noOfArgs, char *pArg[]) 

    if(noOfArgs != 2) { 
        cout << "Usage is: " << pArg[0] <<  
                " fileName" << endl; 
        return -1; 
    } 
    double *pData; 
    char *fileName = pArg[1]; 
    int count = readData(fileName, pData); 
    if(count) { 
        double average = getAverage(pData, count); 
        double result = getResult(pData, count, average); 
        cout << "Result = " << result << endl; 
    } else 
        cout << "Error!" << endl; 
 
    return 0; 

 
int readData(char *fileName, double *&pData) 

    const int start = 200; 
    ifstream inp; 
    inp.open(fileName, ios::in | ios::nocreate); 
    int count = 0; 
    if(inp.is_open()) { 
        pData = new double [start]; 
        int len = start; 
        double tmp; 
        while(tmp = 0, inp >> tmp, tmp) { 
            if(count == len) { 
                double *ptr = new double [len *= 2]; 
                for(int j = 0; j < len /2 ; j++) 
                    ptr[j] = pData[j]; 
                delete [] pData; 
                pData = ptr; 
            } 
            pData[count++] = tmp; 
        } 
    } 
    return count; 

 

background image

 

55 

double getAverage(double *pData, int count) 

    double sum = 0; 
    for(int i = 0; i < count ; i++) 
        sum += pData[i]; 
    return sum / count; 

 
double getResult(double *pData, int count, double average) 

    double sumSqr = 0; 
    for(int i = 0; i < count ; i++) { 
        double dif = pData[i] - average; 
        sumSqr += dif * dif; 
    } 
    return sqrt(sumSqr) / count; 

 
void freeMemory(double *pData) 

    delete [] pData; 

 
 

Struktura listowa 

 

#include <iostream.h> 
#include <fstream.h> 
#include <math.h> 
 
struct Item { 
    Item *pNext; 
    double value; 
}; 
 
struct List { 
    Item *pFirst; 
    int count; 
}; 
 
List list = { 0 }; 
 
int readData(char *fileName, List &list); 
double getAverage(List &list); 
double getResult(List &list, double average); 
void freeMemory(List &list); 
 
int main(int noOfArgs, char *pArg[]) 

    if(noOfArgs != 2) { 
        cout << "Usage is: " << pArg[0] <<  
                " fileName" << endl; 
        return -1; 
    } 
    char *fileName = pArg[1]; 
    int count = readData(fileName, list); 
    if(count) { 
        double average = getAverage(list); 
        double result = getResult(list, average); 
        cout << "Result = " << result << endl; 
    } else 
        cout << "Error!" << endl; 
    freeMemory(list); 
 
    return 0; 

 
int readData(char *fileName, List &list) 

    ifstream inp; 
    inp.open(fileName, ios::in | ios::nocreate); 

background image

 

56 

    int count = 0; 
    if(inp.is_open()) { 
        double tmp; 
        while(tmp = 0, inp >> tmp, tmp) { 
            Item *pItem = new Item; 
            pItem->pNext = list.pFirst; 
            pItem->value = tmp; 
            list.pFirst = pItem; 
            count++; 
        } 
    } 
    return list.count = count; 

 
double getAverage(List &list) 

    double sum = 0; 
    Item *pItem = list.pFirst; 
    while(pItem) { 
        sum += pItem->value; 
        pItem = pItem->pNext; 
    } 
    return sum / list.count; 

 
double getResult(List &list, double average) 

    double sumSqr = 0; 
    Item *pItem = list.pFirst; 
    while(pItem) { 
        double dif = pItem->value - average; 
        sumSqr += dif * dif; 
        pItem = pItem->pNext; 
    } 
    return sqrt(sumSqr) / list.count; 

 
void freeMemory(List &list) 

    Item *pItem = list.pFirst, *pTmp; 
    while(pItem) { 
        pTmp = pItem->pNext; 
        delete pItem; 
        pItem = pTmp; 
    } 

 

 

background image

 

57 

Dodatek A  

 

Priorytety operatorów 

 
 
 
 
Operatory wyszczególniono w kolejnoœci malej¹cego priorytetu. 
 
 

Wi¹zanie  Operator 

 
 

prawe 

:: 

 

lewe 

Type:: 

 

lewe 

[]  .  ->  ()  Type() 

 

lewe 

++  --  (nastêpnikowe) 

 

prawe 

++  --  (poprzednikowe) 

 

prawe 

sizeof  +  -  ~  !  &  *  new  delete  (Type)  throw 

 

lewe 

.*  ->* 

 

lewe 

*  /  % 

 

lewe 

+  - 

 

lewe 

<<  >> 

 

lewe 

<  <=  >  >= 

 

lewe 

==  != 

 

lewe 

& 

 

lewe 

^ 

 

lewe 

| 

 

lewe 

&& 

 

lewe 

|| 

 

prawe 

?: 

 

prawe 

=  *=  /=  %=  +=  -=  <<=  >>=  &=  ^=  |= 

 

lewe 

 
l-nazwą  zmiennej  (por.  Dodatek  B)  jest  tylko:  operacja  przypisania  (np.  a+=b),  przedrostkowego  zwiększenia 
(np. ++a), przedrostkowego zmniejszenia (np. --a), indeksowania (np. ptr[i]), wyłuskania  (np. *ptr), wyboru 
(np.  str.f  i  ptr->f),  warunku  którego  dwa  ostatnie  argumenty  są  l-nazwami  (np.  a>0?a:b),  konwersji  do  typu 
odnośnikowego (np. (int &)a) oraz globalności (np. ::) i zakresu (np. Child::name). 
 

background image

 

58 

 

Dodatek B 

 

Opracowywanie wyraŜeń 

 
 
 
 
WyraŜenia  są  zapisami  operacji.  O  kolejności  wykonywania  operacji  decyduje  sposób  uŜycia  nawiasów  oraz 
uwzględnienie priorytetów wiązań operatorów (por. Dodatek A). 
 
Jeśli  kilka  operatorów  zapisano  spójnie  (tj.  bez  odstępów),  wówczas  za  pierwszy  uznaje  się  najdłuŜszy.  A 
zatem: poniewaŜ w C++ istnieją operatory + i ++, ale nie istnieje operator +++, więc wyraŜenie 
 

 

a +++ b 

 
jest traktowane jak 
 

 

(a++) + b         // a nie jak: a + (++b) 

 
 

Priorytety 

 
PoniewaŜ w C++ priorytet mnoŜenia jest wyŜszy niŜ priorytet dodawania, więc wyraŜenie 
 

 

a + b * c 

 
jest traktowane jak 
 

 

a + (b * c)       // a nie jak: (a + b) * c 

 
 
Podobnie, poniewaŜ w C++ priorytet następnikowej operacji zwiększenia (++) jest wyŜszy niŜ priorytet operacji 
wyłuskania (*), więc wyraŜenie 
 

 

*ptr++ 

 
jest traktowane jak 
 

 

*(ptr++)             // a nie jak: (*ptr)++ 

 
 

Wiązania 

 
PoniewaŜ  w  C++  priorytet  odejmowania  (-)  jest  równy  priorytetowi  dodawania  (+),  więc  jeśli  pewnego 
podwyraŜenia dotyczą oba takie operatory, to odwołanie się do priorytetów nie wystarcza i trzeba odwołać się do 
wiązań.  
 
PoniewaŜ w C++ wiązanie operacji odejmowania i dodawania jest lewe, więc wyraŜenia 
 

 

a - b + c 

 

cout << a << b 

 
są traktowane jak 
 

 

(a - b) + c          // a nie jak a - (b + c) 

background image

 

59 

 

(cout << a) << b     // a nie jak: cout << (a << b) 

 
(środkowe podwyraŜenia dowiązano do lewej). 
 
 
Dla porównania, poniewaŜ wiązanie operacji przypisania jest prawe, więc wyraŜenie 
 

 

a = b = c 

 
jest traktowane jak 
 

 

a = ( b = c)         // a nie jak: (a = b) = c 

 
 

Kolejność 

 
Kolejność opracowywania argumentów operacji jest nieokreślona. Dotyczy to zarówno argumentów wywołania 
funkcji, jak i argumentów operacji dwuargumentowych, takich jak przypisanie. 
 
Dlatego  zaleca  się,  aby  w  wyraŜeniu,  w  którym  następuje  zmiana  wartości  zmiennej,  nie  odwoływano  się 
(dodatkowo!) do tej zmiennej. 
 

fun(cout << 100, cout << 200); 
int tab[4] = { 10, 20, 30 }, 
    pos = 1; 
tab[pos] = ++pos; 

 
Nie  wiadomo,  czy  przed  wykonaniem  ciała  funkcji  fun  zostanie  wyprowadzona  liczba  100  czy  200.  W  Visual 
C++ zostanie wyprowadzona liczba 200
 
Nie wiadomo, czy przypisanie dotyczy elementu tab[1] czy elementu tab[2]. W Visual C++ dotyczy ono tab[2]
 
 

Promocja 

 
Niektóre operacje są wykonywane dopiero po promocji argumentu. Dotyczy to w szczególności zmiennych typu 
char (poddawanych promocji do typu int). 
 

    char chr = 'a'; 
    char &ref1 = chr; 
    char &ref2 = +chr;    // bł

ą

    char &ref3 = 'a';     // bł

ą

 
 

Typ wspólny 

 
Jeśli  argumenty  operacji  są  róŜnych  typów,  to  wykonuje  się  ją  w  ich  typie  wspólnym.  W  szczególności  typem 
wspólnym dla char int jest int, a typem wspólnym dla double int jest double
 
Uwaga:  Jeśli  wyraŜenie  jest  pewnego  typu,  to  nie  oznacza  to,  Ŝe  wszystkie  jego  operacje  wykonuje  się  w  tym 
typie.  
 

#include <iostream.h> 
#include <limits.h> 
 
int main(void) 
{     
    int max = INT_MAX;                   
    cout << max * max << endl;          // 1 (sic!) 
    cout << 0.0 +  max * max << endl;   // 1 (sic!) 
    cout << double(max) * max << endl;  // ok. 4.6e18 

background image

 

60 

 
    return 0; 

 
Mimo iŜ typem wyraŜenia zawierającego liczbę 0.0 jest double, iloczyn max * max jest obliczany w typie int
 
 

Punkty charakterystyczne 

 
Punktem  charakterystycznym  jest  miejsce  w  programie,  w  którym  realizuje  się  wszystkie  "zaległe"  skutki 
uboczne, takie jak operacje wejścia-wyjścia i przypisania. 
 
Punkt  charakterystyczny  występuje  m.in.  po  kaŜdym  kompletnym  wyraŜeniu,  przed  kaŜdym  średnikiem,  przed 
pierwszą instrukcją funkcji oraz przed operatorami koniunkcji i dysjunkcji. 
 
Programy zaleŜne od połoŜenia punktu charakterystycznego naleŜy konstruować ze szczególną ostroŜnością. 
 

int fix = 10; 
++fix = fix; 
cout << fix; 

 
PoniewaŜ  operacja  zwiększenia  (++)  moŜe  być  zrealizowana  dopiero  w  punkcie  charakterystycznym,  więc  nie 
wiadomo, czy zostanie wyprowadzona liczba 10 czy 11. W Visual C++ zostanie wyprowadzona liczba 11
 

Nazwy 

 
KaŜde wyraŜenie i podwyraŜenie (w szczególności zapis operacji), moŜna rozpatrywać jako nazwę pomocniczej 
zmiennej tymczasowej. Podczas opracowywania wyraŜenia, kaŜdą z operacji zastępuje się nazwą jej rezultatu. 
 
Uwaga:  Pomocniczą  zmienną  tymczasową  niszczy  się  bezpośrednio  po  opracowaniu  kompletnego  wyraŜenia, 
którego opracowania wymagało utworzenia tej zmiennej. 
 
W szczególności, jeśli przyjąć, Ŝe zmiennymi tymczasowymi są t1t2 i t3 to instrukcja 
 

cout << 1 + 2 * 3; 

 
jest wykonywana tak, jak  
 

int t1, t2, t3; 
t1
 = 2 * 3, t2 = 1 + t1, cout << t2 

 
a zmienne tymczasowe zostaną zniszczone w chwili, gdy sterowanie "przepłynie przez średnik". 
 

l-nazwy 

 
Przyjmuje  się  z  definicji,  Ŝe  l-nazwą  jest  tylko:  identyfikator  zmiennej  nie-ustalonej,  rezultat  funkcji  o  typie 
odnośnikowym  oraz  rezultat  operacji  wymienionych  w  Dodatku  A.  Nie  jest  l-nazwą  literał,  ani  wskaźnik 
powstały z niejawnego przekształcenia nazwy tablicy. 
 
Posługując się taką definicją moŜna podać następujące wymagania 
 
1)  Odnośnik  do  zmiennej  nie-ustalonej  moŜe  być  zainicjowany  tylko  takim  wyraŜeniem,  które  jest  l-nazwą 
zmiennej. 
np.  

 

const int fix1 = 10; 

 

int &fix2 = 20;        // bł

ą

 
2)  Argumentem operacji zwiększenia (++), zmniejszenia (--), wskazywania (&) i wyboru (. i ->) moŜe być tylko 
takie wyraŜenie, które jest l-nazwą zmiennej. 

background image

 

61 

np. 

 

int fix = 10; 

 

++(int)fix;            // bł

ą

 

fix++++;               // bł

ą

 

int *ptr = &20;        // bł

ą

 
3)  Lewym argumentem przypisania (=+= itp.) moŜe być tylko takie wyraŜenie, które jest l-nazwą zmiennej. 
np. 

 

int fix = 10; 

 

fix++ = 20;            // bł

ą

 

int tab[] = { 10 }; 

 

tab = 20;              // bł

ą

 
Uwaga:  Niepoprawność  operacji  fix++++  wynika  stąd,  Ŝe  fix++  nie  jest  l-nazwą,  a  więc  nie  moŜe  być 
argumentem ponownej operacji zwiększenia. 

background image

 

62 

Dodatek C 

 

Konwersje standardowe 

 
 
 
 
Konwersją  standardową,  jest  taka  predefiniowana  konwersja,  która  moŜe  być  wstawiona  do  programu 
niejawnie
 
Konwersjami standardowymi są m.in.:  
 
0)  Przekształcenie promocyjne (np. zmiennej typu char w zmienna typu int). 
1)  Przekształcenie zmiennej arytmetycznej albo wskaźnika w orzecznik (np. zmiennej typu int w zmienną typu 

bool). 

2)  Przekształcenie  zmiennej  arytmetycznej  w  zmienną  arytmetyczną  innego  typu  (np.  zmiennej typu double w 

zmienną typu int). 

3)  Przekształcenie nazwy tablicy na wskaźnik do jej zerowego elementu. 
4)  Przekształcenie nazwy zmiennej na odnośnik do tej zmiennej. 
5)  Przekształcenie wskaźnika do obiektu na wskaźnik do jego podobiektu. 
6)  Przekształcenie odnośnika do obiektu na odnośnik do jego podobiektu. 
7)  Przekształcenie wskaźnika do zmiennej na wskaźnik lokalizujący tę zmienną. 
 
Nie są nimi m.in. 
 
1)  Przekształcenie wskaźnika do elementu tablicy na wskaźnik do tej tablicy. 
2)  Przekształcenie wskaźnika do tablicy na wskaźnik do jej elementu. 
3)  Przekształcenie wskaźnika do podobiektu na wskaźnik do jego obiektu. 
4)  Przekształcenie odnośnika do podobiektu na odnośnik do jego obiektu. 
5)  Przekształcenie wskaźnika lokalizującego zmienną na wskaźnik do tej zmiennej. 
 
Uwaga: Poza konwersjami standardowymi, niejawne moŜe być zastosowany jedynie konstruktor konwerter

background image

 

63 

Dodatek D 

 

Operatory bitowe 

 
 
 
 
Operatorami bitowymi są: ~ (zanegowanie bitów), & (iloczyn bitów), | (suma bitów), ^ (suma modulo 2 bitów), 
<< (przesunięcie bitów w lewo), >> (przesunięcie bitów w prawo). 
 
Podczas wykonywania operacji na bitach przydatne okazują się literały szesnastkowe. Literał szesnastkowy ma 
postać 0xh, w której h jest spójnym ciągiem cyfr szesnastkowych (0-9 i a-f). 
 

cout << 0x12;      // 18 
cout << 0xffff;    // 65535 

 
 

Operator ~ 

 
Operacja zanegowania bitów ma postać 
 

 

~exp 

 
w której exp jest wyraŜeniem całkowitym. 
 
Rezultatem  operacji  zanegowania  bitów  jest  zmienna  tymczasowa  takiego  samego  typu  jak  zmienna  exp,  po 
poddaniu jej  promocjom, a następnie zanegowaniu kaŜdego jej bitu. 
 
Uwaga: Negacją bitu 1 jest bit 0, a negacją bitu 0 jest bit 1
 

int red = 1, green = 2, blue = 4; 
int hue = red | green;        // ... 011 (kolor 

Ŝ

ółty) 

hue = ~hue;                   // ... 100 (kolor niebieski) 

 
Trzy  najmniej  znaczące  bity  zmiennej  hue  reprezentują  jeden  z  8  kolorów.  Wykonanie  operacji  zanegowania 
bitów powoduje zmian
ę koloru na dopełniający. 
 
 

Operator & 

 
Operacja iloczynu bitów ma postać 
 

 

expL & expR 

 
w której expL i expR są wyraŜeniami całkowitymi. 
 
W  celu  utworzenia  wyniku  operacji,  zmienne  expL  i  expR  poddaje  się  konwersjom  do  typu  wspólnego,  a 
następnie  kaŜdy  bit  wyniku  tworzy  się  z  odpowiadających  sobie  bitów  argumentów  wyznaczając  ich  iloczyn 
logiczny
.  
 
Uwaga: Iloczyn logiczny pary bitów ma wartość 1 tylko wówczas gdy oba bity są jedynkowe
 

int fix = 6;                  //    00 ... 110 
const int mask = '\x3';       //    00 ... 011 
fix &= ~mask;              
cout << Fix;                  // 4 (00 ... 100) 

 

background image

 

64 

Wykonanie  operacji  na  zmiennej  fix  powoduje  wyzerowanie  tych  wszystkich  jej  bitów,  które  w  mask  są 
jedynkowe.
 
 
 

Operator ^ 

 
Operacja sumy modulo 2 bitów ma postać 
 

 

expL ^ expR 

 
w której expL i expR są wyraŜeniami całkowitymi. 
 
W  celu  utworzenia  wyniku  operacji,  zmienne  expL  i  expR  poddaje  się  konwersjom  do  typu  wspólnego,  a 
następnie  kaŜdy  bit  wyniku  tworzy  się  z  odpowiadających  sobie  bitów  argumentów  wyznaczając  ich  sumę 
logiczn
ą modulo 2
 
Uwaga: Suma logiczna modulo 2 pary bitów ma wartość 1 tylko wówczas gdy bity są Ŝne
 

int fix = 6;                  //    00 ... 110 
const int mask = '\x3';       //    00 ... 011 
fix ^= mask;              
cout << fix;                  // 5 (00 ... 101) 

 
Wykonanie  operacji  na  zmiennej  fix  powoduje  zanegowanie  tych  wszystkich  jej  bitów,  które  w  mask  są 
jedynkowe.
 
 
 

Operator | 

 
Operacja sumy bitów ma postać 
 

 

expL expR 

 
w której expL i expR są wyraŜeniami całkowitymi. 
 
W  celu  utworzenia  wyniku  operacji,  zmienne  expL  i  expR  poddaje  się  konwersjom  do  typu  wspólnego,  a 
następnie  kaŜdy  bit  wyniku  tworzy  się  z  odpowiadających  sobie  bitów  argumentów  wyznaczając  ich  sumę 
logiczn
ą.  
 
Uwaga: Suma logiczna pary bitów ma wartość 0 tylko wówczas gdy oba bity są zerowe
 

int fix = 5;                  //    00 ... 101 
const int mask = '\x3';       //    00 ... 011 
fix |= mask;              
cout << Fix;                  // 7 (00 ... 111) 

 
Wykonanie operacji na zmiennej fix powoduje ustawienie tych wszystkich jej bitów, które w mask są jedynkowe. 
 
 

Operator << 

 
Operacja przesunięcia bitów w lewo ma postać 
 

 

expL << n 

 
w której expL i n są wyraŜeniami całkowitymi. 
 
W celu utworzenia wyniku operacji, zmienną expL poddaje się promocji, a następnie kaŜdy bit wyniku tworzy 
się z bitów tej nowej zmiennej po przesunięciu ich o n pozycji w lewo
 

background image

 

65 

Uwaga: Podczas przesuwania w lewo bity najbardziej znaczące są odrzucane, a na pozycje najmniej znaczące 
wchodzą bity 0
 

int fix = 7;                  // 00 ... 0111 
fix <<= 2; 
cout << fix;                  // 28 (00 ... 011100) 

 
Bity zmiennej fix przesunięto o 2 pozycje w lewo. 
 
 

Operator >> 

 
Operacja przesunięcia bitów w prawo ma postać 
 

 

expL >> n 

 
w której expL i n są wyraŜeniami całkowitymi. 
 
W  celu  utworzenia  wyniku operacji, zmienną expL poddaje się promocji, a następnie kaŜdy bit wyniku tworzy 
się z bitów tej nowej zmiennej po przesunięciu ich o n pozycji w prawo
 
Uwaga: Podczas przesuwania w prawo bity najmniej znaczące są odrzucane
 

int fix = 15;                 // 00 ... 01111 
fix >>= 2; 
cout << fix;                  // 3 (00 ... 011) 

 
Bity zmiennej fix przesunięto o 2 pozycje w prawo. 
 

background image

 

66 

Dodatek E 

 
 
 
 
 
 
 

Operacje wejścia-wyjścia 

 
 
 
 
Większość operacji wejścia-wyjścia moŜna wykonać za pomocą operatorów. Do specjalnych celów przydają się 
niekiedy funkcje wejścia-wyjścia. 
 
 

Funkcje get i put 

 

inp.get(chr) 

Wprowadza  ze  strumienia  inp  najbliŜszy  znak  (w  tym  znak  odstępu)  i  jego  kod  przypisuje  zmiennej  chr  typu 
char. Dostarcza odnośnik do inp
 

 

out.put(chr) 

Wyprowadza do strumienia out znak o kodzie chr. Dostarcza odnośnik do out
 

#include <iostream.h> 
#include <fstream.h> 
#include <string.h> 
 
int main(void) 

    ifstream inp; 
    inp.open("Data.txt", ios::in); 
    if(!inp.is_open()) 
        return -1; 
 
    char chr; 
    while(inp.get(chr)) 
        cout.put(chr); 
 
    return 0; 

 
Program kopiuje na konsolę zawartość pliku Data.txt. Kopiowanie odbywa się znak-po-znaku. 
 
 

Funkcje read i write 

 

inp.read(ptr, len) 

Wprowadza  ze  strumienia  inp  ciąg  len  najbliŜszych  znaków  i  ich  kody  umieszcza  w  tablicy  znakowej  o 
elemencie wskazywanym przez ptr. Dostarcza odnośnik do inp
 

out.write(ptr, len) 

Wyprowadza  do  strumienia  out  ciąg  len  znaków  z  tablicy  znakowej,  począwszy  od  elementu  wskazywanego 
przez ptr. Dostarcza odnośnik do out
 
inp.gcount() 

background image

 

67 

Dostarcza liczbę znaków wprowadzonych za pomocą ostatnio wywołanej funkcji read albo getline
 

#include <iostream.h> 
#include <fstream.h> 
 
const int Size = 10; 
 
int main(void) 

    ifstream inp; 
    inp.open("Data.txt", ios::in); 
    if(!inp.is_open()) 
        return -1; 
 
    char buf[Size]; 
    while(true) { 
        inp.read(buf, Size); 
        int len = inp.gcount(); 
        if(len > 0) 
            cout.write(buf, len); 
        if(len < Size) 
            break; 
    } 
 
    return 0; 

 
Program kopiuje na konsolę zawartość pliku Data.txt. Kopiowanie odbywa się porcjami po Size znaków. 
 
 

Funkcja getline 

 

inp.getline(ptr, len) 

Wprowadza ze strumienia inp jeden wiersz, ale nie więcej niŜ len-1 najbliŜszych znaków, a ich kody, bez kodu 
'\n', ale z dodatkowym kodem 0, umieszcza w tablicy znakowej o elemencie wskazywanym przez ptr. Dostarcza 
odnośnik do inp
 

#include <iostream.h> 
#include <fstream.h> 
 
const int Size = 100; 
 
int main(void) 

    ifstream inp; 
    inp.open("Data.txt", ios::in); 
    if(!inp.is_open()) 
        return -1; 
 
    char buf[Size]; 
    while(inp) { 
        inp.getline(buf, Size); 
        int len = inp.gcount(); 
        if(len > 0) 
            cout << buf << endl; 
    } 
 
    return 0; 

 
Program kopiuje na konsolę zawartość pliku Data.txt. Kopiowanie odbywa się wierszami. 
 
 

Funkcje peek i putback 

 

inp.peek() 

Dostarcza kod najbliŜszego znaku strumienia inp, ale znaku ze strumienia nie wprowadza (sic!). 

background image

 

68 

 

inp.putback(chr) 

Cofa do strumienia inp znak o kodzie chr. Dostarcza odnośnik do inp
 

#include <iostream.h> 
#include <fstream.h> 
#include <ctype.h> 
 
const int Size = 100; 
 
int main(void) 

    ifstream inp; 
    inp.open("Data.txt", ios::in); 
    if(!inp.is_open()) 
        return -1; 
 
    while(inp) { 
        char chr; 
        inp >> chr; 
        inp.putback(chr); 
        if(chr == '-' || chr == '+' || isdigit(chr)) { 
            double num; 
            inp >> num; 
            cout << num << endl; 
        } else { 
            char buf[Size]; 
            inp >> buf; 
            cout << buf << endl; 
        } 
    } 
 
    return 0; 

 
Program wprowadza z pliku Data.txt zawarte w nim liczby i łańcuchy, a następnie wyprowadza je na konsolę
ka
Ŝdy w osobnym wierszu. 
 
 

Funkcje tellg i tellp 

 
Funkcje  tellg  i  tellp  słuŜą  do  określania  pozycji  pliku.  Pozycja  jest  daną  typu  streampos.  W  Visual  C++  typ 
streampos jest identyczny z typem int
 

inp.tellg() 

Dostarcza bieŜącą pozycję pliku otwartego w trybie ios::in
 

inp.tellp() 

Dostarcza bieŜącą pozycję pliku otwartego w trybie ios::out
 

#include <iostream.h> 
#include <fstream.h> 
 
int main(void) 

    ifstream inp; 
    inp.open("C:\\config.sys", ios::in); 
    if(!inp.is_open()) 
        return -1; 
 
    char chr; 
    while(inp >> chr) 
        ; 
    streampos pos = inp.tellg(); 
    cout << "Size = " << pos << endl; 
 
    return 0; 

background image

 

69 

 
Program wyznacza rozmiar pliku config.sys.
 
 
 

Funkcje seekg i seekp 

 
Funkcje  seekg  i  seekp  słuŜą  do  ustawiania  pozycji  pliku.  Nowa  pozycja  pliku  moŜe  być  podana  względem 
początku pliku (ios::beg), względem pozycji bieŜącej (ios::cur), albo względem pozycji końcowej (ios::end). 
 

inp.seekg(pos)            // inp.seekg(pos, ios::beg) 
inp.seekg(pos, from) 

Ustawia plik otwarty w trybie ios::in w pozycji pos, liczonej względem from (ios::begios::curios::end). 
 

inp.seekp(pos)            // inp.seekp(pos, ios::beg) 
inp.seekp(pos, from) 

Ustawia plik otwarty w trybie ios::out w pozycji pos, liczonej względem from (ios::begios::curios::end). 
 

inp.seek(0); 

 
Instrukcja ustawia strumień w pozycji początkowej. 
 
 

Funkcja clear 

 
Funkcja clear słuŜy do ustawienia stanu strumienia. 
 

str.clear()               
str.clear(ios::badbit) 

Wywołanie bezargumentowe ustawia strumień str w stan dobry. Wywołanie z argumentem ios::badbit ustawia 
go w stan zły
 

#include <iostream.h> 
#include <fstream.h> 
 
int main(void) 

    ifstream inp; 
    inp.open("C:\\autoexec.bat", ios::in); 
    if(!inp.is_open()) 
        return -1; 
 
    char chr; 
    for(int i = 0; i < 3 ; i++) { 
        while(inp.get(chr)) 
            cout << chr; 
        inp.clear(); 
        inp.seekg(0); 
    } 
 
    return 0; 

 
Program ma na celu 3-krotne wyprowadzenie na konsolę zawartości pliku autoexec.bat
 
Poniewa
Ŝ po zakończeniu instrukcji while strumień inp znajduje się w stanie nie-dobrym, więc naleŜy ustawić go 
w  stan  dobry.  W  przeciwnym  razie  wszystkie  operacje  wej
ścia-wyjścia  dotyczące  tego  strumienia  byłyby 
pomijane, a zawarto
ść pliku zostałaby wyprowadzona tylko 1 raz. 
 
 

Operacje w pamięci 

 

background image

 

70 

Operacje  wejścia-wyjścia  mogą  dotyczyć  nie  tylko  plików,  ale  równieŜ  pamięci  operacyjnej.  Do  wykonywania 
operacji  w  pamięci  słuŜą  obiekty  klas  istrstream  i  ostrstream,  zadeklarowanych  w  pliku  nagłówkowym 
strstream.h
 
Argumentem  konstruktora  klasy  istrstream  jest  wskaźnik  łańcucha.  Argumentami  konstruktora  klasy  ostrstream 
jest  wskaźnik  elementu  tablicy  znakowej  i  maksymalna  liczba  jej elementów, które mogą być uŜyte w operacji 
wyjścia. 
 
Uwaga: Operacja wyjścia nie zapisuje znaku końca łańcucha. NaleŜy to wykonać jawnie, na przykład za pomocą 
symbolu ends
 

#include <iostream.h> 
#include <strstream.h> 
 
int main(void) 

    char data[] = "10 20 30"; 
 
    istrstream(data) >> a >> b >> c; 
    char buf[100]; 
    ostrstream(buf, sizeof(buf)) << "Sum = " <<  
                                     a + b + c << ends; 
 
    cout << buf << endl; 
 
    return 0; 

 
 

Przetwarzanie wyrywkowe 

 
Wyrywkowo przetwarza się zazwyczaj pliki binarne. Plik binarny otwiera się w trybie  
 

 

ios::in | ios::out | ios::binary 

 
Operacje na pliku wykonuje się za pomocą funkcji read i write
 

#include <iostream.h> 
#include <fstream.h> 
 
const char *const SrcName = "Data.txt"; 
const int Size = sizeof(int); 
const char *const TrgName = "Random"; 
 
int main(void) 
{     
    ifstream inp; 
    inp.open(SrcName, ios::in | ios::nocreate); 
    if(!inp.is_open()) { 
        cout << "Source failure" << endl; 
        return -1; 
    } 
 
    ofstream out; 
    out.open(TrgName, ios::out | ios::binary); 
    if(!out.is_open()) { 
        cout << "Target failure" << endl; 
        return -2; 
    } 
 
        // wprowadzanie 
    cout << endl << "reading ... " << endl; 
    int count = 0, tmp; 
    while(inp >> tmp) { 
        count++; 
        cout << tmp << endl; 
        out.write((char *)&tmp, Size); 

background image

 

71 

    } 
    out.close(); 
    inp.close(); 
    if(count == 0) { 
        cout << "No data" << endl; 
        return -4; 
    } 
 
        // sprawdzanie 
    cout << endl << "checking ... " << endl; 
    inp.open(TrgName, ios::in | ios::binary | ios::nocreate); 
    if(!inp.is_open()) { 
        cout << "Check failure" << endl; 
        return -5; 
    } 
    while(inp.read((char *)&tmp, Size)) 
        cout << tmp << endl; 
    inp.close(); 
 
        // sortowanie 
    cout << endl << "sorting ... "; 
    fstream rio; 
    rio.open(TrgName, ios::in | ios::out | ios::binary); 
    if(!rio.is_open()) { 
        cout << "Sort failure" << endl; 
        return -6; 
    } 
 
    bool sorted = false; 
    while(!sorted) { 
        cout << endl; 
        sorted = true; 
        for(int i = 0; i < count-1 ; i++) { 
            rio.seekp(i * Size, ios::beg); 
            if(!rio) 
                goto Exit; 
            int num1, num2; 
                rio.read((char *)&num1, Size). 
                    read((char *)&num2, Size); 
            cout << num1 << " " << num2 << endl; 
            if(num2 < num1) { 
                rio.seekp(-2 * Size, ios::cur); 
                rio.write((char *)&num2, Size). 
                    write((char *)&num1, Size); 
                sorted = false; 
            } 
        } 
    } 
    Exit:; 
    if(!sorted) { 
        cout << "Seek error" << endl; 
        return -7; 
    } 
        // wyprowadzanie 
    cout << endl << "showing ... " << endl; 
    rio.seekp(0, ios::beg); 
    for(int i = 0; i < count ; i++) { 
        rio.read((char *)&tmp, Size); 
        cout << tmp << endl; 
    } 
    
    return 0; 

 
Program  tworzy  plik  binarny,  do  którego  zapisuje  dane  pochodzące  pliku  Data.txt.  Następnie  dane  sortuje  i 
wyprowadza. 
 
Poniewa
Ŝ  funkcje  read  i  write  oczekują  argumentów  typu  char *  i  int,  wskazana  zmiennych  całkowitych 
(np. &tmp) poddano jawnej konwersji do typu char *.
 
 
 

background image

 

72