Podstawy informatyki 2013/2014 Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego Matuszyka
Tablice Tablica – agregat składający się z określonej liczby obiektów tego samego typu zajmujący w pamięci ciągły obszar Typy tablicowe są typami pochodnymi od dostępnych typów danych Typ tablicowy pochodzący od typu T oznaczany jest jako T[]
typ_elementu nazwa_tablicy[liczba_elementów]; Tablice Postać definicji tablicy: typ_elementu nazwa_tablicy[liczba_elementów]; Typ elementu: jeden z typów fundamentalnych (z wyjątkiem void) typ wyliczeniowy (enum) wskaźnik inna tablica typ zdefiniowany przez użytkownika wskaźnik do pokazywania na składniki klasy Liczba elementów rozmiar tablicy musi być wyrażeniem stałym (lub stałą), o wartości znanej w trakcie kompilacji Nazwa tablicy jest jednocześnie adresem jej zerowego elementu!
Definiowanie tablic - przykłady int a[4]; - 4-ro elementowa tablica elementów typy int const int n=10; char tab[n]; int n=10; Niektóre kompilatory dopuszczają powyższą formę, ale nie jest ona zgodna ze standardem języka (taki zapis nie „kompiluje się” na kartce). a a[0] a[1] a[2] OK a[3] BŁĄD
Stała, ale nie jest znana na etapie kompilacji Definiowanie tablic Próba oszukania kompilatora int a; cin>>a; const int n=a; int tab[n]; się nie uda. Będzie błąd. Stała, ale nie jest znana na etapie kompilacji
Odwołania do elementów tablicy Do elementów tablicy można się odwołać korzystając z operatora indeksowania [] Elementy tablicy są numerowane kolejnymi liczbami naturalnymi, począwszy od zera Numery elementów w tablicy nazywane są indeksami. Tablica n-elementowa ma elementy o indeksach od 0 do n-1 const int n=5; int tab[n]; tab[0]=1; tab[1]=tab[0]; tab[2]=4; tab[3]=tab[4]=7; tab[5]=10; Element o indeksie 5 nie istnieje
Inicjalizacja tablic Tablice podobnie jak inne zmienne można inicjalizować podczas definiowania Inicjalizacja tablicy – nadanie wartości elementom tablicy w momencie jej definicji. int t[4]={1,2,3,4}; jest równoważne z int t[4]; t[0]=1; t[1]=2; t[2]=3; t[3]=4; Tablice można zainicjalizować częściowo: int t[4]={1,2}; int t[4]={1,2,0,0};
Inicjalizacja tablic Lista inicjalizacyjna tablicy może zawierać stałe oraz zmienne: int n1 = 0, n2 = 1, n3 = 2; int t[4] = {n1, n2, n3, 5};
Inicjalizacja tablic Możliwe jest zdefiniowanie tablicy bez jawnego podawania jej rozmiaru – wymaga to jednak jednoczesnej inicjalizacji tablicy. Kompilator na podstawie liczby elementów podanych na liście inicjalizacyjnej rezerwuje odpowiednią ilość miejsca pamięci: int t[]={1,2,3,4};
Odwołania do elementów tablicy Najczęstszą konstrukcją języka używaną do przetwarzania tablic jest pętla for const int n = 4; int t[n]; for(int i = 0; i < n; i++) t[i] = i+2; for(int i = 0; i <= n - 1; i++)
Wypisywanie tablic int main() { const int n=4; int tab[n]; for (int i=0; i<n; i++) tab[i]=i+2; cout<<tab<<endl; cout<<tab[i]<<endl; return 0; } 0027FD40 2 3 4 5
Stałe tablice Stała tablica – tablica zawierająca stałe elementy. Tablica obiektów stałych musi podczas definicji zostać zainicjalizowana co najmniej jednym elementem lub pustą listą inicjalizacyjną. Elementy niezainicjalizowane są inicjalizowane zerami odpowiedniego typu.
Stałe tablice const int a = 7; int b = 7; const int t1[3] = {2, 4, a}; const int t2[3] = {2, 4, b}; const int t3[3] = {2}; //równoważne {2,0,0} const int t4[3] = {}; //równoważne {0,0,0} const int t5[3]; BŁĄD t1[1] = 0; BŁĄD
char tekst[20]={"przyklad"}; Tablice znakowe Tablice ciągów znaków reprezentujące napis – na końcu zawsze dodawany jest znak o kodzie 0, NULL: '\0‘ char tekst[20]={"przyklad"}; Znak NULL jest automatycznie dodawany do końca ciągu, ponieważ ciąg "przyklad" ujęto w cudzysłów.
Tablice znakowe AGH ----- AGH-- 3 AGH 3 AGH - int main() { char tab1[]={"AGH"}; char tab2[]={"-----"}; char tab3[]={"-----"}; cout<<tab1<<endl<<tab2<<endl<<tab3<<endl; int i=0; while (tab1[i]) tab2[i]=tab1[i]; i++; } cout<<tab2<<endl; cout<<i<<endl; i=-1; while (i++,tab3[i]=tab1[i]); cout<<tab3<<endl; for (i=0; i<5; i++) cout<<tab3[i]; cout<<endl; return 0; AGH ----- AGH-- 3 AGH 3 AGH -
char tekst[20] = {'p','r','z','y','k','l','a','d'}; Tablice znakowe Tablice o elementach typu znakowego nie muszą być traktowane jako napisy: char tekst[20] = {'p','r','z','y','k','l','a','d'}; tu jest znak Null ponieważ reszta tablicy jest wypełniana zerami char tekst[] = {'p','r','z','y','k','l','a','d'}; tu znaku Null nie będzie ponieważ zostanie stworzona tablica 8 elementowa
Tablice wielowymiarowe Postać definicji tablicy: typ_elementu nazwa_tablicy[l1][l2]...[ln]; Liczba elementów tablicy równa jest l1*l2*...*ln Liczba wymiarów tablicy odpowiada liczbie użytych par nawiasów kwadratowych [] w definicji, np.: // tablica dwuwymiarowa, łącznie 16 elementów double tab2D[2][8]; // tablica czterowymiarowa, łącznie 400 elementów unsigned int tab4D[2][2][5][20];
Tablice wielowymiarowe Deklaracja wymiarów umożliwia obliczenie położenia elementów tablicy w kolejnych wymiarów, np.: int a[2][4]; Element a[i][j] leży w stosunku do początku tablicy o (i*4)+j elementów od początku tablicy Statycznie zdefiniowana tablica wielowymiarowa może być traktowana jako ciągły obszar pamięci a a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[1][2] leży 1*4+2 elementów od początku tablicy
Przekazywanie tablic do funkcji Argumentem formalnym funkcji może być typ tablicowy. Jako argument aktualny przesyłany jest tylko adres początku tablicy. Rozmiar tablicy można przekazać tylko jawnie jako inny parametr funkcji. // deklaracja funkcji int fx1(double tab[]); int fx2(double tab[], unsigned int size); // definicja tablicy double tablica[4]; // wywołania funkcji: fx1(tablica); fx2(tablica, 4);
Przekazywanie tablic do funkcji int fx1(double tab[]); double tablica[4]; W wywołaniu funkcji przyjmującej tablicę podajemy tylko nazwę tablicy! fx1(tablica); fx1(tablica[]); fx1(tablica[4]); OK Błąd kompilacji Próba wysłania do funkcji elementu tablicy o indeksie 4 (JEDNEJ LICZBY)
Przekazywanie tablic do funkcji void f(double t[]) { cout<<sizeof(t)<<endl; } int main() double t[5]; f(t); return 0; 4 40
Przekazywanie tablic do funkcji Przekazanie do funkcji tablicy n–wymiarowej wymaga zadeklarowania co najmniej n-1 ostatnich wymiarów void fx3D(float tab[][4][3]); void fx3D(float tab[][][3]); BŁĄD Podane wymiary muszą być wyrażeniami stałymi, znanymi na etapie kompilacji Zadeklarowane wymiary są używane przez kompilator w celu: sprawdzenia zgodności typu tablicowego z argumentem aktualnym – przy wywołaniu funkcji obliczenia położenia kolejnych wierszy najwyższego wymiaru tablicy i wierszy następnych wymiarów – wewnątrz ciała funkcji
Przekazywanie tablic do funkcji const int a = 4, b = 3; void fx3D(int tab[][a][b], int n) { int l = 0; for (int i = 0; i < n; i++) for (int j = 0; j < a; j++) for (int k = 0; k < b; k++) tab[i][j][k] = l++; } int main() int t1[5][4][3]; int t2[5][3][4]; fx3D(t1,5); fx3D(t2,5); BŁĄD
Przekazywanie tablic do funkcji #include<iostream> using namespace std; void podwoj(int a[][2]); void wypisz(int a[][2]); int main() { int tab[][2]={1,2,3,4}; wypisz(tab); podwoj(tab); return 0; } void podwoj(int a[][2]) { for (int i=0; i<2; i++) for (int j=0; j<2; j++) a[i][j]*=2; } wypisz(a); void wypisz(int a[][2]) cout << a[i][j] << "\t"; cout << endl; 2 4 6 8 2 3 4 2 4 6 8
Wskaźniki Dla typu T zapis T* oznacza wskaźnik do T, czyli zmienna typu T* może przechowywać adres (w pamięci) obiektu typu T. Podstawową operacją na wskaźniku jest wyłuskanie, czyli odwołanie się do obiektu pokazywanego przez ten wskaźnik. Operatorem adresowania pośredniego jest przedrostkowy jednoargumentowy operator (wyłuskania) *. Operatorem dualnym do operatora wyłuskania jest przedrostkowy jednoargumentowy operator pobrania adresu & (zwraca adres obiektu).
Wskaźniki int a = 1; int* p = &a; cout << a; cout << *p; cout << p; cout << &a; 1 1 0032FEA0 0032FEA0
Wskaźniki Z definicji wskaźnika wynika, że wskaźnik pokazuje na obiekt. Referencja nie jest obiektem nie można definiować wskaźnika do referencji. Obiekt rejestrowy nie ma adresu nie można definiować wskaźnika do obiektu rejestrowego. Typ wskaźnika precyzyjnie określa, na jakie obiekty można takim wskaźnikiem pokazywać. Generalnie, wskaźnikiem typu A* nie można pokazywać na obiekty typu B.
Wskaźniki Definicja wskaźnika tworzy jedynie obiekt wskaźnikowy – wskaźnik nie pokazuje na konkretny obiekt: int* p; // p pokazuje na "nie-wiadomo-co" Aby bezpiecznie używać wskaźnika, należy go ustawić: p = &n; // teraz p pokazuje na n Najbezpieczniej ustawić wskaźnik od razu (w definicji): int* p = 0; // p pokazuje na adres 0x00000000 Żaden obiekt nie może być umieszczony w pamięci pod adresem 0. Zero pełni rolę literału wskaźnikowego, oznaczającego wskaźnik, który nie pokazuje na żaden obiekt.
Tablice a wskaźniki Tablica (lub jej początek) jest obiektem w pamięci, ma więc zatem swój własny adres. Nazwa tablicy tab typu T[] jest adresem jej pierwszego elementu &tab[0] Tablica tab typu T[] może być niejawnie skonwertowana do typu T*; Rezultatem jest wskaźnik do pierwszego elementu tablicy tab.
Tablice a wskaźniki Jeżeli mamy wskaźnik T* p, to operatory: * oraz [] działają identycznie: *p == p[0] *(p + i) == p[i] Przemienność dodawania: a+b=b+a *(p+i)==*(i+p) p[0]==0[p]
Tablice a wskaźniki int t[] = {1, 4, -2}; // zmieniamy trzeci element tablicy *(t+2) = 5; // wynik: {1, 4, 5} int * p; // równoważnie można zapisać: p = &t[0]; // *p wynosi 1 p = t; // *p wynosi 1 // p wskazuje na ostatni element p = &t[2]; // *p wynosi 5 p = t+2; // *p wynosi 5
Tablice a wskaźniki Jeżeli wskaźnik pokazuje na element tablicy można użyć go zgodnie z notacją typową dla tablic: double tab[5] = {-2,-1,0,1,2}; double * ptr; ptr = &tab[0]; // lub: ptr = tab; for (int i = 0; i < 5; i++) cout << ptr[i] << ' ' << tab[i] << endl;
Wskaźnik taki można przesuwać po elementach tablicy: Tablice a wskaźniki Wskaźnik taki można przesuwać po elementach tablicy: ptr++; for (int i = 0; i < 4; i++) cout << ptr[i] << ' ' << tab[i] << endl; //tab++; BŁĄD
Tablice a wskaźniki Tablica obiektów typu int: int a[10]; // a typu: int [10] int* pn = &a[3]; // pn typu: int* *pn = 7; // a[3] = 7 int* pa = a; // pa typu: int* // nastąpiła konwersja: int[] int* pa[3] = 7; // a[3] = 7 Tablica obiektów typu int* (wskaźników): int n; // n typu: int int* ap[10]; // ap typu: int *[10] ap[3] = &n; // ustawiamy adres n *ap[3] = 7; // n = 7
Wskaźniki w argumentach funkcji #include<iostream> using namespace std; void podwoj(int *wsk); int main() { int a=2; cout << a << endl; podwoj(&a); return 0; } void podwoj(int *wsk) *wsk*=2; 2 4
cout<<&ww<<endl; cout<<ww<<endl; int, int*, int** int a=1; int *w=&a; int **ww=&w; cout<<&ww<<endl; cout<<ww<<endl; cout<<*ww<<endl; cout<<**ww<<endl; 0043F8CC 0043F8CF 1 a 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww 0043F8D4 0043F8D0 0043F8CC 1
void f1(int fa){} int main() { int a=1; int *w=&a; int **ww=&w; f1(a); void f1(int), void f3(int*), void f5(int**) void f2(int&), void f4(int*&), void f6(int**&) void f1(int fa){} int main() { int a=1; int *w=&a; int **ww=&w; f1(a); return 0; } 0043F8CC 0043F8CF 1 a Nie możemy zmienić: a, w, ww Możemy zmienić: 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww 0043F8D8 0043F8DB 1 fa
void f2(int &fa){} int main() { int a=1; int *w=&a; int **ww=&w; void f1(int), void f3(int*), void f5(int**) void f2(int&), void f4(int*&), void f6(int**&) void f2(int &fa){} int main() { int a=1; int *w=&a; int **ww=&w; f2(a); return 0; } 0043F8CC 0043F8CF 1 a fa Nie możemy zmienić: w, ww Możemy zmienić: a 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww
void f3(int *fw){} int main() { int a=1; int *w=&a; int **ww=&w; void f1(int), void f3(int*), void f5(int**) void f2(int&), void f4(int*&), void f6(int**&) void f3(int *fw){} int main() { int a=1; int *w=&a; int **ww=&w; f3(w); return 0; } 0043F8CC 0043F8CF 1 a Nie możemy zmienić: w, ww Możemy zmienić: a 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww 0043F8D8 0043F8DB 0043f8CC fw
void f4(int *&fw){} int main() { int a=1; int *w=&a; int **ww=&w; void f1(int), void f3(int*), void f5(int**) void f2(int&), void f4(int*&), void f6(int**&) void f4(int *&fw){} int main() { int a=1; int *w=&a; int **ww=&w; f4(w); return 0; } 0043F8CC 0043F8CF 1 a Nie możemy zmienić: ww Możemy zmienić: a, w 0043F8D0 0043F8D3 0043F8CC w fw 0043F8D4 0043F8D7 0043F8D0 ww
void f(int), void f(int. ), void f(int. ) void f(int&), void f(int void f(int), void f(int*), void f(int**) void f(int&), void f(int*&), void f(int**&) void f5(int **fww){} int main() { int a=1; int *w=&a; int **ww=&w; f5(ww); return 0; } 0043F8CC 0043F8CF 1 a Nie możemy zmienić: ww Możemy zmienić: a, w 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww 0043F8D8 0043F8DB 0043F8D0 fww
void f(int), void f(int. ), void f(int. ) void f(int&), void f(int void f(int), void f(int*), void f(int**) void f(int&), void f(int*&), void f(int**&) void f6(int **&fww){} int main() { int a=1; int *w=&a; int **ww=&w; f6(ww); return 0; } 0043F8CC 0043F8CF 1 a Nie możemy zmienić: Możemy zmienić: a, w, ww 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww fww
Możliwe wywołania funkcji int a=1; int *w=&a; int **ww=&w; 0043F8CC 0043F8CF 1 a 0043F8D0 0043F8D3 0043F8CC w 0043F8D4 0043F8D7 0043F8D0 ww int a; int *w; int **ww; void f1(int); f1(a); f1(*w); f1(**ww); void f2(int&); f2(a); f2(*w); f2(**ww); void f3(int*); f3(&a); f3(w); f3(*ww); void f4(int*&); -- f4(w); f4(*ww); void f5(int**); -- f5(&w); f5(ww); void f6(int**&); -- -- f6(ww);
Prezentacja udostępniona na licencji Creative Commons: Uznanie autorstwa, Na tych samych warunkach 3.0. Pewne prawa zastrzeżone na rzecz autorów. Zezwala się na dowolne wykorzystywanie treści pod warunkiem wskazania autorów jako właścicieli praw do prezentacji oraz zachowania niniejszej informacji licencyjnej tak długo, jak tylko na utwory zależne będzie udzielana taka sama licencja. Tekst licencji dostępny jest na stronie: http://creativecommons.org/licenses/by-sa/3.0/deed.pl