Jan Bielecki Visual C 6 podstawy programowania

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 o 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 s 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. 12, 0xff 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

0

// 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 specyfikatory, deklaratory i inicjatory.

np.

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


Specyfikatorami sa const i 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 i iomanip.h, math.h, string.h i stdlib.h. Dwa pierwsze
wlaczaja do modulu deklaracje zmiennych i operatorów wejscia-wyjscia (cin, cout, >>, <<), 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 i
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. int, double, char).

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

2

20

double

4

20000

char

1

49


Podczas wykonywania operacji wyjscia, wyprowadza sie liczby o wartosci zmiennych typu int i 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: skalarna, tablicowa, strukturowa.

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 0, 1 i 2 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 78, 111 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: skalarna, tablicowa, strukturowa.

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 i int, albo ze zmiennych typu bool i 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 // Debug, Program 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 i ptr2 wskazuja odpowiednio elementy o indeksach i oraz j 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 a i 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 deklaracje, definicje i 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: one, loc, two.

Program wyprowadzi dwie pary liczb: 10 10 i 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 i 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 i 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 i int jest int, a typem wspólnym dla double i 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 t1, t2 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 i 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::beg, ios::cur, ios::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::beg, ios::cur, ios::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 *.


Wyszukiwarka

Podobne podstrony:
Jan Bielecki Visual C 6 0 Podstawy programowania
Bielecki Visual C 6.0 (Podstawy programowania), Jan Bielecki
Visual C 6 0 Podstawy programowania Jan Bielecki(1)
Visual C 6 0 Podstawy programowania(1)
Visual C 6 0 Podstawy Programowania [73 strony]
Bielecki - Visual C++, Visual C++ 6.0 Programowanie obiektowe, Jan Bielecki
Jan Bielecki, Java od Podstaw
Nowa podstawa programowa WF (1)
1 Podstawy programowania dialogowego
nowa podstawa programowa sp
11-nkb~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
2-eukl~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
Zmiany w podstawie programowej w zakresie edukcji matematycznej, Wczesna edukacja, Materiały do prac
1-algo~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
c-zadania-w3, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, kol
Wychowanie w nowej podstawie programowej katechezy, szkoła, Rady Pedagogiczne, wychowanie, profilakt

więcej podobnych podstron