prof. Jan Bielecki
Visual C++ 6.0
Podstawy programowania
1.
Pierwsze kroki
2.
Ś rodowisko Visual C++
3.
Wskaźniki i odnośniki
4.
Przetwarzanie łań cuchów
5.
Posługiwanie się funkcjami
6.
Zarzą dzanie pamię cią
7.
Widoczność deklaracji
8.
Studia programowe
Dodatki
Priorytety operatorów
Opracowywanie wyrażeń
Konwersje standardowe
Operatory bitowe i warunkowe
Operacje wejścia-wyjścia
1
Pierwsze kroki
Program jest zbiorem modułów źródłowych. Każ dy moduł składa się z deklaracji typów, zmiennych i funkcji.
Napis od znakó w // do końca wiersza jest komentarzem. Jako taki nie ma wpływu na przebieg wykonania
programu.
Dokładnie jeden moduł, nazywany głównym, zawiera deklarację funkcji main. Wykonanie programu polega na
opracowaniu wszystkich jego globalnych deklaracji, a następnie przystą pieniu do wykonywania instrukcji
zawartych w funkcji main. Zakończenie wykonywania programu następuje po wykonaniu w funkcji głó wnej
instrukcji return, albo tuż po powrocie z funkcji exit. Moż e to nastą pić jeszcze przed podjęciem wykonywania
funkcji głó wnej.
int main(void) // deklaracja funkcji g
łównej
{
return 0; // instrukcja return
}
void exit(int); // deklaracja funkcji exit
struct Empty { // deklaracja typu Empty
Empty(void)
{
exit(0); // wywo
łanie funkcji exit
}
};
Empty obj; // deklaracja zmiennej
Program napisano w taki sposó b, aby jego wykonanie zakończył
o się przed podjęciem wykonywania funkcji
gł
ó wnej.
Komunikacja z otoczeniem
W najprostszym przypadku, program pobiera dane z klawiatury i wyprowadza je na monitor. Operacje
wprowadzania danych odbywają się za pomocą operatora >>, a operacje wyprowadzania danych za pomocą
operatora <<. Klawiatura jest reprezentowana przez zmienną cin, a monitor przez zmienną cout. Posłuż enie się
nimi wymaga uż ycia dyrektywy #include wyszczegó lniają cej nazwę iostream.h.
Daną wprowadzoną z klawiatury kończy odstę p, uzyskany przez naciśnięcie klawisza Space, Tab albo Enter.
Analiza danych wejściowych następuje wierszami, to jest dopiero po naciśnięciu klawisza Enter. W
szczegó lności, jeśli program oczekuje 3 danych, to każ dą moż na podać w osobnym wierszu, albo wszystkie
podać w jednym wierszu. Przed wprowadzeniem kolejnej danej pomija się poprzedzają ce ją odstępy.
Uwaga: Wygodnym sposobem wyprowadzenia odstępu Enter jest uż ycie symbolu endl, a wygodnym sposobem
wyprowadzenia znaku o kodzie 0 jest uż ycie symbolu ends.
Ponieważ operacja wejścia-wyjścia dostarcza w miejscu jej uż ycia jej lewy argument, więc zapis pary instrukcji
cin >> one;
cin >> two;
moż na uprościć do
2
cin >> one >> two;
Właściwość tę, nazywaną łą czeniem operacji, moż na zastosować takż e do wyprowadzania danych.
#include <iostream.h>
int main(void)
{
int one, two;
cout << "Enter 2 numbers:" << endl;
cin >> one >> two;
cout << "Sum = " << one + two << endl;
return 0;
}
Program wyprowadza zachętę do wprowadzenia 2 liczb, a następnie wyznacza i wyprowadza ich sumę.
Wykonywanie operacji
Wykonanie programu sprowadza się do wykonania operacji na danych. W Dodatku A zamieszczono kompletny
wykaz operacji, a w Dodatku B omó wiono zasady opracowywania wyraż eń. Podane tam opisy stanowią istotny
element niniejszego opracowania.
Operacje przypisania
Prosta operacja przypisania ma postać
a = b
w któ rej a i b są wyraż eniami, ale ponadto a jest l-nazwą zmiennej (por. Dodatek B).
Wykonanie operacji polega na przypisaniu zmiennej a wartości wyraż enia b.
Złoż ona operacja przypisania ma postać
a
@
= b
w któ rej @= jest jednym z operatoró w wymienionych w Dodatku A (np. +=, -=, *= , /=).
Operację a @= b (np. a += b) wykonuje tak, jak operację
a = a + b
ale wymaga się, aby opracowanie a i b było jednokrotne.
Operacja połą czenia
Operacja połą czenia ma postać
a , b
Jej wykonanie składa się z opracowania wyraż enia a (wyłą cznie dla jego skutkó w ubocznych) oraz z niejawnego
zastą pienia całej operacji nazwą zmiennej reprezentowanej przez wyraż enie b.
3
Uwaga: Nie jest operatorem połą czenia przecinek oddzielają cy parametry i argumenty funkcji.
W szczegó lności wykonanie instrukcji
return a = 10, cout << a, b = 20;
jest ró wnoważ ne wykonaniu instrukcji
a = 10;
cout << a;
b = 20;
return b;
Operacje arytmetyczne
Operacje arytmetyczne wykonuje się za pomocą operatoró w wymienionych w tabeli Operacje arytmetyczne.
Tabela Operacje arytmetyczne
###
+
(dodawanie)
-
(odejmowanie)
*
(mnożenie)
/
(dzielenie)
%
(reszta)
++ (zwiększenie o 1)
--
(zmniejszenie o 1)
+= (dodanie)
-=
(odjęcie)
*= (pomnożenie)
/=
(podzielenie)
###
Sposó b wykonania podstawowych działań arytmetycznych nie wymaga opisu. Należ y jedynie zauważ yć , ż e
rezultat dzielenia całkowitego jest całkowity, a argumenty wyznaczania reszty muszą być całkowite (np. 11 / 4
ma wartość 2, a 11 % 4 ma wartość 3).
Operacje przedrostkowe
Wykonanie operacji ++num powoduje zwiększenie wartości zmiennej num o 1. W miejsce wykonania operacji
jest dostarczana nowa wartość num.
Wykonanie operacji --num powoduje zmniejszenie wartości zmiennej num o 1. W miejsce wykonania operacji
jest dostarczana nowa wartość num.
int fix = 10;
cout << ++fix; // 11
cout << fix; // 11
Operacje przyrostkowe
Wykonanie operacji num++ powoduje zwiększenie wartości zmiennej num o 1. W miejsce wykonania operacji
jest dostarczana pierwotna wartość num.
Wykonanie operacji num-- powoduje zmniejszenie wartości zmiennej num o 1 W miejsce wykonania operacji
jest dostarczana pierwotną wartość num.
int fix = 10;
cout << fix--; // 10
cout << fix; // 9
4
Operacje porównania
Operacje poró wnania wykonuje się za pomocą operatoró w wymienionych w tabeli Operacje porównania.
Tabela Operacje porównania
###
==
(ró wne)
!=
(nie ró wne),
<
(mniejsze)
>
(większe),
<=
(mniejsze lub ró wne)
>=
(większe lub ró wne)
###
Jeśli poró wnanie wyraż a orzeczenie prawdziwe, to jego rezultat ma wartość true (prawda). W przeciwnym razie
ma wartość false (fał
sz).
Uwaga: Poró wnanie na ró wność wykonuje się za pomocą operacji ==, a nie za pomocą operacji =. Zaniedbanie
tego faktu jest źró dłem trudnych do wykrycia błędó w semantycznych.
#include <iostream.h>
int main(void)
{
int num = 0;
while(num == 0)
cin >> num;
cout << num << endl;
return 0;
}
Program wyprowadza liczbę 0 albo pierwszą niezerową liczbę wprowadzoną z klawiatury. Gdyby operator
poró wnania zastą piono operatorem przypisania, to zawsze wyprowadzał
by liczbę 0.
Operacje orzecznikowe
Operacje orzecznikowe wykonuje się za pomocą operatoró w wymienionych w tabeli Operacje orzecznikowe.
Tabela Operacje orzecznikowe
###
!
(zaprzeczenie)
&&
(koniunkcja)
||
(dysjunkcja)
###
Argumenty i rezultaty operacji orzecznikowych są typu bool i mają wartości true albo false.
Rezultat zaprzeczenia ma wartość true tylko wó wczas, gdy argument ma wartość false.
Rezultat koniunkcji ma wartość true tylko wó wczas, gdy oba argumenty mają wartość true.
Rezultat dysjunkcji ma wartość false tylko wó wczas, gdy oba argumenty mają wartość false.
Uwaga: Operacja koniunkcji i dysjunkcji jest wykonywana w taki sposó b, ż e jeśli po opracowaniu pierwszego
argumentu jest znana wartość rezultatu całej operacji (bo dla koniunkcji ma wartość false, a dla dysjunkcji ma
wartość true), to rezygnuje się z opracowania drugiego argumentu.
#include <iostream.h>
int vec[] = { 10, 20, 30, 40, 50 };
int main(void)
{
5
int pos;
cin >> pos;
pos >= 0 && pos < 5 && (cout << vec[pos]);
return 0;
}
Program wyprowadza wartoś ć tego elementu tablicy, któ rego indeks wprowadzono z klawiatury.
Jeś li wprowadzi się indeks, któ ry nie ma wartoś ci z przedział
u [0 ; 4], to program nie wyprowadzi nic.
Dzięki użyciu operatora &&, nigdy nie dojdzie do opracowania wyrażenia vec[pos] z niedozwolonym indeksem.
Operacje konwersji
Wykonanie konwersji ma na celu przekształcenie zmiennej pewnego typu w zmienną typu docelowego.
Operacja konwersji wyraż enia e do typu Type ma postać
(Type)e
Jeśli nazwę typu docelowego Type moż na wyrazić za pomocą identyfikatora (np. int), to operację konwersji
moż na zapisać jako
Type(e)
W szczegó lności, jeśli w programie występuje instrukcja
int num = 4.8;
w któ rej zmienna num jest typu int, a wyraż enie 4.8 jest typu double, to ponieważ danej typu double (zazwyczaj
8-bajtowej) nie moż na pomieścić w zmiennej typu int (zazwyczaj 4-bajtowej), więc najprościej byłoby taką
instrukcje uznać za błędną .
Ponieważ w C++ przekształcenie zmiennej typu double w zmienną typu int zdefiniowano jako konwersję
standardową (polega ona na odrzuceniu części ułamkowej), więc rozpatrywana instrukcja zostanie niejawnie
zmieniona w poprawną instrukcję
int num = int(4.8);
ró wnoważ ną
int num = 4;
w któ rej wyraż enie inicjują ce jest już typu int.
Uwaga: Waż ne informacje na temat konwersji zamieszczono w Dodatku C.
Operacje warunkowe
Operacje warunkowe wykonuje się za pomocą tró jargumentowego operatora ?: (pytajnik, dwukropek).
Rezultatem operacji
e ? eT : eL
6
jest zmienna o wartości eT jeśli orzeczenie wyraż one przez e jest prawdziwe, albo zmienna o wartości eF w
przeciwnym razie..
Uwaga: Po opracowaniu wyraż enia e, opracowuje się tylko jedno z wyraż eń eT i eF.
num = fix > 0 ? fix1 : fix2;
num < 0 ? fix1 : fix2 = 30;
Operatory :: i Name::
Jeśli id jest identyfikatorem, to ::id jest nazwą globalną , a Name::id jest nazwą składnika typu strukturowego
Name.
int num = 0;
struct Fix {
int num;
void set(int num =::num)
{
Fix::num = num;
}
};
Napis ::num jest nazwą zmiennej globalnej, a napis Fix::num jest nazwą skł
adnika num.
Prawy argument przypisania Fix::num = num jest nazwą parametru.
Wykonywanie instrukcji
Do napisania dowolnego programu wystarczy zaledwie kilka instrukcji. Najważ niejszymi z nich są : instrukcja
pusta, grupują ca, warunkowa (if), iteracyjna (while) i powrotu (return). Opis pozostałych ograniczono do
przykładó w.
Instrukcja pusta
Instrukcja pusta składa się ze średnika.
;
Jej wykonanie nie ma ż adnych skutkó w.
Instrukcja grupują ca
Instrukcja grupują ca składa się z nawiasó w klamrowych zawierają cych dowolną sekwencję instrukcji.
Jeśli w miejscu, w któ rym składnia wymaga uż ycia dokładnie jednej instrukcji, chce się umieścić ich więcej, to
wystarczy ują ć je w nawiasy klamrowe i powstanie jedna instrukcja.
{ int a; cin >> a; a++; cout << a; }
Instrukcja warunkowa
Instrukcja warunkowa ma postać
7
if(c)
s
albo
if(c)
s1
else
s2
w któ rej c jest wyraż eniem orzecznikowym o wartości true albo false, a s1 oraz s2 jest pojedynczą instrukcją
(np. instrukcją grupują cą ).
Wykonanie instrukcji warunkowej zaczyna się od opracowania wyraż enia c (np. a > 2). Jeśli wyraż one przez nie
orzeczenie jest prawdziwe, to w pierwszym przypadku jest wykonywana instrukcja s, a w drugim instrukcja s1.
W przeciwnym razie, w pierwszym przypadku nie robi się nic, a w drugim wykonuje instrukcję s2.
if(a > 2)
{ a++; cout << a; }
else
{ cout << a; a-- }
albo ró wnoważ nie
if(a > 2) {
a++;
cout << a;
} else {
cout << a;
a--;
}
Jeśli podczas opracowywania instrukcji warunkowej napotka się słowo kluczowe else, to przyjmuje się, ż e
dotyczy ono najbliż szego z lewej słowa if, nie połą czonego jeszcze z else.
W szczegó lności instrukcja
if(fix1 > fix2) if(fix1) fix1++; else fix2++;
jest wykonywana jak instrukcja
if(fix1 > fix2) {
if(fix1)
fix1++;
else fix2++;
}
a nie jak instrukcja
if(fix1 > fix2) {
if(fix1)
fix1++;
} else
fix2++;
Instrukcje iteracyjne
Instrukcja iteracyjna while ma postać
while(c)
s
8
w któ rej c jest wyraż eniem orzecznikowym, a s jest pojedynczą instrukcją .
Wykonanie instrukcji iteracyjnej while polega na cyklicznym badaniu orzeczenia wyraż onego przez wyraż enie c
i wykonywaniu instrukcji s.
Iteracja kończy się w chwili stwierdzenia, ż e orzeczenie jest nieprawdziwe. Jeśli okaż e się to już na wstępie, to
instrukcja s nie będzie wykonana wcale.
int i = 3;
while(i > 0) {
int t = i * i;
cout << t << endl; // 9 4 1
i--;
}
Często uż ywa się instrukcji iteracyjnej for
for(d c ; e) {
s s ... s
}
w któ rej d jest instrukcją deklaracyjną , a c i e są wyraż eniami.
Tak zapisana instrukcja for jest ró wnoważ na instrukcji
d
while(c) {
s s ... s
e;
}
Instrukcja for dobrze opisuje czynności o znanej liczbie powtó rzeń.
int tab[5] = { 10, 20, 30, 40, 50 }, sum = 0;
for(int i = 0; i < 5 ; i++)
sum += tab[i];
cout << "Sum = " << sum << endl;
Instrukcja zaniechania
Instrukcja zaniechania ma postać
break;
Wykonanie instrukcji zaniechania powoduje zakończenie wykonywania najwę ższej obejmują cej ją instrukcji
iteracyjnej albo decyzyjnej.
int sum = 0;
while(true) {
int tmp = 0;
cin >> tmp; // wprowad
ź daną
if(tmp == 0) // zbadaj czy 0
break; // zako
ńcz iterację
sum += tmp; // dosumuj
}
cout << "Sum = " << sum << endl;
albo
int tmp = 0, sum = 0;
while(cin >> tmp, tmp) // wprowad
ź i zbadaj
sum += tmp; // dosumuj
cout << "Sum = " << sum << endl;
9
lub
for(int tmp = 0, sum = 0; cin >> tmp, tmp ; sum += tmp);
cout << "Sum = " << sum << endl;
Instrukcja powrotu
Instrukcja powrotu ma postać
return e;
w któ rej e jest wyraż eniem.
Wykonanie instrukcji powrotu powoduje zakończenie wykonywania funkcji i dostarczenie rezultatu o wartości
określonej przez e.
int sum(int one, int two)
{
return one + two;
}
Jeśli typem funkcji jest void, to uż yta w niej instrukcja powrotu nie moż e zawierać wyraż enia. Uż ycie takiej
instrukcji jest zazwyczaj zbyteczne, ponieważ domniemywa się ją tuż przed klamrą zamykajacą ciało funkcji.
void sum(int one, int two)
{
cout << one + two << endl;
return; // zb
ędne
}
Instrukcja decyzyjna
Instrukcja decyzyjna uogó lnia instrukcję warunkową i jest przydatna wó wczas, gdy w programie występują
więcej niż dwie gałęzie decyzyjne. W szczegó lności instrukcję warunkową
if(a == 2)
b = 3;
else if(a == 1)
b = 5;
else if(a == 4)
b = -1;
else
b = 0;
moż na zapisać w postaci
switch(a) {
case 2: // je
śli a == 2
b = 3;
break;
case 1: // je
śli a == 1
b = 5;
break;
case 4: // je
śli a == 4
b = -1;
break;
default: // w pozosta
łych przypadkach
b = 0;
}
10
Deklarowanie zmiennych i typów
Każ dy moduł programu jest kompilowany niezależnie od pozostałych. Analiza składniowa modułu odbywa się
od-gó ry-do-doł
u i od-lewej-do-prawej i polega na rozpoznawaniu jednostek leksykalnych: identyfikatorów
(np. exit), literałów (np. 0), operatorów (np. +) i ograniczników (np. ;).
Identyfikatory
Identyfikatorem jest spójna sekwencja liter i cyfr, zaczynają ca się od litery. Identyfikator nie moż e mieć postaci
słowa kluczowego (np. return). Za jego literę uznaje się ró wnież znak podkreślenia (_).
Litery małe uznaje się za ró ż ne od duż ych. Zaleca się, aby w wielosłowowych nazwach zmiennych i funkcji,
wszystkie słowa, z wyją tkiem pierwszego, były zapisane za pomocą duż ych liter.
np.
forSale speedLimit veryLongName
Literały
Literałami są liczby (np. 12, 0xff i 2.e-3), znaki (np. ’a’) i łań cuchy (np. "Hello"). Każ dy literał jest nazwą
zmiennej ustalonej. Jej typ wynika z zapisu literału.
Uwaga: Jeśli łańcuch ma zawierać znak \ (ukoś nik), to należ y go zapisać jako \\ (np. "C:\\Data.txt).
np.
’a’
’\n’
’\0’ // nazwy zmiennych typu char
12
-12
0
// nazwy zmiennych typu int
-2.4
2.e4
.2 // nazwy zmiennych typu double
"a"
"N"
"\n" // nazwy zmiennych typu char [2]
Deklaracje
Każ de uż ycie identyfikatora musi być poprzedzone jego deklaracją . Deklaracja kompletnie opisują ca zmienną
(określają ca jej wartość począ tkową ), typ (wyszczegó lniają ca strukturę jego obiektó w) i funkcję (podają ca jej
ciało) jest nazywana definicją .
W skład deklaracji wchodzą specyfikatory, deklaratory i inicjatory.
np.
const int tab[3] = { -1, 0, +1 };
Specyfikatorami są const i int, deklaratorem jest tab[3], a inicjatorem jest = { -1, 0, +1 }.
Nagłówki
Deklaracje typó w i funkcji są ujmowane w nagłówki. Każ dy nagłó wek jest zapisany w odrę bnym pliku.
Włą czenie nagłó wka odbywa się w miejscu wystą pienia wyszczegó lniają cej go dyrektywy #include.
Do najczęściej uż ywanych nagłó wkó w należ ą : iostream.h i iomanip.h, math.h, string.h i stdlib.h. Dwa
pierwsze włą czają do modułu deklaracje zmiennych i operatoró w wejścia-wyjścia (cin, cout, >>, <<), dwa
następne włą czają deklaracje funkcji matematycznych (sqrt, sin, cos) i łańcuchowych (strlen, strcpy, strcat,
strcmp), a ostatni włą cza m.in. deklarację funkcji exit.
11
#include <iostream.h>
#include <math.h>
int main(void)
{
double number; // deklaracja zmiennej
cin >> number; // wprowadzenie liczby
cout << sqrt(number); // wyprowadzenie pierwiastka
return 0; // zako
ńczenie wykonywania
}
Zmienne
Zmienną jest obszar pamięci do przechowywania danych określonego typu: skalarnych, tablicowych i
strukturowych. Każ de odwołanie do zmiennej musi być poprzedzone deklaracją jej typu.
int fix; // zmienna ca
łkowita
char chr; // zmienna znakowa
double num; // zmienna rzeczywista
Zmienna fix jest typu int, zmienna chr jest typu char, zmienna num jest typu double.
Rozmiar zmiennej
Rozmiar zmiennej w bajtach określa się za pomocą operatora sizeof. Argumentem operatora sizeof moż e być
nazwa zmiennej albo nazwa typu.
Uwaga: Rozmiar zmiennej zależ y od implementacji. W Visual C++ zmienne typu char są 1-bajtowe, zmienne
typu int są 2-bajtowe, a zmienne typu double są 8-bajtowe.
int age = 24;
cout << sizeof(age); // 4
cout << sizeof(int); // 4
int tab[3];
cout << sizeof(tab); // 12
Zmienne ustalone
Zmienna zadeklarowana ze specyfikatorem const jest zmienną ustaloną . Zmienna ustalona musi być
zainicjowana, ale nadana jej wartość nie moż e ulec zmianie.
Uwaga: Zmiennymi ustalonymi są takż e zmienne reprezentowane przez literały. W szczegó lności liczba 12e2
jest nazwą zmiennej ustalonej o wartości 1200.
const int size = 100;
const double width = -2e-7, height = 2e2;
const int tab[2] = { 10, 20 };
Zmienne skalarne
Deklaracja zmiennej skalarnej określa jej identyfikator oraz wyszczegó lnia typ danych jakie moż na przypisywać
zmiennej (np. int, double, char).
int number;
double speedLimit;
char separator;
12
Wartość począ tkową zmiennej określa się za pomocą inicjatora. Jeśli deklaracja zmiennej zawiera jawny albo
domniemany inicjator, to jest jej definicją .
int minValue = 10, maxValue = 90;
double width = 2.4, height = 4.5e+2, area;
char lastChar = ’.’;
Składnia inicjatora
Inicjatory dzielą się na wyrażeniowe, klamrowe i nawiasowe. Inicjator zmiennej ustalonej musi mieć postać
wyrażenia stałego. W jego skład wchodzą odwołania do literałó w i zmiennych ustalonych, ale nie mogą
wchodzić odwołania do zmiennych nie-ustalonych.
int base = 100; // inicjator wyra
żeniowy
int min = { base + 20 }; // inicjator klamrowy
int max(base + 40); // inicjator nawiasowy
const int size = max - min; // b
łąd
Punkt zadeklarowania
Identyfikator zmiennej uważ a się za zadeklarowany w punkcie tuż przed inicjatorem wyraż eniowym i
klamrowym, ale tuż po inicjatorze nawiasowym. Ta subtelna ró ż nica ma niekiedy wpływ na poprawność i skutek
wykonania programu.
#include <iostream.h>
const int val = 10; // definicja zmiennej globalnej
int main(void)
{
int val(val); // definicja zmiennej lokalnej
cout << val; // 10
return 0;
}
Punkt zadeklarowania zmiennej lokalnej występuje tuż po inicjatorze (val). Gdyby inicjator nawiasowy
zastą piono jednym z pozostał
ych inicjatoró w, to program stał
by się bł
ędny, ponieważ zmienna lokalna byłaby
wó wczas inicjowana nie wartoś cią zmiennej globalnej, ale nieokreś loną jeszcze wartoś cią zmiennej lokalnej.
Operacje wejścia-wyjścia
Zmienne typu int, double i char są zmiennymi arytmetycznymi, przystosowanymi odpowiednio do
przechowywania liczb całkowitych, zmiennopozycyjnych i kodó w znakó w.
Podczas wykonywania operacji wejścia, do zmiennych typu int i double wprowadza się dane liczbowe, a do
zmiennych typu char wprowadza się kody znakó w. A zatem, jeśli z klawiatury wprowadzi się na przykład napis
20e3, to liczba pobranych znakó w i otrzymana wartość będzie zależ eć od typu zmiennej, zgodnie z tabelą
Wprowadzanie danych.
Tabela Wprowadzanie danych
Typ zmiennej
Pobrano znaków
Wprowadzono wartość
int
2
20
double
4
20000
char
1
49
13
Podczas wykonywania operacji wyjścia, wyprowadza się liczby o wartości zmiennych typu int i double oraz
znaki o kodach określonych przez wartości zmiennych typu char.
#include <iostream.h>
int main(void)
{
int mant, exp;
char sep;
cin >> mant >> sep >> exp;
int value = mant;
while(exp > 0) {
value = value * 10;
exp--;
}
cout << mant << sep << exp <<
" == " << value << endl;
return 0;
}
Jeś li wprowadzi się napis 2e3, to program wyprowadzi ten napis oraz liczbę 2000.
Zmienne tablicowe
Zmienną tablicową , w skró cie tablicą , jest zestaw są siadują cych ze sobą elementów tablicy. Każ dy element jest
zmienną takiego samego typu: skalarną , tablicową , strukturową .
int tab[20];
Zmienna tab jest tablicą o 20-elementach typu int.
Z każ dym elementem tablicy jest zwią zany indeks, określają cy położ enie elementu w obrębie tablicy. Elementy
tablicy są indeksowane od 0. W deklaracji tablicy podaje się liczbę jej elementó w, a nie indeks jej ostatniego
elementu. Jeśli deklarator nie podaje liczby elementó w, ale deklaracja zawiera inicjator, to za liczbę elementó w
uznaje się liczbę fraz inicjują cych.
Uwaga: Liczba fraz inicjują cych nie moż e przekraczać liczby elementó w tablicy. Jesli jest od niej mniejsza, to
jest niejawnie dopełniana frazami 0.
int tab[100] = { 4, 4 };
Zerowy i pierwszy element tablicy tab ma wartoś ć 4. Wszystkie pozostał
e mają wartoś ć 0.
Liczba elementó w tablicy musi być wyraż ona za pomocą wyrażenia stałego. Wyraż enie stałe moż e zawierać
literały i identyfikatory zmiennych ustalonych, ale nie moż e zawierać operatora połą czenia.
const int Count = 3;
double sizes[Count] = { 2.4, 3.8, 5.2 };
int values[] = { 10, 20, 30, 40, 50 };
int Size = 4;
double reals[Size]; // b
łąd
Tablica sizes skł
ada się z 3 zmiennych, z któ rych każda jest typu double.
Tablica values skł
ada się z 5 zmiennych, z któ rych każda jest typu int.
Identyfikowanie elementów tablicy
14
Jeśli nazwą tablicy jest vec, to nazwą jej elementu o indeksie ind jest vec[ind]. Jest to prawdziwe tylko wó wczas,
gdy wyraż enie ind ma wartość większą -lub-ró wną 0 i jednocześnie mniejszą od liczby elementó w tablicy.
Uwaga: Jeśli tablica vec ma n elementó w, to zezwala się, aby wyraż enie ind miało wartość -1 oraz n, ale tylko
wó wczas, gdy opracowanie wyraż enia vec[ind] nie ma na celu dokonania zmiany albo dostarczenia wartości
elementu.
#include <iostream.h>
int values[5] = { 10, 20, 30, 40, 50 };
int main(void)
{
int index;
cin >> index;
if(index >= 0 && index < 5)
cout << values[index] << endl;
else
cout << "Wrong index" << endl;
return 0;
}
Program wyprowadza wartoś ć elementu o podanym indeksie. Jeś li indeks nie mieś ci się w domkniętym
przedziale [0 ; 4], to program wyprowadza napis Wrong index.
Tablice znakowe
Tablicą znakową jest tablica o elementach typu char. Przechowuje się w niej zazwyczaj małe liczby oraz kody
znakó w.
Ponieważ Visual C++ uż ywa kodu ASCII, w któ rym kodem cyfry 0 jest 48, więc zainicjowanie 4-elementowej
tablicy znakowej kodami cyfr 0, 1 i 2 oraz kodem znaku ’\0’ moż na wykonać na wiele sposobó w, w tym m.in.
char digits[] = { ’0’, ’1’, ’2’, ’\0’ };
char digits[] = { 48, 49, 50, 0 };
char digits[4] = { ’0’, ’0’+1, ’3’-1 };
char digits[4] = "012";
Z klawiatury moż na wprowadzać tylko spójne cią gi znakó w. Za ostatnim wprowadzonym znakiem umieszcza się
wó wczas specjalny znak o kodzie 0.
Jeśli chce się wyprowadzić cią g znakó w utworzony w tablicy programowo, to należ y zakończyć go znakiem o
kodzie 0 (jego rozpoznanie spowoduje zakończenie wyprowadzania znakó w).
#include <iostream.h>
char name[100];
int main(void)
{
cin >> name;
name[1] = 0;
cout << "Your initial is: " << name << endl;
return 0;
}
Program wprowadza imię, a następnie wyprowadza jego inicjał
.
Literały łań cuchowe
15
Literał łańcuchowy, na przykład "Hello", ma postać cią gu znakó w ujętego w cudzysłowy. Znaki specjalne są w
tym cią gu reprezentowane przez nastepują ce symbole
\\ (ukoś nik)
\n (nowy wiersz)
\t (tabulator),
\’ (apostrof)
\" (cudzysł
ó w)
\0 (znak o kodzie 0).
Każ dy literał łańcuchowy, jest nazwą tablicy o elementach typu char, zainicjowanych kodami kolejnych znakó w
literału oraz kodem znaku \0. W szczegó lności (w kodzie ASCII) literał "No" jest nazwą 3-elementowej tablicy
zainicjowanej liczbami 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 każdym znaku dodają c spację . Zakończenie wykonywania
następuje po rozpoznaniu elementu zainicjowanego liczbą 0.
Literały łańcuchowe mogą być uż yte do inicjowania tablic znakowych. Tak zainicjowana tablica musi mieć co
najmniej tyle elementó w ile ma tablica reprezentowana przez literał. Jeśli jest dłuż sza, to jej nadmiarowe
elementy są inicjowane liczbami 0.
char name1[10] = { ’I’, ’s’, ’a’, 0 };
char name2[10] = "Isa";
char name3[] = "Isa";
char name4[3] = "Isa"; // b
łąd
Operacje wejścia-wyjścia
Tablice znakowe mogą być wykorzystane do wprowadzania z klawiatury spó jnych cią gó w znakó w. W takim
przypadku argumentem operacji jest zazwyczaj nazwa tablicy, a wykonanie operacji powoduje umieszczenie w
tablicy kodó w znakó w łańcucha oraz kodu o wartości 0.
Ponieważ moż e wó wczas dojść do przepełnienia tablicy, zaleca się uż ycie manipulatora setw, zadeklarowanego
w nagłó wku iomanip.h, ograniczają cego liczbę wprowadzonych znakó w.
Uwaga: Manipulator setw moż e być uż yty takż e podczas wyprowadzania danych. W takim wypadku określa on
szerokość pola zewnętrznego, w któ rym umieszcza się dane wyjściowe.
#include <iostream.h>
#include <iomanip.h>
char name[20];
int main(void)
{
cin >> setw(20) >> name;
name[1] = 0;
cout << "Your initial is: " << name << endl;
return 0;
}
16
Program wprowadza imię, a następnie wyprowadza jego inicjał
. Aby zabezpieczyć się przed wpisaniem do
tablicy name więcej niż 20 znakó w, użyto manipulatora setw(20) zadeklarowanego w nagł
ó wku iomanip.h.
Zmienne strukturowe
Zmienną strukturową , w skró cie strukturą , jest zestaw są siadują cych ze sobą elementów struktury. Każ dy
element struktury moż e być zmienną innego typu: skalarną , tablicową , strukturową .
Przed zadeklarowaniem zmiennej strukturowej należ y zdefiniować jej typ. Deklaracja typu strukturowego składa
się z deklaracji pól struktury. Deklaracja pola struktury ma postać deklaracji zmiennej.
struct Child {
char name[20];
int age;
};
Child isa = { "Isabel", 15 };
Struktura isa skł
ada się z 2 zmiennych, opisanych przez pola name i age. Wartoś ci począ tkowe elementó w
struktury okreś lono za pomocą inicjatora klamrowego. Użycie innych inicjatoró w jest zabronione.
Identyfikowanie elementów
Jeśli nazwą struktury jest str, a w opisie jej typu występuje pole fld, to nazwą zmiennej odpowiadają cej temu
polu jest str.fld.
#include <iostream.h>
#include <iomanip.h>
struct Child {
char name[20];
int age;
};
Child child;
int main(void)
{
cin >> setw(20) >> child.name >> child.age;
cout << child.name << " is "
<< child.age << " now" << endl;
return 0;
}
Zmienna child skł
ada się z tablicy o elementach typu char i zmiennej skalarnej typu int. Program wprowadza
imię i wiek dziecka, a następnie wyprowadza je.
Kopiowanie struktur
W odró ż nieniu od tablic, któ re moż na kopiować tylko element-po-elemencie, kopiowanie struktur moż e dotyczyć
pełnego zestawu jej elementó w i to nawet wó wczas, gdy struktura zawiera tablice.
#include <iostream.h>
struct Child {
char name[20];
int age;
};
Child girl;
17
int main(void)
{
Child isa = { "Isabel", 15 };
girl = isa;
cout << girl.name << " is " << girl.age << endl;
return 0;
}
Program wyprowadza te same dane, któ rymi zainicjowano strukturę isa.
Unia elementów
Struktura, któ rej elementy są rozmieszczone w pamięci nie jeden-za-drugim, ale zawsze od tego samego miejsca,
jest nazywana unią . W celu zadeklarowania unii należ y zamiast słowa kluczowego struct uż yć słowa union.
Definicja unii, w któ rej pominięto nazwę typu, jest definicją unii anonimowej. Pola unii anonimowej są
zadeklarowane w miejscu zdefiniowania unii.
struct Number {
bool isFixed;
union { // unia anonimowa
int fixed;
double real;
};
};
Number num = { true, 12 };
if(num.isFixed)
cout << num.fixed << endl; // 12
else
cout << num.real << endl;
cout << num.real << endl; // b
łąd
W każdej chwili struktura num skł
ada się ze zmiennych typu bool i int, albo ze zmiennych typu bool i double.
Bł
ą d polega na tym, że w chwili gdy zmienna num składa się ze zmiennych typu bool i int, następuje odwoł
anie
do zmiennej typu double.
Przetwarzanie plików
Przetwarzanie plikó w odbywa się za pośrednictwem zmiennych strumieniowych klas ifstream i ofstream,
zadeklarowanych w nagłó wku fstream.h. Po utworzeniu zmiennej strumieniowej należ y otworzyć skojarzony z
nią plik, a następnie upewnić się, ż e otwarcie było pomyślne.
Po pomyślnym otwarciu pliku, pochodzą cy z niego strumień danych moż na przetwarzać w taki sam sposó b, jak
strumień danych zwią zany z klawiaturą albo z monitorem.
Stany strumienia
Począ tkowo strumień znajduje się w stanie dobrym, ale na skutek błędu operacji wejścia-wyjścia albo pró by
wprowadzenia nieistnieją cej danej, moż e znaleźć się w stanie nie-dobrym (fail).
W stanie nie-dobrym wszystkie operacje na strumieniu są ignorowane. Jeśli dane przygotowano właściwie, a
jakość pamięci zewnętrznej jest zadowalają ca, to stan nie-dobry oznacza, ż e napotkano koniec strumienia.
18
Uwaga: W programach przykładowych nie bę dzie rozpatrywany przypadek wystą pienia błę du przesyłania
danych.
Szczegó lnym przypadkiem stanu nie-dobrego jest stan zły (bad). Powstaje on w przypadku rozpoznania danych o
złym formacie. Niestety, na skutek niefortunnych domniemań, wprowadzenie takiej "danej" jak 3e, zamiast 3e0
(w kontekście 3ex) nie zmienia stanu strumienia na zły.
Uwaga: Do sprawdzenia czy stan strumienia jest zły, służ y funkcja bad, a do sprawdzenia, czy strumień znajduje
się w pozycji za końcem pliku, służ y funkcja eof. Funkcji tych uż ywa się bardzo rzadko.
Zmienna plikowa
Jeśli w miejscu wystą pienia operacji wejścia-wyjścia odbywa się takie badanie zmiennej plikowej, jakby
dotyczyło wyraż enia o wartości orzecznikowej, na przykład
while(cin >> num) ...
albo
if(cin) ...
to w stanie dobrym jest dostarczana wartość true, a w stanie nie-dobrym wartość false.
Wprowadzanie danych
Zmienna strumieniowa uż yta do wprowadzania danych z pliku jest typu ifstream. Otwarcie pliku odbywa się za
pomocą funkcji open, któ rej pierwszym argumentem jest nazwa, a drugim tryb otwarcia pliku: ios::in. Jeśli
otwierany plik nie istnieje, to zostanie utworzony jako pusty. Aby tego unikną ć , plik należ y otworzyć w trybie
ios::in | ios::nocreate.
Do zbadania, czy otwarcie pliku się powiodło, służ y funkcja is_open. Jej rezultat ma wartość nie-zero tylko
wó wczas, gdy otwarcie było pomyślne.
#include <iostream.h>
#include <fstream.h>
#include <assert.h>
int sum = 0;
int main(void)
{
ifstream inp; // zmienna plikowa
inp.open("Data.txt", ios::in | ios::nocreate);
if(!inp.is_open()) {
cout << "File does not exist" << endl;
return -1;
}
int val;
while(inp >> val) // wprowad
ź i sprawdź stan
sum += val; // dosumuj
assert(!inp.bad()); // raczej zb
ędne
cout << "Sum = " << sum << endl;
return 0;
}
Wykonanie programu powoduje wyprowadzenie sumy liczb cał
kowitych zawartych w pliku Data.txt.
Wywoł
anie funkcji assert ma na celu upewnienie się, że strumień nie znajduje się w zł
ym stanie. Gdyby tak był
o,
to wykonanie programu został
oby zaniechane.
19
Wyprowadzanie danych
Zmienna strumieniowa uż yta do wyprowadzania danych do pliku jest typu ofstream. Otwarcie pliku odbywa się
za pomocą funkcji open, któ rej pierwszym argumentem jest nazwa, a drugim tryb otwarcia pliku: ios::out.
Jeśli otwierany plik nie istnieje, to zostanie utworzony i otworzony jako pusty. Jeśli już istnieje, to zostanie
otworzony jako pusty.
Do zbadania, czy otwarcie pliku się powiodło, służ y funkcja is_open. Jej rezultat ma wartość nie-zero tylko
wó wczas, gdy otwarcie było pomyślne.
#include <iostream.h>
#include <fstream.h>
int main(void)
{
ifstream inp;
inp.open("Data.txt", ios::in | ios::nocreate);
if(!inp.is_open()) {
cout << "Source does not exist" << endl;
return -1;
}
ofstream out;
out.open("Data2.txt", ios::out);
if(!out.is_open()) {
cout << "Target not opened" << endl;
return -1;
}
int val;
while(inp >> val)
out << val << endl;
cout << "Done!" << endl;
return 0;
}
Program kopiuje liczby cał
kowite z pliku Data.txt do pliku Data2.txt. Każdą kopiowaną liczbę umieszcza w
nowym wierszu.
Użycie klawiatury
Jeśli dane wprowadza się z klawiatury, to koniec strumienia określa się za pomocą znaku koń ca: Ctrl-Z (na
polskiej klawiaturze Ctrl-Y). W Visual C++ nastą pi wó wczas pominięcie pierwszego znaku wyprowadzonego na
konsolę.
Uwaga: Zaleca się, aby znak końca został wprowadzony na począ tku nowego wiersza (po Enter).
#include <iostream.h>
int main(void)
{
int count = 0;
double tmp;
while(cin >> tmp)
count++;
cout << endl; // na po
żarcie
cout << "Count = " << count << endl;
return 0;
}
20
Program zlicza dane liczbowe wprowadzone z klawiatury.
21
Ś rodowisko Visual C++
Program źró dłowy składa się z modułów źródłowych. Każ dy moduł jest umieszczony w odrębnym pliku z
rozszerzeniem .cpp. Dodatkowo, w skład programu mogą wchodzić moduły skompilowane (*.obj) i biblioteczne
(*.lib).
W celu przekształcenia zestawu modułó w w program wykonalny, należ y utworzyć projekt, umieścić go w
przestrzeni roboczej, włą czyć do projektu nazwy plikó w z rozszerzeniami .cpp, .obj i .lib, a następnie zbudować
program. Zostanie on umieszczony w pliku z rozszerzeniem .exe.
Katalog
Zaleca się, aby pliki programu znajdowały się we własnym katalogu. Jeśli dysponuje się wolnym miejscem na
przykład w katalogu głó wnym dysku D:, to należ y wywołać Eksplorator Windows, klikną ć na nazwie katalogu
głó wnego i wydać polecenie Plik / Nowy obiekt / Folder, a następnie określić nazwę swojego katalogu, na
przykład jbVisual.
Przestrzeń
W celu utworzenia przestrzeni roboczej należ y wydać polecenie File / New, a następnie (w zakładce
Workspaces) podać nazwę przestrzeni, np. Workspace: jbSpace oraz określić jej położ enie, np. Location:
D:\jbVisual\jbSpace, po czym nacisną ć przycisk OK.
Jeśli przestrzeń już istnieje, to aby ją otworzyć , należ y wydać polecenie File / Open Workspace, wejść do
katalogu przestrzeni (np. jbSpace), a następnie dwu-klikną ć na nazwie jbSpace.dsw.
Projekt
W celu utworzenia projektu należ y wydać polecenie File / New, a w zakładce Projects podać typ projektu: Win
32 Console Application i jego nazwę, np. Project name: jbTests. Po upewnieniu się, ż e projekt zostanie
włą czony
do
bież ą cej
przestrzeni
(Add
to
current
workspace)
o
czym
zaświadczy
Location: D:\jbVisual\jbSpace\jbTests, należ y nacisną ć przycisk OK.
Pliki
W celu utworzenia plikó w projektu należ y wydać polecenie File / New, a następnie (w zakładce Files), określić
rodzaj pliku
C/C++ Source File
dla pliku z rozszerzeniem .cpp
C++ Header File
dla pliku z rozszerzeniem .h
Text File
dla pliku z rozszerzeniem .txt
nie zapominają c o podaniu jego nazwy (bez rozszerzenia), np. File name: Sum.
Po wykonaniu tych czynności, w katalogu D:\jbVisual\jbSpace\jbTests powstanie plik Sum.cpp, a jego
(począ tkowo pusta) zawartość ujawni się odrębnym oknie edycyjnym.
22
Jeśli program wymaga utworzenia plikó w z danymi, to zaleca się je umieścić w tym samym katalogu co pliki
źró dłowe. Dla wygody moż na je dołą czyć do plikó w projektu.
Budowanie projektu
W celu zbudowania projektu, to jest skompilowania jego wszystkich plikó w *.cpp, oraz ewentualnie jego plikó w
*.obj i *.lib, należ y klikną ć ikonę Build. Spowoduje to niezależ ne kompilacje wszystkich modułó w źró dłowych
oraz połą czenie ich w program wykonalny.
Przebieg budowania projektu jest diagnozowany w oknie Output. Jeśli okno nie jest widoczne, to moż na je
wyświetlić wydają c polecenie View / Output.
Błędy modułu wyszczegó lnia się w oknie Output. Po rozpoznaniu każ dego z nich podaje się kró tki opis
przyczyny błędu i numer wiersza programu. Dwu-kliknięcie w obrębie opisu błędu powoduje przeniesienie
kursora w pobliż e miejsca, w któ rym wykryto błą d.
W rzadkich przypadkach, gdy poprawność diagnozy budzi wą tpliwości, zaleca się zastą pienie polecenia Build
poleceniem Build / Rebuild All.
Wykonanie programu
Program wykonalny, pod nazwą jbTests.exe jest umieszczany w podkatalogu jbTests\Debug. Jeśli wykonuje się
bezbłędnie i jest należ ycie wytestowany, to moż e zostać zoptymalizowany.
W celu zoptymalizowania programu należ y wydać polecenie Build / Set Active Configuration, a następnie
zamiast konfiguracji Win 32 Debug, wybrać konfigurację Win 32 Release. Po ponownym zbudowaniu projektu,
w katalogu jbTests\Release, powstanie program znacznie kró tszy, ale już bez informacji uruchomieniowych.
Zarzą dzanie projektami
Przestrzeń robocza moż e zawierać więcej niż jeden projekt, a projekt moż e składać się z więcej niż jednego
pliku.
Jeśli przestrzeń zawiera więcej niż jeden projekt, to tylko jeden z nich moż e być aktywny, to jest taki, któ rego
dotyczą polecenia Build. Uaktywnienie projektu odbywa się przez p-kliknięcie jego nazwy i wydanie polecenia
Set Active Project.
W celu umieszczenia w przestrzeni dodatkowego projektu należ y p-klikną ć nazwę przestrzeni, wydać polecenie
Add New Project to Workspace, a dalej postępować tak, jak podczas tworzenia pierwszego projektu.
W celu włą czenia do projektu dodatkowego pliku należ y p-klikną ć nazwę projektu, a następnie wydać polecenie
Add Files to Project i wybrać skopiowany plik.
Jeśli włą czany do projektu plik źró dłowy już istnieje, to należ y skopiować go do katalogu projektowego
(posługują c się np. Eksploratorem Windows), a następnie postą pić tak, jak podczas dodawania pliku do
projektu.
Dopasowanie oblicza
Oblicze środowiska uruchomieniowego składa się z menu oraz z paskó w, któ re moż na konfigurować . Odbywa
się to za pomocą polecenia Tools / Customize umoż liwiają cego zarzą dzanie wyświetlaniem paskó w edycyjnych,
uruchomieniowych i innych.
23
Uruchamianie programu
Systematyczne wyszukiwanie błędó w w programie odbywa się za pomocą uruchamiacza. W celu wyświetlenia
paska zawierają cego jego narzędzia należ y wydać polecenie Tools / Customize / Toolbars, a następnie odhaczyć
nastawę Debug.
Wykonanie programu nadzorowanego przez uruchamiacz zaczyna się w konfiguracji Win32 Debug po wydaniu
polecenia Build / Start Debug / Step into (F10). Program zatrzyma się tuż przed przystą pieniem do wykonania
pierwszej funkcji (zazwyczaj funkcji main).
Począ wszy od tego momentu moż na
Określać argumenty funkcji głó wnej
Project / Settings // Debug, Program arguments
Zastawiać / usuwać pułapki
ikona Hand (F9)
Usuwać pułapki
Edit / Breakpoints / Remove All (Alt-F9)
Wykonywać program krokowo
ikona Go (po zastawieniu pułapki)
ikona Step over (F10)
ikona Step into (F11)
ikona Step out (Shift-F11)
Obserwować zmienne
ikona Quick Watch (Shift-F9)
Kompilacja warunkowa
Podczas uruchamiania programu przydaje się ignorowanie jego wybranych fragmentó w. Odbywa się to za
pomocą dyrektyw kompilacji warunkowej: #if, #else, #endif.
Zinterpretowanie dyrektywy
#if c
kod-źró dł
owy
#else
kod-alternatywny
#endif
zaczyna się od wyznaczenia wartości wyraż enia c (najczęściej liczby 1 albo 0). Jeśli wyraż enie ma wartość ró ż ną
od 0, to całą dyrektywę zastępuje się napisem kod-źró dł
owy. W przeciwnym razie zastępuje się ją napisem kod-
alternatywny.
Uwaga: Jeśli napis kod-źró dł
owy jest pusty, to dyrektywę moż na zapisać bez frazy #else.
#include <iostream.h>
int main(void)
{
int one, two;
cin >> one >> two;
#if 1
24
cout << "Sum = ";
#endif
cout << one + two << endl;
return 0;
}
Program wyprowadza sumę pary danych wejś ciowych poprzedzają c ją napisem Sum =. Jeś li w dyrektywie #if
zmieni się 1, na 0, to powstanie program, któ ry takiego napisu nie wyprowadzi.
25
Wskaźniki i odnośniki
Wskaźniki i odnośniki są zmiennymi, któ re służ ą do identyfikowania innych zmiennych. Wskaźnik moż e
identyfikować wiele zmiennych pokrewnego mu typu, natomiast odnośnik moż e identyfikować tylko jedną
zmienną .
Wskaźnikom przypisuje się wskazania, a odnośnikom odniesienia. Mimo iż w typowych implementacjach
zaró wno wskazania jak i odniesienia są reprezentowane przez adresy, posługiwanie się pojęciem adres jest
całkowicie zbyteczne i dowodzi myślenia o C++ nie w kategoriach języka wysokiego poziomu, ale w
kategoriach implementacji. Dlatego o adresach nie będzie już mowy.
Zmienne wskaźnikowe
Wskaźnikiem jest zmienna, któ rej moż na przypisywać wskazania. Deklarację wskaźnika moż na poznać po tym,
ż e jej identyfikator jest poprzedzony symbolem * (gwiazdka).
Jeśli w pewnym miejscu programu jest wymagane uż ycie wskazania zmiennej, to otrzymuje się je poprzedzają c
nazwę zmiennej operatorem wskazywania & (ampersand).
Po przypisaniu wskaźnikowi ptr wskazania zmiennej, napis *ptr staje się chwilową nazwą tej zmiennej. Po
przypisaniu wskaźnikowi wskazania pustego (reprezentowanego przez liczbę 0), uż ycie nazwy *ptr albo nazwy
jej ró wnoważ nej (np. ptr[0]) jest zabronione.
int fix1 = 10,
fix2 = 20;
int *ptr = &fix1;
cout << *ptr; // 10
*ptr = 11;
cout << *ptr << fix; // 11 11
ptr = &fix2;
cout << *ptr; // 20
*ptr = 22;
cout << *ptr << fix; // 22 22
ptr = 0;
cout << *ptr; // b
łąd
Wskaźnik ptr jest przystosowany do wskazywania zmiennych typu int. Przypisano mu kolejno: wskazanie
zmiennej fix1, wskazanie zmiennej fix2 i wskazanie puste.
Po przypisaniu wskaźnikowi ptr wskazania zmiennej fix1, napis *ptr jest chwilową nazwą zmiennej fix1, a po
przypisaniu mu wskazania zmiennej fix2, jest chwilową nazwą zmiennej fix2.
Po przypisaniu wskaźnikowi ptr wskazania pustego, aż do chwili przypisania mu wskazania zmiennej, użycie
nazwy *ptr jest zabronione.
Dla dociekliwych
Typ wyraż enia inicjują cego wskaźnik musi być zgodny z typem wskaźnika. Przyjmuje się z definicji, ż e zgodne
ze wskaźnikiem typu Type jest każ de wyraż enie typu Type oraz każ de wyraż enie, któ re moż e być poddane
niejawnej konwersji do typu Type (por. Dodatek C).
26
char *ptr1 = "0\0\0\0" // niejawna konwersja
int *ptr2 = "0\0\0\0"; // b
łąd
int *ptr = (int *)"0\0\0\0"; // jawna konwersja
cout << *ptr; // 48 (kod cyfry 0)
Skutek użytej tu jawnej konwersji zależy od implementacji. W Visual C++ powoduje to potraktowanie obszaru
pamięci zajętego przez pierwsze 4 bajty literał
u jako zmiennej cał
kowitej.
Wskaźniki i tablice
Zwią zki między wskaźnikami i tablicami są bardzo bliskie. Każ da nazwa tablicy jest niejawnie przekształcana na
wskaźnik jej zerowego elementu, a każ da nazwa wskaźnika moż e być indeksowana tak, jak nazwa tablicy.
Jeśli wskaźnik ptr wskazuje pewien element tablicy, to zaró wno *ptr jak i ptr[0] jest nazwą tego elementu.
Elementy położ one z lewej strony elementu wskazywanego maj ą nazwy ptr[-1], ptr[-2], itd., a elementy położ one
z prawej mają nazwy ptr[1], ptr[2], itd.
Jeśli i jest wyraż eniem całkowitym, to wyraż enie ptr+i jest wskaźnikiem elementu odległego o i elementó w od
wskazywanego (dla i ujemnego - w lewo, a dla i dodatniego - w prawo).
Jeśli wskaźniki ptr1 i ptr2 wskazują odpowiednio elementy o indeksach i oraz j tej samej n-elementowej tablicy
(a takż e gdy wskazują nie istnieją ce "elementy" o indeksach -1 i n), to wyraż enie ptr1-ptr2 ma wartość i-j.
int vec[3] = { 10, 20, 30 };
int *ptr = vec + 2;
cout << ptr++[-1]; // 20
cout << *(vec + 2); // 30
cout << vec - ptr; // -3
Nazwa vec zostaje niejawnie przekształ
cona na wskazanie elementu vec[0], to jest na &vec[0].
Wyrażenie vec + 2 wskazuje element o wartoś ci 30.
Wyrażenie ptr++[-1] jest nazwą elementu o wartoś ci 20.
W wyrażeniu vec - ptr pierwszy argument wskazuje element zerowy, a drugi argument wskazuje nie istnieją cy
element vec[3].
Wskaźniki i struktury
Jeśli wskaźnik ptr wskazuje strukturę o polu f, to nazwą zmiennej odpowiadają cej temu polu jest (*ptr).f, albo
kró cej ptr->f.
#include <iostream.h>
struct Child {
char name[20];
int age;
Child *pNext;
};
Child bob = { "Robert", 20 },
tom = { "Thomas", 30, 0 };
Child *pBob = &bob,
*pTom = &tom;
int main(void)
{
cout << pBob->name << endl; // Robert
27
pBob->pNext = pTom;
cout << pBob->pNext->age << endl; // 30
return 0;
}
Zmienna pBob jest wskaźnikiem przystosowanym do wskazywania zmiennych typu Child. Przypisano jej
wskazanie struktury bob.
Napis pBob->name jest chwilową nazwą tego elementu struktury bob, któ ry jest opisany przez pole name.
Napis pBob->pNext jest nazwą wskaźnika opisanego przez pole pNext. Ponieważ wskazuje on strukturę tom,
więc pBob->pNext->age jest nazwą tego elementu struktury tom, któ ry jest opisany przez pole age.
Tablice wskaźników
Tablicą wskaźnikó w jest tablica, któ rej elementami są wskaźniki. W deklaracji tablicy wskaźnikó w jej
identyfikator jest poprzedzony znakiem * (gwiazdka).
W deklaracji wskaźnika, któ ry służ y do wskazywania-wskaźnikó w, jego identyfikator jest poprzedzony dwiema
znakami * (gwiazdka).
#include <iostream.h>
const int Count = 3;
struct Child {
char name[20];
int age;
};
Child john = { "John Smith", 30 },
tom = { "Thomas Mill", 10 },
bill = { "Robert Dole", 20 };
Child *pBoys[Count] = { &john, &tom, &bill };
int main(void)
{
for(int i = 0; i < Count-1 ; i++) {
int minAge = pBoys[i]->age;
for(int j = i+1; j < Count ; j++) {
if(pBoys[j]->age < minAge) {
minAge = pBoys[j]->age;
Child *ptr = pBoys[i];
pBoys[i] = pBoys[j];
pBoys[j] = ptr;
}
}
}
Child **ptr = pBoys;
for(i = 0; i < Count ; i++)
cout << (*ptr++)->name << endl;
return 0;
}
Program wyprowadza nazwiska chł
opcó w, w kolejnoś ci ich rosną cego wieku. Sortowanie dotyczy tylko
elementó w tablicy wskaźnikó w i nie powoduje kopiowania struktur typu Child.
Wskaźniki a ustalenia
28
Podobnie jak zwykła zmienna, tak i wskaźnik moż e być ustalony albo nie-ustalony. Ponadto wskaźnik moż e być
przystosowany do wskazywania zmiennych ustalonych albo nie-ustalonych. Daje to cztery moż liwości.
Uwaga: Zabrania się, aby wskaźnikowi przystosowanemu do wskazywania zmiennych nie-ustalonych przypisano
wskazanie zmiennej ustalonej.
#include <iostream.h>
int main(void)
{
int mod = 10;
const int fix = 20;
int *ptr1 = &mod;
int *const ptr2 = &mod;
const int *ptr3 = &mod;
const int *const ptr4 = &fix;
cout << *ptr1 << endl; // 10
cout << *ptr2 << endl; // 10
cout << *ptr3 << endl; // 10
cout << *ptr4 << endl; // 20
ptr1 = &fix; // b
łąd
++ptr2; // b
łąd
++*ptr3; // b
łąd
ptr1 = &(int &)fix; // dozwolone
cout << *ptr1 << endl; // 20
return 0;
}
Wskaźnik ptr1 sł
uży do wskazywania zmiennych nie-ustalonych. Wskaźnik ptr2 jest wskaźnikiem ustalonym, któ ry
sł
uży do wskazywania zmiennych nie-ustalonych. Wskaźnik ptr3 jest wskaźnikiem nie-ustalonym, któ ry sł
uży do
wskazywania zmiennych ustalonych. Wskaźnik ptr4 jest wskaźnikiem ustalonym, któ ry sł
uży do wskazywania
zmiennych ustalonych.
Zmienne odnośnikowe
Odnośnikiem jest zmienna, któ rą moż na zainicjować odniesieniem. Deklarację odnośnika moż na poznać po tym,
ż e jej identyfikator jest poprzedzony symbolem & (ampersand). Istnieją odnośniki do zmiennych, ale nie istnieją
tablice odnośnikó w. Każ dy odnośnik musi być zainicjowany.
Uwaga: Jeśli w pewnym miejscu programu występuje nazwa zmiennej, a program byłby poprawny tylko
wó wczas, gdyby występowała tam nazwa odnośnika do zmiennej, to nazwę zmiennej niejawnie przekształca się
w odnośnik.
int fix = 10;
int &ref = fix; // int &ref = (int &)fix;
Ponieważ odnoś nik ref jest typu int &, więc nie może być zainicjowany wartoś cią zmiennej fix, któ ra jest typu
int. Dlatego, za pomocą niejawnej konwersji (int &)fix, nazwę zmiennej fix niejawnie przekształ
ca się w
odnoś nik.
Po zainicjowaniu odnośnika ref odniesieniem do zmiennej, napis ref staje się trwałą nazwą tej zmiennej. A więc
odnośnik moż na zainicjować , ale nie moż na mu przypisać odniesienia.
#include <iostream.h>
29
int main(void)
{
int fix = 10;
int &ref = fix;
ref = 10;
cout << fix << ref << endl; // 10 10
return 0;
}
Po zainicjowaniu odnoś nika, napis ref staje się trwał
ą nazwą zmiennej fix. Dlatego przypisanie ref = 10 zmienia
wartoś ć zmiennej fix, ale nie zmienia wartoś ci odnoś nika ref.
Dla dociekliwych
Typ wyraż enia inicjują cego odnośnik musi być zgodny z typem odnośnika. Przyjmuje się z definicji, ż e typ
"odnoś nik do zmiennej typu Type" (np. int &) jest zgodny z typem Type (np. int). Jeśli wyraż enie inicjują ce jest
innego typu, to moż e być poddane niejawnej konwersji do typu zgodnego, ale tylko wó wczas, gdy typ odnośnika
jest ustalony (const). W takim wypadku odnośnik zostanie zainicjowany odniesieniem do zmiennej pomocniczej
typu z nim zgodnego, zainicjowanej wartością wyraż enia po konwersji.
int &ref1 = 2.4; // b
łąd
const int &ref = 2.4;
Identyfikator ref2 jest trwał
ą nazwą zmiennej pomocniczej o wartoś ci (int)2.4.
Wskaźniki i odnośniki
Podejmują c decyzję o uż yciu wskaźnika, czy odnośnika, należ y kierować się wytyczną , ż e wszędzie tam gdzie
jest to moż liwe, należ y stosować odnoś niki, gdyż zwiększa to czytelność programu.
W rzadkich przypadkach stosuje się odnośniki do wskaźnikó w. Jest to niezbędne wó wczas, gdy poprzez
odnośnik należ y zmodyfikować wskaźnik.
#include <iostream.h>
int main(void)
{
int vec[3] = { 10, 20, 30 };
int *ptr = vec;
int *&ref = ptr;
++ref;
cout << *ptr << endl; // 20
return 0;
}
Po zadeklarowaniu odnoś nika, napis ref jest trwał
ą nazwą wskaźnika ptr. Dlatego po wykonaniu operacji ++ref
wskaźnik ptr wskazuje element vec[1] o wartoś ci 20.
Gdyby z deklaracji odnoś nika usunięto znak &, to napis ref stał
by się nazwą wskaźnika zainicjowanego
wskazaniem elementu vec[0], a wykonanie operacji ++ref nie miał
oby żadnego wpł
ywu na wskaźnik ptr. W takim
wypadku nastą pił
oby wyprowadzenie liczby 10.
30
Przetwarzanie łań cuchów
Ł ańcuchem jest dowolna sekwencja elementó w tablicy znakowej, zakończona elementem o wartości 0. Ponieważ
każ dy literał łańcuchowy jest nazwą takiej właśnie sekwencji elementó w, więc jest nazwą łańcucha.
W szczegó lności, literał "Hello" jest nazwą 6-elementowej tablicy znakowej, któ rej element "Hello"[0] ma
wartość ’H’, a element "Hello"[5] ma wartość 0.
Do typowych operacji wykonywanych na łańcuchach należ ą : wprowadzenie i wyprowadzenie łańcucha,
wyznaczenie długości łańcucha (strlen), skopiowanie łańcucha (strcpy), połą czenie łańcuchó w (strcat) i
poró wnanie łańcuchó w (strcmp). Operacje te moż na wykonać za pomocą funkcji bibliotecznych,
zadeklarowanych w nagłó wku string.h.
Uwaga: Jeśli wskaźnik wskazuje pierwszy element łańcucha, to mó wi się w skró cie, ż e wskazuje łańcuch..
int strlen(char *pStr)
Dostarcza liczbę znakó w łańcucha wskazanego przez argument.
np.
cout << strlen("Hello"); // 5
char *strcpy(char *pTrg, const char *pSrc)
Dostarcza pierwszy argument. Ponadto kopiuje, począ wszy od miejsca wskazanego przez pierwszy argument,
łańcuch wskazany przez drugi argument.
np.
char buf[100] = "Hello ";
cout << strcpy(buf + 6, "World")- 6; // Hello World
char *strcat(char *pTrg, const char *pSrc)
Dostarcza pierwszy argument. Ponadto kopiuje, poczawszy od miejsca, w któ rym znajduje się znak końca
łańcucha wskazanego przez pierwszy argument, łańcuch wskazany przez drugi argument.
np.
char buf[100] = "Hello ";
cout << strcat(buf, "World"); // Hello World
int strcmp(const char *pOne, const char *pTwo)
Dostarcza wartość +1 jeśli łańcuch wskazany przez pierwszy argument jest wię kszy niż łańcuch wskazany przez
drugi argument, dostarcza wartość 0 jeśli są ró wne, albo wartość -1 jeśli pierwszy jest mniejszy.
Uwaga: Poró wnanie łańcuchó w zastępuje się poró wnaniem pierwszej pary znakó w ró ż nych. Jeśli jeden z
łańcuchó w jest podłańcuchem drugiego, to za większy uznaje się dłuż szy.
np.
cout << strcmp("abc", "abaaaaa"); // -1
cout << strcmp("abcde", "ab"); // 1
cout << strcmp("ab", "ab"); // 0
Wprowadzanie i wyprowadzanie łań cuchów
Operacja wprowadzenia łańcucha ma postać cin >> ptr, w któ rej ptr jest wskaźnikiem elementu tablicy
znakowej. Jej wykonanie powoduje umieszczenie w tablicy, począ wszy od jej elementu *ptr, kodó w spó jnego
cią gu znakó w wejściowych oraz kodu znaku ’\0’ (o wartości 0).
Przed wprowadzeniem znakó w zostaną pominięte odstępy wiodą ce. W celu zabezpieczenia się przed
przepełnieniem tablicy moż na uż yć manipulatora setw.
31
Operacja wyprowadzenia łańcucha ma postać cout << ptr, w któ rej ptr jest wskaźnikiem. Zabrania się, aby ptr
było wskaźnikiem elementu tablicy, któ ry nie jest zerowym elementem łańcucha.
#include <iostream.h>
#include <string.h>
const int Size = 100;
char buffer[Size] = "prof. ";
int main(void)
{
int len1 = strlen(buffer);
cin >> buffer + len1;
int len2 = strlen(buffer);
buffer[len2] = ’ ’;
buffer[len2+1] = 0;
cin >> buffer + len2 + 1;
cout << buffer << endl;
cout << "dr " << buffer + len1 << endl;
return 0;
}
Jeś li z klawiatury wprowadzi się imię i nazwisko (np. Jan Bielecki), to program wyprowadzi to imię i to nazwisko
poprzedzone napisem prof. (np. prof. Jan Bielecki), a ponadto tylko to imię i to nazwisko.
Wyznaczenie długości
#include <iostream.h>
#include <string.h>
char str[6] = "Hello";
int main(void)
{
cout << strlen("Hello") << endl; // 5
char *ptr = str;
int len = 0;
while(*ptr != 0) {
len++;
ptr++;
}
cout << len << endl; // 5
ptr = str;
len = 0;
while(*ptr++)
len++;
cout << len << endl; // 5
return 0;
}
Pokazano trzy sposoby wyznaczenia dł
ugoś ci ł
ańcucha zapisanego w tablicy znakowej.
Wyrażenie *ptr++ jest nazwą zmiennej, wskazywanej przez wskaźnik ptr, przed wykonaniem na nim operacji
zwiększenia.
Kopiowanie
#include <iostream.h>
#include <string.h>
char src[7] = "Hello ";
32
char trg[100];
int main(void)
{
char *pSrc = src,
*pTrg = trg;
strcpy(pTrg, pSrc);
cout << trg << endl; // Hello
while(*pSrc != 0) {
*pTrg = *pSrc;
pSrc++;
pTrg++;
}
pTrg = 0;
cout << trg << endl; // Hello
pSrc = src;
pTrg = trg;
while(*pTrg++ = *pSrc++)
;
cout << trg << endl; // Hello
return 0;
}
Pokazano trzy sposoby kopiowania ł
ańcucha znakó w.
Łą czenie
#include <iostream.h>
#include <string.h>
char *pSrc = "Hello",
buf[100];
int main(void)
{
strcat(strcpy(buf, pSrc), "!");
cout << buf << endl; // Hello!
char *pBuf = buf;
strcpy(pBuf, pSrc);
while(*pBuf++)
;
char *pSrc = "!";
while(pBuf++[-1] = *pSrc++)
;
cout << buf << endl; // Hello!
return 0;
}
Pokazano dwa sposoby ł
ą czenia ł
ańcuchó w.
Porównanie
#include <iostream.h>
#include <string.h>
char one[100],
two[100];
int main(void)
{
cin >> one >> two;
33
cout << one;
switch(strcmp(one, two)) {
case +1:
cout << " > ";
break;
case -1:
cout << " < ";
break;
default:
cout << " == ";
}
cout << two << endl;
char *pOne = one,
*pTwo = two;
cout << one;
while(*pOne == *pTwo && *pOne != 0) {
pOne++;
pTwo++;
}
if(*pOne == 0 && *pTwo == 0)
cout << " == ";
else if(*pOne > *pTwo)
cout << " > ";
else
cout << " < ";
cout << two << endl;
pOne = one;
pTwo = two;
cout << one;
while(*pOne || *pTwo) {
if(*pOne++ != *pTwo++) {
if(pOne[-1] > pTwo[-1])
cout << " > ";
else
cout << " < ";
cout << two << endl;
return 0;
}
}
cout << " == ";
cout << two << endl;
return 0;
}
Pokazano trzy sposoby poró wnywania ł
ańcuchó w wprowadzonych z klawiatury.
34
Posługiwanie się funkcjami
Funkcja jest sparametryzowanym opisem czynności. W miejscu wywołania funkcji musi być znana jej
deklaracja albo definicja. W szczegó lności oznacza to, ż e wywołanie
sum(10, 20)
funkcji sumują cej argumenty, musi być poprzedzone
albo jej definicją
int sum(int one, int two)
{
return one + two;
}
albo jej deklaracją
int sum(int one, int two);
albo włą czeniem nagłó wka zawierają cego deklarację.
Uwaga: W deklaracji funkcji moż na pominą ć dowolny zestaw identyfikatoró w parametró w. Jeśli uczyni się to w
definicji, to uniemoż liwi to odwoływanie się do argumentó w.
Parametry i argumenty
Wywołanie funkcji zaczyna się od skojarzenia jej parametró w z argumentami. Skojarzenie parametru z
argumentem odbywa się przez-wartość, co oznacza, ż e parametr jest traktowany tak, jak lokalna zmienna funkcji,
zadeklarowana tuż przed jej pierwszą instrukcją i zainicjowana wartością argumentu.
A zatem, jeśli definicją funkcji jest
int sum(int one, int two)
{
return one + two;
}
to dla wywołania
sum(10, 20)
funkcja jest traktowana tak, jakby miała postać
int sum()
{
int one = 10;
int two = 20;
return one + two;
}
35
Parametry zwykłe
Parametr funkcji jest "zwykły", jeśli nie jest wskaźnikiem ani odnośnikiem. Z parametrem zwykłym moż na
skojarzyć argument, któ ry jest takiego samego typu jak parametr, albo któ ry moż na poddać niejawnej konwersji
do typu parametru.
Zainicjowanie parametru polega na skopiowaniu argumentu. Jeśli argument jest strukturą , to kopiuje się
wszystkie jej elementy (co w przypadku duż ych struktur ma oczywiste wady!).
Po dokonaniu skojarzenia, wszelkie operacje wykonywane na parametrze dotyczą lokalnej zmiennej
zainicjowanej argumentem i nie powodują zmiany wartości skojarzonego z nim argumentu.
#include <iostream.h>
int main(void)
{
void inc(int par);
int fix = 10;
cout << fix << endl; // 10
inc(fix);
cout << fix << endl; // 10
return 0;
}
void inc(int par)
{
++par;
}
Program potwierdza, że wykonanie operacji na parametrze "zwykł
ym" nie powoduje zmiany wartoś ci
skojarzonego z nim argumentu.
Parametry wskaźnikowe
Z parametrem wskaźnikowym moż na skojarzyć argument, któ ry jest takiego samego typu jak parametr, albo
któ ry moż na poddać niejawnej konwersji do typu parametru.
Zainicjowanie parametru polega na skopiowania wskaźnika. Nie pocią ga to za sobą kopiowania zmiennej
identyfikowanej przez argument (co moż na wykorzystać w przypadku duż ych struktur!).
Po dokonaniu skojarzenia, wszelkie operacje wykonywane na parametrze dotyczą lokalnej zmiennej
zainicjowanej argumentem, ale operacje wykonywane za po średnictwem parametru (np. *par, par[i] albo par->f)
dotyczą zmiennej wskazywanej przez argument. Moż e to mieć wpływ na wartość argumentu.
#include <iostream.h>
int main(void)
{
void inc(int *ptr);
int fix = 10;
cout << fix << endl; // 10
inc(&fix);
cout << fix << endl; // 11
return 0;
}
void inc(int *ptr)
{
++*ptr;
}
36
Program potwierdza, że wykonanie operacji za poś rednictwem parametru wskaźnikowego może powodować
zmianę wartoś ci zmiennej wskazywanej przez skojarzony z nim argument.
Parametry tablicowe
Każ dy parametr tablicowy jest niejawnie zastępowany parametrem wskaźnikowym, któ ry powstaje po
zastą pieniu deklaratora vec[i] deklaratorem (* const vec).
W szczegó lności funkcja
int sum(int tab[20])
{
int sum = 0;
for(int i = 0; i < 3 ; i++)
sum += tab[i];
return sum;
}
jest niejawnie przekształcana w funkcję
int sum(int *const tab)
{
int sum = 0;
for(int i = 0; i < 3 ; i++)
sum += tab[i];
return sum;
}
Powoduje to, ż e jeśli chce się zdefiniować funkcję do sumowania tablic, nie odwołują cą się do zmiennych
globalnych, to jeden z jej argumentó w musi określać liczbę elementó w tablicy.
#include <iostream.h>
int sum(int tab[], int count)
{
int sum = 0;
for(int i = 0; i < count ; i++)
sum += tab[i];
return sum;
}
int main(void)
{
int small[] = { 10 },
large[] = { 10, 20, 30 };
cout << sum(small, 1) << endl;
cout << sum(large, 3) << endl;
return 0;
}
Parametry odnośnikowe
Z parametrem odnośnikowym moż na skojarzyć argument, któ ry jest takiego samego typu jak parametr, albo
któ ry moż na poddać niejawnej konwersji do typu parametru.
Zainicjowanie parametru polega na skopiowania odnośnika. Podobnie jak dla parametru wskaźnikowego, nie
pocią ga to za sobą kopiowania zmiennej identyfikowanej przez argument.
Po dokonaniu skojarzenia, wszelkie operacje wykonywane na parametrze dotyczą zmiennej identyfikowanej
przez argument. Moż e to powodować zmianę wartości skojarzonego z nim argumentu.
37
#include <iostream.h>
int main(void)
{
void inc(int &ref);
int fix = 10;
cout << fix << endl; // 10
inc(fix);
cout << fix << endl; // 11
return 0;
}
void inc(int &ref)
{
++ref;
}
Program potwierdza, że wykonanie operacji za poś rednictwem parametru odnoś nikowego może powodować
zmianę wartoś ci skojarzonego z nim argumentu.
Parametry funkcji main
Funkcja głó wna moż e być zadeklarowana jako bezparametrowa albo dwu-parametrowa. Jeśli jest
dwuparametrowa, to jej pierwszy parametr jest typu int i ma wartość ró wną liczbie argumentó w programu
zwiększonej o 1, a drugi jest typu char *[] i jest tablicą odnośnikó w do łańcuchó w zainicjowanych nazwą
programu oraz nazwami jego argumentó w.
#include <iostream.h>
int main(int argc, char *argv[])
{
cout << "My name is: " << argv[0] << endl;
cout << "My arguments are: " << endl;
for(int i = 1; i < argc ; i++)
cout << argv[i] << endl;
return 0;
}
Program wyprowadza swoją nazwę i argumenty; każde w osobnym wierszu.
Skojarzenia powrotne
W chwili zakończenia wykonywania funkcji rezultatowej (o typie ró ż nym od void) następuje skojarzenie jej
rezultatu z wyraż eniem występują cym w instrukcji powrotu. Odbywa się to według tych samych zasad co
skojarzenie parametru z argumentem i polega na zainicjowaniu rezultatu funkcji wyraż eniem występują cym w
instrukcji powrotu.
Uwaga: Rezultat funkcji jest zmienną . Typ rezultatu jest identyczny z typem funkcji. Nazwą rezultatu jest
wywołanie funkcji. Z punktu widzenia łą czenia operacji (np. ++*fun(1,2)+3), nazwa funkcji jest zastępowana
nazwą rezultatu.
#include <iostream.h>
#include <math.h>
double sqr(double val)
{
return val * val;
}
38
int main(void)
{
double a, b;
cin >> a >> b;
cout << sqrt(sqr(a) + sqr(b)) << endl;
return 0;
}
Wywoł
anie sqr(a) jest nazwą rezultatu o wartoś ci "kwadrat a", wywoł
anie sqr(b) jest nazwą rezultatu o wartoś ci
"kwadrat b", a wyrażenie sqrt(sqr(a) + sqr(b)) jest nazwą rezultatu o wartoś ci "pierwiastek z sumy kwadrató w a
i b".
Typ nie-odnośnikowy
Jeśli typ funkcji jest nie-odnoś nikowy, to zainicjowanie rezultatu polega na skopiowaniu zmiennej, któ rej nazwą
jest wyraż enie występują ce w instrukcji powrotu.
Jeśli typ wyraż enia nie jest identyczny z typem funkcji, to wyraż enie jest poddawane konwersji do typu rezultatu.
Zezwala się na niejawne wykonanie co najwyżej jednej konwersji standardowej i jednej definiowanej.
#include <iostream.h>
double getSqr(int par);
int main(void)
{
cout << getSqr(3) << endl; // 9
return 0;
}
double getSqr(int par)
{
return par * par; // return double(par * par);
}
Wyrażenie par * par jest nazwą zmiennej typu int zainicjowanej daną o wartoś ci 9.
Ponieważ typ rezultatu jest ró żny od typu tej zmiennej, więc zostanie zastosowana niejawna konwersja
standardowa z typu int do double.
Po zainicjowaniu rezultatu zmienną double(par * par), wywoł
anie getSqr(3) można traktować nazwę rezultatu.
dla dociekliwych
#include <iostream.h>
struct Child {
char name[20];
int age;
};
Child isa = { "Isabel", 15 };
Child getOlder(Child child, int val);
void show(Child &child);
int main(void)
{
show(isa); // Isabel is 15
show(getOlder(isa, 2)); // Isabel is 17
show(isa); // Isabel is 15
39
return 0;
}
Child getOlder(Child child, int val)
{
child.age += val;
return child;
}
void show(Child &child)
{
cout << child.name << " is " <<
child.age << endl;
}
Wywoł
anie funkcji getOlder(isa, 2) powoduje skopiowanie struktury isa do lokalnej zmiennej funkcji getOlder.
Operacja child.age += val jest wykonywana na tej zmiennej lokalnej.
Wywoł
anie getOlder(isa, 2) jest nazwą zmiennej, do któ rej skopiowano tę zmienną lokalną .
Typ odnośnikowy
Jeśli typ funkcji jest odnoś nikowy, to zainicjowanie rezultatu polega na skopiowaniu odnośnika do tej zmiennej,
któ rej nazwą jest wyraż enie występują ce w instrukcji powrotu. A zatem wywołanie funkcji jest nazwą tej
zmiennej.
#include <iostream.h>
int &refVal(void)
{
static int val = -1;
return ++val;
}
int main(void)
{
cout << refVal() << endl; // 0
cout << refVal() << endl; // 1
refVal() = 5;
cout << refVal() << endl; // 6
++refVal();
cout << refVal() << endl; // 9
return 0;
}
Wywoł
anie refVal() jest nazwą statycznej zmiennej val. A zatem każda operacja wykonana na refVal() dotyczy
tej wł
aś nie zmiennej.
dla dociekliwych
Jeśli typ wyraż enia w instrukcji powrotu nie jest zgodny z typem funkcji, to typ funkcji musi być ustalony
(const), a ponadto musi istnieć niejawna konwersja z typu wyraż enia do typu zgodnego z typem funkcji.
#include <iostream.h>
const int &refVal(double par)
{
return par * par; // return double(par * par);
}
40
int main(void)
{
cout << refVal(3) << endl;
return 0;
}
Uwaga: Wyraż enie zawarte w instrukcji powrotu moż e tylko wówczas identyfikować zmienną lokalną funkcji,
gdy typ funkcji jest ustalony.
#include <iostream.h>
int &getInc(int par);
int main(void)
{
cout << getInc(3) << endl; // b
łąd
return 0;
}
int &getInc(int par)
{
return ++par;
}
Wywoł
anie getInc(3) jest nazwą lokalnej zmiennej par. Ponieważ po powrocie z funkcji getInc zmienna par już
nie istnieje, więc odwoł
anie się do niej jest zabronione. W Visual C++ program wyprowadza liczbę 4.
Program można poprawić, nadają c mu postać
#include <iostream.h>
const int &getInc(int par);
int main(void)
{
cout << getInc(3) << endl; // 4
return 0;
}
const int &getInc(int par)
{
return ++par;
}
Deklarowanie funkcji
Deklaracja funkcji podaje jej identyfikator oraz określa typ funkcji oraz typy jej parametró w. Jeśli ponadto
podaje ciało funkcji, to jest jej definicją .
Funkcje bezrezultatowe
Funkcja, któ rej typem jest void, jest funkcją bezrezultatową . Jej wywołanie kończy się w chwili wykonania
instrukcji powrotu nie zawierają cej wyraż enia, albo w chwili zakończenia wykonywania jej ciała.
void outDiv(int a, int b)
{
if(b == 0)
return;
cout << a / b;
}
41
Funkcje otwarte i zamknię te
Funkcja zadeklarowana ze specyfikatorem inline jest realizowana jako otwarta. W odró ż nieniu od funkcji
zamknię tej, ciało funkcji otwartej wstawia się w każ dym miejscu jej wywołania. Powoduje to przyspieszenie
wykonania programu, ale niekiedy wydłuż a jego kod wynikowy.
Uwaga: Wystą pienie specyfikatora inline nie ma wpływu na skutek wykonania programu. Jeśli funkcja otwarta
zostanie uznana za zbyt skomplikowaną , to moż e być zrealizowana jako zamknięta.
inline in sum(int a int b)
{
return a + b;
}
Funkcje przecią żone
Jeśli w pewnym zakresie są widoczne deklaracje dwó ch lub większej liczby funkcji o takiej samej nazwie, ale
ró ż nią cych się typami parametró w, to ogó ł takich funkcji stanowi wieloaspektową funkcję przecią żoną .
W miejscu wywołania funkcji przecią ż onej wywołuje się ten z jej aspektó w, do któ rego parametró w najlepiej
pasują podane argumenty. Ma to miejsce wó wczas, gdy istnieje taki aspekt, ż e do każ dego z jego parametró w
podany argument pasuje nie gorzej niż do pozostałych, ale istnieje taki parametr, do któ rego jeden z argumentó w
pasuje lepiej niż do pozostałych.
Uwaga: Jeśli argument nie pasuje do parametru dokładnie, to moż e być poddany konwersji dopasowują cej, ale
im konwersja ta jest bardziej złoż ona, tym dopasowanie pierwotnego argumentu uznaje się za gorsze.
#include <iostream.h>
void out(char par);
void out(int par);
int main(void)
{
out(’a’);
out(2);
out(2.0); // b
łąd (niejednoznaczność)
return 0;
}
void out(char par)
{
cout << par << endl;
}
void out(int par)
{
cout << par << endl;
}
Argument ’a’ typu char najlepiej pasuje do parametru typu char, a argument 2 typu int najlepiej pasuje do
parametru typu int.
Argument 2.0 typu double pasuje ró wnie dobrze do parametru typu char jak i do parametru typu int. Ponieważ
do żadnego z nich nie pasuje najlepiej, więc wywoł
anie out(2.0) jest bł
ędne.
Gdyby z programu usunięto dowolną z funkcji out, to wszystkie odwoł
ania do out był
yby poprawne.
42
Argumenty domniemane
W deklaracji parametru funkcji moż e wystą pić inicjator wyraż eniowy określają cy domniemaną wartość
argumentu kojarzonego z tym parametrem.
int sum(int a, int b =0, int c =0);
Jeśli pewien parametr wyposaż ono w argument domniemany, to każ dy z następnych parametró w takż e musi być
wyposaż ony w argument domniemany.
int sum(int a, int b =0, int c); // b
łąd
Z każ dym parametrem nie wyposaż onym w argument domniemany musi być skojarzony jawny argument.
Końcowy zestaw argumentó w, dla któ rych podano domniemania, moż na pominą ć . W ich miejscu zostaną uż yte
argumenty domniemane.
#include <iostream.h>
int sum(int a, int b, int c =0, int d =0);
int main(void)
{
cout << sum(1, 2, 3) << endl; // 6
cout << sum(1, 2) << endl; // 3
cout << sum(1) << endl; // b
łąd
return 0;
}
int sum(int a, int b, int c, int d)
{
return a + b + c + d;
}
dla dociekliwych
Wyraż enie określają ce wartość argumentu domniemanego nie musi być wyraż eniem stałym. W takim wypadku
jest opracowywane w kontekście jego deklaracji, a nie w kontekście jego uż ycia.
#include <iostream.h>
int p = 20;
int sub(int a =p*p)
{
return a;
}
int main(void)
{
int p = 10;
cout << sub() << endl; // 400
::p = 10;
cout << sub() << endl; // 100
return 0;
}
Wywołania rekurencyjne
43
Wywołanie funkcji jest rekurencyjne, jeśli nastą pi przed powrotem z jej poprzedniego wywołania. Uż ycie
rekurencji moż e uczynić program czytelniejszym, ale w wielu wypadkach powoduje zwiększenie rozmiaru
pamięci operacyjnej niezbędnej do jego wykonania.
#include <iostream.h>
#include <limits.h>
#include <stdlib.h>
int sqrt(int par, int min =0, int max =INT_MAX)
{
int mid = (min + max) / 2;
if(mid == min)
return mid;
if(par < double(mid) * mid)
return sqrt(par, min, mid);
else
return sqrt(par, mid, max);
}
int main(void)
{
int val;
cin >> val;
val = abs(val);
cout << "sqrt(" << val << ") = " <<
sqrt(val) << endl;
return 0;
}
Funkcja sqrt dostarcza pierwiastek z jej nieujemnego argumentu. Nieobowią zkowe argumenty dodatkowe
okreś lają przedział
, w któ rym znajduje się pierwiastek.
Definiowanie funkcji
Zdefiniowanie funkcji polega na podaniu jej ciała. Dobry styl programowania poznaje się po uż yciu wielu
kró tkich, a nie małej liczby długich funkcji.
Tak dalece jak jest to moż liwe, należ y posługiwać się funkcjami bibliotecznymi. Ilustruje to następują cy
program, któ ry napisano w dwó ch wersjach: z uż yciem i bez uż ycia funkcji bibliotecznych.
#include <iostream.h>
#include <iomanip.h>
#include <string.h>
const int Size = 100;
int main(void)
{
char srcOne[Size],
srcTwo[Size];
cin >> setw(Size) >> srcOne >>
setw(Size) >> srcTwo;
char trg[2*Size-1];
strcat(strcpy(trg, srcOne), " ");
int len = strlen(strcat(trg, srcTwo));
cout << trg << endl << len << endl;
return 0;
}
44
Program wprowadza dwa ł
ańcuchy, ł
ą czy je oddzielają c spacją , a następnie wyprowadza: ł
ańuch docelowy,
dł
ugoś ć ł
ańcucha docelowego i wynik poró wnania ł
ańcuchó w źró dł
owych.
#include <iostream.h>
const int Size = 100;
int strLen(char *ptr);
char *strCpy(char *pTrg, char *pSrc);
char *strCat(char *pTrg, char *pSrc);
int strCmp(char *pOne, char *pTwo);
int main(void)
{
char srcOne[Size],
srcTwo[Size];
cin >> setw(Size) >> srcOne >>
setw(Size) >> srcTwo;
char trg[2*Size-1];
strCat(strCpy(trg, srcOne), " ");
int len = strLen(strCat(trg, srcTwo));
cout << trg << endl << len << endl;
cout << srcOne << ’ ’;
char chr = ’=’;
switch(strCmp(srcOne, srcTwo)) {
case +1:
chr = ’>’;
break;
case -1:
chr = ’<’;
break;
}
cout << chr << ’ ’ << srcTwo << endl;
return 0;
}
int strLen(char *ptr)
{
int len = 0;
while(*ptr++)
len++;
return len;
}
char *strCpy(char *pTrg, char *pSrc)
{
char *pTrg2 = pTrg;
while(*pTrg++ = *pSrc++);
return pTrg2;
}
char *strCat(char *pTrg, char *pSrc)
{
char *pTrg2 = pTrg;
strCpy(pTrg += strLen(pTrg), pSrc);
return pTrg2;
}
int strCmp(char *pOne, char *pTwo)
{
while(*pOne || *pTwo)
if(*pOne++ != *pTwo++)
if(pOne[-1] > pTwo[-1])
return +1;
else
return -1;
45
return 0;
}
46
Zarzą dzanie pamię cią
Wykonanie programu polega na przepływie sterowania przez jego deklaracje, definicje i instrukcje.
W pierwszej kolejności sterowanie przepływa przez wszystkie deklaracje globalne (takie, któ re nie wchodzą w
skład innych deklaracji). Następnie jest wyszukiwana funkcja główna i sterowanie przepływa przez zawarte w
niej instrukcje. Przepływ sterowania kończy się po powrocie z wywołania funkcji exit albo po wykonaniu
instrukcji powrotu z funkcji głó wnej.
#include <iostream.h>
#include <stdlib.h>
int main(void)
{
int num;
cin >> num;
if(num != 0) {
cout << num << endl;
exit(num);
}
return 0;
}
W zależnoś ci od tego, jaką wartoś ć ma wprowadzona liczba, program kończy się po napotkaniu instrukcji
powrotu albo po wywoł
aniu funkcji exit.
Zmienne statyczne
Jeśli sterowanie przepłynie przez definicję zmiennej globalnej, albo przez definicję zmiennej lokalnej
zadeklarowanej ze specyfikatorem static, to zostanie utworzona zmienna statyczna. Tuż przed zakończeniem
wykonywania programu wszystkie zmienne statyczne zostaną zniszczone. Odbędzie się to w kolejności
odwrotnej do ich tworzenia.
Uwaga: Zmienna statyczna jest tworzona w obszarze statycznym. Inicjator zmiennej statycznej jest brany pod
uwagę tylko podczas pierwszego opracowania jej deklaracji.
#include <iostream.h>
int main(void)
{
void fun(int par);
fun(10);
static int one = 1;
fun(20);
return 0;
}
int two = 2;
void fun(int par)
{
static int loc = par;
cout << loc << ’ ’ << par << endl;
loc++;
47
}
Zmienne statyczne one, two, loc zostaną utworzone w kolejnoś ci: two, loc, one, a zostaną zniszczone w
kolejnoś ci: one, loc, two.
Program wyprowadzi dwie pary liczb: 10 10 i 11 20.
Zmienne automatyczne
Jeśli sterowanie przepłynie przez definicję zmiennej lokalnej, nie zadeklarowanej ze specyfikatorem static albo
extern, to zostanie utworzona zmienna automatyczna. Jawny albo niejawny inicjator zmiennej automatycznej
będzie brany pod uwagę podczas każdego opracowania tej definicji.
Uwaga: Zmienne automatyczne tworzy się na stosie. Stos jest obszarem pamięci, w któ rym moż na tworzyć
zmienne, ale takim, ż e moż na je niszczyć tylko w kolejności odwrotnej do ich tworzenia.
void sub(void)
{
int num; // int num = int();
cout << num; // b
łąd
// ...
}
Zmienną automatyczną num wyposażono w niejawny inicjator = int() dostarczają cy wartoś ć nieokreś loną .
Zmienna automatyczna zostanie zniszczona tuż przed zakończeniem wykonywania bloku (wnętrza instrukcji
grupują cej), w któ rym ją zadeklarowano. Jeśli w bloku zadeklarowano więcej niż jedną zmienną automatyczną ,
to ich niszczenie odbędzie się w kolejności odwrotnej do ich tworzenia, ale przed przystą pieniem do niszczenia
zmiennych statycznych.
#include <iostream.h>
int main(void)
{
int cnt = 2;
while(cnt > 0) {
int val = cnt--;
cout << val << endl;
}
return 0;
}
int one = 10;
Najpierw zostanie utworzona zmienna statyczna one, a po niej zmienna automatyczna cnt. Następnie zostanie
utworzona i zniszczona zmienna automatyczna val zainicjowana wartoś cią 2, a po tym zostanie utworzona i
zniszczona zmienna automatyczna val zainicjowana wartoś cią 1. Tuż przed wykonaniem instrukcji powrotu
zostanie zniszczona zmienna cnt, a po niej zmienna one.
Zmienne kontrolowane
Zmienna kontrolowana powstaje na skutek wykonania operacji new, a jest niszczona po jawnym wykonaniu
operacji delete. Zmienne kontrolowane są tworzone na stercie. Sterta jest obszarem pamięci, do któ rego moż na
dokładać zmienne, a następnie usuwać je w dowolnej kolejności.
48
Jeśli wykonanie operacji new jest niemoż liwe, ponieważ wyczerpano obszar sterty, to rezultatem operacji
przydzielenia pamięci jest wskaźnik pusty (o wartości reprezentowanej przez 0).
Uwaga: Programiści rzadko badają rezultat operacji new, bo są z natury optymistami.
int *ptr = new char [10000000];
if(ptr == 0) {
cout << "No memory" << endl;
exit(-1);
}
Zmienne skalarne
Wykonanie operacji
new Type
w któ rej Type jest opisem typu skalarnego (tj. nie-tablicowego!), powoduje utworzenie na stercie zmiennej typu
Type. Rezultatem operacji jest wskaźnik zainicjowany wskazaniem utworzonej zmiennej.
Wykonanie operacji
delete ptr
w któ rej ptr wskazuje zmienną utworzoną na stercie, powoduje zniszczenie tej zmiennej.
#include <iostream.h>
int main(void)
{
int *pOne = new int;
double &two = *new double;
two = 2.8;
*pOne = (int)two;
cout << *pOne << endl; // 2
delete pOne;
delete &two;
return 0;
}
Najpierw zostanie utworzona zmienna typu int, a następnie zmienna typu double. Najpierw zostanie zniszczona
zmienna typu int, a następnie zmienna typu double.
Zmienne tablicowe
Wykonanie operacji
new Type
w któ rej Type jest opisem typu tablicowego (np. int [12]), powoduje utworzenie na stercie zmiennej typu Type.
Rezultatem operacji jest wskaźnik zainicjowany wskazaniem zerowego elementu utworzonej tablicy.
Jeśli elementami tablicy są obiekty, to do ich zainicjowania jest niejawnie stosowany konstruktor domyślny.
Uwaga: Wyraż enie określają ce liczbę elementó w tablicy nie musi być wyraż eniem stałym.
Wykonanie operacji
delete [] ptr
49
w któ rej ptr wskazuje zerowy element tablicy utworzonej na stercie, powoduje zniszczenie tej tablicy.
#include <iostream.h>
#include <string.h>
int main(void)
{
char *ptr = new char [100];
cin >> ptr;
char &vec = *new char [strlen(ptr) + 1];
cout << strcpy(&vec, ptr) << endl;
delete [] ptr;
delete [] &vec;
return 0;
}
Program tworzy na stercie 100-elementową tablicę znakową i wprowadza do niej cią g znakó w. Następnie tworzy
na stercie najmniejszą tablicę, w któ rej można pomieś cić wprowadzony cią g znakó w oraz tworzy na stosie
odnoś nik vec identyfikują cy zerowy element tej tablicy.
Przed zakończeniem wykonywania program niszczy obie tablice, w kolejnoś ci ich utworzenia.
Ostrzeżenie
W ż adnym wypadku nie wolno zmiennej utworzonej za pomocą operacji new dla zmiennych skalarnych niszczyć
za pomocą operacji delete dla zmiennych tablicowych, a zmiennej utworzonej za pomocą operacji new dla
zmiennych tablicowych niszczyć za pomocą operacji delete dla zmiennych skalarnych.
Nie wolno takż e uż ywać operacji delete ze wskaźnikiem ptr identyfikują cym co innego niż zmienna skalarna
albo zerowy element tablicy utworzonej za pomocą operacji new, ani przyjmować , ż e po wykonaniu operacji
delete wskaźnik ptr ma wartość określoną .
Uwaga: W celu uniknięcia trudnych do wykrycia błędó w, zaleca się (o ile to moż liwe) zerowanie wskaźnika ptr
bezpośrednio po uż yciu go w operacji delete.
#include <iostream.h>
int main(void)
{
int *ptr = new int [5];
delete [] (ptr + 2); // b
łąd
int &vec = *new int [5];
delete &vec; // b
łąd
int &ref = (*new int) = 3;
delete &ref;
cout << ref << endl; // b
łąd
return 0;
}
Mimo iż program jest poprawny skł
adniowo, zawiera 3 poważne bł
ędy logiczne. Wykonany w ś rodowisku
Visual C++, program ten zał
amuje system zarzą dzania stertą .
50
Widoczność deklaracji
Identyfikatorem zmiennej, funkcji i typu moż na posługiwać się tylko w miejscu, w któ rym jest widoczna jego
deklaracja.
Zaleca się, aby w tym samym zakresie, identyfikator uż yty do zadeklarowania zmiennej, funkcji albo typu nie
został uż yty do zadeklarowania innej zmiennej, funkcji albo typu.
Uwaga: Podano zalecenie, a nie zakaz, ponieważ w tym samym zakresie mogą wystą pić , nie kolidują ce za sobą ,
deklaracje funkcji i typu.
void id(int id)
{
struct id {
};
extern void id(id id);
int id = 10; // b
łąd
}
Z każ dą deklaracja jest zwią zany jej zakres i zasię g. Jeśli w pewnym module zdefiniowano identyfikator o
zasięgu globalnym, a w innym zadeklarowano go ze specyfikatorem extern, to oba dotyczą tej samej zmiennej,
funkcji albo typu.
plik Main.cpp
#include <iostream.h>
int fix = 10; // definicja
int main(void)
{
extern void fun(void); // deklaracja
fun();
return 0;
}
plik One.cpp
#include <iostream.h>
void fun() // definicja
{
extern int fix; // deklaracja
cout << fix << endl; // 10
}
Gdyby pominięto wszystkie specyfikatory extern, to program stał
by się statycznie poprawny, ale dynamicznie
bł
ędny. Bł
ą d polegałby na użyciu wartoś ci zmiennej, któ rej nie zainicjowano.
Deklaracje lokalne
Zakresem deklaracji identyfikatora zadeklarowanego w bloku jest obszar programu od punktu zadeklarowania
do końca bloku. Zasięgiem deklaracji jest ta część zakresu, któ ra nie jest zakresem innej deklaracji takiego
samego identyfikatora.
51
#include <iostream.h>
int main(void)
{
int num = 10;
cout << num << endl; // 10
{
cout << num << endl; // 10
int num = 20;
cout << num << endl; // 20
}
cout << num << endl; // 10
return 0;
}
Zakresem deklaracji pierwszej zmiennej num jest obszar zaczynają cy się od = 10 i kończą cy na klamrze
zamykają cej funkcję main.
Zakresem deklaracji drugiej zmiennej num jest obszar zaczynają cy się od = 20 i kończą cy na klamrze
zamykają cej blok wewnętrzny.
Zasięgiem deklaracji pierwszej zmiennej num jest zakres deklaracji pierwszej zmiennej num, pomniejszony o
zakres deklaracji drugiej zmiennej num.
Deklaracje globalne
Zakresem deklaracji identyfikatora zadeklarowanego w module (tj. poza blokiem), jest obszar programu od
punktu zadeklarowania do końca modułu. Zasięgiem deklaracji jest ta część zakresu, któ ra nie jest zakresem
innej deklaracji takiego samego identyfikatora.
Uwaga: Modułem jest zawartość pliku *.cpp projektu, po zastosowaniu uż ytych w nim dyrektyw (#include, #if,
#endif, itp.).
#include <iostream.h>
int num = 10;
int main(void)
{
cout << num << endl; // 10
{
cout << num << endl; // 10
int num = 20;
cout << num << endl; // 20
}
cout << num << endl; // 10
return 0;
}
int num2 = num;
Zasięg deklaracji pierwszego identyfikatora num obejmuje m.in. deklarację występują cą po funkcji main.
Deklaracje i definicje
52
Jeśli deklaracja globalna zawiera specyfikator static, to jest widoczna tylko w jej module. Jeśli deklaracja
globalna jest definicją , ale nie zawiera specyfikatora static, to jest widoczna w tych obszarach pozostałych
modułó w programu, w któ rych jest widoczna zgodna z nią deklaracja ze specyfikatorem extern bez inicjatora,
nie dotyczą ca deklaracji globalnej ze specyfikatorem static.
Uwaga: Globalne zmienne ustalone są domyślnie wyposaż one w specyfikator static. Specyfikator extern
występują cy w deklaracji funkcji moż na pominą ć .
plik Main.cpp
#include <iostream.h>
int main(void)
{
int fun(void); // pomini
ęto extern
cout << fun() << endl; // 10
extern int num;
cout << num << endl; // 20
return 0;
}
plik One.cpp
static int num = 10;
int fun(void)
{
extern int num; // zb
ędne
return num;
}
plik Two.cpp
int num = 20;
Deklaracje typów
Globalna deklaracja typu, na przykład
struct Child;
nie wystarczy do tego, aby moż na było nawią zać do definicji tego typu podanej w innym module.
W odró ż nieniu od definicji zmiennej i funkcji, któ ra w zbiorze modułó w programu moż e wystą pić tylko jeden
raz, definicja struktury musi być powtó rzona w każdym z odwołują cych się do niej modułó w.
plik Main.cpp
#include <iostream.h>
struct Child {
char name[20];
int age;
};
int main(void)
{
Child getIsa(void);
Child isa = getIsa();
53
cout << isa.name << " is " <<
isa.age << endl;
return 0;
}
plik Isa.cpp
struct Child {
char name[20];
int age;
};
Child isa = { "Isabel", 15 };
Child getIsa(void)
{
return isa;
}
albo lepiej i bezpieczniej
plik child.h
struct Child {
char name[20];
int age;
};
plik Main.cpp
#include <iostream.h>
#include "child.h"
int main(void)
{
Child getIsa(void);
Child isa = getIsa();
cout << isa.name << " is " <<
isa.age << endl;
return 0;
}
plik Isa.cpp
#include "child.h"
Child isa = { "Isabel", 15 };
Child getIsa(void)
{
return isa;
}
54
Studia programowe
Przedstawiono dwa rozwią zania następują cego problemu
Napisać program, któ ry wprowadza z pliku sekwencję danych arytmetycznych, a następnie wyprowadza
ich ś rednie odchylenie standardowe: pierwiastek z sumy kwadrató w ró żnic dana-ś rednia, podzielony
liczbę danych.
W szczegó lności, jeśli w pliku Data.txt umieści się liczby 6 9 12, a jako argument programu poda Data.txt
(polecenie Project / Settings // Debug), to nastą pi wyprowadzenie liczby 1.41421.
Struktura tablicowa
#include <iostream.h>
#include <fstream.h>
#include <math.h>
int readData(char *fileName, double *&pData);
double getAverage(double *pData, int count);
double getResult(double *pData, int count, double average);
void freeMemory(double *pData);
int main(int noOfArgs, char *pArg[])
{
if(noOfArgs != 2) {
cout << "Usage is: " << pArg[0] <<
" fileName" << endl;
return -1;
}
double *pData;
char *fileName = pArg[1];
int count = readData(fileName, pData);
if(count) {
double average = getAverage(pData, count);
double result = getResult(pData, count, average);
cout << "Result = " << result << endl;
} else
cout << "Error!" << endl;
return 0;
}
int readData(char *fileName, double *&pData)
{
const int start = 200;
ifstream inp;
inp.open(fileName, ios::in | ios::nocreate);
int count = 0;
if(inp.is_open()) {
pData = new double [start];
int len = start;
double tmp;
while(tmp = 0, inp >> tmp, tmp) {
if(count == len) {
double *ptr = new double [len *= 2];
for(int j = 0; j < len /2 ; j++)
ptr[j] = pData[j];
delete [] pData;
pData = ptr;
}
pData[count++] = tmp;
}
}
return count;
}
55
double getAverage(double *pData, int count)
{
double sum = 0;
for(int i = 0; i < count ; i++)
sum += pData[i];
return sum / count;
}
double getResult(double *pData, int count, double average)
{
double sumSqr = 0;
for(int i = 0; i < count ; i++) {
double dif = pData[i] - average;
sumSqr += dif * dif;
}
return sqrt(sumSqr) / count;
}
void freeMemory(double *pData)
{
delete [] pData;
}
Struktura listowa
#include <iostream.h>
#include <fstream.h>
#include <math.h>
struct Item {
Item *pNext;
double value;
};
struct List {
Item *pFirst;
int count;
};
List list = { 0 };
int readData(char *fileName, List &list);
double getAverage(List &list);
double getResult(List &list, double average);
void freeMemory(List &list);
int main(int noOfArgs, char *pArg[])
{
if(noOfArgs != 2) {
cout << "Usage is: " << pArg[0] <<
" fileName" << endl;
return -1;
}
char *fileName = pArg[1];
int count = readData(fileName, list);
if(count) {
double average = getAverage(list);
double result = getResult(list, average);
cout << "Result = " << result << endl;
} else
cout << "Error!" << endl;
freeMemory(list);
return 0;
}
int readData(char *fileName, List &list)
{
ifstream inp;
inp.open(fileName, ios::in | ios::nocreate);
56
int count = 0;
if(inp.is_open()) {
double tmp;
while(tmp = 0, inp >> tmp, tmp) {
Item *pItem = new Item;
pItem->pNext = list.pFirst;
pItem->value = tmp;
list.pFirst = pItem;
count++;
}
}
return list.count = count;
}
double getAverage(List &list)
{
double sum = 0;
Item *pItem = list.pFirst;
while(pItem) {
sum += pItem->value;
pItem = pItem->pNext;
}
return sum / list.count;
}
double getResult(List &list, double average)
{
double sumSqr = 0;
Item *pItem = list.pFirst;
while(pItem) {
double dif = pItem->value - average;
sumSqr += dif * dif;
pItem = pItem->pNext;
}
return sqrt(sumSqr) / list.count;
}
void freeMemory(List &list)
{
Item *pItem = list.pFirst, *pTmp;
while(pItem) {
pTmp = pItem->pNext;
delete pItem;
pItem = pTmp;
}
}
57
Dodatek A
Priorytety operatorów
Operatory wyszczegó lniono w kolejnoœ ci malej¹cego priorytetu.
Wi¹zanie Operator
prawe
::
lewe
Type::
lewe
[] . -> () Type()
lewe
++ -- (nastêpnikowe)
prawe
++ -- (poprzednikowe)
prawe
sizeof + - ~ ! & * new delete (Type) throw
lewe
.* ->*
lewe
* / %
lewe
+ -
lewe
<< >>
lewe
< <= > >=
lewe
== !=
lewe
&
lewe
^
lewe
|
lewe
&&
lewe
||
prawe
?:
prawe
= *= /= %= += -= <<= >>= &= ^= |=
lewe
,
l-nazwą zmiennej (por. Dodatek B) jest tylko: operacja przypisania (np. a+=b), przedrostkowego zwię kszenia
(np. ++a), przedrostkowego zmniejszenia (np. --a), indeksowania (np. ptr[i]), wyłuskania (np. *ptr), wyboru
(np. str.f i ptr->f), warunku któ rego dwa ostatnie argumenty są l-nazwami (np. a>0?a:b), konwersji do typu
odnośnikowego (np. (int &)a) oraz globalności (np. ::) i zakresu (np. Child::name).
58
Dodatek B
Opracowywanie wyrażeń
Wyraż enia są zapisami operacji. O kolejności wykonywania operacji decyduje sposó b uż ycia nawiasó w oraz
uwzględnienie priorytetów i wią zań operatoró w (por. Dodatek A).
Jeśli kilka operatoró w zapisano spójnie (tj. bez odstępó w), wó wczas za pierwszy uznaje się najdłuższy. A
zatem: ponieważ w C++ istnieją operatory + i ++, ale nie istnieje operator +++, więc wyraż enie
a +++ b
jest traktowane jak
(a++) + b // a nie jak: a + (++b)
Priorytety
Ponieważ w C++ priorytet mnoż enia jest wyższy niż priorytet dodawania, więc wyraż enie
a + b * c
jest traktowane jak
a + (b * c) // a nie jak: (a + b) * c
Podobnie, ponieważ w C++ priorytet następnikowej operacji zwiększenia (++) jest wyż szy niż priorytet operacji
wyłuskania (*), więc wyraż enie
*ptr++
jest traktowane jak
*(ptr++) // a nie jak: (*ptr)++
Wią zania
Ponieważ w C++ priorytet odejmowania (-) jest ró wny priorytetowi dodawania (+), więc jeśli pewnego
podwyraż enia dotyczą oba takie operatory, to odwołanie się do priorytetó w nie wystarcza i trzeba odwołać się do
wią zań.
Ponieważ w C++ wią zanie operacji odejmowania i dodawania jest lewe, więc wyraż enia
a - b + c
cout << a << b
są traktowane jak
(a - b) + c // a nie jak a - (b + c)
59
(cout << a) << b // a nie jak: cout << (a << b)
(środkowe podwyraż enia dowią zano do lewej).
Dla poró wnania, ponieważ wią zanie operacji przypisania jest prawe, więc wyraż enie
a = b = c
jest traktowane jak
a = ( b = c) // a nie jak: (a = b) = c
Kolejność
Kolejność opracowywania argumentó w operacji jest nieokreś lona. Dotyczy to zaró wno argumentó w wywołania
funkcji, jak i argumentó w operacji dwuargumentowych, takich jak przypisanie.
Dlatego zaleca się, aby w wyraż eniu, w któ rym następuje zmiana wartości zmiennej, nie odwoływano się
(dodatkowo!) do tej zmiennej.
fun(cout << 100, cout << 200);
int tab[4] = { 10, 20, 30 },
pos = 1;
tab[pos] = ++pos;
Nie wiadomo, czy przed wykonaniem ciał
a funkcji fun zostanie wyprowadzona liczba 100 czy 200. W Visual
C++ zostanie wyprowadzona liczba 200.
Nie wiadomo, czy przypisanie dotyczy elementu tab[1] czy elementu tab[2]. W Visual C++ dotyczy ono tab[2].
Promocja
Niektó re operacje są wykonywane dopiero po promocji argumentu. Dotyczy to w szczegó lności zmiennych typu
char (poddawanych promocji do typu int).
char chr = ’a’;
char &ref1 = chr;
char &ref2 = +chr; // b
łąd
char &ref3 = ’a’; // b
łąd
Typ wspólny
Jeśli argumenty operacji są ró ż nych typó w, to wykonuje się ją w ich typie wspólnym. W szczegó lności typem
wspó lnym dla char i int jest int, a typem wspó lnym dla double i int jest double.
Uwaga: Jeśli wyraż enie jest pewnego typu, to nie oznacza to, ż e wszystkie jego operacje wykonuje się w tym
typie.
#include <iostream.h>
#include <limits.h>
int main(void)
{
int max = INT_MAX;
cout << max * max << endl; // 1 (sic!)
cout << 0.0 + max * max << endl; // 1 (sic!)
cout << double(max) * max << endl; // ok. 4.6e18
60
return 0;
}
Mimo iż typem wyrażenia zawierają cego liczbę 0.0 jest double, iloczyn max * max jest obliczany w typie int.
Punkty charakterystyczne
Punktem charakterystycznym jest miejsce w programie, w któ rym realizuje się wszystkie "zaległe" skutki
uboczne, takie jak operacje wej ścia-wyjścia i przypisania.
Punkt charakterystyczny występuje m.in. po każ dym kompletnym wyraż eniu, przed każ dym średnikiem, przed
pierwszą instrukcją funkcji oraz przed operatorami koniunkcji i dysjunkcji.
Programy zależ ne od położ enia punktu charakterystycznego należ y konstruować ze szczegó lną ostroż nością .
int fix = 10;
++fix = fix;
cout << fix;
Ponieważ operacja zwiększenia (++) może być zrealizowana dopiero w punkcie charakterystycznym, więc nie
wiadomo, czy zostanie wyprowadzona liczba 10 czy 11. W Visual C++ zostanie wyprowadzona liczba 11.
Nazwy
Każ de wyraż enie i podwyraż enie (w szczegó lności zapis operacji), moż na rozpatrywać jako nazwę pomocniczej
zmiennej tymczasowej. Podczas opracowywania wyraż enia, każ dą z operacji zastępuje się nazwą jej rezultatu.
Uwaga: Pomocniczą zmienną tymczasową niszczy się bezpośrednio po opracowaniu kompletnego wyraż enia,
któ rego opracowania wymagało utworzenia tej zmiennej.
W szczegó lności, jeśli przyją ć , ż e zmiennymi tymczasowymi są 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 zostaną zniszczone w chwili, gdy sterowanie "przepłynie przez średnik".
l-nazwy
Przyjmuje się z definicji, ż e l-nazwą jest tylko: identyfikator zmiennej nie-ustalonej, rezultat funkcji o typie
odnośnikowym oraz rezultat operacji wymienionych w Dodatku A. Nie jest l-nazwą literał, ani wskaźnik
powstały z niejawnego przekształcenia nazwy tablicy.
Posługują c się taką definicją moż na podać następują ce wymagania
1) Odnośnik do zmiennej nie-ustalonej moż e być zainicjowany tylko takim wyraż eniem, któ re jest l-nazwą
zmiennej.
np.
const int fix1 = 10;
int &fix2 = 20; // b
łąd
2) Argumentem operacji zwiększenia (++), zmniejszenia (--), wskazywania (&) i wyboru (. i ->) moż e być tylko
takie wyraż enie, któ re jest l-nazwą zmiennej.
61
np.
int fix = 10;
++(int)fix; // b
łąd
fix++++; // b
łąd
int *ptr = &20; // b
łąd
3) Lewym argumentem przypisania (=, += itp.) moż e być tylko takie wyraż enie, któ re jest l-nazwą zmiennej.
np.
int fix = 10;
fix++ = 20; // b
łąd
int tab[] = { 10 };
tab = 20; // b
łąd
Uwaga: Niepoprawność operacji fix++++ wynika stą d, ż e fix++ nie jest l-nazwą , a więc nie moż e być
argumentem ponownej operacji zwiększenia.
62
Dodatek C
Konwersje standardowe
Konwersją standardową , jest taka predefiniowana konwersja, któ ra moż e być wstawiona do programu
niejawnie.
Konwersjami standardowymi są m.in.:
0) Przekształcenie promocyjne (np. zmiennej typu char w zmienna typu int).
1) Przekształcenie zmiennej arytmetycznej albo wskaźnika w orzecznik (np. zmiennej typu int w zmienną typu
bool).
2) Przekształcenie zmiennej arytmetycznej w zmienną arytmetyczną innego typu (np. zmiennej typu double w
zmienną typu int).
3) Przekształcenie nazwy tablicy na wskaźnik do jej zerowego elementu.
4) Przekształcenie nazwy zmiennej na odnośnik do tej zmiennej.
5) Przekształcenie wskaźnika do obiektu na wskaźnik do jego podobiektu.
6) Przekształcenie odnośnika do obiektu na odnośnik do jego podobiektu.
7) Przekształcenie wskaźnika do zmiennej na wskaźnik lokalizują cy tę zmienną .
Nie są nimi m.in.
1) Przekształcenie wskaźnika do elementu tablicy na wskaźnik do tej tablicy.
2) Przekształcenie wskaźnika do tablicy na wskaźnik do jej elementu.
3) Przekształcenie wskaźnika do podobiektu na wskaźnik do jego obiektu.
4) Przekształcenie odnośnika do podobiektu na odnośnik do jego obiektu.
5) Przekształcenie wskaźnika lokalizują cego zmienną na wskaźnik do tej zmiennej.
Uwaga: Poza konwersjami standardowymi, niejawne moż e być zastosowany jedynie konstruktor i konwerter.
63
Dodatek D
Operatory bitowe
Operatorami bitowymi są : ~ (zanegowanie bitó w), & (iloczyn bitó w), | (suma bitó w), ^ (suma modulo 2 bitó w),
<< (przesunięcie bitó w w lewo), >> (przesunięcie bitó w w prawo).
Podczas wykonywania operacji na bitach przydatne okazują się literały szesnastkowe. Literał szesnastkowy ma
postać 0xh, w któ rej h jest spó jnym cią giem cyfr szesnastkowych (0-9 i a-f).
cout << 0x12; // 18
cout << 0xffff; // 65535
Operator ~
Operacja zanegowania bitó w ma postać
~exp
w któ rej exp jest wyraż eniem całkowitym.
Rezultatem operacji zanegowania bitó w jest zmienna tymczasowa takiego samego typu jak zmienna exp, po
poddaniu jej promocjom, a następnie zanegowaniu każ dego jej bitu.
Uwaga: Negacją bitu 1 jest bit 0, a negacją bitu 0 jest bit 1.
int red = 1, green = 2, blue = 4;
int hue = red | green; // ... 011 (kolor
żółty)
hue = ~hue; // ... 100 (kolor niebieski)
Trzy najmniej znaczą ce bity zmiennej hue reprezentują jeden z 8 koloró w. Wykonanie operacji zanegowania
bitó w powoduje zmianę koloru na dopełniają cy.
Operator &
Operacja iloczynu bitó w ma postać
expL & expR
w któ rej expL i expR są wyraż eniami całkowitymi.
W celu utworzenia wyniku operacji, zmienne expL i expR poddaje się konwersjom do typu wspólnego, a
następnie każ dy bit wyniku tworzy się z odpowiadają cych sobie bitó w argumentó w wyznaczają c ich iloczyn
logiczny.
Uwaga: Iloczyn logiczny pary bitó w ma wartość 1 tylko wó wczas gdy oba bity są jedynkowe.
int fix = 6; // 00 ... 110
const int mask = ’\x3’; // 00 ... 011
fix &= ~mask;
cout << Fix; // 4 (00 ... 100)
64
Wykonanie operacji na zmiennej fix powoduje wyzerowanie tych wszystkich jej bitó w, któ re w mask są
jedynkowe.
Operator ^
Operacja sumy modulo 2 bitó w ma postać
expL ^ expR
w któ rej expL i expR są wyraż eniami całkowitymi.
W celu utworzenia wyniku operacji, zmienne expL i expR poddaje się konwersjom do typu wspólnego, a
następnie każ dy bit wyniku tworzy się z odpowiadają cych sobie bitó w argumentó w wyznaczają c ich sumę
logiczną modulo 2.
Uwaga: Suma logiczna modulo 2 pary bitó w ma wartość 1 tylko wó wczas gdy bity są różne.
int fix = 6; // 00 ... 110
const int mask = ’\x3’; // 00 ... 011
fix ^= mask;
cout << fix; // 5 (00 ... 101)
Wykonanie operacji na zmiennej fix powoduje zanegowanie tych wszystkich jej bitó w, któ re w mask są
jedynkowe.
Operator |
Operacja sumy bitó w ma postać
expL | expR
w któ rej expL i expR są wyraż eniami całkowitymi.
W celu utworzenia wyniku operacji, zmienne expL i expR poddaje się konwersjom do typu wspólnego, a
następnie każ dy bit wyniku tworzy się z odpowiadają cych sobie bitó w argumentó w wyznaczają c ich sumę
logiczną .
Uwaga: Suma logiczna pary bitó w ma wartość 0 tylko wó wczas gdy oba bity są zerowe.
int fix = 5; // 00 ... 101
const int mask = ’\x3’; // 00 ... 011
fix |= mask;
cout << Fix; // 7 (00 ... 111)
Wykonanie operacji na zmiennej fix powoduje ustawienie tych wszystkich jej bitó w, któ re w mask są jedynkowe.
Operator <<
Operacja przesunięcia bitó w w lewo ma postać
expL << n
w któ rej expL i n są wyraż eniami całkowitymi.
W celu utworzenia wyniku operacji, zmienną expL poddaje się promocji, a następnie każ dy bit wyniku tworzy
się z bitó w tej nowej zmiennej po przesunięciu ich o n pozycji w lewo.
65
Uwaga: Podczas przesuwania w lewo bity najbardziej znaczą ce są odrzucane, a na pozycje najmniej znaczą ce
wchodzą bity 0.
int fix = 7; // 00 ... 0111
fix <<= 2;
cout << fix; // 28 (00 ... 011100)
Bity zmiennej fix przesunięto o 2 pozycje w lewo.
Operator >>
Operacja przesunięcia bitó w w prawo ma postać
expL >> n
w któ rej expL i n są wyraż eniami całkowitymi.
W celu utworzenia wyniku operacji, zmienną expL poddaje się promocji, a następnie każ dy bit wyniku tworzy
się z bitó w tej nowej zmiennej po przesunięciu ich o n pozycji w prawo.
Uwaga: Podczas przesuwania w prawo bity najmniej znaczą ce są odrzucane.
int fix = 15; // 00 ... 01111
fix >>= 2;
cout << fix; // 3 (00 ... 011)
Bity zmiennej fix przesunięto o 2 pozycje w prawo.
66
Dodatek E
Operacje wejścia-wyjścia
Większość operacji wejścia-wyjścia moż na wykonać za pomocą operatorów. Do specjalnych celó w przydają się
niekiedy funkcje wejścia-wyjścia.
Funkcje get i put
inp.get(chr)
Wprowadza ze strumienia inp najbliż szy znak (w tym znak odstępu) i jego kod przypisuje zmiennej chr typu
char. Dostarcza odnośnik do inp.
out.put(chr)
Wyprowadza do strumienia out znak o kodzie chr. Dostarcza odnośnik do out.
#include <iostream.h>
#include <fstream.h>
#include <string.h>
int main(void)
{
ifstream inp;
inp.open("Data.txt", ios::in);
if(!inp.is_open())
return -1;
char chr;
while(inp.get(chr))
cout.put(chr);
return 0;
}
Program kopiuje na konsolę zawartoś ć pliku Data.txt. Kopiowanie odbywa się znak-po-znaku.
Funkcje read i write
inp.read(ptr, len)
Wprowadza ze strumienia inp cią g len najbliż szych znakó w i ich kody umieszcza w tablicy znakowej o
elemencie wskazywanym przez ptr. Dostarcza odnośnik do inp.
out.write(ptr, len)
Wyprowadza do strumienia out cią g len znakó w z tablicy znakowej, począ wszy od elementu wskazywanego
przez ptr. Dostarcza odnośnik do out.
inp.gcount()
67
Dostarcza liczbę znakó w wprowadzonych za pomocą ostatnio wywołanej funkcji read albo getline.
#include <iostream.h>
#include <fstream.h>
const int Size = 10;
int main(void)
{
ifstream inp;
inp.open("Data.txt", ios::in);
if(!inp.is_open())
return -1;
char buf[Size];
while(true) {
inp.read(buf, Size);
int len = inp.gcount();
if(len > 0)
cout.write(buf, len);
if(len < Size)
break;
}
return 0;
}
Program kopiuje na konsolę zawartoś ć pliku Data.txt. Kopiowanie odbywa się porcjami po Size znakó w.
Funkcja getline
inp.getline(ptr, len)
Wprowadza ze strumienia inp jeden wiersz, ale nie więcej niż len-1 najbliż szych znakó w, a ich kody, bez kodu
’\n’, ale z dodatkowym kodem 0, umieszcza w tablicy znakowej o elemencie wskazywanym przez ptr. Dostarcza
odnośnik do inp.
#include <iostream.h>
#include <fstream.h>
const int Size = 100;
int main(void)
{
ifstream inp;
inp.open("Data.txt", ios::in);
if(!inp.is_open())
return -1;
char buf[Size];
while(inp) {
inp.getline(buf, Size);
int len = inp.gcount();
if(len > 0)
cout << buf << endl;
}
return 0;
}
Program kopiuje na konsolę zawartoś ć pliku Data.txt. Kopiowanie odbywa się wierszami.
Funkcje peek i putback
inp.peek()
Dostarcza kod najbliż szego znaku strumienia inp, ale znaku ze strumienia nie wprowadza (sic!).
68
inp.putback(chr)
Cofa do strumienia inp znak o kodzie chr. Dostarcza odnośnik do inp.
#include <iostream.h>
#include <fstream.h>
#include <ctype.h>
const int Size = 100;
int main(void)
{
ifstream inp;
inp.open("Data.txt", ios::in);
if(!inp.is_open())
return -1;
while(inp) {
char chr;
inp >> chr;
inp.putback(chr);
if(chr == ’-’ || chr == ’+’ || isdigit(chr)) {
double num;
inp >> num;
cout << num << endl;
} else {
char buf[Size];
inp >> buf;
cout << buf << endl;
}
}
return 0;
}
Program wprowadza z pliku Data.txt zawarte w nim liczby i ł
ańcuchy, a następnie wyprowadza je na konsolę,
każdy w osobnym wierszu.
Funkcje tellg i tellp
Funkcje tellg i tellp służ ą do określania pozycji pliku. Pozycja jest daną typu streampos. W Visual C++ typ
streampos jest identyczny z typem int.
inp.tellg()
Dostarcza bież ą cą pozycję pliku otwartego w trybie ios::in.
inp.tellp()
Dostarcza bież ą cą pozycję pliku otwartego w trybie ios::out.
#include <iostream.h>
#include <fstream.h>
int main(void)
{
ifstream inp;
inp.open("C:\\config.sys", ios::in);
if(!inp.is_open())
return -1;
char chr;
while(inp >> chr)
;
streampos pos = inp.tellg();
cout << "Size = " << pos << endl;
return 0;
}
69
Program wyznacza rozmiar pliku config.sys.
Funkcje seekg i seekp
Funkcje seekg i seekp służ ą do ustawiania pozycji pliku. Nowa pozycja pliku moż e być podana względem
począ tku pliku (ios::beg), względem pozycji bież ą cej (ios::cur), albo względem pozycji końcowej (ios::end).
inp.seekg(pos) // inp.seekg(pos, ios::beg)
inp.seekg(pos, from)
Ustawia plik otwarty w trybie ios::in w pozycji pos, liczonej względem from (ios::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 względem from (ios::beg, ios::cur, ios::end).
inp.seek(0);
Instrukcja ustawia strumień w pozycji począ tkowej.
Funkcja clear
Funkcja clear służ y do ustawienia stanu strumienia.
str.clear()
str.clear(ios::badbit)
Wywołanie bezargumentowe ustawia strumień str w stan dobry. Wywołanie z argumentem ios::badbit ustawia
go w stan zły.
#include <iostream.h>
#include <fstream.h>
int main(void)
{
ifstream inp;
inp.open("C:\\autoexec.bat", ios::in);
if(!inp.is_open())
return -1;
char chr;
for(int i = 0; i < 3 ; i++) {
while(inp.get(chr))
cout << chr;
inp.clear();
inp.seekg(0);
}
return 0;
}
Program ma na celu 3-krotne wyprowadzenie na konsolę zawartoś ci pliku autoexec.bat.
Ponieważ po zakończeniu instrukcji while strumień inp znajduje się w stanie nie-dobrym, więc należy ustawić go
w stan dobry. W przeciwnym razie wszystkie operacje wejś cia-wyjś cia dotyczą ce tego strumienia był
yby
pomijane, a zawartoś ć pliku został
aby wyprowadzona tylko 1 raz.
Operacje w pamię ci
70
Operacje wejścia-wyjścia mogą dotyczyć nie tylko plikó w, ale ró wnież pamięci operacyjnej. Do wykonywania
operacji w pamięci służ ą obiekty klas istrstream i ostrstream, zadeklarowanych w pliku nagłó wkowym
strstream.h.
Argumentem konstruktora klasy istrstream jest wskaźnik łańcucha. Argumentami konstruktora klasy ostrstream
jest wskaźnik elementu tablicy znakowej i maksymalna liczba jej elementó w, któ re mogą być uż yte w operacji
wyjścia.
Uwaga: Operacja wyjścia nie zapisuje znaku końca łańcucha. Należ y to wykonać jawnie, na przykład za pomocą
symbolu ends.
#include <iostream.h>
#include <strstream.h>
int main(void)
{
char data[] = "10 20 30";
istrstream(data) >> a >> b >> c;
char buf[100];
ostrstream(buf, sizeof(buf)) << "Sum = " <<
a + b + c << ends;
cout << buf << endl;
return 0;
}
Przetwarzanie wyrywkowe
Wyrywkowo przetwarza się zazwyczaj pliki binarne. Plik binarny otwiera się w trybie
ios::in | ios::out | ios::binary
Operacje na pliku wykonuje się za pomocą funkcji read i write.
#include <iostream.h>
#include <fstream.h>
const char *const SrcName = "Data.txt";
const int Size = sizeof(int);
const char *const TrgName = "Random";
int main(void)
{
ifstream inp;
inp.open(SrcName, ios::in | ios::nocreate);
if(!inp.is_open()) {
cout << "Source failure" << endl;
return -1;
}
ofstream out;
out.open(TrgName, ios::out | ios::binary);
if(!out.is_open()) {
cout << "Target failure" << endl;
return -2;
}
// wprowadzanie
cout << endl << "reading ... " << endl;
int count = 0, tmp;
while(inp >> tmp) {
count++;
cout << tmp << endl;
out.write((char *)&tmp, Size);
71
}
out.close();
inp.close();
if(count == 0) {
cout << "No data" << endl;
return -4;
}
// sprawdzanie
cout << endl << "checking ... " << endl;
inp.open(TrgName, ios::in | ios::binary | ios::nocreate);
if(!inp.is_open()) {
cout << "Check failure" << endl;
return -5;
}
while(inp.read((char *)&tmp, Size))
cout << tmp << endl;
inp.close();
// sortowanie
cout << endl << "sorting ... ";
fstream rio;
rio.open(TrgName, ios::in | ios::out | ios::binary);
if(!rio.is_open()) {
cout << "Sort failure" << endl;
return -6;
}
bool sorted = false;
while(!sorted) {
cout << endl;
sorted = true;
for(int i = 0; i < count-1 ; i++) {
rio.seekp(i * Size, ios::beg);
if(!rio)
goto Exit;
int num1, num2;
rio.read((char *)&num1, Size).
read((char *)&num2, Size);
cout << num1 << " " << num2 << endl;
if(num2 < num1) {
rio.seekp(-2 * Size, ios::cur);
rio.write((char *)&num2, Size).
write((char *)&num1, Size);
sorted = false;
}
}
}
Exit:;
if(!sorted) {
cout << "Seek error" << endl;
return -7;
}
// wyprowadzanie
cout << endl << "showing ... " << endl;
rio.seekp(0, ios::beg);
for(int i = 0; i < count ; i++) {
rio.read((char *)&tmp, Size);
cout << tmp << endl;
}
return 0;
}
Program tworzy plik binarny, do któ rego zapisuje dane pochodzą ce pliku Data.txt. Następnie dane sortuje i
wyprowadza.
Ponieważ funkcje read i write oczekują argumentó w typu char * i int, wskazana zmiennych cał
kowitych
(np. &tmp) poddano jawnej konwersji do typu char *.