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 lub: for (k=0; k<rozmiar; k++) 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; int a[4]; a[0]=15; a[1]=3; a[2]=0; a[3]=-7; /*równowaŝne poniŝszemu ciągowi*/ 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; /* legalna instrukcja */ d = cokolwiek; /* 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 0 0 1 2 3 4 5 78 79 char napis[80] = k, o, t ; k o t 0 0 0... 0 0 0 1 2 3 4 5 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 ; // operacja niedozwolona!!! a = kot ; // 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[0][1] aaa[1][0] aaa[1][1] aaa[2][0] aaa[2][1] aaa[3][0] 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