Wstęp do programowania Dariusz Wardecki, wyk. VI
Wskaźniki Wskaünik (ang. pointer) Zmienna, której wartoúciπ jest adres innej zmiennej. Deklaracja wskaünika Umieszcza sií * przy nazwie zmiennej, np.: int *ptr; char *p; // Wartoúciπ jest adres zmiennej typu int // Wartoúciπ jest adres zmiennej typu char Operator & obliczanie adresu zmiennej n zmienna typu int, ptr wskaünik zdefiniowany jak wyøej. ptr = &n; // Adres n staje sií wartoúciπ wskaünika ptr
Wskaźniki Dostęp do zmiennej poprzez wskaźnik Gdy wskaünik wsk zawiera adres zmiennej n Wtedy nastípujπce operacje dajπ ten sam wynik (wstawienie liczby 2 do zmiennej n): n = 2; *wsk = 2; OperacjÍ oznaczonπ symbolem * powyøej nazywa sií wy uskaniem.
Wskaźniki Dostęp do elementów tablicy poprzez wskaźnik Elementy tablicy i wskaünik tego samego typu ptr = &a[j]; // Adres a[j] staje sií wartoúciπ wskaünika ptr ptr + 1 // Adres a[j+1] ptr - 1 // Adres a[j-1] ptr + k // Adres a[j+k] Dla ptr == &a[j] równowaøne sπ nastípujπce zapisy a[j+k] // WartoúÊ elementu a[] o indeksie j+k *(ptr+k) // WartoúÊ zmiennej pod adresem ptr przesuniítym o k ptr[k] // WartoúÊ zmiennej pod adresem ptr przesuniítym o k *(a+j+k) // WartoúÊ zmiennej pod adresem a przesuniítym o j+k NazwÍ tablicy moøna traktowaê jako sta π wskaünikowπ.
Wskaźniki Operacje na wskaźnikach ZwiÍkszanie i zmniejszanie, += i -= Prawa strona musi byê ca kowitego typu. Oznacza przesuniície adresu we wskaüniku o pewnπ liczbí pozycji. Dla wskaünika typu int ptr += k; // Adres w ptr jest zwiíkszany o k * sizeof(int) Inkrementacja i dekrementacja, ++ i -- PrzesuniÍcie do przodu lub do ty u o jednπ zmiennπ danego typu (np. element tablicy). Moøna πczyê z innymi wyraøeniami (jak dla zwyk ych zmiennych). Dla wskaünika typu int ptr++; // Adres w ptr jest zwiíkszany o sizeof(int)
Wskaźniki Odejmowanie wskaźników Moøna odjπê jeden wskaünik od drugiego (tego samego typu). Ma to sens, gdy wskaüniki zawierajπ adresy elementów tej samej tablicy. Wynikiem jest róønica miídzy adresami wyraøona jako przesuniície mierzone liczbπ pozycji w tablicy (tzn. róønica indeksów odpowiadajπcych elementom tablicy, których adresy zawierajπ te wskaüniki).
Wskaźniki Przekazywanie tablic do funkcji W C++ tablica nie moøe byê argumentem funkcji 1 Argumenty funkcji to zmienne: Których wartoúci poczπtkowe pochodzπ z innych czíúci programu (przekazywanie przez wartoúê). Które zosta y zdefiniowane w innych czíúciach programu (przekazywanie przez referencjí). 2 Tablice nie sπ zmiennymi! Tablica jest zespo em zmiennych. Nazwa tablicy jest sta π wskaünikowπ.
Wskaźniki Wskaüniki mogπ byê argumentami funkcji 1 Wskaünikowe argumenty funkcji moøna w treúci tej funkcji traktowaê tak, jakby by y nazwami tablic. 2 WartoúÊ (poczπtkowa) parametru wskaünikowego oznacza lokacjí (w pamiíci), którπ funkcja ma traktowaê jako poczπtek tablicy. 3 Funkcja musi poznaê liczbí elementów tablicy niezaleønie od adresu jej poczπtku. Informacja o liczbie elementów nie jest automatycznie przekazywana wraz z adresem poczπtku tablicy. Musi ona byê przekazana do funkcji w inny sposób (np. jako dodatkowy argument funkcji). void sort(double *a, int n);
Wskaźniki Przykład void sort(double *a, int n) { // Sortowanie tablicy. for (int i = 0; i < n-1; i++) { // Poszukiwanie indeksu j. int j = i; for (int k = i + 1; k < n; k++) if (a[k] < a[j]) j = k; } } // Zamiana miejscami a[i] z a[j]. if (j > i) { double m = a[j]; a[j] = a[i]; a[i] = m; }
Napisy i tablice Litera y tekstowe (napisy) w czasie kompilacji sπ zamieniane na tablice o elementach typu char. Podczas uruchamiania programu zwykle sπ one umieszczane w obszarze pamiíci tylko do odczytu. 1 Napis w kodzie üród owym reprezentuje adres, pod którym znajduje sií ciπg znaków w postaci tablicy. 2 Dla zbioru znaków ASCII kaødy element tej tablicy reprezentuje 1 znak. Znaki odpowiadajπ liczbom ca kowitym, zgodnie ze zbiorem znaków wykorzystywanym przez kompilator. W kodzie üród owym mogπ byê zapisywane bezpoúrednio (np. a ) lub jako kody (liczby ca kowite). 3 Ostatnim elementem tej tablicy zawsze jest znak o kodzie 0.
Napisy i wskaźniki Napisy mogπ byê odczytywane z pomocπ wskaüników: char *s = "Ala ma kota"; // s zawiera adres poczπtku napisu cout << s << endl; // Drukowanie napisu // Drukowanie napisu for (int i = 0; i < 11; i++) cout << s[i]; // Drukuj znak pod adresem s + i cout << endl; // Drukowanie napisu do cout << *s; // Drukuj znak pod adresem s while (*s++); cout << endl;
Tablice typu char Kaødπ tablicí o elementach typu char moøna wykorzystaê do drukowania ciπgu znaków. W tym celu adres poszπtku tablicy przekazuje sií do cout, jakbyby on adresem napisu: char tab[6]; tab[5] = \0 ; // Znak o kodzie 0. tab[0] = A ; for (int i = 1; i < 5; i++) tab[i] = tab[i-1] + 1; cout << tab << endl; Drukowanie koòczy sií w momencie napotkania znaku \0.
Argumenty programu Argumenty funkcji main() argc liczba argumentów programu + 1. *argv[] tablica wskaüników zawierajπcych adresy argumentów programu. int main(int argc, char *argv[]) 1 Pierwszy (o indeksie 0) element argv[] to adres nazwy programu (uøytej do uruchomienia go). 2 Element argv[] o indeksie argc ma wartoúê NULL (czyli 0). 3 Pozosta e elementy argv[] wskazujπ na ciπgi znaków wprowadzone w linii poleceò (po nazwie programu).
Argumenty programu 1 Argumenty programu rozdziela sií spacjami (tzn. spacja stanowi separator dla argumentów programu). 2 Jeøeli argumentem programu ma byê ciπg znaków zawierajπcy spacjí, trzeba uøyê cudzys owu lub znaku \ (ang. backslash). Przyk ad./program raz dwa trzy cztery Drukowanie argumentów programu for (int j = 1; j < argc; j++) cout << argv[j] << endl;
Wykorzystanie pamięci przez program Zasady wykorzystania pamiíci 1 Kaødy program jest wykonywany jako jedno z wielu zadaò (ang. task). 2 Zadania nie mogπ przeszkadzaê sobie nawzajem. 3 Kaøde zadanie dysponuje w asnπ pamiíciπ. ZawartoúÊ pamiíci wykorzystywanej przez zadanie (na ogó ) nie moøe byê modyfikowana przez inne zadania. Jπdro systemu operacyjnego przydziela pamiíê zadaniom wed ug potrzeb. PamiÍÊ statyczna 1 Kod (ang. code) rozkazy dla procesora. 2 Dane tylko do odczytu (np. litera y tekstowe). 3 Zmienne globalne i statyczne (wartoúê przeøywa zakoòczenie wykonywania funkcji).
Pamięć dynamiczna Zmienne lokalne 1 Tworzone przed rozpoczíciem wykonywania bloku (np. treúci funkcji) lub w trakcie wykonywania go. 2 Usuwane po zakoòczeniu wykonywania bloku. 3 Znajdujπ sií w obszarze pamiíci zwanym stosem (ang. stack), w którym pamiíê jest przydzielana automatycznie (przez jπdro systemu operacyjnego). Stos jest wykorzystywany takøe do innych celów (argumenty funkcji, adresy powrotne dla funkcji) i moøe mieê ograniczone rozmiary. PamiÍÊ rezerwowana na øπdanie Przydzielana programowi po zg oszeniu przez niego jawnego zapotrzebowania na okreúlonπ iloúê pamiíci.
Pamięć dynamiczna Rezerwowanie pamiíci z pomocπ new Zasady Rezerwowanie pamiíci na zmiennπ: ptr = new double; Rezerwowanie pamiíci na tablicí: ptr = new double[n]; 1 Typ danych wskaünika ptr musi odpowiadaê typowi danych rezerwowanej zmiennej lub tablicy (void * pasuje do kaødego typu danych). 2 W przypadku niepowodzenia wskaünik ptr bídzie mieê wartoúê NULL (czyli 0).
Pamięć dynamiczna PamiÍÊ przydzielona na øπdanie pozostaje do dyspozycji programu, aø zostanie przez niego jawnie zwolniona (nie ma znaczenia to, w którym miejscu programu zosta a ona zarezerwowana). Zwalnianie pamiíci z pomocπ delete Zwalnianie pamiíci zarezerwowanej na zmiennπ: delete ptr; Zwalnianie pamiíci zarezerwowanej na tablicí: delete [] ptr; Wskaünik ptr musi zawieraê adres obszaru pamiíci przydzielonego na øπdanie.
Pamięć dynamiczna PamiÍÊ rezerwowana na øπdanie i wskaüniki 1 Zmienne rezerwowane z pomocπ new sπ dostípne tylko przez wskaüniki (jest to g ówne zastosowanie wskaüników w C++). Trzeba przechowywaê ich adresy, øeby moøna by o zwolniê pamiíê zajmowanπ przez nie. 2 Tablice rezerwowane z pomocπ new majπ takie same w asnoúci, jak zwyk e tablice, ale nie majπ nazw (sπ dostípne tylko za poúrednictwem wskaüników). Zbyt czíste rezerwowania i zwalnianie pamiíci moøe powodowaê problemy Po zwolnieniu pamiíê moøe byê przydzielona innym zadaniom. NastÍpnym razem moøemy dostaê pamiíê z innego obszaru. Moøe to prowadziê do rozdrobnienia pamiíci (ang. memory fragmentation).
Zmienne w pamięci statycznej Zmienne globalne 1 Definiowane na zewnπtrz definicji funkcji. 2 Tworzone przy uruchamianiu programu. 3 Ich zasiíg (ang. scope) obejmuje ca y program (deklaracje muszπ byê widoczne dla funkcji, które majπ z nich korzystaê). Zmienne statyczne 1 Definiowane ze s owem kluczowym static. 2 Tworzone przy uruchamianiu programu. 3 Ich zasiíg zaleøy od miejsca,w którym sπ zdefiniowane. Zdefiniowane na zewnπtrz funkcji sπ widoczne od pierwszej deklaracji do koòca pliku zawierajπcego ich definicje. Zdefiniowane wewnπtrz bloku sπ widoczne tylko w tym bloku, ale ich wartoúci przeøywajπ zakoòczenie wykonywania go.
Dyrektywy preprocesora Kod üród owy programu w C++ jest wstípnie przetwarzany przed w aúciwπ kompilacjπ przez program zwany preprocesorem (ang. preprocessor). Dyrektywy preprocesora Preprocesor wykonuje tzw. dyrektywy (ang. directive) okreúlajπce pewne przekszta cenia kodu üród owego. Dyrektywy preprocesora zaczynajπ sií znakiem #. Dyrektywa #include #include nakazuje w πczenie do przetwarzanego pliku zawartoúci innego pliku o podanej úcieøce. åcieøka w cudzys owie wzglídem bieøπcego katalogu. åcieøka w nawiasie <>jest interpretowana w specjalny sposób.
Dyrektywy preprocesora Dyrektywa #define #define pozwala na zdefiniowanie dowolnego symbolu, który jest rozwijany przed w aúciwπ kompilacjπ, np.: #define M_PI 3.14159265358979323846 Makrodefinicja (ang. macrodefinition, macro) Definicja symbolu z pomocπ #define. Makrodefinicja musi mieúciê sií w 1 wierszu(moøna go przed uøyê uøywajπc znaków\ do sklejania wierszy).
Dyrektywy preprocesora Makrodefinicja moøe zaleøeê od pewnej liczby parametrów, które sπ zastípowane odpowiednimi ciπgami znaków przez preprocesor, np.: #define MAX(a,b) ((a) > (b)? (a) : (b)) Kompilacja warunkowa Uzaleønienie sposobu kompilacji programu od spe nienia (lub nie) okreúlonych warunków. Do tego celu s uøπ dyrektywy #if, #ifdef, #ifndef, #else oraz #endif, np.:
Dyrektywy preprocesora
Dyrektywy preprocesora