Tablice
Transkrypt
Tablice
Tablice • grupa obiektów tego samego typu tablica • tablica – ciąg obiektów tego samego typu, zajmujący ciągły obszar w pamięci • korzyść – zamiast wielu definicji poszczególnych obiektów jedna wspólna; odniesienia do n-tego elementu tablicy • składnia definicji typ nazwa_tablicy[rozmiar] np. int a[10]; rezerwuje w pamięci miejsce dla 10 liczb typu int rozmiar – to musi być stała, znana juŜ w trakcie kompilacji; niedopuszczalne rozwiązanie jak niŜej: int rozmiar; cout << ”Jaki ma być rozmiar tabeli ? ”; cin >> rozmiar; { int tablica[rozmiar]; /* TU JEST ŹLE */ ... } • moŜliwość definicji tablicy o wielkości dopasowanej do potrzeb – dynamiczna alokacja tablicy Elementy tablicy • definicja int a[4]; określa tablicę 4-elementów: a[0], a[1], a[2], a[3] • numeracja zaczyna się od zera, kończy na (rozmiar–1) (inaczej niŜ w innych językach !!!) • gdy się będziemy odnosili do zakresu 1..rozmiar, to: o zapominamy o a[0] tracimy jeden element o próbujemy zmieniać wartość elementu a[rozmiar] – to juŜ nie jest element tablicy o kompilator nie kontroluje dopuszczalnego zakresu indeksu tablicy, nie blokuje próby zmiany wartości a[rozmiar] – to na ogół prowadzi do zniszczenia zawartości innej komórki pamięci (zwykle przyległej do tablicy a) Przykład: int a, b[4], c; int k; a=c=0; for (k=0; k<=4; k++) b[k]=2*k+1; cout << a << endl; a=9 Wnioski: • tablica n-elementowa: numeracja 0..n-1 • element o indeksie n (czyli a[n]) nie istnieje • kompilator nie zajmuje się sprawdzaniem poprawności zakresu – to jest rola programisty • zysk – szybszy dostęp do tablicy • próba podstawienia elementu o indeksie n – zniszczenie wartości innych zmiennych Obsługa tablic • na ogół pętle for; dwie moŜliwe formy for (k=0; k<rozmiar; k++) lub: for (k=0; k<=rozmiar-1; k++) Inicjalizacja tablic • podobnie jak dla typów fundamentalnych moŜliwa inicjalizacja w trakcie definicji int a[4] = {15,3,0,-7}; /*równowaŜne poniŜszemu ciągowi*/ int a[4]; a[0]=15; a[1]=3; a[2]=0; a[3]=-7; • gdy lista krótsza niŜ rozmiar tablicy – pozostałe elementy podstawiane zerami int a[4] = {15, 3}; // a[0]=15; a[1]=3; a[2]=0; a[3]=0; int a[4] = { }; // zerowanie tablicy • gdy lista dłuŜsza niŜ rozmiar tablicy – błąd kompilacji int a[4] = {15, 3, 0, -7, 8} // błąd kompilacji !!! • jeszcze jedna forma definicji i inicjalizacji zbiorczej int a[ ] = {1, 2, 3, 4, 5, 6}; o nie podajemy rozmiaru tablicy o kompilator liczy ilość elementów w liście, na tej podstawie ustala rozmiar tablicy, rezerwuje pamięć i ją inicjalizuje Działania na tablicach • w C nie ma globalnych operacji na tablicach • musimy określić działanie dla kaŜdej ze składowych int a[4] = {0, 1, 2, 3}; int b[4] = {1, 2, 3, 4}; int c[4], k; c = a + b; // operacja nielegalna !!!!! for (k=0; k<4; k++) c[k] = a[k] + b[k]; Tablica jako argument funkcji • dwa przypadki o funkcja zaleŜy od pojedynczego (skalarnego) argumentu i jest wywoływana z argumentem aktualnym równym jednemu z elementów tablicy o funkcja zaleŜy od tablicy jako całości • przypadek pierwszy int kwadrat(int k) { return k*k; } int main() { int w1, w2, m=5; int a[100]={1,2,3,4,5,6}; w1=kwadrat(m); w2=kwadrat(a[4]); } • przekazanie poprzez wartość (jak do tej pory) • mechanizm taki sam, jak dla typów fundamentalnych • przypadek drugi Przykłady: • funkcja, która oblicza długość wektora reprezentowanego przez tablicę • transformacja tablicy zawierającej temp. Fahrenheita na tablicę z temperaturami w skali Celsjusza Problemy: • jak przekazać całą tablicę (często – duŜo wartości)? • jak zwrócić wynik, który jest tablicą? Rozwiązanie – przekazanie tablicy przez adres. Jest ono oparte na dwóch zasadach ogólnych: 1. Tablicę przekazujemy do funkcji podając adres jej początku 2. Nazwa tablicy jest jednocześnie adresem jej zerowego elementu #include <cmath> #include <iostream> using namespace std; double dlugosc(double x[ ], int n) { int k; double s=0.0; for (k=0; k<n; k++) s+=x[k]*x[k]; return sqrt(s); } int main() { double a[3]={1.1, -0.7, 1.0}; cout << “Dlugosc wektora ddd = ” << dlugosc(a, 3)) << endl; } void konwertuj_t(float t[ ], int n) { int k; for (k=0; k<n; k++) t[k]=5.0*(t[k] – 32.0)/9.0; } int main() { int m; float temp[101]; for (m=0; m<101; m++) temp[m]=m; konwertuj_t(temp, 101); } UWAGI: • wywołanie tablicy jako argumentu funkcji – wypisanie nazwy tablicy (bez nawiasów) – to określa adres początku tablicy • w definicji funkcji (nagłówku) – określamy tylko typ tablicy; nie określamy jej rozmiaru. Dlatego funkcja będzie działać dobrze dla kaŜdej tablicy danego typu (bez względu na rozmiar) • podanie tylko adresu początku nie określa rozmiaru tablicy – zwykle potrzebny drugi parametr określający jej rozmiar • przekazany adres słuŜy do zbudowania takiego sposobu obsługi tablicy t[ ] w funkcji, by odniesienie się do t[5] było dokładnie tym, co odniesienie się do temp[5] – manipulujemy oryginałem tablicy • jeŜeli d[ ] – tablica (np. int d[100]; ) to: o d – adres zerowego elementu o d – stała (nie wolno zmieniać jej wartości) o d nie jest l-wartością int d[100]; *wd; wd=d; d = cokolwiek; /* legalna instrukcja */ /* nielegalna instrukcja; błąd !!! */ Tablice znakowe • specjalny, jeden z najczęściej uŜywanych rodzajów tablic; słuŜący do przechowywaniu napisów • definicja (deklaracja) char nazwa[rozmiar]; • przechowywanie tekstu: o kolejne elementy – kody ASCII kolejnych znaków o po umieszczeniu wszystkich znaków kolejnemu elementowy przypisujemy ‘\0’ (inaczej NULL) – znak końca stringu o pozycja znaku NULL – długość stringu o napis w C/C++ - ciąg liter zakończony znakiem NULL – C-string Wniosek: tablica tekstowa o rozmiarze NMAX moŜe przechować string o maksymalnej długości NMAX – 1 Inicjalizacja tablic tekstowych • jak dla wszystkich tablic moŜliwa inicjalizacja przy okazji definicji: • moŜliwe róŜne formy inicjalizacji char napis[80] = { ”kot”} k o t 0 ..... 0 1 2 3 4 5 0 0 78 79 char napis[80] = { ‘k’, ‘o’, ‘t’}; k o t 0 0 0 0 1 2 3 4 5 ..... 0 0 78 79 char napis[ ] = { ”kot”} k o t 0 0 1 2 3 char napis[ ] = { ‘k’, ‘o’, ‘t’}; k o t 0 1 2 • ostatnia inicjalizacja nie daje C-stringu (brak znaku NULL zaznaczającego koniec napisu) – jest to tylko zbiór pojedynczych liter • długość C-stringu – ilość aktualnie przechowywanych znaków #include <cstring> int k; char s[80]=”Ala ma kota”; cout << “sizeof: “<<sizeof(s) << endl; k=0; while(s[k]) k++; cout << “zliczenia: “ <<k << endl; cout << “strlen: “ << strlen(s); Działania na stringach • zbiorcze podstawienie moŜliwe tylko w czasie inicjalizacji; później nie jest moŜliwe: char a[80]; a[80] = ”kot”; a = ”kot”; // operacja niedozwolona !!! // operacja niedozwolona !!! • aby zmienić juŜ istniejący C-string – uŜyj odpowiedniej funkcji Przykłady: kopiowanie stringu z tablicy wzor[ ] do tablicy kopia[ ] #include <iostream> using namespace std; void strcpy(char kopia[], char wzor[]) { int k; for (k=0; ; k++) { kopia[k]=wzor[k]; if (kopia[k]==0) break; } } int main() { char tekst[30]={”To jest napis 1”}; char a[30]; strcpy(a, tekst); cout << a << ”\n”; strcpy(a,”A to jest napis 2”); cout << a << endl; } To jest napis 1 A to jest napis 2 • podawanie rozmiarów tablic niekonieczne – kompilator sam je ustali na podstawie połoŜenie znaku NULL • istnieje wiele moŜliwości realizacji funkcji strcpy – oto przykład: void strcpy(char kopia[ ], char wzor[ ]); { int k=0; while (kopia[k]=wzor[k]) k++; } • niebezpieczeństwo: rozmiar tablicy wzor większy niŜ maksymalny dopuszczalny rozmiar tablicy kopia • zagroŜenie – brak sprawdzenia dopuszczalnego zakresu indeksu tablicy, zniszczenie zawartości pewnej części pamięci • rozwiązanie - modyfikacja: funkcja strncpy – kopiowanie maksymalnie n znaków void strncpy(char kopia[ ],char wzor[ ],int n); { int k=0; while (k<n && (kopia[k]=wzor[k])) k++; kopia[k]=0; } • wiele moŜliwych funkcji operujących na stringach • wiele juŜ gotowych – część biblioteki standardowej • uŜycie wymaga włączenia string.h (lub cstring) Przykłady - string.h (lub cstring) char *strcpy(s, ct) kopiuje tekst z ct do s łącznie ze znakiem ‘\0’; zwraca s char *strncpy(s,ct,n) kopiuje co najwyŜej n znaków z ct do s; zwraca s char *strcat(s, ct) dopisuje znaki z ct na koniec s; zwraca s char *strncat(s,ct,n) dopisuje co najwyŜej n znaków z ct na koniec s; kończy s znakiem ‘\0’, zwraca s Int strcmp(cs, ct) porównuje teksty zawarte w cs i ct; zwraca wartość<0 gdy cs<ct; zero gdy cs==ct; >0 gdy cs>ct char *strchr(cs,c) zwraca wskaźnik do pierwszego wystąpienia znaku c w tekście cs lub NULL, jeśli ten znak nie występuje Stringi – podsumowanie: • • • • • kolejne znaki – kolejne miejsca tabeli koniec stringu – znak NULL nazwa – jednoznaczny adres początku tablicy do funkcji – wysyłamy nazwę (adres stringu) nie podajemy długości – funkcja moŜe wyznaczyć ją sama Tablice wielowymiarowe • tablice moŜna tworzyć z róŜnych obiektów – w szczególności z tablic; tablica wielowymiarowa float aaa[4][2]; /* aaa jest tablicą czterech elementów, z których kaŜdy jest tablicą dwuelementową liczb typu float */ • obowiązuje zapis: aaa[i][j] a nie aaa[i,j] • elementy tablicy aaa[4][2]: aaa[0][0] aaa[2][0] aaa[0][1] aaa[2][1] aaa[1][0] aaa[3][0] aaa[1][1] aaa[3][1] przechowywane są w pamięci tak, by najszybciej zmieniał się skrajny prawy indeks • inicjalizacja zbiorcza float aaa[4][2]={1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0} jest równowaŜna ciągowi instrukcji: aaa[0][0]=1.0; aaa[0][1]=2.0; aaa[1][0]=3.0; ... aaa[4][0]=7.0; aaa[4][1]=8.0; • wyznaczenie względnego adresu elementu tablicy t[N][M] – tablica dwuwymiarowa element t[1][0] – przesunięty o M pozycji w stosunku do t[0][0] element t[i][j] – przesunięty o i*M + j pozycji w stosunku do t[0][0] Wniosek: znajomość M konieczna do wyznaczenia offsetu – waŜne gdy przekazujemy tablicę wielowymiarową jako argument funkcji • tablice wielowymiarowe jako argumenty funkcji #include <iostream> #define N 4 #define M 3 #define K 2 using namespace std; void mno_mac(int a[ ][M], int b[ ][K], int c[ ][K]) { int n,m,k,s; for (n=0; n<N; n++) { for (k=0; k<K; k++) { for (s=0,m=0; m<M; m++) s+=a[n][m]*b[m][k]; c[n][k]=s; } } } int main() { int aa[N][M]={1,2,3, 1,2,3, 1,2,3, 1,2,3}; int bb[M][K]={1,2,1,2,1,2}; int cc[N][K]; mno_mac(aa, bb, cc); cout << cc[0][0] << cc[0][1]) << eoln; } Musimy przekazać: • typ elementów tablicy • wymiary tablicy (oprócz lewego skrajnego) • bliski związek tablic i wskaźników – temat następnego wykładu