5.1 Definiowanie tablic

Tablice są podstawową złożoną strukturą danych. Obecne są we wszystkich niemal językach programowania. Generalnie

tablice są uporządkowanymi agregatami danych tego samego typu o wspólnym identyfikatorze (nazwie); do poszczególnych elementów mamy dostęp poprzez ich numer kolejny w tablicy.

Numerowanie elementów zawsze rozpoczyna się od zera, tzn. element pierwszy ma numer 0, a element ostatni numer (indeks) o jeden mniejszy od rozmiaru tablicy (czyli liczby elementów tablicy). Jeśli tab jest tablicą, to jej element o indeksie k jest oznaczany tab[k], a więc używamy tu nawiasów kwadratowych.

Jak zadeklarować/zdefiniować tablicę? Można to zrobić na kilka sposobów. Zajmujemy się na razie tablicami statycznymi, to znaczy, że ich rozmiar musi być znany już na etapie kompilacji programu. Tak więc deklarując tablicę musimy określić jej rozmiar. Tablice statyczne nie będące składowymi obiektów klas będą w czasie wykonania tworzone na stosie (a nie na stercie, czyli w tzw. pamięci wolnej, gdzie tworzone są obiekty). Jeśli tablica statyczna jest zadeklarowana w ciele funkcji (ogólniej: w bloku ograniczonym nawiasami klamrowymi), to jest zmienną lokalną i zostanie usunięta po wyjściu przepływu sterowania z tej funkcji.

Rozpatrzmy tablice zdefiniowane w poniższym przykładzie:

      1.      const int N = 20;
      2.      int tab1[100],
      3.          tab2[N],
      4.          tab3[] = {1,2,3,4,5},
      5.          tab4[5] = {1,2};
      6.  
      7.      int tab5[]{1,2,3,4,5}, // C++11
      8.          tab6[5]{1,2},      // C++11
      9.          tab7[5]{};         // C++11
W linii drugiej powyższego przykładu zdefiniowaliśmy tablicę tab1 o 100 elementach typu int.

W linii trzeciej zdefiniowaliśmy podobną tablicę, ale o 20 elementach. Do zwymiarowania tablicy tab2 użyliśmy wartości zmiennej N i jest to prawidłowe jeśli, jak w tym przykładzie, zmienna ta zadeklarowana została z modyfikatorem const lub, jeszcze lepiej, constexpr, (C++11) i przypisana jej została wartość stała (w naszym przykładzie 20), której nie da się już zmienić. Takie stałe mogą być użyte do wymiarowania tablic, bo ich wartość jest już znana w czasie kompilacji. Natomiast nie mogą być do tego celu użyte zwykłe zmienne całkowite, a więc zadeklarowane bez modyfikatora const (większość kompilatorów dopuszcza taką konstrukcję, ale nie jest to zgodne ze standardem; program staje się zatem nieprzenośny). Takie tablice są zatem statyczne — ustalenia ich rozmiaru nie można odłożyć do etapu wykonywania programu, a więc, na przykład, uzależnić od wartości przez program wczytywanych.

Tablice tworzone na stosie jako zmienne lokalne muszą być deklarowane z rozmiarem, który jest znany w czasie kompilacji; rozmiar ten musi więc być podany jako literał liczbowy, nazwa zmiennej ustalonej (const) lub jako wyrażenie zbudowane z literałów i zmiennych ustalonych (w nowym standardzie C++11 można, i należy, użyć tzw. wyrażenia stałego —  constexpr).

Po takich deklaracjach tablice tab1tab2 zostały utworzone, tzn. został im przydzielony odpowiedni obszar w pamięci, ale wartości poszczególnych elementów nie zostały określone i pozostają niezainicjowane: ich wartości są z punktu widzenia programisty całkowicie przypadkowe.

Inaczej jest w przypadku tab3tab4. Elementy tablicy tab3 będą zainicjowana podanymi wartościami; tablica będzie miała taki rozmiar, jaki wynika z liczby elementów podanych w nawiasach klamrowych. Zauważmy, że w ogóle nie podaliśmy tu jawnie wymiaru: kompilator policzy zainicjowane elementy i dobierze rozmiar sam.

A jak będzie z  tab4? Tu liczbę elementów podaliśmy: ma ich być pięć. Z drugiej strony, zainicjowaliśmy tylko dwa elementy. Otóż kompilator utworzy teraz tablicę pięcioelemenową, zainicjuje dwa pierwsze elementy podanymi liczbami, natomiast pozostałe zainicjuje zerami. Wynika z tego, że jeśli chcemy utorzyć tablicę o określonym wymiarze i od razu wypełnić ją zerami, to możemy użyć składni

       int tab[100000] = {};
bo podanie inicjatora klamrowego, nawet pustego, wymusza zainicjowanie wszystkich elementów zerami.

Zauważmy też, że w nowym standardzie (C++11) znaki równości przed inicjatorem klamrowym mogą być opuszczone, ja to widzimy w wtrzech ostatnich liniach przykładu.

T.R. Werner, 25 lutego 2017; 22:31