Tablice danych – przykłady i wykorzystanie w algorytmach
Transkrypt
Tablice danych – przykłady i wykorzystanie w algorytmach
Tablice danych – przykłady i wykorzystanie w algorytmach Charakterystyka tablic Wyobraź sobie, że chcesz napisać program, który pobiera od użytkownika jego oceny, oblicza ich średnią, a na koniec wypisuje wszystkie oceny, z których ta średnia została obliczona. Przy założeniu, że wprowadzimy k liczb, realizacja tego zadania wymagałaby zadeklarowania k zmiennych, a operowanie na nich w wypadku dużych k byłoby niezwykle uciążliwe (zastanów się, jak wyglądałaby deklaracja zmiennych, jeśli wprowadzilibyśmy 100 ocen). Tu właśnie przychodzi nam z pomocą złożona struktura danych, jaką jest tablica. Definicja Tablicą nazywamy ciąg ustalonej liczby elementów tego samego typu, do których odwołujemy się za pośrednictwem wspólnej nazwy. Dostęp do konkretnego elementu tablicy uzyskuje się za pomocą nazwy i indeksów określających pozycję, jaką element zajmuje w tablicy. Tablica może mieć kilka wymiarów lub jeden, w zależności od problemu algorytmicznego. W tablicy jednowymiarowej każdy element jest identyfikowany przez nazwę tablicy i jeden indeks. Na przykład lista uczniów w klasie może być przedstawiona jako jednowymiarowa tablica nazwisk, gdzie indeksem jest numer w dzienniku, czyli jedna liczba. W tablicy dwuwymiarowej zaś każdy element jest jednoznacznie identyfikowany przez nazwę tablicy i parę indeksów. Na przykład w tablicy dwuwymiarowej może być przedstawiony zbiór pionków na szachownicy: każda figura ma podane dwie współrzędne, czyli dwie liczby. W C++ pierwszy element tablicy jednowymiarowej ma indeks równy zeru. Tablice jednowymiarowe Aby pracować z tablicą, musimy najpierw ją zadeklarować, tak jak każdą zmienną w programie. Deklaracja tablicy ma postać: typ_elementow nazwa_tablicy[liczba_elementow]; Stosowane oznaczenia: typ_elementow - należy podać nazwę typu elementów w tablicy; nazw_atablicy - jest dowolną nazwą (podlega tym samym regułom poprawności, co nazwa każdej innej zmiennej); liczba_eiementow - w nawiasie kwadratowym należy podać liczbę elementów tablicy; musi to być liczba podana wprost (tak zwany literat), na przykład 7, lub stała typu całkowitego (zdefiniowana wcześniej). Załóżmy, że chcemy zadeklarować tablicę, w której przechowywać będziemy osiem liczb całkowitych. Jeśli tablica ma mieć nazwę tab, to deklaracja takiej tablicy w programie będzie wyglądać następująco: int tab[8]; Na rycinie 5.1 przedstawiono graficznie część obszaru pamięci komputera, gdzie zadeklarowana została tablica ośmioelementowa liczb całkowitych. Elementy tablicy przedstawiono w postaci prostokątów, a nad prostokątami zapisano przykładowe adresy komórek pamięci komputera - zauważ, że adresy te zmieniają się o 4. Liczba 4 jest rozmiarem typu int elementów tej tablicy. Elementy tablicy zajmują w pamięci komputera obszar ciągły, to znaczy, że każdy kolejny element następuje bezpośrednio po poprzednim. 7840 7844 7848 7852 7856 7860 7864 7868 3 247 0 0 12547 -1 1 -247 tab[0] tab[1] tab[2] tab[3] tab[4] tab[5] tab[6] tab[7] Ryc. 5.1. Tablica jednowymiarowa ośmioelementowa po deklaracji, przed wyletnieniem Ŝądanymi wartościami Kolejne elementy tablicy są identyfikowane jako tab[ 0 ], tab [ 1 ], . . . , Pierwszy element ma indeks o wartości 0, dlatego ósmy element ma indeks o wartości 7. Rycina 5.1 przedstawia tablicę po deklaracji, ale jeszcze przed przypisaniem wartości do tablicy. Zauważ, że tablica nie jest pusta, lecz ma już jakieś wartości. Liczby te są przypadkowymi wartościami, jakie mogą pojawić się w tablicy, zanim wpiszemy do niej właściwe wartości. Tablicę można zadeklarować i od razu nadać wartości jej elementom : za pomocą instrukcji (przy tak skonstruowanej deklaracji nie trzeba wpisywać rozmiaru tablicy, ponieważ kompilator pozna go po liczbie elementów w klamrze): tab [ 7 ]. int tab[] = {6,8,7,2,3,5,7,2}; Tablica będzie teraz wyglądać jak na rycinie 5.2: 6 8 7 2 3 5 7 2 Ryc. 5.2. Tablica jednowymiarowa ośmioelementowa po deklaracji i nadaniu wartości elementom tablicy Ten sposób wypełniania tablicy można wykorzystać tylko na etapie pisania kodu programu. Do wypełnienia tablicy podczas wykonywania programu używać będziemy najczęściej sposobu iteracyjnego, przedstawionego na schemacie blokowym (ryc. 5.3). Schemat przedstawia wypełnianie tablicy o nazwie tab, która ma n elementów (gdzie n oraz zmienna pomocnicza i mają wartości całkowite dodatnie). Ryc. 5.3. Schemat blokowy wypełniania tablicy jednowymiarowej Zmienna i jest licznikową zmienną pomocniczą, która przyjmując wartości indeksów kolejnych elementów tablicy, pozwala przypisać tym elementom pobrane z zewnątrz wartości. Przypominamy, że jeśli tablica o nazwie tab ma n elementów, to jej elementami są: tab[ 0 ], tab[ 1 ], . . . , tab[ n-1 ]. Indeksami tablicy są liczby ze zbioru {0, ..., n - 1}. Odwołanie się do elementu o indeksie innym aniżeli liczba z tego zbioru nazywamy przekroczeniem zakresu tablicy (lub wyjściem poza zakres tablicy). Przekroczenie zakresu, na przykład przy wypełnianiu tablicy, spowoduje zapis poza obszarem pamięci przeznaczonym na zadeklarowaną tablicę. W obszarze tym mogą się znajdować inne zmienne tego programu lub nawet dane innych programów. Wpisanie tam nieprawidłowych wartości grozi więc błędem w programie, zawieszeniem uruchomionego programu lub nawet całego systemu. Jest to niezgodne z regułami języka C+ + . Załóżmy, że tablica jest n-elementowa, gdzie n jest dodatnią liczbą całkowitą, ustaloną w trakcie deklarowania tablicy. Fragment kodu realizujący wypełnianie tablicy liczbami podanymi z klawiatury ma postać: for (int i=0; i<n; i++) { cout « "Podaj wartość elementu"; cin » tab[i]; } Jeśli w programie chcemy wypełnić kilka tablic, to wygodniejsze będzie umieszczenie powyższego kodu w funkcji, którą nazwiemy na przykład wypełnij. Wówczas parametrami przekazywanymi do funkcji będą: tablica, którą chcemy wypełnić, oraz liczba elementów tablicy do wypełnienia. Funkcja, po otrzymaniu tablicy jako argumentu, ma pełen dostęp do zawartości komórek, w których przechowywane są elementy tablicy - działa bezpośrednio na tablicy. Nie jest więc potrzebne odwołanie przez referencję, aby z wnętrza funkcji modyfikować wartości elementów tablicy. W celu sprawdzenia efektów działania funkcji wypełnij zdefiniowaliśmy funkcję wyświetl, która wyświetli nam na ekranie wypełnione tablice: Zwróć uwagę na postać parametru aktualnego przy wywołaniu funkcji wypełnij oraz funkcji wyświetl. Przekazując funkcji tablicę jako argument (czyli w wywołaniu funkcji), podajemy tylko i wyłącznie jej nazwę, bez nawiasów kwadratowych i bez podawania rozmiaru. Do obsługi obydwu tablic zastosowaliśmy zdefiniowane przez nas funkcje. Zauważ, że argumentem przekazywanym do funkcji jest tu właśnie tablica, na której funkcje wykonają zdefiniowane operacje: a zatem jedna z nich wypełni tablicę, druga ją wyświetli. Moglibyśmy za pomocą tych funkcji zainicjować i wyświetlić każdą tablicę o elementach całkowitych, ale pod warunkiem, że miałaby ona osiem elementów. Co więc w wypadku, gdy chcemy wypełnić i wyświetlić kilka tablic, ale o różnej liczbie elementów? Czy musimy napisać kilka różnych funkcji, przeznaczonych dla tablic o różnych liczbach elementów? Oczywiście, nie. Napiszemy jedną funkcję, ale oprócz tablicy przekażemy do jej wnętrza informację o rozmiarze tej tablicy: void wypełnij(int tab[], int rozmiar) { for (int i=0; i<rozmiar; i++) { cout << "Podaj wartość elementu"; cin » tab[i]; } } Zauważ, że deklarując tablicę jako argument funkcji, nie podaliśmy w nawiasach kwadratowych jej rozmiaru - nie ma takiej potrzeby. W wypadku tablicy jednowymiarowej na etapie definicji funkcji kompilatorowi wystarczy znajomość typu elementów tablicy (nieco inaczej wygląda to w tablicach o większej liczbie wymiarów). Fragment funkcji głównej z zastosowaniem zdefiniowanej funkcji wypełniającej dwie tablice o różnych liczbach elementów wygląda następująco: int main() { int tablical[10]; int tablica2[4]; wypełnij(tablical,10); wypełnij(tablica2,4); return 0; } Skoro już wiesz, że tablice mogą być argumentami przekazywanymi do funkcji, to analiza działania poniższej funkcji pozwoli ci łatwo określić, jakie zadanie ma ona do wykonania: float oblicz(float tab_l[5], float tab_2[5]) { float suma = 0; for (int i=0; i<5; i++) suma = suma+tabl[i]+tab2[i] ; return suma; } Funkcja ta pobiera dwie jednowymiarowe tablice pięcioelementowe, a jej wynikiem jest suma wszystkich elementów obu tablic. Moglibyśmy napisać funkcję, która sumuje elementy pojedynczej tablicy, wywołać tę funkcję dla dwóch tablic i zsumować oba wyniki. My dokonaliśmy tego w jednej funkcji, aby pokazać, że funkcji można przekazać więcej niż jedną tablicę.