background image

 
 
 
 
prof. Jan Bielecki 

 
 
 
 
 
 
 

Visual C++ 6.0 
Podstawy programowania 

 
 
 

1. 

Pierwsze kroki 

2. 

Srodowisko Visual C++ 

3. 

Wskazniki i odnosniki 

4. 

Przetwarzanie lancuchów 

5. 

Poslugiwanie sie funkcjami 

6. 

Zarzadzanie pamiecia 

7. 

Widocznosc deklaracji 

8. 

Studia programowe 

 
 

Dodatki 

 

 

Priorytety operatorów 

 

Opracowywanie wyrazen 

 

Konwersje standardowe 

 

Operatory bitowe i warunkowe 

 

Operacje wejscia-wyjscia 

 

 

 

background image

 

1

Pierwsze kroki 

 
 
 
 
Program jest zbiorem  modulów zródlowych. Kazdy modul sklada sie z deklaracji  typów,  zmiennych  i  funkcji
Napis od znaków  // do konca wiersza jest  komentarzem. Jako taki nie ma wplywu na przebieg wykonania 
programu. 
 
Dokladnie jeden modul, nazywany glównym, zawiera deklaracje funkcji  main. Wykonanie programu polega na 
opracowaniu wszystkich jego globalnych deklaracji, a nastepnie przystapieniu do wykonywania instrukcji 
zawartych w funkcji  main. Zakonczenie wykonywania programu nastepuje po wykonaniu w funkcji glównej 
instrukcji  return, albo tuz po powrocie z funkcji  exit. Moze to nastapic jeszcze przed  podjeciem wykonywania 
funkcji glównej. 
 

int main(void)      // deklaracja funkcji glównej 

    return 0;       // instrukcja return 

 

void exit(int);     // deklaracja funkcji exit 

 

struct Empty {      // deklaracja typu Empty 

    Empty(void) 

    { 

        exit(0);    // wywolanie funkcji exit 

    } 

}; 

 

Empty obj;          // deklaracja zmiennej 

 
 
Program napisano w taki sposób, aby jego wykonanie zakonczylo sie przed podjeciem wykonywania funkcji 
glównej. 
 
 

Komunikacja z otoczeniem 

 
W najprostszym przypadku, program pobiera dane z  klawiatury  i wyprowadza je na  monitor. Operacje 
wprowadzania danych  odbywaja sie za pomoca operatora  >>, a operacje wyprowadzania danych za pomoca 
operatora  <<. Klawiatura jest reprezentowana przez zmienna cin, a monitor przez zmienna cout. Posluzenie sie nimi 
wymaga uzycia dyrektywy #include wyszczególniajacej nazwe iostream.h
 
Dana wprowadzona z klawiatury konczy  odstep, uzyskany przez nacisniecie klawisza  Space,  Tab  albo Enter
Analiza danych wejsciowych nastepuje  wierszami, to jest dopiero po nacisnieciu klawisza  Enter. W 
szczególnosci, jesli program oczekuje 3 danych, to kazda mozna podac w osobnym wierszu, albo wszystkie podac 
w jednym wierszu. Przed wprowadzeniem kolejnej danej pomija sie poprzedzajace ja odstepy. 
 
Uwaga: Wygodnym sposobem wyprowadzenia odstepu Enter jest uzycie symbolu endl , a wygodnym sposobem 
wyprowadzenia znaku o kodzie 0 jest uzycie symbolu ends 
 
Poniewaz operacja wejscia-wyjscia dostarcza w miejscu jej uzycia jej lewy argument, wiec zapis pary instrukcji 
 
cin >> one; 
cin >> two; 

background image

 

2

 
mozna uproscic do 
 
cin >> one >> two; 
 
Wlasciwosc te, nazywana laczeniem operacji, mozna zastosowac takze 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 zachete do wprowadzenia 2 liczb, a nastepnie wyznacza i wyprowadza ich sume. 
 
 

Wykonywanie operacji 

 
Wykonanie programu sprowadza sie do wykonania operacji na danych. W Dodatku A zamieszczono kompletny 
wykaz operacji, a w Dodatku B omówiono zasady opracowywania wyrazen. Podane tam opisy stanowia istotny 
element niniejszego opracowania. 
 
 

Operacje przypisania 

 
Prosta operacja przypisania ma postac  
 
 

a = b 

 
w której a i b sa wyrazeniami, ale ponadto a jest l-nazwa zmiennej (por. Dodatek B ). 
 
Wykonanie operacji polega na przypisaniu zmiennej a wartosci wyrazenia b
 
 
Zlozona operacja przypisania ma postac 
 

 

a 

@

b  

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

 

a = a + b 

 
ale wymaga sie, aby opracowanie a i b bylo jednokrotne
 
 

Operacja polaczenia 

background image

 

3

 
Operacja polaczenia ma postac 
 

 

a , b 

 
Jej wykonanie sklada sie z opracowania wyrazenia a (wylacznie dla jego skutków ubocznych) oraz z niejawnego 
zastapienia calej operacji nazwa zmiennej reprezentowanej przez wyrazenie b
 
Uwaga: Nie jest operatorem polaczenia przecinek oddzielajacy parametry i argumenty funkcji. 
 
W szczególnosci wykonanie instrukcji 
 

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

 
jest równowazne wykonaniu instrukcji 
 

a = 10; 

cout << a; 

b = 20; 

return b; 

 
 

Operacje arytmetyczne 

 
Operacje arytmetyczne wykonuje sie za pomoca operatorów wymienionych w tabeli Operacje arytmetyczne
 
Tabela Operacje arytmetyczne 

### 

 

+ 

(dodawanie)   

- 

(odejmowanie

 

 

* 

(mnozenie

/ 

(dzielenie

(reszta

 
 

++  (zwiekszenie o 1 

-- 

(zmniejszenie o 1

 
 

+=  (dodanie

-= 

(odjecie

 

*=  (pomnozenie

/= 

(podzielenie

### 

 
Sposób wykonania podstawowych dzialan arytmetycznych nie wymaga opisu. Nalezy jedynie zauwazyc, ze 
rezultat dzielenia calkowitego jest calkowity, a argumenty wyznaczania reszty musza byc calkowite (np. 11 / 4  ma 
wartosc 2, a 11 % 4  ma wartosc 3). 
 
 
Operacje przedrostkowe 
 
Wykonanie operacji ++num powoduje zwiekszenie wartosci zmiennej num 1. W miejsce wykonania operacji jest 
dostarczana nowa wartosc num
 
Wykonanie operacji --num powoduje zmniejszenie wartosci zmiennej num o 1. W miejsce wykonania operacji jest 
dostarczana nowa wartosc num
 

int fix = 10; 

cout << ++fix;    // 11 

cout << fix;      // 11 

 
 
Operacje przyrostkowe 
 

background image

 

4

Wykonanie operacji num++ powoduje zwiekszenie wartosci zmiennej num o 1. W miejsce wykonania operacji jest 
dostarczana pierwotna wartosc num
 
Wykonanie operacji num-- powoduje zmniejszenie wartosci zmiennej num o 1 W miejsce wykonania operacji jest 
dostarczana pierwotna wartosc num
 

int fix = 10; 

cout << fix--;    // 10 

cout << fix;      // 9 

 
 

 

Operacje porównania 

 
Operacje porównania wykonuje sie za pomoca operatorów wymienionych w tabeli Operacje porównania
 
Tabela Operacje porównania 

### 

 

== 

(równe

!= 

(nie równe), 

 

< 

(mniejsze

> 

(wieksze), 

 

<= 

(mniejsze lub równe

>= 

(wieksze lub równe

### 

 
Jesli porównanie wyraza orzeczenie prawdziwe, to jego rezultat ma wartosc true (prawda). W przeciwnym razie ma 
wartosc false (falsz). 
 
Uwaga: Porównanie na równosc wykonuje sie za pomoca operacji ==, a nie za pomoca operacji =. Zaniedbanie 
tego faktu jest zródlem trudnych do wykrycia bledów semantycznych. 
 

#include <iostream.h> 

 

int main(void) 

{     

    int num = 0; 

    while(num == 0) 

        cin >> num; 

    cout << num << endl; 

 

    return 0; 

 
Program wyprowadza liczbe  0 albo pierwsza niezerowa liczbe wprowadzona z klawiatury. Gdyby operator 
porównania zastapiono operatorem przypisania, to zawsze wyprowadzalby liczbe 0
 
 

Operacje orzecznikowe 

 
Operacje orzecznikowe wykonuje sie za pomoca operatorów wymienionych w tabeli Operacje orzecznikowe
 
Tabela Operacje orzecznikowe 

### 

 

!

  (zaprzeczenie

&&

  (koniunkcja 

||

  (dysjunkcja

 

### 

 
Argumenty i rezultaty operacji orzecznikowych sa typu bool i maja wartosci true albo false
 
Rezultat zaprzeczenia ma wartosc true tylko wówczas, gdy argument ma wartosc false

background image

 

5

 
Rezultat koniunkcji ma wartosc true tylko wówczas, gdy oba argumenty maja wartosc true
 
Rezultat dysjunkcji ma wartosc false tylko wówczas, gdy oba argumenty maja wartosc false
 
Uwaga: Operacja koniunkcji i dysjunkcji jest wykonywana w taki sposób, ze jesli po opracowaniu pierwszego 
argumentu jest znana wartosc rezultatu calej operacji (bo dla koniunkcji ma wartosc  false, a dla dysjunkcji ma 
wartosc true), to rezygnuje sie z opracowania drugiego argumentu. 
 

#include <iostream.h> 

 

int vec[] = { 10, 20, 30, 40, 50 }; 

 

int main(void)  

 

    int pos; 

    cin >> pos; 

 

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

 

    return 0; 

 
Program wyprowadza wartosc tego elementu tablicy, którego indeks wprowadzono z klawiatury.  
 
Jesli wprowadzi sie indeks, który nie ma wartosci z przedzialu [0 ; 4], to program nie wyprowadzi nic. 
 
Dzieki uzyciu operatora  &&, nigdy nie dojdzie do opracowania wyrazenia  vec[pos] z niedozwolonym 
indeksem. 
 
 

Operacje konwersji 

 
Wykonanie konwersji ma na celu przeksztalcenie zmiennej pewnego typu w zmienna typu docelowego.  
 
Operacja konwersji wyrazenia e do typu Type ma postac 
 

 

(Type)e 

 
Jesli nazwe typu docelowego Type mozna wyrazic za pomoca identyfikatora (np. int), to operacje konwersji mozna 
zapisac jako 
 

 

Type(e)         

 
 
W szczególnosci, jesli w programie wystepuje instrukcja 
 

int num = 4.8; 

 
w której zmienna num  jest typu int, a wyrazenie 4.8 jest typu double, to poniewaz danej typu double (zazwyczaj 8-
bajtowej) nie mozna pomiescic w zmiennej typu int (zazwyczaj 4-bajtowej), wiec najprosciej byloby taka instrukcje 
uznac za bledna. 
 
Poniewaz w  C++ przeksztalcenie zmiennej typu  double  w zmienna typu  int zdefiniowano jako konwersje 
standardowa (polega ona na odrzuceniu czesci ulamkowej), wiec rozpatrywana instrukcja zostanie niejawnie 
zmieniona w poprawna instrukcje 
 

int num = int(4.8); 

background image

 

6

 
równowazna  
 

int num = 4; 

 
w której wyrazenie inicjujace jest juz typu int
 
Uwaga: Wazne informacje na temat konwersji zamieszczono w Dodatku C
 
 

Operacje warunkowe 

 
Operacje warunkowe wykonuje sie za pomoca trójargumentowego operatora ?: (pytajnik dwukropek ). 
 
Rezultatem operacji 
 

 

e ? eT : eL 

 
jest zmienna o wartosci  eT  jesli orzeczenie wyrazone przez  e jest prawdziwe, albo zmienna o wartosci  eF w 
przeciwnym razie..  
 
Uwaga: Po opracowaniu wyrazenia e, opracowuje sie tylko jedno z wyrazen eT i eF
 

num =  fix > 0 ? fix1 : fix2;   

num < 0 ? fix1 : fix2 = 30;    

 
 

Operatory :: i Name:: 

 
Jesli  id jest identyfikatorem, to ::id jest nazwa globalna, a  Name::id jest nazwa skladnika typu strukturowego 
Name
 

int num = 0; 

 

struct Fix { 

    int num;                   

    void set(int num =::num)   

    { 

        Fix::num = num; 

    } 

}; 

 
Napis ::num jest nazwa zmiennej globalnej, a napis Fix::num jest nazwa skladnika num
 
Prawy argument przypisania Fix::num = num jest nazwa parametru. 
 
 

Wykonywanie instrukcji 

 
Do napisania dowolnego programu wystarczy zaledwie kilka instrukcji. Najwazniejszymi z nich sa: instrukcja 
pusta,  grupujaca,  warunkowa  (if),  iteracyjna  (while) i  powrotu  (return). Opis pozostalych ograniczono do 
przykladów. 
 
 

Instrukcja pusta 

background image

 

7

 
Instrukcja pusta sklada sie ze srednika.  
 

 

 
Jej wykonanie nie ma zadnych skutków.  
 
 

Instrukcja grupujaca 

 
Instrukcja grupujaca sklada sie z nawiasów klamrowych zawierajacych dowolna sekwencje instrukcji.  
 
Jesli w miejscu, w którym skladnia wymaga uzycia dokladnie jednej instrukcji, chce sie umiescic ich wiecej, to 
wystarczy ujac je w nawiasy klamrowe i powstanie jedna instrukcja. 
 

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

 
 

Instrukcja warunkowa 

 
Instrukcja warunkowa ma postac 
 

 

if(c

 

    s   

albo 
 

 

if(c

 

    s1 

 

else 

 

    s2 

 
w której c jest wyrazeniem orzecznikowym o wartosci true albo false, a s1 oraz s2 jest pojedyncza instrukcja (np. 
instrukcja grupujaca). 
 
Wykonanie instrukcji warunkowej zaczyna sie od opracowania wyrazenia c (np. a > 2). Jesli wyrazone 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 sie nic, a w drugim wykonuje instrukcje s2
 

if(a > 2)  

    { a++; cout << a; } 

else 

    { cout << a; a-- } 

 
albo równowaznie 
 

if(a > 2) { 

    a++; 

    cout << a; 

} else { 

    cout << a; 

    a--; 

 
Jesli podczas opracowywania instrukcji warunkowej napotka sie slowo kluczowe else, to przyjmuje sie, ze dotyczy 
ono najblizszego z lewej slowa if, nie polaczonego jeszcze z else
 
W szczególnosci instrukcja 

background image

 

8

 

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 postac 
 

 

while(c

 

    s 

 
w której c jest wyrazeniem orzecznikowym, a jest pojedyncza instrukcja. 
 
Wykonanie instrukcji iteracyjnej while polega na cyklicznym badaniu orzeczenia wyrazonego przez wyrazenie c i  
wykonywaniu instrukcji s
 
Iteracja konczy sie w chwili stwierdzenia, ze orzeczenie jest nieprawdziwe . Jesli okaze sie to juz na wstepie, to 
instrukcja  s nie bedzie wykonana wcale
 

int i = 3; 

while(i > 0) { 

    int t = i * i; 

    cout << t << endl;    //  9 4 1 

    i--; 

 
Czesto uzywa sie instrukcji iteracyjnej for 
 

 

for(d c ; e) { 

 

    s s ... s 

 

 
w której d jest instrukcja deklaracyjna, a c i e sa wyrazeniami. 
 
Tak zapis ana instrukcja for jest równowazna instrukcji 
 

 

d 

 

while(c) { 

 

    s s ... s 

 

    e

 

 

 

background image

 

9

Instrukcja for dobrze opisuje czynnosci o znanej liczbie powtórzen. 
 

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 postac 
 

 

 

break; 

 
Wykonanie instrukcji zaniechania powoduje zakonczenie wykonywania  najwezszej obejmujacej ja instrukcji 
iteracyjnej albo decyzyjnej. 
 

int sum = 0; 

while(true) { 

    int tmp = 0; 

    cin >> tmp;         // wprowadz dana 

    if(tmp == 0)        // zbadaj czy 0 

        break;          // zakoncz iteracje 

    sum += tmp;         // dosumuj 

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

 
albo 
 

int tmp = 0, sum = 0; 

while(cin >> tmp, tmp)  // wprowadz i zbadaj 

    sum += tmp;         // dosumuj           

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

 
lub 
 

for(int tmp = 0, sum = 0; cin >> tmp, tmp ; sum += tmp); 

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

 
 

Instrukcja powrotu 

 
Instrukcja powrotu ma postac 
 

 

return e

 
w której e jest wyrazeniem. 
 
Wykonanie instrukcji powrotu powoduje zakonczenie wykonywania funkcji i dostarczenie rezultatu o wartosci 
okreslonej przez e.  
 

int sum(int one, int two) 

    return one + two; 

 
Jesli typem funkcji jest  void, to uzyta w niej instrukcja powrotu nie moze zawierac wyrazenia. Uzycie takiej 
instrukcji jest zazwyczaj zbyteczne, poniewaz domniemywa sie ja tuz przed klamra zamykajaca cialo funkcji. 

background image

 

10

 

void sum(int one, int two) 

    cout << one + two << endl; 

 

    return;      // zbedne 

 
 

Instrukcja decyzyjna 

 
Instrukcja decyzyjna uogólnia instrukcje warunkowa i jest przydatna wówczas, gdy w programie wystepuja wiecej 
niz dwie galezie decyzyjne. W szczególnosci instrukcje warunkowa 
 

if(a == 2) 

    b = 3; 

else if(a == 1) 

    b = 5; 

else if(a == 4) 

    b = -1; 

else 

    b = 0; 

 
mozna zapisac w postaci 
 

switch(a) { 

    case 2:         // jesli a == 2 

        b = 3; 

        break; 

    case 1:         // jesli a == 1 

        b = 5; 

        break; 

    case 4:         // jesli a == 4 

        b = -1; 

        break; 

    default:        // w pozostalych przypadkach 

        b = 0; 

 
 

Deklarowanie zmiennych i typów 

 
Kazdy modul programu jest kompilowany niezaleznie  od pozostalych. Analiza skladniowa modulu odbywa sie 
od-góry-do-dolu  i  od-lewej-do-prawej i polega na rozpoznawaniu  jednostek leksykalnych:  identyfikatorów 
(np. exit), literalów (np. 0), operatorów (np. +) i ograniczników (np. ;). 
 
 
Identyfikatory  
 
Identyfikatorem jest spójna  sekwencja liter i cyfr, zaczynajaca sie od litery. Identyfikator nie moze miec postaci 
slowa kluczowego (np. return). Za jego litere uznaje sie równiez znak podkreslenia (_). 
 
Litery male uznaje sie za rózne od duzych. Zaleca sie, aby w wieloslowowych nazwach zmiennych i funkcji, 
wszystkie slowa, z wyjatkiem pierwszego, byly zapisane za pomoca duzych liter. 
 
np. 

 

forSale  speedLimit  veryLongName 

 

background image

 

11

 
Literaly 
 
Literalami sa liczby  (np. 120xff i 2.e-3), znaki (np. 'a') i lancuchy (np. "Hello"). Kazdy literal jest nazwa zmiennej 
ustalonej. Jej typ wynika z zapisu literalu. 
 
Uwaga: Jesli lancuch ma zawierac znak \ (ukosnik ), to nalezy go zapisac 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 
 
Kazde uzycie identyfikatora  musi byc poprzedzone jego  deklaracja. Deklaracja kompletnie opisujaca zmienna 
(okreslajaca jej wartosc poczatkowa),  typ  (wyszczególniajaca strukture jego obiektów) i  funkcje  (podajaca jej 
cialo) jest nazywana definicja.  
 
W sklad deklaracji wchodza specyfikatorydeklaratory inicjatory
 
np. 

 

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

 
Specyfikatorami sa const int, deklaratorem jest tab[3], a inicjatorem jest = { -1, 0, +1 }
 
 
Naglówki 
 
Deklaracje typów i funkcji sa ujmowane w naglówki. Kazdy naglówek jest zapisany w odrebnym pliku. Wlaczenie 
naglówka odbywa sie w miejscu wystapienia wyszczególniajacej go dyrektywy #include
 
Do najczesciej uzywanych naglówków naleza:  iostream.h iomanip.hmath.hstring.h stdlib.h. Dwa pierwsze 
wlaczaja do modulu deklaracje zmiennych i operatorów wejscia-wyjscia (cincout>><<), dwa nastepne wlaczaja 
deklaracje funkcji matematycznych (sqrt,  sin,  cos) i lancuchowych (strlen,  strcpy,  strcat,  strcmp), a ostatni 
wlacza m.in. deklaracje funkcji exit
 

#include <iostream.h> 

#include <math.h> 

 

int main(void) 

    double number;          // deklaracja zmiennej 

    cin >> number;          // wprowadzenie liczby 

    cout << sqrt(number);   // wyprowadzenie pierwiastka 

 

    return 0;               // zakonczenie wykonywania 

 
 

Zmienne 

 
Zmienna jest obszar pamieci  do przechowywania danych okreslonego typu:  skalarnych,  tablicowych  
strukturowych. Kazde odwolanie do zmiennej musi byc poprzedzone deklaracja jej typu. 
 

background image

 

12

int fix;           // zmienna calkowita 

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 okresla sie za pomoca operatora sizeof. Argumentem operatora sizeof moze byc nazwa 
zmiennej albo nazwa typu. 
 
Uwaga: Rozmiar zmiennej zalezy od implementacji. W Visual C++ zmienne typu char sa 1-bajtowe, zmienne typu 
int sa 2-bajtowe, a zmienne typu double sa 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 zmienna ustalona. Zmienna ustalona musi byc zainicjowana, 
ale nadana jej wartosc nie moze ulec zmianie. 
 
Uwaga: Zmiennymi ustalonymi sa takze zmienne reprezentowane przez literaly. W szczególnosci liczba 12e2 jest 
nazwa zmiennej ustalonej o wartosci 1200
 

const int size = 100; 

const double width = -2e-7, height = 2e2; 

const int tab[2] = { 10, 20 }; 

 
 

Zmienne skalarne 

 
Deklaracja zmiennej skalarnej okresla jej  identyfikator  oraz wyszczególnia typ danych jakie mozna przypisywac 
zmiennej (np. intdoublechar). 
 

int number; 

double speedLimit; 

char separator; 

 
Wartosc poczatkowa zmiennej okresla sie za pomoca  inicjatora . Jesli deklaracja zmiennej zawiera jawny albo 
domniemany inicjator, to jest jej definicja
 

int minValue = 10, maxValue = 90; 

double width = 2.4, height = 4.5e+2, area; 

char lastChar = '.'; 

 
 
Skladnia inicjatora  
 
Inicjatory dziela sie na  wyrazeniowe,  klamrowe  i  nawiasowe. Inicjator zmiennej  ustalonej  musi miec postac 
wyrazenia stalego. W jego sklad wchodza odwolania do literalów i zmiennych ustalonych, ale nie moga wchodzic 
odwolania do zmiennych nie-ustalonych. 
 

background image

 

13

int base = 100;             // inicjator wyrazeniowy 

int min =  { base + 20 };   // inicjator klamrowy 

int max(base + 40);         // inicjator nawiasowy 

 

const int size = max - min; // blad 

 
 
Punkt zadeklarowania 
 
Identyfikator zmiennej uwaza sie za zadeklarowany w punkcie tuz  przed  inicjatorem wyrazeniowym i klamrowym, 
ale tuz  po inicjatorze nawiasowym. Ta subtelna róznica ma niekiedy wplyw na poprawnosc 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 wystepuje tuz po inicjatorze  (val). Gdyby inicjator nawiasowy 
zastapiono jednym z pozostalych inicjatorów, to program stalby sie bledny, poniewaz zmienna lokalna bylaby 
wówczas inicjowana nie wartoscia zmiennej globalnej, ale nieokreslona jeszcze wartoscia zmiennej lokalnej.
 
 
 
Operacje wejscia-wyjscia 
 
Zmienne typu  int,  double i  char sa zmiennymi  arytmetycznymi, przystosowanymi odpowiednio do 
przechowywania liczb calkowitych, zmiennopozycyjnych i kodów znaków.  
 
Podczas wykonywania operacji wejscia, do zmiennych typu  int i  double wprowadza sie dane liczbowe, a do 
zmiennych typu char  wprowadza sie kody znaków. A zatem, jesli z klawiatury wprowadzi sie na przyklad napis 
20e3, to liczba pobranych znaków i otrzymana wartosc bedzie zalezec od typu zmiennej, zgodnie z tabela 
Wprowadzanie danych
 
Tabela Wprowadzanie danych 
 
 

Typ zmiennej 

Pobrano znaków 

Wprowadzono wartosc  

 

 

int 

20 

 

double 

20000 

 

char 

49 

 
Podczas wykonywania operacji wyjscia, wyprowadza sie liczby o wartosci zmiennych typu int double oraz znaki 
o kodach okreslonych przez wartosci zmiennych typu char
 

#include <iostream.h> 

 

int main(void) 

    int mant, exp; 

    char sep; 

    cin >> mant >> sep >> exp; 

    int value = mant; 

background image

 

14

    while(exp > 0) { 

        value = value * 10; 

        exp--; 

    } 

    cout << mant << sep << exp << 

         " == " << value << endl; 

 

    return 0; 

 
Jesli wprowadzi sie napis 2e3, to program wyprowadzi ten napis oraz liczbe 2000
 
 

Zmienne tablicowe 

 
Zmienna tablicowa, w skrócie tablica, jest zestaw sasiadujacych ze soba  elementów tablicy. Kazdy element jest 
zmienna takiego samego typu: skalarnatablicowastrukturowa
 

int tab[20]; 

 
Zmienna tab jest tablica o 20-elementach typu int
 
Z kazdym  elementem  tablicy jest zwiazany indeks, okreslajacy polozenie elementu w obrebie tablicy. Elementy 
tablicy sa indeksowane od  0. W deklaracji tablicy podaje sie liczbe jej elementów, a nie indeks jej ostatniego 
elementu. Jesli deklarator nie podaje liczby elementów, ale deklaracja zawiera inicjator, to za liczbe elementów 
uznaje sie liczbe fraz inicjujacych. 
 
Uwaga: Liczba fraz inicjujacych nie moze przekraczac liczby elementów tablicy. Jesli jest od niej mniejsza, to jest 
niejawnie dopelniana frazami 0
 

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

 
Zerowy i pierwszy element tablicy tab ma wartosc 4. Wszystkie pozostale maja wartosc 0
 
 
Liczba elementów tablicy musi byc wyrazona za pomoca  wyrazenia stalego. Wyrazenie stale moze zawierac 
literaly i identyfikatory zmiennych ustalonych, ale nie moze zawierac operatora polaczenia. 
 

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];      // blad 

 
Tablica sizes sklada sie z 3 zmiennych, z których kazda jest typu double
 
Tablica values sklada sie z 5 zmiennych, z których kazda jest typu int.  
 
 
Identyfikowanie elementów tablicy 
 
Jesli nazwa tablicy jest vec, to nazwa jej elementu o indeksie ind jest vec[ind]. Jest to prawdziwe tylko wówczas, 
gdy wyrazenie ind ma wartosc wieksza -lub-równa 0 i jednoczesnie mniejsza od liczby elementów tablicy. 
 
Uwaga: Jesli tablica  vec ma  n elementów, to zezwala sie, aby wyrazenie  ind mialo wartosc  -1 oraz  n, ale tylko 
wówczas, gdy opracowanie wyrazenia  vec[ind] nie ma na celu dokonania zmiany albo dostarczenia wartosci 
elementu. 

background image

 

15

 

#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 wartosc elementu o podanym indeksie. Jesli indeks nie miesci sie w domknietym 
przedziale [0 ; 4], to program wyprowadza napis Wrong index
 
 
Tablice znakowe 
 
Tablica znakowa jest tablica o elementach typu char. Przechowuje sie w niej zazwyczaj male liczby oraz kody 
znaków.  
 
Poniewaz  Visual C++ uzywa kodu ASCII, w którym  kodem cyfry 0  jest  48, wiec zainicjowanie 4-elementowej 
tablicy znakowej kodami cyfr 02 oraz kodem znaku '\0' mozna wykonac 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 mozna wprowadzac tylko spójne ciagi znaków. Za ostatnim wprowadzonym znakiem umieszcza sie 
wówczas specjalny znak o kodzie 0.   
 
Jesli chce sie wyprowadzic ciag znaków utworzony w tablicy programowo, to nalezy zakonczyc go znakiem o 
kodzie 0 (jego rozpoznanie spowoduje zakonczenie 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 imie, a nastepnie wyprowadza jego inicjal. 
 
 
Literaly lancuchowe 
 
Literal lancuchowy, na przyklad "Hello", ma postac ciagu znaków ujetego w cudzyslowy. Znaki specjalne sa w 
tym ciagu reprezentowane przez nastepujace symbole 

background image

 

16

 
 

\\ (ukosnik 

\n (nowy wiersz

\t (tabulator),  

 

\' (apostrof

\" (cudzyslów

\0 (znak o kodzie 0). 

 
Kazdy literal lancuchowy, jest nazwa tablicy o elementach typu char, zainicjowanych kodami kolejnych znaków 
literalu oraz kodem znaku \0. W szczególnosci (w kodzie  ASCII) literal  "No" jest nazwa  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 kazdym znaku dodajac  spacje. Zakonczenie 
wykonywania nastepuje po rozpoznaniu elementu zainicjowanego liczba 0
 
Literaly lancuchowe moga byc uzyte do inicjowania tablic znakowych. Tak zainicjowana tablica musi miec  co 
najmniej 
tyle elementów ile ma tablica reprezentowana przez literal. Jesli jest dluzs za, to jej nadmiarowe elementy 
sa inicjowane liczbami 0
 

char name1[10] = { 'I', 's', 'a', 0 };  

char name2[10] = "Isa";   

char name3[]   = "Isa"; 

char name4[3]  = "Isa";        // blad 

 
 
Operacje wejscia-wyjscia 
 
Tablice znakowe moga byc wykorzystane do  wprowadzania z klawiatury spójnych ciagów znaków. W takim 
przypadku argumentem operacji jest zazwyczaj nazwa tablicy, a wykonanie operacji powoduje umieszczenie w 
tablicy kodów znaków lancucha oraz kodu o wartosci 0
 
Poniewaz moze wówczas dojsc do przepelnienia tablicy, zaleca sie uzycie manipulatora setw, zadeklarowanego w 
naglówku iomanip.h, ograniczajacego liczbe wprowadzonych znaków. 
 
Uwaga: Manipulator  setw  moze byc uzyty takze podczas wyprowadzania danych. W takim wypadku okresla on 
szerokosc pola zewnetrznego, w którym umieszcza sie dane wyjsciowe. 
 

#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

 

17

 
Program wprowadza imie, a nastepnie wyprowadza jego inicjal. Aby zabezpieczyc sie przed wpisaniem do 
tablicy name wiecej niz 20 znaków, uzyto manipulatora setw(20) zadeklarowanego w naglówku iomanip.h.
 
 
 

Zmienne strukturowe 

 
Zmienna strukturowa, w skrócie  struktura, jest zestaw sasiadujacych ze soba  elementów struktury. Kazdy 
element struktury moze byc zmienna innego typu: skalarnatablicowastrukturowa
 
Przed zadeklarowaniem zmiennej strukturowej nalezy zdefiniowac jej typ. Deklaracja typu strukturowego sklada sie 
z deklaracji pól struktury. Deklaracja pola struktury ma postac deklaracji zmiennej. 
 

struct Child { 

    char name[20]; 

    int age; 

}; 

 

Child isa = { "Isabel", 15 }; 

 
Struktura  isa  sklada sie z  2  zmiennych, opisanych przez pola  name  i  age. Wartosci poczatkowe elementów 
struktury okreslono za pomoca inicjatora klamrowego. Uzycie innych inicjatorów jest zabronione. 
 
 
Identyfikowanie elementów 
 
Jesli nazwa struktury jest str, a w opisie jej typu wystepuje pole fld, to nazwa zmiennej odpowiadajacej 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 sklada sie z tablicy o elementach typu char i zmiennej skalarnej typu int. Program wprowadza 
imie i wiek dziecka, a nastepnie wyprowadza je. 
 
 
 
Kopiowanie struktur 
 
W odróznieniu od tablic, które mozna kopiowac tylko element-po-elemencie, kopiowanie struktur moze dotyczyc 
pelnego zestawu jej elementów i to nawet wówczas, gdy struktura zawiera tablice. 
 

background image

 

18

#include <iostream.h> 

 

struct Child { 

    char name[20]; 

    int age; 

}; 

 

Child girl; 

 

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 strukture isa
 
 
Unia elementów 
 
Struktura, której elementy sa rozmieszczone w pamieci nie jeden-za-drugim, ale zawsze od tego samego miejsca, 
jest nazywana unia. W celu zadeklarowania unii nalezy zamiast slowa kluczowego struct uzyc slowa union
 
Definicja unii, w której pominieto nazwe typu, jest definicja  unii anonimowej. Pola unii anonimowej sa 
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;        // blad 

 
W kazdej chwili struktura num sklada sie ze zmiennych typu bool int, albo ze zmiennych typu bool double
 
Blad polega na tym, ze w chwili gdy zmienna num sklada sie ze zmiennych typu bool i int, nastepuje odwolanie 
do zmiennej typu double
  
 

Przetwarzanie plików 

 
Przetwarzanie plików odbywa sie za posrednictwem zmiennych strumieniowych klas  ifstream i  ofstream
zadeklarowanych w naglówku fstream.h. Po utworzeniu zmiennej strumieniowej nalezy otworzyc skojarzony z nia 
plik, a nastepnie upewnic sie, ze otwarcie bylo pomyslne.  
 
Po pomyslnym otwarciu pliku, pochodzacy z niego strumien danych mozna przetwarzac w taki sam sposób, jak 
strumien danych zwiazany z klawiatura albo z monitorem. 

background image

 

19

 
 

Stany strumienia 

 
Poczatkowo strumien znajduje sie w stanie  dobrym, ale na skutek bledu operacji wejscia-wyjscia albo próby 
wprowadzenia nieistniejacej danej, moze znalezc sie w stanie nie-dobrym (fail).  
 
W stanie nie-dobrym wszystkie operacje na strumieniu sa  ignorowane. Jesli dane przygotowano wlasciwie, a 
jakosc pamieci zewnetrznej jest zadowalajaca, to stan nie-dobry oznacza, ze napotkano koniec strumienia.  
 
Uwaga: W programach przykladowych  nie bedzie rozpatrywany przypadek wystapienia bledu przesylania 
danych
.  
 
Szczególnym przypadkiem stanu nie-dobrego jest stan zly (bad). Powstaje on w przypadku rozpoznania danych o 
zlym formacie. Niestety, na skutek niefortunnych domnieman, wprowadzenie takiej "danej" jak 3e, zamiast 3e0 (w 
kontekscie 3ex) nie zmienia stanu strumienia na zly. 
 
Uwaga: Do sprawdzenia czy stan strumienia jest zly, sluzy funkcja bad, a do sprawdzenia, czy strumien znajduje sie 
w pozycji za koncem pliku, sluzy funkcja eof. Funkcji tych uzywa sie bardzo rzadko. 
 
 

Zmienna plikowa 

 
Jesli w miejscu wystapienia operacji wejscia-wyjscia odbywa sie takie badanie zmiennej plikowej, jakby dotyczylo 
wyrazenia o wartosci orzecznikowej, na przyklad 
 

 

while(cin >> num) ... 

albo 

 

if(cin) ... 

 
to w stanie dobrym jest dostarczana wartosc true, a w stanie nie-dobrym wartosc false
 
 

Wprowadzanie danych 

 
Zmienna strumieniowa uzyta do wprowadzania danych z pliku jest typu ifstream. Otwarcie pliku odbywa sie za 
pomoca funkcji  open, której pierwszym argumentem jest  nazwa, a drugim  tryb otwarcia pliku:  ios::in. Jesli 
otwierany plik  nie istnieje, to zostanie utworzony jako pusty. Aby tego uniknac, plik nalezy otworzyc w trybie 
ios::in | ios::nocreate
 
Do zbadania, czy otwarcie pliku sie powiodlo, sluzy funkcja  is_open. Jej rezultat ma wartosc  nie-zero tylko 
wówczas, gdy otwarcie bylo pomyslne. 
 

#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; 

    } 

background image

 

20

    int val; 

    while(inp >> val)     // wprowadz i sprawdz stan 

        sum += val;       // dosumuj 

    assert(!inp.bad());   // raczej zbedne 

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

 

    return 0; 

 
Wykonanie programu powoduje wyprowadzenie sumy liczb calkowitych zawartych w pliku Data.txt. 
 
Wywolanie funkcji assert ma na celu upewnienie sie, ze strumien nie znajduje sie w zlym stanie. Gdyby tak bylo, 
to wykonanie programu zostaloby zaniechane. 
 
 

Wyprowadzanie danych 

 
Zmienna strumieniowa uzyta do wyprowadzania danych do pliku jest typu ofstream. Otwarcie pliku odbywa sie za 
pomoca funkcji open, której pierwszym argumentem jest nazwa, a drugim tryb otwarcia pliku: ios::out.  
 
Jesli otwierany plik  nie istnieje, to zostanie utworzony i otworzony jako pusty. Jesli juz istnieje, to zostanie 
otworzony jako pusty. 
 
Do zbadania, czy otwarcie pliku sie powiodlo, sluzy funkcja  is_open. Jej rezultat ma wartosc  nie-zero tylko 
wówczas, gdy otwarcie bylo pomyslne. 
 
 

#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 calkowite z pliku Data.txt do pliku Data2.txt. Kazda kopiowana liczbe umieszcza w 
nowym wierszu.
 
 
 

Uzycie klawiatury 

background image

 

21

 
Jesli dane wprowadza sie z klawiatury, to koniec strumienia okresla sie za pomoca  znaku konca:  Ctrl-Z (na 
polskiej klawiaturze Ctrl-Y). W Visual C++ nastapi wówczas pominiecie pierwszego znaku wyprowadzonego na 
konsole. 
 
Uwaga: Zaleca sie, aby znak konca zostal wprowadzony na poczatku nowego wiersza (po Enter). 
 

#include <iostream.h> 

 

int main(void) 

{     

    int count = 0; 

    double tmp; 

    while(cin >> tmp) 

        count++; 

 

    cout << endl;     // na pozarcie 

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

 

    return 0; 

 
Program zlicza dane liczbowe wprowadzone z klawiatury. 
 

background image

 

22

 

Srodowisko Visual C++  

 
 
 
 
Program zródlowy sklada sie z  modulów zródlowych. Kazdy modul jest umieszczony w odrebnym pliku z 
rozszerzeniem  .cpp. Dodatkowo, w sklad programu moga wchodzic moduly skompilowane (*.obj) i biblioteczne 
(*.lib). 
 
W celu przeksztalcenia zestawu modulów w program wykonalny, nalezy utworzyc  projekt, umiescic go w 
przestrzeni roboczej, wlaczyc do projektu nazwy plików z rozszerzeniami .cpp.obj i .lib, a nastepnie zbudowac 
program. Zostanie on umieszczony w pliku z rozszerzeniem .exe
 
 

Katalog 

 
Zaleca sie, aby pliki programu znajdowaly sie we wlasnym katalogu. Jesli dysponuje sie wolnym miejscem na 
przyklad w katalogu glównym dysku  D:, to nalezy wywolac Eksplorator Windows, kliknac na nazwie katalogu 
glównego i wydac polecenie  Plik / Nowy obiekt / Folder, a nastepnie okreslic nazwe  swojego katalogu, na 
przyklad jbVisual
 
 

Przestrzen 

 
W celu utworzenia przestrzeni roboczej nalezy wydac polecenie File / New, a nastepnie (w zakladce Workspaces
podac nazwe przestrzeni, np. Workspace: jbSpace oraz okreslic jej polozenie, np. Location: D:\jbVisual\jbSpace
po czym nacisnac przycisk OK
 
Jesli przestrzen juz istnieje, to aby ja  otworzyc, nalezy wydac polecenie  File / Open Workspace, wejsc do 
katalogu przestrzeni (np. jbSpace), a nastepnie dwu-kliknac na nazwie jbSpace.dsw
 
 

Projekt 

 
W celu utworzenia projektu nalezy wydac polecenie File / New, a w zakladce Projects podac typ projektu: Win 32 
Console Application
 i jego nazwe, np. Project name: jbTests. Po upewnieniu sie, ze projekt zostanie wlaczony do 
biezacej przestrzeni (Add to current workspace) o czym zaswiadczy  Location: D:\jbVisual\jbSpace\jbTests
nalezy nacisnac przycisk OK
 
 

Pliki 

 
W celu utworzenia plików projektu nalezy wydac polecenie File / New, a nastepnie (w zakladce Files), okreslic 
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 zapominajac o podaniu jego nazwy (bez rozszerzenia), np. File name: Sum
 

background image

 

23

Po wykonaniu tych czynnosci, w katalogu  D:\jbVisual\jbSpace\jbTests powstanie plik  Sum.cpp, a jego 
(poczatkowo pusta) zawartosc ujawni sie odrebnym oknie edycyjnym. 
 
Jesli program wymaga utworzenia plików z danymi, to zaleca sie je umiescic w  tym samym katalogu co pliki 
zródlowe. Dla wygody mozna je dolaczyc 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, nalezy kliknac ikone Build. Spowoduje to niezalezne kompilacje wszystkich modulów zródlowych oraz 
polaczenie ich w program wykonalny. 
 
Przebieg budowania projektu jest diagnozowany w oknie  Output. Jesli okno nie jest widoczne, to mozna je 
wyswietlic wydajac polecenie View / Output
 
Bledy modulu wyszczególnia sie w oknie Output. Po rozpoznaniu kazdego z nich podaje sie krótki opis przyczyny 
bledu i numer wiersza programu. Dwu-klikniecie w obrebie opisu bledu powoduje przeniesienie kursora w poblize 
miejsca, w którym wykryto blad.  
 
W rzadkich przypadkach, gdy poprawnosc diagnozy budzi watpliwosci, zaleca sie zastapienie polecenia Build 
poleceniem Build / Rebuild All
 
 

Wykonanie programu 

 
Program wykonalny, pod nazwa jbTests.exe jest umieszczany w podkatalogu jbTests\Debug. Jesli wykonuje sie 
bezblednie i jest nalezycie wytestowany, to moze zostac zoptymalizowany. 
 
W celu zoptymalizowania programu nalezy wydac polecenie  Build / Set Active Configuration, a nastepnie 
zamiast konfiguracji Win 32 Debug, wybrac konfiguracje Win 32 Release. Po ponownym zbudowaniu projektu, w 
katalogu  jbTests\Release, powstanie program znacznie krótszy, ale juz bez informacji uruchomieniowych. 
 
 

Zarzadzanie projektami 

 
Przestrzen robocza moze zawierac wiecej niz jeden projekt, a projekt moze skladac sie z wiecej niz jednego pliku.  
 
Jesli przestrzen zawiera wiecej niz jeden projekt, to tylko jeden z nich moze byc  aktywny, to jest taki, którego 
dotycza polecenia Build. Uaktywnienie projektu odbywa sie przez p-klikniecie jego nazwy i wydanie polecenia Set 
Active Project

 
W celu umieszczenia w przestrzeni dodatkowego projektu nalezy p-kliknac nazwe przestrzeni, wydac polecenie 
Add New Project to Workspace, a dalej postepowac tak, jak podczas tworzenia pierwszego projektu. 
 
W celu wlaczenia do projektu dodatkowego pliku nalezy p-kliknac nazwe projektu, a nastepnie wydac polecenie 
Add Files to Project i wybrac skopiowany plik. 
 
Jesli wlaczany do projektu plik zródlowy  juz istnieje, to nalezy skopiowac go do katalogu projektowego 
(poslugujac sie np. Eksploratorem Windows), a nastepnie postapic tak, jak podczas dodawania pliku do projektu.  
 
 

Dopasowanie oblicza 

 

background image

 

24

Oblicze srodowiska uruchomieniowego sklada sie z menu oraz z pasków, które mozna konfigurowac. Odbywa sie 
to za pomoca polecenia  Tools / Customize umozliwiajacego zarzadzanie wyswietlaniem pasków edycyjnych, 
uruchomieniowych i innych. 
 
 

Uruchamianie programu 

 
Systematyczne wyszukiwanie bledów w programie odbywa sie za pomoca uruchamiacza. W celu wyswietlenia 
paska zawierajacego jego narzedzia nalezy wydac polecenie Tools / Customize / Toolbars, a nastepnie odhaczyc 
nastawe Debug
 
Wykonanie programu nadzorowanego przez uruchamiacz zaczyna sie w konfiguracji Win32 Debug po wydaniu 
polecenia  Build / Start Debug / Step into (F10). Program zatrzyma sie tuz przed przystapieniem do wykonania 
pierwszej funkcji (zazwyczaj funkcji main). 
 
Poczawszy od tego momentu mozna  
 
 

Okreslac argumenty funkcji glównej 

 

 

Project / Settings // DebugProgram arguments 

 
 

Zastawiac / usuwac pulapki 

 

 

ikona Hand (F9

 
 

Usuwac pulapki 

 

 

Edit / Breakpoints / Remove All (Alt-F9) 

 
 

Wykonywac program krokowo 

 

 

ikona Go (po zastawieniu pulapki) 

 

 

ikona Step over (F10

 

 

ikona Step into (F11

 

 

ikona Step out (Shift-F11

 
 

Obserwowac zmienne 

 

 

ikona Quick Watch (Shift-F9

 
 

Kompilacja warunkowa 

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

 

#if c 

 

    kod-zródlowy 

 

#else 

 

    kod-alternatywny 

 

#endif 

 
zaczyna sie od wyznaczenia wartosci wyrazenia c (najczesciej liczby 1 albo 0). Jesli wyrazenie ma wartosc rózna od 
0, to cala dyrektywe zastepuje sie napisem  kod-zródlowy. W przeciwnym razie zastepuje sie ja napisem  kod-
alternatywny

 
Uwaga: Jesli napis kod-zródlowy jest pusty, to dyrektywe mozna zapisac bez frazy #else
 

#include <iostream.h> 

background image

 

25

 

int main(void) 

    int one, two; 

    cin >> one >> two; 

#if 1 

    cout << "Sum = "; 

#endif 

    cout << one + two << endl; 

 

    return 0; 

 
Program wyprowadza sume pary danych wejsciowych poprzedzajac ja napisem  Sum =. Jesli w dyrektywie  #if 
zmieni sie 1, na 0, to powstanie program, który takiego napisu nie wyprowadzi.
 

background image

 

26

Wskazniki i odnosniki 

 
 
 
 
Wskazniki i odnosniki sa zmiennymi, które sluza do  identyfikowania  innych zmiennych. Wskaznik moze 
identyfikowac  wiele  zmiennych pokrewnego mu typu, natomiast odnosnik moze identyfikowac tylko  jedna 
zmienna.  
 
Wskaznikom przypisuje sie  wskazania, a odnosnikom  odniesienia. Mimo iz w typowych implementacjach 
zarówno wskazania jak i odniesienia sa reprezentowane przez  adresy, poslugiwanie sie pojeciem adres jest 
calkowicie zbyteczne i dowodzi myslenia o C++ nie w kategoriach jezyka wysokiego poziomu, ale w kategoriach 
implementacji. Dlatego o adresach nie bedzie juz mowy. 
 
 

Zmienne wskaznikowe 

 
Wskaznikiem jest zmienna, której mozna przypisywac wskazania. Deklaracje wskaznika mozna poznac po tym, ze 
jej identyfikator jest poprzedzony symbolem (gwiazdka). 
 
Jesli w pewnym miejscu programu jest wymagane uzycie wskazania zmiennej, to otrzymuje sie je poprzedzajac 
nazwe zmiennej operatorem wskazywania & (ampersand). 
 
Po przypisaniu wskaznikowi  ptr wskazania zmiennej, napis  *ptr staje sie  chwilowa nazwa tej zmiennej. Po 
przypisaniu wskaznikowi wskazania pustego (reprezentowanego przez liczbe 0), uzycie nazwy *ptr albo nazwy jej 
równowaznej (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;         // blad 

 
Wskaznik  ptr  jest przystosowany do wskazywania zmiennych typu  int. Przypisano mu kolejno: wskazanie 
zmiennej fix1, wskazanie zmiennej fix2 i wskazanie puste. 
 
Po przypisaniu wskaznikowi ptr  wskazania zmiennej  fix1, napis  *ptr  jest chwilowa nazwa zmiennej fix1, a po 
przypisaniu mu wskazania zmiennej fix2, jest chwilowa nazwa zmiennej fix2
 
Po przypisaniu wskaznikowi ptr  wskazania pustego, az do chwili przypisania mu wskazania zmiennej, uzycie 
nazwy *ptr jest zabronione. 
 
 
Dla dociekliwych 
 

background image

 

27

Typ wyrazenia inicjujacego wskaznik musi byc zgodny z typem wskaznika. Przyjmuje sie z definicji, ze zgodne ze 
wskaznikiem typu Type  jest kazde wyrazenie typu Type  oraz kazde wyrazenie, które moze byc poddane niejawnej 
konwersji do typu Type (por. Dodatek C ). 
 

char *ptr1 = "0\0\0\0"           // niejawna konwersja 

int *ptr2 = "0\0\0\0";           // blad 

int *ptr = (int *)"0\0\0\0";     // jawna konwersja 

cout << *ptr;                    // 48 (kod cyfry 0) 

 
Skutek uzytej tu jawnej konwersji za lezy od implementacji. W Visual C++ powoduje to potraktowanie obszaru 
pamieci zajetego przez pierwsze 4 bajty literalu jako zmiennej calkowitej. 
 
 

Wskazniki i tablice 

 
Zwiazki miedzy wskaznikami i tablicami sa bardzo bliskie. Kazda nazwa tablicy jest niejawnie przeksztalcana na 
wskaznik jej zerowego elementu, a kazda nazwa wskaznika moze byc indeksowana tak, jak nazwa tablicy. 
 
Jesli wskaznik  ptr wskazuje pewien element tablicy, to zarówno  *ptr jak i  ptr[0] jest nazwa tego elementu. 
Elementy polozone z lewej strony elementu wskazywanego maja nazwy ptr[-1]ptr[-2], itd., a elementy polozone z 
prawej maja nazwy ptr[1]ptr[2], itd. 
 
Jesli  i jest wyrazeniem calkowitym, to wyrazenie  ptr+i jest wskaznikiem elementu odleglego o i elementów od 
wskazywanego (dla i ujemnego - w lewo, a dla i dodatniego - w prawo). 
 
Jesli wskazniki ptr1 ptr2 wskazuja odpowiednio elementy o indeksach  oraz tej samej n-elementowej tablicy (a 
takze gdy wskazuja nie istniejace "elementy" o indeksach -1 i n), to wyrazenie ptr1-ptr2 ma wartosc 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 przeksztalcona na wskazanie elementu vec[0], to jest na &vec[0]
 
Wyrazenie vec + 2  wskazuje element o wartosci 30
 
Wyrazenie ptr++[-1] jest nazwa elementu o wartosci 20
 
W wyrazeniu  vec - ptr pierwszy argument wskazuje element zerowy, a drugi argument wskazuje nie istniejacy 
element vec[3]
 
 

Wskazniki i struktury 

 
Jesli wskaznik  ptr wskazuje strukture o polu f, to nazwa zmiennej odpowiadajacej temu polu jest (*ptr).f, albo 
krócej ptr->f
 

#include <iostream.h> 

 

struct Child { 

    char name[20]; 

    int age; 

    Child *pNext; 

}; 

 

background image

 

28

Child bob = { "Robert", 20 }, 

      tom = { "Thomas", 30, 0 }; 

 

Child *pBob = &bob, 

      *pTom = &tom; 

 

int main(void)  

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

 

    pBob->pNext = pTom; 

     

    cout << pBob->pNext->age << endl;   // 30 

 

    return 0; 

 
Zmienna  pBob jest wskaznikiem przystosowanym do wskazywania zmiennych typu  Child. Przypisano jej 
wskazanie struktury bob
 
Napis pBob->name jest chwilowa nazwa tego elementu struktury bob, który jest opisany przez pole name
 
Napis  pBob->pNext jest nazwa wsk aznika opisanego przez pole  pNext. Poniewaz wskazuje on strukture  tom
wiec pBob->pNext->age jest nazwa tego elementu struktury tom, który jest opisany przez pole age
 
 

Tablice wskazników 

 
Tablica wskazników jest tablica, której elementami sa wskazniki. W deklaracji tablicy wskazników jej identyfikator 
jest poprzedzony znakiem * (gwiazdka). 
 
W deklaracji wskaznika, który sluzy do wskazywania-wskaznikó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; 

            } 

background image

 

29

        } 

    } 

 

    Child **ptr = pBoys; 

    for(i = 0; i < Count ; i++) 

        cout << (*ptr++)->name << endl; 

 

    return 0; 

 
Program wyprowadza nazwiska chlopców, w kolejnosci ich rosnacego wieku. Sortowanie dotyczy tylko 
elementów tablicy wskazników i nie powoduje kopiowania struktur typu Child
 
 

Wskazniki a ustalenia 

 
Podobnie jak zwykla zmienna, tak i wskaznik moze byc ustalony albo nie-ustalony. Ponadto wskaznik moze byc 
przystosowany do wskazywania zmiennych ustalonych albo nie-ustalonych. Daje to cztery mozliwosci. 
 
Uwaga: Zabrania sie, aby wskaznikowi 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;            // blad 

    ++ptr2;                 // blad 

    ++*ptr3;                // blad 

 

    ptr1 = &(int &)fix;     // dozwolone 

    cout << *ptr1 << endl;  // 20 

 

    return 0; 

 
Wskaznik  ptr1  sluzy do wskazywania zmiennych nie-ustalonych. Wskaznik  ptr2  jest wskaznikiem ustalonym, 
który sluzy do wskazywania zmiennych nie-ustalonych. Wskaznik ptr3 jest wskaznikiem nie-ustalonym, który 
sluzy do wskazywania zmiennych ustalonych. Wskaznik  ptr4 jest wskaznikiem ustalonym, który sluzy do 
wskazywania zmiennych ustalonych. 
 
 

Zmienne odnosnikowe 

 

background image

 

30

Odnosnikiem jest zmienna, która mozna zainicjowac odniesieniem. Deklaracje odnosnika mozna poznac po tym, ze 
jej identyfikator jest poprzedzony symbolem  (ampersand). Istnieja odnosniki do zmiennych, ale nie istnieja 
tablice odnosników. Kazdy odnosnik musi byc zainicjowany. 
 
Uwaga: Jesli w pewnym miejscu programu wystepuje nazwa zmiennej, a program bylby poprawny tylko wówczas, 
gdyby wystepowala tam nazwa odnosnika do zmiennej, to nazwe zmiennej niejawnie przeksztalca sie w odnosnik. 
 

int fix = 10; 

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

 
Poniewaz odnosnik ref jest typu int &, wiec nie moze byc zainicjowany wartoscia zmiennej  fix, która jest typu 
int. Dlatego, za pomoca niejawnej konwersji  (int &)fix, nazwe zmiennej  fix niejawnie przeksztalca sie w 
odnosnik. 
 
Po zainicjowaniu odnosnika  ref odniesieniem do zmiennej, napis ref staje sie  trwala nazwa tej zmiennej. A wiec 
odnosnik mozna zainicjowac, ale nie mozna mu przypisac odniesienia. 
 

#include <iostream.h> 

 

int main(void)  

    int fix = 10; 

    int &ref = fix; 

    ref = 10; 

    cout << fix << ref << endl; // 10 10 

 

    return 0; 

 
Po zainicjowaniu odnosnika, napis  ref  staje sie trwala nazwa zmiennej  fix. Dlatego przypisanie  ref = 10 
zmienia wartosc zmiennej fix, ale nie zmienia wartosci odnosnika ref.
 
 
 
Dla dociekliwych 
 
Typ wyrazenia inicjujacego odnosnik musi byc zgodny z typem odnosnika. Przyjmuje sie z definicji, ze typ 
"odnosnik do zmiennej typu Type" (np. int &) jest zgodny z typem  Type  (np. int). Jesli wyrazenie inicjujace jest 
innego typu, to moze byc poddane niejawnej konwersji do typu zgodnego, ale tylko wówczas, gdy typ odnosnika 
jest ustalony (const). W takim wypadku odnosnik zostanie zainicjowany odniesieniem do zmiennej pomocniczej 
typu z nim zgodnego, zainicjowanej wartoscia wyrazenia po konwersji. 
 

int &ref1 = 2.4;           // blad 

const int &ref = 2.4; 

 
Identyfikator ref2 jest trwala nazwa zmiennej pomocniczej o wartosci (int)2.4
 
 

Wskazniki i odnosniki 

 
Podejmujac decyzje o uzyciu wskaznika, czy odnosnika, nalezy kierowac sie wytyczna, ze wszedzie tam gdzie jest 
to mozliwe, nalezy stosowac odnosniki, gdyz zwieksza to czytelnosc programu.  
 
W rzadkich przypadkach stosuje sie odnosniki do wskazników. Jest to niezbedne wówczas, gdy poprzez odnosnik 
nalezy zmodyfikowac wskaznik. 
 

#include <iostream.h> 

 

background image

 

31

int main(void) 

    int vec[3] = { 10, 20, 30 }; 

    int *ptr = vec; 

    int *&ref = ptr; 

 

    ++ref; 

 

    cout << *ptr << endl;            // 20 

 

    return 0; 

 
Po zadeklarowaniu odnosnika, napis ref  jest trwala nazwa wskaznika  ptr. Dlatego po wykonaniu operacji 
++ref wskaznik ptr wskazuje element vec[1] o wartosci 20
 
Gdyby z deklaracji odnosnika usunieto znak  &, to napis  ref stalby sie nazwa wskaznika zainicjowanego 
wskazaniem elementu  vec[0], a wykonanie operacji  ++ref nie mialoby zadnego wplywu na wskaznik  ptr. W 
takim wypadku nastapiloby wyprowadzenie liczby 10

background image

 

32

Przetwarzanie lancuchów 

 
 
 
 
Lancuchem jest dowolna sekwencja elementów tablicy znakowej, zakonczona elementem o wartosci 0. Poniewaz 
kazdy literal lancuchowy jest nazwa takiej wlasnie sekwencji elementów, wiec jest nazwa lancucha. 
 
W szczególnosci, literal  "Hello" jest nazwa  6-elementowej tablicy znakowej,  której element  "Hello"[0] ma 
wartosc 'H' , a element "Hello"[5] ma wartosc 0
 
Do typowych operacji wykonywanych na lancuchach naleza: wprowadzenie i wyprowadzenie lancucha, 
wyznaczenie dlugosci lancucha (strlen),  skopiowanie lancucha (strcpy), polaczenie lancuchów (strcat) i 
porównanie lancuchów (strcmp). Operacje te mozna wykonac za pomoca funkcji bibliotecznych, zadeklarowanych 
w naglówku string.h
 
Uwaga: Jesli wskaznik wskazuje pierwszy element lancucha, to mówi sie w skrócie, ze wskazuje lancuch.. 
 

int strlen(char *pStr) 

Dostarcza liczbe znaków lancucha wskazanego przez argument. 
np. 

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

 

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

Dostarcza pierwszy argument. Ponadto kopiuje, poczawszy od  miejsca wskazanego przez pierwszy argument, 
lancuch 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 sie znak konca lancucha 
wskazanego przez pierwszy argument, lancuch wskazany przez drugi argument. 
np. 

char buf[100] = "Hello "; 

cout << strcat(buf, "World");         // Hello World 

 

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

Dostarcza wartosc  +1 jesli lancuch wskazany przez pierwszy argument jest wiekszy niz lancuch wskazany przez 
drugi argument, dostarcza wartosc 0 jesli sa równe, albo wartosc -1 jesli pierwszy jest mniejszy.  
Uwaga: Porównanie lancuchów zastepuje sie porównaniem pierwszej pary znaków róznych. Jesli jeden z 
lancuchów jest podlancuchem drugiego, to za wiekszy uznaje sie dluzszy.  
np. 

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

cout << strcmp("abcde", "ab");        // 1 

cout << strcmp("ab", "ab");           // 0 

 
 
Wprowadzanie i wyprowadzanie lancuchów 
 
Operacja wprowadzenia lancucha ma postac cin >> ptr, w której ptr jest wskaznikiem elementu tablicy znakowej. 
Jej wykonanie powoduje umieszczenie w tablicy, poczawszy od jej elementu *ptr, kodów spójnego ciagu znaków 
wejsciowych oraz kodu znaku '\0' (o wartosci 0).  
 
Przed wprowadzeniem znaków zostana pominiete odstepy wiodace. W celu zabezpieczenia sie przed 
przepelnieniem tablicy mozna uzyc manipulatora setw.  

background image

 

33

 
Operacja wyprowadzenia lancucha ma postac cout << ptr, w której ptr jest wskaznikiem. Zabrania sie, aby ptr bylo 
wskaznikiem elementu tablicy, który nie jest zerowym elementem lancucha. 
 

#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; 

 
Jesli z klawiatury wprowadzi sie imie i nazwisko (np. Jan Bielecki), to program wyprowadzi to imie i to 
nazwisko poprzedzone napisem prof. (np. prof. Jan Bielecki), a ponadto tylko to imie i to nazwisko.  
 
 
Wyznaczenie dlugosci 
 

#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 dlugosci lancucha zapisanego w tablicy znakowej. 
 

background image

 

34

Wyrazenie  *ptr++ jest nazwa zmiennej, wskazywanej przez wskaznik  ptr, przed wykonaniem na nim operacji 
zwiekszenia.  
 
 
Kopiowanie 
 

#include <iostream.h> 

#include <string.h> 

 

char src[7] = "Hello "; 

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 lancucha znaków. 
 
 
Laczenie 
 

#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! 

background image

 

35

 

    return 0; 

 
Pokazano dwa sposoby laczenia lancuchów. 
 
 
Porównanie 
 

#include <iostream.h> 

#include <string.h> 

 

char one[100], 

     two[100]; 

 

int main(void)  

    cin >> one >> two; 

 

    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 << " == "; 

background image

 

36

    cout << two << endl; 

 

    return 0; 

 
Pokazano trzy sposoby porównywania lancuchów wprowadzonych z klawiatury. 
 

background image

 

37

Poslugiwanie sie funkcjami 

 
 
 
 
Funkcja jest sparametryzowanym opisem czynnosci. W miejscu wywolania funkcji musi byc znana jej deklaracja 
albo definicja. W szczególnosci oznacza to, ze wywolanie 
 
sum(10, 20)  
 
funkcji sumujacej argumenty, musi byc poprzedzone 
 
albo jej definicja 
 

int sum(int one, int two) 

    return one + two; 

 
albo jej deklaracja 
 

int sum(int one, int two); 

 
albo wlaczeniem naglówka zawierajacego deklaracje. 
 
 
Uwaga: W deklaracji funkcji mozna pominac dowolny zestaw identyfikatorów parametrów. Jesli uczyni sie to w 
definicji, to uniemozliwi to odwolywanie sie do argumentów. 
 
 

Parametry i argumenty 

 
Wywolanie funkcji zaczyna sie od skojarzenia jej parametrów z argumentami. Skojarzenie parametru z argumentem 
odbywa sie przez-wartosc, co oznacza, ze parametr jest traktowany tak, jak lokalna zmienna funkcji, zadeklarowana 
tuz przed jej pierwsza instrukcja i zainicjowana wartoscia argumentu. 
 
A zatem, jesli definicja funkcji jest 
 

int sum(int one, int two) 

    return one + two; 

 
to dla wywolania  
 

sum(10, 20) 

 
funkcja jest traktowana tak, jakby miala postac 
 

int sum() 

    int one = 10; 

    int two = 20; 

 

    return one + two; 

background image

 

38

 
 

Parametry zwykle 

 
Parametr funkcji jest "zwykly", jesli nie jest wskaznikiem ani odnosnikiem. Z parametrem zwyklym mozna skojarzyc 
argument, który jest takiego samego typu jak parametr, albo który mozna poddac niejawnej konwersji do typu 
parametru.  
 
Zainicjowanie parametru polega na skopiowaniu argumentu. Jesli argument jest struktura, to kopiuje sie wszystkie 
jej elementy (co w przypadku duzych struktur ma oczywiste wady!). 
 
Po dokonaniu skojarzenia, wszelkie operacje wykonywane na parametrze dotycza lokalnej zmiennej zainicjowanej 
argumentem i nie powoduja zmiany wartosci 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, ze wykonanie operacji na parametrze "zwyklym" nie powoduje zmiany wartosci 
skojarzonego z nim argumentu. 
 
 

Parametry wskaznikowe 

 
Z parametrem wskaznikowym mozna skojarzyc argument, który jest takiego samego typu jak parametr, albo który 
mozna poddac niejawnej konwersji do typu parametru.  
 
Zainicjowanie parametru polega na skopiowania wskaznika. Nie pociaga to za soba kopiowania zmiennej 
identyfikowanej przez argument (co mozna wykorzystac w przypadku duzych struktur!). 
 
Po dokonaniu skojarzenia, wszelkie operacje wykonywane na parametrze dotycza lokalnej zmiennej zainicjowanej 
argumentem, ale operacje wykonywane za posrednictwem parametru (np.  *par,  par[i] albo  par->f) dotycza 
zmiennej wskazywanej przez argument. Moze to miec wplyw na wartosc argumentu. 
 

#include <iostream.h> 

 

int main(void)  

    void inc(int *ptr); 

    int fix = 10; 

    cout << fix << endl;      // 10 

    inc(&fix); 

    cout << fix << endl;      // 11     

background image

 

39

 

    return 0; 

 

void inc(int *ptr) 

    ++*ptr; 

 
Program potwierdza, ze wykonanie operacji za posrednictwem parametru wskaznikowego moze powodowac 
zmiane wartosci zmiennej wskazywanej przez skojarzony z nim argument. 
 
 

Parametry tablicowe 

 
Kazdy parametr tablicowy jest niejawnie zastepowany parametrem wskaznikowym, który powstaje po zastapieniu 
deklaratora vec[i] deklaratorem (* const vec)
 
W szczególnosci funkcja 
 

int sum(int tab[20]) 

    int sum = 0; 

    for(int i = 0; i < 3 ; i++) 

        sum += tab[i]; 

    return sum; 

 
jest niejawnie przeksztalcana w funkcje 
 

int sum(int *const tab) 

    int sum = 0; 

    for(int i = 0; i < 3 ; i++) 

        sum += tab[i]; 

    return sum; 

 
Powoduje to, ze jesli chce sie zdefiniowac funkcje do sumowania tablic, nie odwolujaca sie do zmiennych 
globalnych, to jeden z jej argumentów musi okreslac liczbe 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; 

background image

 

40

 
 

Parametry odnosnikowe 

 
Z parametrem odnosnikowym mozna skojarzyc argument, który jest takiego samego typu jak parametr, albo który 
mozna poddac niejawnej konwersji do typu parametru.  
 
Zainicjowanie parametru polega na skopiowania odnosnika. Podobnie jak dla parametru wskaznikowego, nie 
pociaga to za soba kopiowania zmiennej identyfikowanej przez argument. 
 
Po dokonaniu skojarzenia, wszelkie operacje wykonywane na parametrze dotycza zmiennej identyfikowanej przez 
argument. Moze to powodowac zmiane wartosci skojarzonego z nim argumentu. 
 

#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, ze wykonanie operacji za posrednictwem parametru odnosnikowego moze powodowac 
zmiane wartosci skojarzonego z nim argumentu. 
 
 

Parametry funkcji main 

 
Funkcja glówna moze byc zadeklarowana jako bezparametrowa albo dwu-parametrowa. Jesli jest dwuparametrowa, 
to jej pierwszy parametr jest typu int i ma wartosc równa liczbie argumentów programu zwiekszonej o 1, a drugi 
jest typu  char *[] i jest tablica odnosników do lancuchów zainicjowanych nazwa 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 swoja nazwe i  argumenty; kazde w osobnym wierszu. 
 
 

background image

 

41

Skojarzenia powrotne 

 
W chwili zakonczenia wykonywania  funkcji rezultatowej  (o typie róznym od  void) nastepuje skojarzenie jej 
rezultatu z wyrazeniem wystepujacym w instrukcji powrotu. Odbywa sie to wedlug tych samych zasad co 
skojarzenie parametru z argumentem i polega na zainicjowaniu rezultatu funkcji wyrazeniem wystepujacym w 
instrukcji powrotu. 
 
Uwaga: Rezultat funkcji jest  zmienna. Typ rezultatu jest identyczny z typem funkcji. Nazwa rezultatu jest 
wywolanie funkcji. Z punktu widzenia laczenia operacji (np.  ++*fun(1,2)+3), nazwa funkcji jest zastepowana 
nazwa rezultatu. 
 

#include <iostream.h> 

#include <math.h> 

 

double sqr(double val) 

    return val * val; 

 

int main(void) 

{    

    double a, b; 

    cin >> a >> b; 

    cout << sqrt(sqr(a) + sqr(b)) << endl; 

 

    return 0; 

 
Wywolanie  sqr(a)  jest nazwa rezultatu o wartosci "kwadrat  a", wywolanie  sqr(b) jest nazwa rezultatu o 
wartosci "kwadrat  b",  a wyrazenie  sqrt(sqr(a) + sqr(b)) jest nazwa rezultatu o wartosci "pierwiastek z sumy 
kwadratów b". 
 
 

Typ nie-odnosnikowy 

 
Jesli typ funkcji jest nie-odnosnikowy, to zainicjowanie rezultatu polega na skopiowaniu zmiennej, której nazwa 
jest wyrazenie wystepujace w instrukcji powrotu. 
 
Jesli typ wyrazenia nie jest identyczny z typem funkcji, to wyrazenie jest poddawane konwersji do typu rezultatu. 
Zezwala sie na niejawne wykonanie co najwyzej 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); 

 
Wyrazenie par * par jest nazwa zmiennej typu int zainicjowanej dana o wartosci 9

background image

 

42

 
Poniewaz typ rezultatu jest rózny od typu tej zmiennej, wiec zostanie zastosowana niejawna konwersja 
standardowa z typu int do double
 
Po zainicjowaniu rezultatu zmienna double(par * par), wywolanie getSqr(3) mozna traktowac nazwe 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 

 

    return 0; 

 

Child getOlder(Child child, int val) 

    child.age += val; 

 

    return child; 

 

void show(Child &child) 

    cout << child.name << " is " <<  

            child.age << endl; 

 
Wywolanie funkcji getOlder(isa, 2) powoduje skopiowanie struktury isa do lokalnej zmiennej funkcji getOlder
 
Operacja child.age += val jest wykonywana na tej zmiennej lokalnej. 
 
Wywolanie getOlder(isa, 2) jest nazwa zmiennej, do której skopiowano te zmienna lokalna. 
 
 

Typ odnosnikowy 

 
Jesli typ funkcji jest odnosnikowy, to zainicjowanie rezultatu polega na skopiowaniu odnosnika do tej zmiennej, 
której nazwa jest wyrazenie wystepujace w instrukcji powrotu. A zatem wywolanie funkcji jest nazwa tej zmiennej. 
 

#include <iostream.h> 

 

int &refVal(void) 

background image

 

43

    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; 

 
Wywolanie  refVal() jest nazwa statycznej zmiennej val. A zatem kazda operacja wykonana na refVal() dotyczy 
tej wlasnie zmiennej. 
 
 
dla dociekliwych 
 
Jesli typ wyrazenia w instrukcji powrotu nie jest zgodny z typem funkcji, to typ funkcji musi byc ustalony (const), 
a ponadto musi istniec niejawna konwersja z typu wyrazenia do typu zgodnego z typem funkcji. 
 

#include <iostream.h> 

 

const int &refVal(double par) 

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

 

int main(void) 

    cout << refVal(3) << endl; 

 

    return 0; 

 
Uwaga: Wyrazenie zawarte w instrukcji powrotu moze tylko wówczas identyfikowac zmienna lokalna funkcji, gdy 
typ funkcji jest ustalony
 

#include <iostream.h> 

 

int &getInc(int par); 

 

int main(void)  

    cout << getInc(3) << endl;   // blad 

 

    return 0; 

 

int &getInc(int par) 

    return ++par; 

 

background image

 

44

Wywolanie  getInc(3) jest nazwa lokalnej zmiennej  par. Poniewaz po powrocie z funkcji getInc zmienna par juz 
nie istnieje, wiec odwolanie sie do niej jest zabronione. W Visual C++ program wyprowadza liczbe 4
 
Program mozna poprawic, nadajac mu postac 

 

#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 okresla typ funkcji oraz typy jej parametrów. Jesli ponadto podaje 
cialo funkcji, to jest jej definicja
 
 

Funkcje bezrezultatowe 

 
Funkcja, której typem jest  void, jest funkcja  bezrezultatowa. Jej wywolanie  konczy sie w chwili wykonania 
instrukcji powrotu nie zawierajacej wyrazenia, albo w chwili zakonczenia wykonywania jej ciala. 
 

void outDiv(int a, int b) 

    if(b == 0) 

        return; 

    cout << a / b; 

 
 

Funkcje otwarte i zamkniete 

 
Funkcja zadeklarowana ze specyfikatorem  inline jest realizowana jako  otwarta. W odróznieniu od funkcji 
zamknietej, cialo funkcji otwartej wstawia sie w kazdym miejscu jej wywolania. Powoduje to przyspieszenie 
wykonania programu, ale niekiedy wydluza jego kod wynikowy. 
 
Uwaga: Wystapienie specyfikatora  inline nie ma wplywu na skutek wykonania programu. Jesli funkcja otwarta 
zostanie uznana za zbyt skomplikowana, to moze byc zrealizowana jako zamknieta. 
 

inline in sum(int a int b) 

    return a + b; 

 
 

background image

 

45

Funkcje przeciazone 

 
Jesli w pewnym zakresie sa widoczne deklaracje dwóch lub wiekszej liczby funkcji o takiej samej nazwie, ale 
rózniacych sie typami parametrów, to ogól takich funkcji stanowi wieloaspektowa funkcje przeciazona
 
W miejscu wywolania funkcji przeciazonej wywoluje sie ten z jej aspektów, do którego parametrów  najlepiej 
pasuja podane argumenty. Ma to miejsce wówczas, gdy istnieje taki aspekt, ze do kazdego z jego parametrów 
podany argument pasuje  nie gorzej niz do pozostalych, ale istnieje taki parametr, do którego jeden z argumentów 
pasuje lepiej niz do pozostalych. 
 
Uwaga: Jesli argument nie pasuje do parametru dokladnie, to moze byc poddany konwersji dopasowujacej, ale im 
konwersja ta jest bardziej zlozona, tym dopasowanie pierwotnego argumentu uznaje sie za gorsze. 
 

#include <iostream.h> 

 

void out(char par); 

void out(int par); 

 

int main(void)  

    out('a'); 

    out(2); 

    out(2.0);    // blad (niejednoznacznosc) 

 

    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. Poniewaz 
do zadnego z nich nie pasuje najlepiej, wiec wywolanie out(2.0) jest bledne. 
 
Gdyby z programu usunieto dowolna z funkcji out, to wszystkie odwolania do out bylyby poprawne. 
 
 

Argumenty domniemane 

 
W deklaracji parametru funkcji moze wystapic inicjator wyrazeniowy okreslajacy domniemana wartosc argumentu 
kojarzonego z tym parametrem. 
 

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

 
Jesli pewien parametr wyposazono w argument domniemany, to kazdy z nastepnych parametrów takze musi byc 
wyposazony w argument domniemany. 
 

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

 

background image

 

46

Z kazdym parametrem nie wyposazonym w argument domniemany musi byc skojarzony jawny argument. 
Koncowy zestaw argumentów, dla których podano domniemania, mozna pominac. W ich miejscu zostana uzyte 
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;         // blad 

 

    return 0; 

 

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

    return a + b + c + d; 

 
 
dla dociekliwych 
 
Wyrazenie okreslajace wartosc argumentu domniemanego nie musi byc wyrazeniem stalym. W takim wypadku jest 
opracowywane w kontekscie jego deklaracji, a nie w kontekscie jego uzycia. 
 

#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; 

 
 

Wywolania rekurencyjne 

 
Wywolanie funkcji jest  rekurencyjne, jesli nastapi przed powrotem z jej poprzedniego wywolania. Uzycie 
rekurencji moze uczynic program czytelniejszym, ale w wielu wypadkach powoduje zwiekszenie rozmiaru pamieci 
operacyjnej niezbednej do jego wykonania. 
 

#include <iostream.h> 

#include <limits.h> 

#include <stdlib.h> 

 

int sqrt(int par, int min =0, int max =INT_MAX) 

background image

 

47

    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. Nieobowiazkowe argumenty dodatkowe 
okreslaja przedzial, w którym znajduje sie pierwiastek. 
 
 

Definiowanie funkcji 

 
Zdefiniowanie funkcji polega na podaniu jej ciala. Dobry styl programowania poznaje sie po uzyciu wielu krótkich, 
a nie malej liczby dlugich funkcji. 
 
Tak dalece jak jest to mozliwe, nalezy poslugiwac sie funkcjami bibliotecznymi . Ilustruje to nastepujacy program, 
który napisano w dwóch wersjach: z uzyciem i bez uzycia 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; 

 
Program wprowadza dwa lancuchy, laczy je oddzielajac spacja, a nastepnie wyprowadza: lanuch docelowy, 
dlugosc lancucha docelowego i wynik porównania lancuchów zródlowych. 
 

background image

 

48

#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) 

background image

 

49

    while(*pOne || *pTwo) 

        if(*pOne++ != *pTwo++) 

            if(pOne[-1] > pTwo[-1]) 

                return +1; 

            else  

                return -1; 

    return 0; 

 

background image

 

50

Zarzadzanie pamiecia 

 
 
 
 
Wykonanie programu polega na przeplywie sterowania przez jego deklaracjedefinicje instrukcje
 
W pierwszej kolejnosci sterowanie przeplywa przez wszystkie deklaracje  globalne (takie, które nie wchodza w 
sklad innych deklaracji). Nastepnie jest wyszukiwana funkcja glówna i sterowanie przeplywa przez zawarte w niej 
instrukcje. Przeplyw sterowania konczy sie po powrocie z wywolania funkcji exit albo po wykonaniu instrukcji 
powrotu z funkcji gló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 zaleznosci od tego, jaka wartosc ma wprowadzona liczba, program konczy sie po napotkaniu instrukcji 
powrotu albo po wywolaniu funkcji exit
 
 

Zmienne statyczne 

 
Jesli sterowanie przeplynie przez definicje zmiennej globalnej, albo przez definicje zmiennej lokalnej zadeklarowanej 
ze specyfikatorem  static, to zostanie utworzona zmienna  statyczna. Tuz przed zakonczeniem wykonywania 
programu wszystkie zmienne statyczne zostana zniszczone. Odbedzie sie to w kolejnosci  odwrotnej  do ich 
tworzenia. 
 
Uwaga: Zmienna statyczna jest tworzona w  obszarze statycznym. Inicjator zmiennej statycznej jest brany pod 
uwage 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; 

 

background image

 

51

void fun(int par) 

    static int loc = par; 

    cout << loc << ' ' << par << endl; 

    loc++; 

 
Zmienne statyczne  one,  two,  loc  zostana utworzone w kolejnosci:  two,  loc,  one, a zostana zniszczone w 
kolejnosci: oneloctwo
 
Program wyprowadzi dwie pary liczb: 10 10 11 20
 
 

Zmienne automatyczne 

 
Jesli sterowanie przeplynie przez definicje zmiennej lokalnej, nie zadeklarowanej ze specyfikatorem  static albo 
extern,  to zostanie utworzona  zmienna automatyczna. Jawny albo niejawny inicjator zmiennej automatycznej 
bedzie brany pod uwage podczas kazdego opracowania tej definicji. 
 
Uwaga: Zmienne automatyczne tworzy sie na  stosie. Stos jest obszarem pamieci, w którym mozna tworzyc 
zmienne, ale takim, ze mozna je niszczyc tylko w kolejnosci odwrotnej do ich tworzenia. 
 

void sub(void) 

    int num;         // int num = int(); 

    cout << num;     // blad 

    // ... 

 
Zmienna automatyczna num wyposazono w niejawny inicjator = int() dostarczajacy wartosc nieokreslona. 
 
 
Zmienna automatyczna zostanie zniszczona tuz przed zakonczeniem wykonywania  bloku  (wnetrza instrukcji 
grupujacej), w którym ja zadeklarowano. Jesli w bloku zadeklarowano wiecej niz jedna zmienna automatyczna, to 
ich niszczenie odbedzie sie w kolejnosci odwrotnej do ich tworzenia, ale przed przystapieniem 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. Nastepnie zostanie 
utworzona i zniszczona zmienna automatyczna  val  zainicjowana wartoscia 2, a po tym zostanie utworzona i 
zniszczona zmienna automatyczna  val  zainicjowana wartoscia  1. Tuz przed wykonaniem instrukcji powrotu 
zostanie zniszczona zmienna cnt, a po niej zmienna one
 

background image

 

52

 

Zmienne kontrolowane 

 
Zmienna kontrolowana powstaje na skutek wykonania operacji  new, a jest niszczona po jawnym wykonaniu 
operacji  delete. Zmienne kontrolowane sa tworzone na  stercie. Sterta jest obszarem pamieci, do którego mozna 
dokladac zmienne, a nastepnie usuwac je w dowolnej kolejnosci. 
 
Jesli wykonanie operacji  new jest niemozliwe, poniewaz wyczerpano obszar sterty, to rezultatem operacji 
przydzielenia pamieci jest wskaznik pusty (o wartosci reprezentowanej przez 0). 
 
Uwaga: Programisci rzadko badaja rezultat operacji new, bo sa 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 wskaznik zainicjowany wskazaniem utworzonej zmiennej. 
 
Wykonanie operacji 
 

 

delete ptr 

 
w której ptr wskazuje zmienna utworzona 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 nastepnie zmienna typu double. Najpierw zostanie zniszczona 
zmienna typu int, a nastepnie zmienna typu double
 
 

Zmienne tablicowe 

 
Wykonanie operacji 

background image

 

53

 

 

new Type 

 
w której  Type  jest opisem typu  tablicowego (np. int [12]), powoduje utworzenie na stercie zmiennej typu Type
Rezultatem operacji jest wskaznik zainicjowany wskazaniem zerowego elementu utworzonej tablicy. 
 
Jesli elementami tablicy sa obiekty, to do ich zainicjowania jest niejawnie stosowany konstruktor domyslny. 
 
Uwaga: Wyrazenie okreslajace liczbe elementów tablicy nie musi byc wyrazeniem stalym. 
 
Wykonanie operacji 
 

 

delete [] ptr 

 
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-elementowa tablice znakowa i wprowadza do niej ciag znaków. Nastepnie 
tworzy na stercie najmniejsza tablice, w której mozna pomiescic wprowadzony ciag znaków oraz tworzy na 
stosie odnosnik vec identyfikujacy zerowy element tej tablicy. 
 
Przed zakonczeniem wykonywania program niszczy obie tablice, w kolejnosci ich utworzenia. 
 
 

Ostrzezenie 

 
W zadnym wypadku nie wolno zmiennej utworzonej za pomoca operacji new dla zmiennych skalarnych niszczyc za 
pomoca operacji delete dla zmiennych tablicowych, a zmiennej utworzonej za pomoca operacji new dla zmiennych 
tablicowych niszczyc za pomoca operacji delete dla zmiennych skalarnych. 
 
Nie wolno takze uzywac operacji delete ze wskaznikiem  ptr identyfikujacym co innego niz zmienna skalarna albo 
zerowy element tablicy utworzonej za pomoca operacji  new, ani przyjmowac, ze po wykonaniu operacji  delete 
wskaznik ptr ma wartosc okreslona. 
 
Uwaga: W celu unikniecia trudnych do wykrycia bledów, zaleca sie (o ile to mozliwe) zerowanie wskaznika ptr 
bezposrednio po uzyciu go w operacji delete
 

#include <iostream.h> 

 

int main(void)  

    int *ptr = new int [5]; 

    delete [] (ptr + 2);        // blad 

 

background image

 

54

    int &vec = *new int [5]; 

    delete &vec;                // blad 

 

    int &ref = (*new int) = 3; 

    delete &ref; 

    cout << ref << endl;        // blad 

 

    return 0; 

 
Mimo iz program jest poprawny skladniowo, zawiera  3 powazne bledy logiczne. Wykonany w srodowisku 
Visual C++, program ten zalamuje system zarzadzania sterta. 

background image

 

55

Widocznosc deklaracji 

 
 
 
 
Identyfikatorem  zmiennej,  funkcji i  typu  mozna poslugiwac sie tylko w miejscu, w którym jest widoczna jego 
deklaracja.  
 
Zaleca sie, aby w tym samym zakresie, identyfikator uzyty do zadeklarowania zmiennej, funkcji albo typu nie zostal 
uzyty do zadeklarowania innej zmiennej, funkcji albo typu. 
 
Uwaga: Podano zalecenie, a nie zakaz, poniewaz w tym samym zakresie moga wystapic, nie kolidujace za soba, 
deklaracje funkcji i typu. 
 

void id(int id) 

{     

    struct id { 

    }; 

    extern void id(id id); 

    int id = 10;    // blad 

 
Z kazda deklaracja jest zwiazany jej zakres  zasieg. Jesli w pewnym module zdefiniowano identyfikator o zasiegu 
globalnym, a w innym zadeklarowano go ze specyfikatorem extern, to oba dotycza 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 pominieto wszystkie specyfikatory  extern, to program stalby sie statycznie poprawny, ale dynamicznie 
bledny. Blad polegalby na uzyciu wartosci zmiennej, której nie zainicjowano. 
 
 

background image

 

56

Deklaracje lokalne 

 
Zakresem deklaracji identyfikatora zadeklarowanego w bloku jest obszar programu od punktu zadeklarowania do 
konca bloku. Zasiegiem deklaracji jest ta czesc zakresu, która nie jest zakresem innej deklaracji takiego samego 
identyfikatora. 
 
 

#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 zaczynajacy sie od  = 10 i konczacy na klamrze 
zamykajacej funkcje main
 
Zakresem deklaracji drugiej zmiennej  num  jest obszar zaczynajacy sie od  = 20 i konczacy na klamrze 
zamykajacej blok wewnetrzny. 
 
Zasiegiem 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 konca modulu. Zasiegiem deklaracji jest ta czesc zakresu, która nie jest zakresem innej 
deklaracji takiego samego identyfikatora. 
 
Uwaga: Modulem jest zawartosc pliku *.cpp projektu, po zastosowaniu uzytych 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; 

background image

 

57

 

int num2 = num; 

 
Zasieg deklaracji pierwszego identyfikatora num obejmuje m.in. deklaracje wystepujaca po funkcji main. 
 
 

Deklaracje i definicje 

 
Jesli deklaracja globalna zawiera specyfikator static, to jest widoczna tylko w jej module. Jesli deklaracja globalna 
jest definicja, ale nie zawiera specyfikatora  static, to jest widoczna w tych obszarach pozostalych modulów 
programu, w których jest widoczna zgodna z nia deklaracja ze specyfikatorem extern bez inicjatora, nie dotyczaca 
deklaracji globalnej ze specyfikatorem static
 
Uwaga: Globalne zmienne ustalone sa domyslnie wyposazone w specyfikator  static. Specyfikator  extern 
wystepujacy w deklaracji funkcji mozna pominac. 
 
plik Main.cpp 
 

#include <iostream.h> 

 

int main(void)  

    int fun(void);           // pominieto 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;          // zbedne 

    return num; 

 
 
plik Two.cpp 
 

int num = 20; 

 
 

Deklaracje typów 

 
Globalna deklaracja typu, na przyklad  
 

 

struct Child; 

 
nie wystarczy do tego, aby mozna bylo nawiazac do definicji tego typu podanej w innym module. 
 

background image

 

58

W odróznieniu od definicji zmiennej i funkcji, która w zbiorze modulów programu moze wystapic tylko jeden raz, 
definicja struktury musi byc powtórzona w kazdym z odwolujacych sie do niej modulów. 
 
plik Main.cpp 
 

#include <iostream.h> 

 

struct Child { 

    char name[20]; 

    int age; 

}; 

 

int main(void)  

    Child getIsa(void); 

    Child isa = getIsa(); 

    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; 

background image

 

59

 
 
plik Isa.cpp 
 

#include "child.h" 

 

Child isa = { "Isabel", 15 }; 

 

Child getIsa(void) 

    return isa; 

 

background image

 

60

Studia programowe 

 
Przedstawiono dwa rozwiazania nastepujacego problemu  
 
 

Napisac program, który wprowadza z pliku sekwencje danych arytmetycznych, a nastepnie wyprowadza 
ich srednie odchylenie standardowe: pierwiastek z sumy kwadratów róznic dana-srednia, podzielony 
liczbe danych.
 

 
W szczególnosci, jesli w pliku  Data.txt umiesci sie liczby  6 9 12, a jako argument programu poda  Data.txt 
(polecenie Project / Settings // Debug), to nastapi 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; 

background image

 

61

                pData = ptr; 

            } 

            pData[count++] = tmp; 

        } 

    } 

    return count; 

 

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; 

background image

 

62

    } 

    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); 

    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

 

63

    } 

 

 

background image

 

64

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-nazwa zmiennej (por. Dodatek B ) jest tylko: operacja przypisania (np. a+=b), przedrostkowego zwiekszenia (np. 
++a), przedrostkowego zmniejszenia (np. --a), indeksowania (np. ptr[i]), wyluskania  (np. *ptr), wyboru (np. str.f 
i  ptr->f),  warunku którego dwa ostatnie argumenty sa  l-nazwami (np.  a>0?a:b),  konwersji  do typu 
odnosnikowego (np. (int &)a) oraz globalnosci (np. ::) i zakresu (np. Child::name). 
 

background image

 

65

 

Dodatek B 

 

Opracowywanie wyrazen 

 
 
 
 
Wyrazenia sa zapisami  operacji. O kolejnosci wykonywania operacji decyduje sposób uzycia nawiasów oraz 
uwzglednienie priorytetów wiazan operatorów (por. Dodatek A ). 
 
Jesli kilka operatorów zapisano spójnie  (tj. bez odstepów), wówczas za pierwszy uznaje sie najdluzszy. A zatem: 
poniewaz w C++ istnieja operatory + i ++, ale nie istnieje operator +++, wiec wyrazenie 
 

 

a +++ b 

 
jest traktowane jak 
 

 

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

 
 

Priorytety 

 
Poniewaz w C++ priorytet mnozenia jest wyzszy niz priorytet dodawania, wiec wyrazenie 
 

 

a + b * c 

 
jest traktowane jak 
 

 

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

 
 
Podobnie, poniewaz w  C++ priorytet nastepnikowej operacji zwiekszenia (++) jest wyzszy niz priorytet operacji 
wyluskania (*), wiec wyrazenie 
 

 

*ptr++ 

 
jest traktowane jak 
 

 

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

 
 

Wiazania 

 
Poniewaz w  C++ priorytet odejmowania (-) jest równy priorytetowi dodawania (+), wiec jesli pewnego 
podwyrazenia dotycza oba takie operatory, to odwolanie sie do priorytetów nie wystarcza i trzeba odwolac sie do 
wiazan.  
 
Poniewaz w C++ wiazanie operacji odejmowania i dodawania jest lewe , wiec wyrazenia 
 

 

a - b + c 

 

cout << a << b 

 

background image

 

66

sa traktowane jak 
 

 

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

 

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

 
(srodkowe podwyrazenia dowiazano do lewej). 
 
 
Dla porównania, poniewaz wiazanie operacji przypisania jest prawe, wiec wyrazenie 
 

 

a = b = c 

 
jest traktowane jak 
 

 

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

 
 

Kolejnosc 

 
Kolejnosc opracowywania argumentów operacji jest nieokreslona. Dotyczy to zarówno argumentów wywolania 
funkcji, jak i argumentów operacji dwuargumentowych, takich jak przypisanie. 
 
Dlatego zaleca sie, aby w wyrazeniu, w którym nastepuje zmiana wartosci zmiennej, nie odwolywano sie 
(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 ciala 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 sa wykonywane dopiero po promocji argumentu. Dotyczy to w szczególnosci zmiennych typu 
char (poddawanych promocji do typu int). 
 

    char chr = 'a'; 

    char &ref1 = chr; 

    char &ref2 = +chr;    // blad 

    char &ref3 = 'a';     // blad 

 
 

Typ wspólny 

 
Jesli argumenty operacji sa róznych typów, to wykonuje sie ja w ich typie wspólnym. W szczególnosci typem 
wspólnym dla char int jest int, a typem wspólnym dla double int jest double
 
Uwaga: Jesli wyrazenie jest pewnego typu, to nie oznacza to, ze wszystkie jego operacje wykonuje sie w tym typie.  
 

#include <iostream.h> 

#include <limits.h> 

 

background image

 

67

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 

 

    return 0; 

 
Mimo iz typem wyrazenia zawierajacego liczbe 0.0 jest double, iloczyn max * max jest obliczany w typie int
 
 

Punkty charakterystyczne 

 
Punktem charakterystycznym jest miejsce w programie, w którym realizuje sie wszystkie "zalegle" skutki uboczne, 
takie jak operacje wejscia-wyjscia i przypisania. 
 
Punkt charakterystyczny wystepuje m.in. po kazdym kompletnym wyrazeniu, przed kazdym srednikiem, przed 
pierwsza instrukcja funkcji oraz przed operatorami koniunkcji i dysjunkcji. 
 
Programy zalezne od polozenia punktu charakterystycznego nalezy konstruowac ze szczególna ostroznoscia. 
 

int fix = 10; 

++fix = fix; 

cout << fix; 

 
Poniewaz operacja zwiekszenia (++) moze byc zrealizowana dopiero w punkcie charakterystycznym, wiec nie 
wiadomo, czy zostanie wyprowadzona liczba 10 czy 11. W Visual C++ zostanie wyprowadzona liczba 11
 

Nazwy 

 
Kazde wyrazenie i podwyrazenie (w szczególnosci zapis operacji), mozna rozpatrywac jako nazwe pomocniczej 
zmiennej tymczasowej. Podczas opracowywania wyrazenia, kazda z operacji zastepuje sie nazwa jej rezultatu. 
 
Uwaga: Pomocnicza zmienna tymczasowa niszczy sie bezposrednio po opracowaniu kompletnego wyrazenia, 
którego opracowania wymagalo utworzenia tej zmiennej. 
 
W szczególnosci, jesli przyjac, ze zmiennymi tymczasowymi sa 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 zostana zniszczone w chwili, gdy sterowanie "przeplynie przez srednik". 
 

l-nazwy 

 
Przyjmuje sie z definicji, ze  l-nazwa jest tylko: identyfikator zmiennej nie-ustalonej, rezultat funkcji o typie 
odnosnikowym oraz rezultat operacji wymienionych w Dodatku A. Nie jest l-nazwa literal, ani wskaznik powstaly 
z niejawnego przeksztalcenia nazwy tablicy. 
 
Poslugujac sie taka definicja mozna podac nastepujace wymagania 
 

background image

 

68

1)  Odnosnik do zmiennej nie-ustalonej moze byc zainicjowany tylko takim wyrazeniem, które jest  l-nazwa 
zmiennej. 
np.  

 

const int fix1 = 10; 

 

int &fix2 = 20;        // blad 

 
2)  Argumentem operacji zwiekszenia (++), zmniejszenia (--), wskazywania (&) i wyboru (. i ->) moze byc tylko 
takie wyrazenie, które jest l-nazwa zmiennej. 
np. 

 

int fix = 10; 

 

++(int)fix;            // blad 

 

fix++++;               // blad 

 

int *ptr = &20;        // blad 

 
3)  Lewym argumentem przypisania (=+= itp.) moze byc tylko takie wyrazenie, które jest l-nazwa zmiennej. 
np. 

 

int fix = 10; 

 

fix++ = 20;            // blad 

 

int tab[] = { 10 }; 

 

tab = 20;              // blad 

 
Uwaga: Niepoprawnosc operacji fix++++ wynika stad, ze fix++ nie jest l-nazwa, a wiec nie moze byc argumentem 
ponownej operacji zwiekszenia. 

background image

 

69

Dodatek C 

 

Konwersje standardowe 

 
 
 
 
Konwersja standardowa, jest taka predefiniowana konwersja, która moze byc wstawiona do programu niejawnie
 
Konwersjami standardowymi sa m.in.:  
 
0)  Przeksztalcenie promocyjne (np. zmiennej typu char w zmienna typu int). 
1)  Przeksztalcenie zmiennej arytmetycznej albo wskaznika w orzecznik (np. zmiennej typu int  w zmienna typu 

bool). 

2)  Przeksztalcenie zmiennej arytmetycznej w zmienna arytmetyczna innego typu (np. zmiennej typu double w 

zmienna typu int). 

3)  Przeksztalcenie nazwy tablicy na wskaznik do jej zerowego elementu. 
4)  Przeksztalcenie nazwy zmiennej na odnosnik do tej zmiennej. 
5)  Przeksztalcenie wskaznika do obiektu na wskaznik do jego podobiektu. 
6)  Przeksztalcenie odnosnika do obiektu na odnosnik do jego podobiektu. 
7)  Przeksztalcenie wskaznika do zmiennej na wskaznik lokalizujacy te zmienna. 
 
Nie sa nimi m.in. 
 
1)  Przeksztalcenie wskaznika do elementu tablicy na wskaznik do tej tablicy. 
2)  Przeksztalcenie wskaznika do tablicy na wskaznik do jej elementu. 
3)  Przeksztalcenie wskaznika do podobiektu na wskaznik do jego obiektu. 
4)  Przeksztalcenie odnosnika do podobiektu na odnosnik do jego obiektu. 
5)  Przeksztalcenie wskaznika lokalizujacego zmienna na wskaznik do tej zmiennej. 
 
Uwaga: Poza konwersjami standardowymi, niejawne moze byc zastosowany jedynie konstruktor konwerter

background image

 

70

Dodatek D 

 

Operatory bitowe 

 
 
 
 
Operatorami bitowymi sa: ~ (zanegowanie bitów), & (iloczyn bitów), | (suma bitów), ^ (suma modulo 2 bitów), 
<< (przesuniecie bitów w lewo), >> (przesuniecie bitów w prawo). 
 
Podczas wykonywania operacji na bitach przydatne okazuja sie literaly szesnastkowe. Literal szesnastkowy ma 
postac 0xh, w której h jest spójnym ciagiem cyfr szesnastkowych (0-9 i a-f). 
 

cout << 0x12;      // 18 

cout << 0xffff;    // 65535 

 
 
Operator ~ 
 
Operacja zanegowania bitów ma postac 
 

 

~exp 

 
w której exp jest wyrazeniem calkowitym. 
 
Rezultatem operacji zanegowania bitów jest zmienna tymczasowa takiego samego typu jak zmienna  exp, po 
poddaniu jej  promocjom, a nastepnie zanegowaniu kazdego jej bitu. 
 
Uwaga: Negacja bitu 1 jest bit 0, a negacja bitu 0 jest bit 1
 

int red = 1, green = 2, blue = 4; 

int hue = red | green;        // ... 011 (kolor zólty) 

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

 
Trzy najmniej znaczace bity zmiennej  hue reprezentuja jeden z  8 kolorów. Wykonanie operacji zanegowania 
bitów powoduje zmiane koloru na dopelniajacy.
 
 
 
Operator & 
 
Operacja iloczynu bitów ma postac 
 

 

expL & expR 

 
w której expL i expR sa wyrazeniami calkowitymi. 
 
W celu utworzenia wyniku operacji, zmienne  expL i  expR poddaje sie konwersjom do typu  wspólnego, a 
nastepnie kazdy bit wyniku tworzy sie z odpowiadajacych sobie bitów argumentów wyznaczajac ich iloczyn 
logiczny
.  
 
Uwaga: Iloczyn logiczny pary bitów ma wartosc 1 tylko wówczas gdy oba bity sa jedynkowe 
 

int fix = 6;                  //    00 ... 110 

const int mask = '\x3';       //    00 ... 011 

background image

 

71

fix &= ~mask;              

cout << Fix;                  // 4 (00 ... 100) 

 
Wykonanie operacji na zmiennej  fix powoduje  wyzerowanie tych wszystkich jej bitów, które w  mask sa 
jedynkowe.
 
 
 
Operator ^ 
 
Operacja sumy modulo 2 bitów ma postac 
 

 

expL ^ expR 

 
w której expL i expR sa wyrazeniami calkowitymi. 
 
W celu utworzenia wyniku operacji, zmienne  expL i  expR poddaje sie konwersjom do typu  wspólnego, a 
nastepnie kazdy bit wyniku tworzy sie z odpowiadajacych sobie bitów argumentów wyznaczajac ich  sume 
logiczna modulo 2 

 
Uwaga: Suma logiczna modulo 2 pary bitów ma wartosc 1 tylko wówczas gdy bity sa rózne
 

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 sa 
jedynkowe.
 
 
 
Operator | 
 
Operacja sumy bitów ma postac 
 

 

expL expR 

 
w której expL i expR sa wyrazeniami calkowitymi. 
 
W celu utworzenia wyniku operacji, zmienne  expL i  expR poddaje sie konwersjom do typu  wspólnego, a 
nastepnie kazdy bit wyniku tworzy sie z odpowiadajacych sobie bitów argumentów wyznaczajac ich  sume 
logiczna
.  
 
Uwaga: Suma logiczna pary bitów ma wartosc 0 tylko wówczas gdy oba bity sa 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 sa 
jedynkowe
.
 
 
 
Operator << 
 
Operacja przesuniecia bitów w lewo ma postac 
 

background image

 

72

 

expL << n 

 
w której expL i n sa wyrazeniami calkowitymi. 
 
W celu utworzenia wyniku operacji, zmienna expL poddaje sie promocji, a nastepnie kazdy bit wyniku tworzy sie 
z bitów tej nowej zmiennej po przesunieciu ich o n pozycji w lewo
 
Uwaga: Podczas przesuwania  w lewo bity najbardziej znaczace sa  odrzucane, a na pozycje najmniej znaczace 
wchodza bity 0
 

int fix = 7;                  // 00 ... 0111 

fix <<= 2; 

cout << fix;                  // 28 (00 ... 011100) 

 
Bity zmiennej fix przesunieto o 2 pozycje w lewo. 
 
 
Operator >> 
 
Operacja przesuniecia bitów w prawo ma postac 
 

 

expL >> n 

 
w której expL i n sa wyrazeniami calkowitymi. 
 
W celu utworzenia wyniku operacji, zmienna expL poddaje sie promocji, a nastepnie kazdy bit wyniku tworzy sie z 
bitów tej nowej zmiennej po przesunieciu ich o n pozycji w prawo
 
Uwaga: Podczas przesuwania w prawo bity najmniej znaczace sa odrzucane
 

int fix = 15;                 // 00 ... 01111 

fix >>= 2; 

cout << fix;                  // 3 (00 ... 011) 

 
Bity zmiennej fix przesunieto o 2 pozycje w prawo. 
 

background image

 

73

Dodatek E 

 
 
 
 
 
 
 

Operacje wejscia-wyjscia 

 
 
 
 
Wiekszosc operacji wejscia-wyjscia mozna wykonac za pomoca operatorów. Do specjalnych celów przydaja sie 
niekiedy funkcje wejscia-wyjscia. 
 
 

Funkcje get i put 

 

inp.get(chr) 

Wprowadza ze strumienia inp  najblizszy znak (w tym znak odstepu) i jego kod przypisuje zmiennej chr typu char
Dostarcza odnosnik do inp
 

 

out.put(chr) 

Wyprowadza do strumienia out znak o kodzie chr. Dostarcza odnosnik 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 konsole zawartosc pliku Data.txt. Kopiowanie odbywa sie znak -po-znaku. 
 
 

Funkcje read i write 

 

inp.read(ptr, len) 

Wprowadza ze strumienia inp  ciag len najblizszych znaków i ich kody umieszcza w tablicy znakowej o elemencie 
wskazywanym przez ptr. Dostarcza odnosnik do inp
 

out.write(ptr, len) 

background image

 

74

Wyprowadza do strumienia out ciag len znaków z tablicy znakowej, poczawszy od elementu wskazywanego przez 
ptr. Dostarcza odnosnik do out
 
inp.gcount() 
Dostarcza liczbe znaków wprowadzonych za pomoca ostatnio wywolanej 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 konsole zawartosc pliku Data.txt. Kopiowanie odbywa sie porcjami po Size znaków. 
 
 

Funkcja getline 

 

inp.getline(ptr, len) 

Wprowadza ze strumienia inp  jeden wiersz, ale nie wiecej niz len-1 najblizszych znaków, a ich kody, bez kodu '\n', 
ale z dodatkowym kodem  0, umieszcza w tablicy znakowej o elemencie wskazywanym przez  ptr. Dostarcza 
odnosnik 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; 

    } 

background image

 

75

 

    return 0; 

 
Program kopiuje na konsole zawartosc pliku Data.txt. Kopiowanie odbywa sie wierszami. 
 
 

Funkcje peek i putback 

 

inp.peek() 

Dostarcza kod najblizszego znaku strumienia inp, ale znaku ze strumienia nie wprowadza (sic!). 
 

inp.putback(chr) 

Cofa do strumienia inp znak o kodzie chr. Dostarcza odnosnik 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 lancuchy, a nastepnie wyprowadza je na konsole, 
kazdy w osobnym wierszu.
 
 
 

Funkcje tellg i tellp 

 
Funkcje  tellg i  tellp sluza do okreslania pozycji pliku. Pozycja jest dana typu streampos. W  Visual C++ typ 
streampos jest identyczny z typem int
 

inp.tellg() 

Dostarcza biezaca pozycje pliku otwartego w trybie ios::in
 

inp.tellp() 

background image

 

76

Dostarcza biezaca pozycje 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; 

 
Program wyznacza rozmiar pliku config.sys.
 
 
 

Funkcje seekg i seekp 

 
Funkcje  seekg  i  seekp  sluza do ustawiania pozycji pliku. Nowa pozycja pliku moze byc podana wzgledem 
poczatku pliku (ios::beg), wzgledem pozycji biezacej (ios::cur), albo wzgledem pozycji koncowej (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 wzgledem 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 wzgledem from (ios::begios::curios::end). 
 

inp.seek(0); 

 
Instrukcja ustawia strumien w pozycji poczatkowej. 
 
 

Funkcja clear 

 
Funkcja clear sluzy do ustawienia stanu strumienia. 
 

str.clear()               

str.clear(ios::badbit) 

Wywolanie bezargumentowe ustawia strumien str w stan dobry. Wywolanie z argumentem ios::badbit ustawia go 
w stan zly
 

#include <iostream.h> 

#include <fstream.h> 

 

int main(void) 

    ifstream inp; 

    inp.open("C:\\autoexec.bat", ios::in); 

background image

 

77

    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 konsole zawartosci pliku autoexec.bat
 
Poniewaz po zakonczeniu instrukcji while strumien inp znajduje sie w stanie nie-dobrym, wiec nalezy ustawic 
go w stan dobry.  W przeciwnym razie wszystkie operacje wejscia-wyjscia dotyczace tego strumienia bylyby 
pomijane, a zawartosc pliku zostalaby wyprowadzona tylko 1 raz. 
 
 

Operacje w pamieci 

 
Operacje wejscia-wyjscia moga dotyczyc nie tylko plików, ale równiez pamieci operacyjnej. Do wykonywania 
operacji w pamieci sluza obiekty klas  istrstream  i  ostrstream, zadeklarowanych w pliku naglówkowym 
strstream.h
 
Argumentem konstruktora klasy istrstream jest wskaznik lancucha. Argumentami konstruktora klasy ostrstream 
jest wskaznik  elementu tablicy znakowej i maksymalna liczba jej elementów, które moga byc uzyte w operacji 
wyjscia. 
 
Uwaga: Operacja wyjscia nie zapisuje znaku konca lancucha. Nalezy to wykonac jawnie, na przyklad za pomoca 
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 sie zazwyczaj pliki binarne. Plik binarny otwiera sie w trybie  
 

 

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

 

background image

 

78

Operacje na pliku wykonuje sie za pomoca 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); 

    } 

    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) { 

background image

 

79

        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 pochodzace pliku Data.txt. Nastepnie dane sortuje i 
wyprowadza. 
 
Poniewaz funkcje  read  i  write  oczekuja argumentów typu  char * i  int, wskazana zmiennych calkowitych 
(np. &tmp) poddano jawnej konwersji do typu char *.