Podstawy Programowania w jezyku C++'
Komentarze
----------------
W jezyku C++ komentarz sygnalizowany jest znakami //.
Przy czym zastosowanie // oznacza komentarz tylko w tej lini i
od tego miejsca w ktorym uzyto // i tylko do konca tej lini. Z
kolei uzycie /* i */ oznacza komentarz pomiedzy /* a */
Zapis liczb
---------------
Przyklady:
89 //liczba dziesietna
037 //liczba osemkowa
0x12 //liczba szesnastkowa
Sekwencje ucieczki
-------------------------
nowy wiersz \n
tabulacja pozioma \t
tabulacja pionowa \v
backspace \b
powrot karetki \r
nowa strona \f
dzwonek \a
backslash \\
znak zapytania \?
pojedynczy apostrof \'
podwojny apostrof \"
Jak sama nazwa wskazuje sekwencje ucieczki to znaki zaczynajace
sie od znaku \ dzieki czemu zmienia sie ich znaczenie. Sekwencje
te maja zastosowanie przy stosowaniu instrukcji pisania na ekran
np.
printf("Komputer\n");
spowoduje wypisanie slowa komputer i przejscie do nowej lini
Deklaracja zmiennych i stalych
----------------------------------------
Skladnia definicji zmiennych:
1typ nazwa;
lub
typ nazwa = wartosc;
Pierwsza definicja tworzy zmienna "nazwa" o typie "typ" i jesli
jest to zmienna liczbowa to nadaje jej wartosc poczatkowa zero-
tzw. inicjacja zmiennej. Jesli jest to ciag to nie bedzie on
zawieral w sobie zadnych znakow. Z kolei druga deklaracja tworzy
tez taka zmienna jednak nadaje jej wartosc poczatkowa zalezna od
nas -> "wartosc"
Przykladowe typy zmiennych i stalych:
int - liczba calkowita dziesietna
char - pojedynczy znak
Skladnia definicji stalych:
const typ nazwa;
lub
const typ nazwa = wartosc;
Powyzsze deklaracje stalych roznia sie tylko od deklaracji
zmiennych wystepowaniem slowa const(stala).
Jednoczesnie mozemy uczynic iz w programie wieloplikowym nasza
zmienna bedzie widoczna we wszystkich jego plikhac. Sluzy do
tego slowo kluczowe extern poprzedzajace deklaracje zmiennej lub
stalej,np.
extern const int liczba;
Z kolei slowo static porzedzajace taka deklaracje oznacza iz
deklarowana zmienna lub stala bedzie dostepna w tym pliku w
ktorym nastapila deklaracja.
Dyrektywa #include
--------------------------
Dyrektywy w jezyku C++ zaczynaja sie od znaku #.
Dyrektywa #include ma skladnie:
#include
Dyrektywa ta odpowiada instrukcji uses z turbo pascala, czyli
zalacza do naszego programu pewna biblioteke,gdzie nazwa_pliku.h
nazwa pliku naglowkowego. Jednoczesnie dyrektywa ta wyszukuje
tego pliku w biezacym katalogu(katalogu naszego programu)
Operator zasiegu
--------------------------
Zalozmy,ze w tym samym programie chcemy uzyc zmiennej
globalnej(dostepnej w calym programie) i lokalnej(dostepnej
tylko np. w procedurze lub funkcji) o tej samej nazwie, to aby
uzyskac dostep do ukrytej zmiennej globalnej stosuje sie
operator zasiegu ::.Porzedzenie nim zmiennej oznacza iz operacja
bedzie dotyczyc zmiennej globalnej.
NP.
#include
int z; /*Deklaracja zmiennej globalnej typu int zainicjowanej
zerem */
void main()
{
char znak;
int x,y;
double z; deklaracja zmiennych lokalnych
z = 1.25816; //przypisanie do zmiennej lokalnej
::z = 12; //przypisanie do zmiennej globalnej
}
Instrukcja przypisania
------------------------------
Skladnia:
zmienna = wyrazenie;
np.
a = c + b;
Oznacza niech "a" rowna sie sumie "c" i "b".
Z kolei:
i+j;
Oznacza wykonaj dodawanie "i" do "j" jednak nie umieszczaj
nigdzie wyniku.
Dwuargumentowe operatory arytmetyczne
-------------------------------------------------------
+ dodawanie
- odejmowanie
* mnozenie
/ dzielenie
% operator reszty z dzielenia(stosowany dla zmiennych tylko typu
short int,int i long int).Np.
18 % 6 daje wynik 0,a 18 % 5 daje wynik 3
Operatory te jak z tylu wynika sa znakami stosowanymi przy
tworzeniu wyrazen arytmetycznych - pewnych obliczen na
zmiennych.
Operatory relacji
-------------------------
< mniejszy
<= mniejszy lub rowny
> wiekszy
>= wiekszy lub rowny
== rowny (tu nalezy zauwazyc,ze operator przypisania to =)
!= nierowny
Przyklad skladni:
wyrazenie1 == wyrazenie2
Jak sama nazwa wskazuje sa to operatory stosowane w
rozkazach sprawdzania wartosci,np. if, do sprawdzenia czy dana
zmienna osiagnela taka czy inna wartosc.
Operatory logiczne
--------------------------
Symbol Znaczenie Skladnia
! negacja !wyrazenie
&& koniunkcja wyrazenie1 &&
wyrazenie2
|| alternatywa wyrazenie1 ||
wyrazenie2
Sa to tekze operatory stosowane do tworzenia wyrazen logicznych.
Operatory bitowe logiczne
------------------------------------
Symbol Znaczenie
Skladnia
& bitowa koniunkcja(AND)
wyrazenie & wyrazenie
| bitowa alternatywa(OR)
wyrazenie | wyrazenie
^ bitowa roznica symetryczna(EXOR)
wyrazenie ^ wyrazenie
<< przesuniecie w lewo
wyrazenie << wyrazenie
>> przesuniecie w prawo
wyrazenie >> wyrazenie
~ bitowa negacja
~wyrazenie
Uwaga: Ich argumenty musza byc calkowite.
Odnosnie ^ to w wyniku ustawia jedynke na kazdej pozycji gdzie
jego bity sie roznia,tam gdzie sa takie same ustawia 0.
Operatory przesuniecia w lewo i prawo przesuwaja reprezentujaca
dana liczbe sekwencje bitow odpowiednia w lewo i
prawo,wypelniajac zwolnione bity zerami,Np. dla i = 6 instrukcja
i = i<< 2 zmieni i na 24.
Operatory przypisania
--------------------------------
Przypadek szczegolnej instrukcji przypisania:
a = a op b;
gdzie:
op - to operator
Jednak mozna ja zastapic instrukcja:
a op= b;
Kilka przykladow:
Symbol operatora Zapis skrocony Zapis rozwiniety
+= a+=b a =
a + b;
<<= a<<=b; a = a
<< b;
%= a%=b; a =
a % b;
Operator SIZEOF
------------------------
Skladnia:
sizeof(nazwa_typu)
sizeof wyrazenie
Zwraca on wielkosc w bajtach wielkosc wyrazenia "wyrazenie" lub
dlugosc typu zmiennej "nazwa_typu"
Operator warunkowy ?:
---------------------------------
Skladnia:
wyrazenie1 ? wyrazenie2 : wyrazenie3
Wartosc takiego wyrazenia liczona jest nastepujaco. Wpierw
wartosciowane jest wyrazenie1. Jesli jego wynik jest
niezerowy(true - prawda) to potem wartosciowane jest wyrazenie2
i wynikiem obliczen jest jego wartosc. Przy zerowym
wyniku(falsz) wyrazenia "wyrazenie1" wynikiem obliczen jest
wyrazeni3.
Np.
#include
int main(){
int a,b,z;
cin >> a >> b;
z = (a > b) ? a : b; // z == max(a,b)
cout << z;
return 0;
}
Operator zwiekszania/zmniejszania
----------------------------------------------
W jezyku C++ mamy operator do zwiekszania o jeden ++ i
zmniejszania o jeden --.Np, poprostu zamiast
n = n + 1 lub n += 1;
piszemy
++n lub n++.
Przy czym uzycie operatora zwiekszania/zmniejszania przed lub za
zmienna ma znaczenie. Uzycie go przed (++n) oznacza,ze wartosc
"n" zostanie zwiekszona zanim wartosc "n" zostanie uzyta, z
kolei n++ oznacza zwiekszenie "n" po uzyciu jej dotychczasowej
wartosci.
NP.
#include
int main(){
int i,j = 5;
i = j++; //przypis 5 do i po czym przypisz 6 do j
cout << "i=" << i << ",j=" << j << endl;
i = ++j; //przypisz 7 do j, po czym przypisz 6 do i
cout << "Teraz i=" << i << ",j=" << j << endl;
// j++ = i; zle! j++ nie jest l-wartosciowa(lewym wyrazeniem)
return 0;
}
Operator przecinkowy
--------------------------------
Operator "," pozwala utworzyc wyrazenie skladajace sie z ciagu
wyrazen skladowych rozdzielonymi przecinkami. Wartoscia takiego
wyrazenia jest wartosc ostatniego z prawej elementu ciagu,zas
wartosciowanie(obliczenia) przebiego od lewej do prawej.Np.
double x,y,z;
z = (x=2.5,y=3.5,y++);
Wynikiem obliczen beda tu wartosci x rowne 2.5,y rowne 4.5 oraz
z rowne 3.5(wartosc z nie bedzie rowna 4.5 bo uzyto y++ a nie
++y)
Instrukcja if
-------------
Jest to instruckcja wyboru,skladnia:
if(wyrazenie) instrukcja
lub
if(wyrazenie) instrukcja1 else instrukcja2
Pamietaj,ze:
if(wyrazenie !=)
mozna zapisac
if(wyrazenie)
Np.
if (a>b)
max = a;
else
max = b;
Pamietaj:
Instrukcje proste jezyka C++ kancza sie srednikami
Instrukcja swith
-----------------------
Sluzy ona do podejmowania decyzji wielowariantowych.Skladnia:
swith(wyrazenie) instrukcja;
gdzie: "instrukcja" jest zwykle instrukcja zlozona(blokiem)
ktorej instrukcje skladowe sa poprzedzone slowem kluczowym case
z etykieta: "wyrazenie" ktore musi przyjmowac wartosci
calkowite,tu spelnia role selektora wyboru.
skladnia w rozwinietym zapisie:
swith(wyrazenie)
{
case etykieta-1 : instrukcje
...
case etykieta-n : instrukcje
default : instrukcje
}
Uwaga: Instrukcje po etykiecie "default" wykonywane sa gdy zadna
z poprzednich etykiet nie ma aktualnej wartosci selektora
wyboru. Przy czym przypadek "default" jest opcja.
Instrukcje BREAK - powoduje po jej wykonaniu opuszczenie
aktualnie wykonywanego bloku.Ma ona zastosowanie przy instrukcji
swith po to gdy zostanie spelniony pewien warunek i wykonane
jego instrukcje dalej komputer juz nie przeprowadzal sprawdzan
kolejnych wariantow.Wtedy jej skladnia:
swith(wyrazenie)
{
case etykieta-1 : instrukcje
break;
...
case etykieta-n : instrukcje
break;
default : instrukcje
break;
}
Np.
#include
int main()
{
char droga;
int czas;
cout << "Wprowadz litere A,B lub C:";
cin >> droga;
cout << endl;
if ((droga == 'A')
||(droga = 'B') || (droga == 'C'))
swith(droga){
case 'A':case 'B':czas = 3;
cout << czas << endl;
break;
case 'C': czas = 4;
cout << czas << endl;
break;
default: droga = 'D';
czas = 5;
cout << czas << endl;
}
else cout << "zostan w domu\n";
return 0;
}
Uwaga: Kazda z instrukcji skladowych moze byc poprzedzona wiecej
niz jedna sekwencja case etykieta:.
Instrukcja iteracyjna while
------------------------------------
Skladnia:
while(wyrazenie) instrukcja
Instrukcja ta tworzy petle w ktorej sprawdzany jest warunek
wejscia do petli.Np.
#include
#include
int main()
{
const int wiersz = 5;
const int kolumna = 15;
int j,i = 1;
while(i <= wiersz) {
cout << setw(kolumna-i) << '*';
j = 1;
while(j <= 2*i-2){
cout << '*';
j++; }
cout << endl;
i++; }
return 0 ;
}
Po ruchomieniu na ekranie wyswietli sie:
*
***
*****
*******
*********
Funkcja setw(int w) pochodzaca z pliku naglowkowego iomanip.h
sluzy do ustawiania szerokosci pola wydruku na podana liczbe "w"
znakow.
Instrukcja do-while
----------------------------
Skladnia:
do instrukcja while (wyrazenie)
Instrukcja ta tworzy petle sprawdzajaca warunek wyjscia z petli.
Instrukcja bedzie zawsze wykonanna conajmniej raz,gdyz
sprawdzanie warunku odbywa sie po jej wykonaniu.
Np.
#include
int main()
{
char znak;
cout << "Wprowadz dowolny znak;\ * oznacza koniec.\n";
do
{
cout << ": ";
cin >> znak;
}
while (znak != '*');
return 0;
}
Instrukcja for
--------------------
Instrukcja ta tworzy petle z sprawdzaniem warunku wejscia do
petli.Skladnia:
for(instrukcja-inicjujaca wyrazenie1; wyrazenie2)instrukcja
Algorytm dzialania petli jest nastepujacy:
1. Wykonaj instrukcje "instrukcja-inicjujaca ".Jest to
najczesciej zainicjowanie jednego lub wiecej licznika petli,np.
for (i = 0;...
for (i = 0,j = 1;...
for (int i = 0;...
Instrukcja inicjujaca moze byc tez instrukcja pusta jesli
licznik zostal juz wczesniej ustawiony
int i = 1; for(;...
2. Oblicz wartosc wyrazenia wyrazenie1 i jesli jego warunek jest
spelniony to wykonaj instrukcje instrukcja .
3. Oblicz wartosc wyrazenia wyrazenie2 ktore najczesciej jest
zwiekszeniem licznika petli
Np.
#include
#include
int main()
{
const int wiersz = 5;
const int kolumna = 15;
for (int i = 1; i <= wiersz; i++)
{
cout << setw(kolumna-1) << '*';
for (int j = 1; j <= 2 * i - 2;j++)
cout << '*';
cout << endl;
}
return 0;
}
Program ten wyswietla na ekranie:
*
***
*****
*******
*********
Instrukcja skoku bezwarunkowego goto
-----------------------------------------------------
Skladnia:
goto etykieta:
Typ tablicowy
-------------
Deklaracja tablicy sklada sie z okreslenia jej
typu,identyfikatora(nazwy ktora delej bedzie sie uzywac) i
wymiaru ujetego w kwadratowe nawiasy [].
Np.
double tabd[4];
czyli stworzylisci tablice zawierajaca cztery liczby typu
double.Przy czym jako wielkoscia tablicy nie moze byc zmienna.
Inicjowanie tablic
a) Umieszczajac deklaracje tablicy na zewnatrz wszystkich
funkcji programu - taka tablica globalna(poprzedzona slowem
static) zostanie automatycznie zainicjowana przez
komilator(zerujac wszytskie jej elementy)
b) Deklarujac tablice ze slowem kluczowym static w bloku
funkcji.Jesli nie podamy wartosci inicjalnych to zrobi to za nas
kompilator
c) Definiujac tablice tj. umieszczajac po jej deklaracji
wartosci inicjalne poprzedzone znakiem "=" . Wartosci inicjalne
oddzielone przecinkami umieszcza sie w nawiasach klamrowych po
znaku "=".Np.
double tabd[4] = {1.5,6.2,2.8,3.7} ;
W deklaracji inicjujacej tablicy mozna pominac rozmiar tablicy
double tabd[] = {1.5,6.2,2.8,3.7} ;
d) Deklarujac tablice a nastepnie przypisujac je elemntom pewne
wartosci
Mozemy tez zdefiniowac tablica o elemntach stalych:
const int tab[4] = {10,20,30,40} ;
Np.
#include
void main()
{
int i;
const int wymiar = 10;
int tab[wymiar];
for (i=0;i < wymiar; i++)
{
tab[i]:=i;
cout << "tab[" << i << "]= " << tab[i] << endl;
}
}
lub tablica znakow:
np.
#include
void main()
{
const int buf = 10;
char a[buf] = "ABCDEFGHIJ";
cout << a <
}
Uwaga: Liczniki petli moga byc deklarowane wewnatrz jej np.
for(int i = 0 ;...)
Typ wskaznikowy
-------------------------
W jezyku C++ dla kazdego typu X istnieje skojarzony z nim typ
wskaznikowy X*. Zbiorem wartosci typu X* sa wskazniki do
obiektow typu X. Do zbioru wartosci X* nalezy rowniez wskaznik
pusty, oznaczony jako 0 lub NULL. Wskaznikiem typu wskaznikowego
jest zmienna wskaznikowa. Deklaracja zmiennej wskaznikowej ma
nastepujaca postac:
nazwa-typu-wskazywanego* nazwa-zmiennej-wskaznikowej;
Zgodnie z powyzszymi okresleniami wartosciami zmiennej
wskaznikowej moga byc wskazniki do uprzednio zadeklarowanych
obiektow(zmiennych,stalych) typu wskazywanego. Wartoscia
zmiennej wskaznikowej nie moze byc stala. Jezeli zadamy, aby
zmienna wskaznikowa nie wskazywala na zadan obiekt programu
przypisujemy jej wskaznik 0(null),np.
int *wski; //typ wskazywany int. Typ wskaznikowy: int*
wski = 0;
W deklaracji zmiennej wskaznikowej typem wskazywanym moze byc
dowolny typ wbudowany, typ pochodny od typu wbudowanego, lub
typ
zdefiniowany przez uzytkownika. W szczegolnosci moze to byc typ
VOID,np.
void* wsk; // typ wskazywany: void; Typ wsk: void*
uzywamy wtedy gdy typ wskazywanego obiektu nie jest dokladnie
znany w chwili deklaracji, lub moze sie zmieniac w fazie
wykonania. Zmiennej "wsk" typu void* mozemy przypisac wskaznik
do obiektu dowolnego typu.
Podobnie jak zmienne typow wbudowanych zmienne wskaznikowe moga
byc deklarowane w zasiegu globalnym lub lokalnym. Globalne
zmienne wskaznikowe sa alokowane w pamieci statycznej programu,
bez wzgledu na to, czy sa poprzedzone slowem kluczowym "static"
czy tez nie. Jesli globalna zmienna wskaznikowa nie jest jawnie
zainicjowana to kompilator przydziela jej niejawnie wartosc
0(null). Tak samo bedzie inicjowana statyczna zmienna lokalna
tj. zmienna wskaznikowa zadeklarowana w bloku funkcji, przy czym
jej deklaracja jest poprzedzona slowem kluczowym "static".
Wskazniki i adresy
-----------------
W ogolnosci wskaznik zawiera informacje o lokalizacji
wskazywanej danej oraz informacje o typie tej danej. Typowa
zatem jest implementacja wskaznikow jako adresow w pamieci;
wartoscia zmiennej wskaznikowej moze byc wtedy adres poczatkowy
obiektu wskazywanego.
Wezmy pod uwage nastepujacy ciag deklaracji:
int i = 1; j = 10;
int* wski; // deklaracja zmiennej wski typu int*
wski = &i; // teraz wski wskazuje na i. *wski==1
j = *wski; // teraz j==1
Deklaracje te wprowadzaja zainicjowane zmienne "i" oraz "j" a
nastepnie zmienna wskaznikowa "wski", ktorej nastepna instrukcja
przypisuje wskaznik do zmiennej "i". Unarny operator adresacji &
przylozony do istniejacego obiektu np. &i daje wskaznik do tego
obiektu. Wskaznik ten w instrukcji przypisania wski = &i; zostal
przypisany zmiennej wskaznikowej "wski". Unarny operator dostepu
bezposredniego * transformuje wskaznik w wartosc na ktora on
wskazuje. Inaczej mowiac, wyrazenie *wski oznacza zawartosc
zmiennej wskazywanej przez wski.Zmienna wskaznikowa "wski" mozna
tez bezposrednio zainicjowac wskaznikiem do zmiennej "i" w jej
deklaracji:
int* wski = &i;
Wskaznik moze byc rowniez inicjowany innym wskaznikiem tego
samego typu,np:
int ii = 38;
int* wsk1 = ⅈ
int* wsk2 = wsk1;
Natomiast deklaracja inicjujaca o postaci:
int* wsk3 = &wsk1;
jest bledna poniewaz wsk3 jest wskaznikiem do zmiennej typu
"int" podczas gdy wartoscia &wsk1 jest adres wskaznika do
zmiennej typu "int". Ostatnia deklaracje mozna jednak zmienic na
poprawna, piszac:
int** wsk3 = &wsk1;
poniewaz wsk3 jest teraz wskaznikiem do wskaznika do zmiennej
typu "int".
Jezeli "int* wski" wskazuje na zmienna "i", to *wski moze
wystapic w kazdym kontekscie dopuszczalnym dla "i". Np.
*wski = *wski + 3;
zwieksza *wski (czyli zawartosc zmiennej "i") o 3, a instrukcja
przypisania:
j = *wski + 5;
pobierze zawartosc spod adresu wskazywanego przez wski(tj.
aktualna wartosc zmiennej "i"), doda do niej 5 i przypisze wynik
do "j"(wartosc *wski pozostanie bez zmiany).
Powyzsze instrukcje dzialaja poprawnie poniewaz *wski jest
wyrazeniem a operatory "*" i "&" wiaza silniej niz operatory
arytmetyczne. Podobnie instrukcja:
j = ++*wski;
zwiekszy o 1 wartosc *wski i przypisze te zwiekszona wartosc do
"j" zas instrukcja:
j = (*wski)++;
przypisze biezaca wartosc *wski do "j", po czym zwiekszy o 1
wartosc *wski. W ostatnim zapisie nawiasy sa konieczne poniewaz
operatory jednoargumentowe jak "*" i "++" wiaza od prawej do
lewej. Bez nawiasow mielibysmy zwiekszenie o 1 wartosc wski
zamiast zwiekszania wartosc *wski.
Zauwaz takze, ze skoro wartoscia wskaznika jest adres obszaru
pamieci przeznaczonego na zmienna danego typu, to wskazniki
roznych typow beda miec taki sam rozmiar. Jest to oczywiste,
poniewaz system adresacji komorek pamieci dla okreslonej
platformy sprzetowej i okreslonego systemu operacyjnego jest
zunifikowany i niezalezny od interpretacji (wymuszonej typem)
ciagu bitow zapisanego pod danym adresem.
Dodajmy na zakonczenie kilka uwag dotyczacych wskaznikow do typu
VOID. Zmiennej typu *void mozna przypisac wskaznik dowolnego
typu, ale nie odwrotnie. I tak np. poprawne sa deklaracje:
void* wskv;
double db;
double* wskd = &db;
wskv = wskd; // konwersja niejawna do void*
ale nie mozna sie odwolac do *wskv. Bledem bylaby tez proba
przypisania wskaznika dowolnego typu do wskv, np. wskd = wskv;
Sprubujmy obecnie krotko podsumowac nasze rozwazania.
Zmienna wskaznikowa(wskaznik) jest szczegolnym rodzajem
zmiennej, przyjmujacej wartosci ze zbioru, ktorego elementami sa
adresy. Przypomnijmy, ze "zwykla" zmienna jest obiektem o
nastepujacych atrybutach:
- typ zmiennej
- nazwa zmiennej. Zmienna moze miec zero, jedna lub kilka nazw.
Nazwy nie musza byc prostymi identyfikatorami, jak np. "a","b"
- lokalizacja zmiennej tj. adres w pamieci, pod ktorym sa
przechowywane jej wartosci
- wartosc zapisana pod adresem okreslonym lokalizacja zmiennej,
nazywana niekiedy r-wartoscia.
Deklaracja wskaznika niezainicjowanego przypisuje mu
zadeklarowany typ, natomiast wskazywany przez niego adres i
przechowywane pod tym adresem dane sa nieokreslone. Jezeli
wskaznik zainicjujemy w jego deklaracji lub przypisujemy mu
adres wczesniej zadeklarowej "zwyklej" zmiennej, to zostanie on
wyposazony we wszystkie wymienione wyzej atrybuty zmiennej
symbolicznej.
Np.
// Wskazniki i adresy
#include
int main()
{
int i = 5, j = 10;
int* wsk;
wsk = &i;
*wsk = 3; //ten sam efekt co i = 3;
j = *wsk + 25; // j==28, *wsk == 3
wsk =&j; // *wsk==28
i = j; //i ==28, &i bez zmiany
j = i; //j ==28, &j bez zmiany
cout << "*wsk= " << *wsk << endl
<< "i= " << i << endl;
return 0;
}
Dynamiczna alokacja pamieci
-------------------------------------------
W srodowisku programowym C++ kazdy program otrzymuje do
dyspozycji pewien obszar pamieci dla alokacji obiektow
tworzonych w fazie wykonywania. Obszar ten nazywany "pamiecia
swobodna" jest zorganizowany w postaci tzw. kopca lub stosu. Na
kopcu(ang. heap) alokowane sa obiekty dynamiczne tj. takie ktore
tworzy sie i niszczy przez zastosowanie operatorow new i delete.
Operator NEW alokuje(przydziela) pamiec na kopcu, zas operator
DELETE zwalnia pamiec alokowana wczesniej przez NEW.
Uproszczona
skladnia instrukcji z operatorem NEW jest nastepujaca:
wsk = new typ;
lub
wsk = new typ(wartosc-inicjalna);
gdzie:
wsk - jest wczesniej zadeklarowana zmienna wskaznikowa np.
int* wsk;
Zmienna wsk mozna bezposrednio zainicjowac adresem tworzonego
dynamicznie obiektu:
typ* wsk = new typ;
lub
wsk = new typ(wartosc inicjalna);
np.
int* wsk = new int(10);
Skladnia operatora DELETE ma postac:
delete wsk;
gdzie:
wsk - jest wskaznikiem do typu o nazwie "typ"
Zadaniem operatora NEW jest proba(nie zawsze pomyslna)
utworzenia "obiektu" typu "typ"; jezeli proba sie powiedzie i
zostanie przydzielona pamiec na nowy obiekt, to NEW zwraca
wskaznik do tego obiektu. Zauwazmy, ze alokowany dynamicznie
obszar pamieci nie ma nazwy, ktora mialaby charakter 1-wartosci.
Po udanej alokacji zastepcza role nazwy zmiennej pelni *wsk, zas
adres obszaru jest zawarty w "wsk".
Operator DELETE niszczy obiekt utworzony przez operator NEW i
zwraca zwolniona pamiec do pamieci swobodnej. Po operacji DELETE
wartosc zmiennej wskaznikowej staje sie nieokreslona. Tym
niemniej zmienna ta moze nada; zawierac stary adres lub,
zaleznie od implementacji wskaznik moze zostac ustawiony na
zero(NULL). Jezeli przez nieuwage skorzystamy z takiego
wymazanego wskaznika ktora nadal zawiera stary adres to
prawdopodobnie program bedzie nadal wykonywany, az dojdzie do
miejsc, gdzie ujawni sie blad. Tego rodzaju bledy sa na ogol
trudne do wykrycia.
UWAGA: Poniewaz alokowane dynamicznie zmienne istnieja az do
chwili zakonczenia programu, brak instrukcji z operatorem DELETE
spowoduje zbedna zajetosc pamieci swobodnej.
Np.
#include
int main()
{
int* wsk; // wsk jest wskaznikiem do int
wsk = new int;
delete wsk;
wsk = new int(9); // *wsk == 9
cout << "*wsk = " << *wsk << endl;
delete wsk;
return 0;
}
Zastosowanie operatora DELETE do pamieci niealokowanej prawie
zawsze grozi nieokreslonym zachowaniem sie programu podczas
wykonania. Mozna temu zapobiec uzalezniajac dealokacje od
powodzenia alokacji pamieci na stogu
Podane nizej przyklady ilustruja kilka sposobow zabezpieczenia
sie przed niepowodzeniem alokacji pamieci za pomoca operatora
NEW.
Np.
#include
int main()
{
int* wsk;
if ((wsk = new int) == 0)
// alternatywny zapis: if (!(wsk = new int))
{cout << "Nieudana alokacja\n"; return 1;}
*wsk = 9;
delete wsk;
return 0;
}
lub np.
#include
int main()
{
int* wsk = new int;
if (wsk == 0)
// alternatywny zapis: if (!wsk)
{
cout << "Nieudana alokacja\n";
return 1;}
*wsk = 9;
delete wsk;
return 0;
}
lub np.
#include
#include
int main()
{
int* wsk = new int (8);
assert (wsk!=0);
cout << "*wsk= " << *wsk << endl;
delete wsk;
wsk = 0; // lub NULL
return 0;
}
albo np.
#include
#include
int main()
{
int* wsk = new int;
if (!wsk)
{
cout << "Nieudana alokacja\n";
abort();// lub exit(-1)
}
*wsk = 9;
delete wsk;
return 0;
}
Wskazniki i tablice
--------------------------
W jezyku C++ istnieje scisla zaleznosc pomiedzy wskaznikami i
tablicami. Zaleznosc ta jest tak scisla, ze kazda operacja na
zmiennej indeksowanej moze byc takze wykonana za pomoca
wskaznikow. Przy tym operacje z uzyciem wskaznikow sa w
ogolnosci wykonywane szybciej.
Podczas kompilacji nazwa tablicy np. "tab" jest automatycznie
przeksztalcana na wskaznik do pierwszego jej elementu czyli
adres "tab[0]". We fregmencie programu:
int tab[4] = {10,20,30,40};
int* wsk;
wsk = tab;
instrukcja:
wsk = tab;
jest rownowazna instrukcji:
wsk = &tab[0];
poniewaz tab==&a[0]. Inaczej mowiac "wsk" oraz "tab" wskazuja
teraz na element poczatkowy tab[0] tablicy tab[]. Istnieje
jednak istotna roznica pomiedzy "wsk" i "tab". Identyfikator
tablicy("tab") jest inicjowany adresem jej pierwszego
elementu("tab[0]"); adres ten nie moze ulec zmianie w
programie(nazwa tablicy nie jest modyfikowalna 1-wartoscia). Tak
wiec identyfikator tablicy jest rownowazny wskaznikowi stalemu i
nie mozna go zwiekszac czy zmniejszac. Natomiast "wsk" jest
zmienna wskaznikowa; w programie musimy najpierw ustawic
wskaznik na adres wczesniej alokowanego obiektu(np. tablicy
"tab"), a nastepnie mozemy zmieniac jego wskazania. Zwrocmy
uwage na to, ze zwiekszanie wartosci wskaznika "wsk" np. o 2
zwiekszy w typ przypadku wskazywany adres o tyle bajtow, ile
zajmuja dwa elementy tablicy "tab". Tak wiec instrukcje:
tab = tab+1;
tab = wsk;
sa bledne, natomiast instrukcja:
wsk = wska + 1;
jest poprawna(jest ona rownowazna: wsk = &tab[1], zatem nowa
wartosc *wsk == tab[1]).
Np.
// Wskazniki i tablice
#include
void main()
{
int t1[10] = {0,1,2,3,4,5,6,7,8,9};
int t2[10], *wt1, *wt2;
wt1 = t1;
// to samo co wt1 = &t1[0], poniewaz t1==&t1[0]
wt2 = t2;
// to samo co wt2 = &t2[0], poniewaz t2==&t2[0]
for (int i = 0; i < 10; i++)
cout << "t1[" << i << "]= " << *wt1++ << endl;
}
}