Tablice

Adam Boduch

Można sobie wyobrazić sytuację, gdy w programie trzeba użyć wielu, naprawdę wielu zmiennych. Czy wygodne jest w takim przypadku deklarowanie dużej liczby zmiennych, z inną nazwą dla każdej? Rozwiązaniem tego problemu są tablice. Tablice są deklarowane jako zmienne za pomocą słowa kluczowego Array.

program arrayApp;

uses
  Dialogs;

var
  Tablica : array[0..1] of String;

begin

end.

Konstrukcja tablic jest dość specyficzna. Po słowie kluczowym array w nawiasach kwadratowych należy wpisać liczbę elementów, z których będzie się składać tablica, a konkretniej ? numery indeksów:

Nazwa_Tablicy : array[Numer_indeksu..Numer_indeksu] of Typ_danych;

W powyższym przypadku tablica składa się z dwóch elementów o indeksach 0 i 1. Należy tu rozróżnić pojęcia element oraz indeks. Popatrzmy na poniższy przykład:

Tablica : array[101..200] of String;

Nic nie stoi na przeszkodzie, aby zadeklarować tablicę 100-elementową o indeksach z zakresu od 101 do 200. W takim przypadku najmniejszym indeksem jest 101, a największym ? 200.

Przydział wartości do zmiennych umieszczonych w tablicy odbywa się także z zastosowaniem nawiasów kwadratowych:

program arrayApp;

var
  Tablica : array[0..1] of String;

begin
  Tablica[0] := 'Pierwszy element tablicy';
  Tablica[1] := 'Drugi element tablicy';
end.

Podsumujmy: z tablic korzysta się tak samo jak ze zmiennych. Jedyną różnicą jest to, że należy zawsze podawać numer elementu (indeks), do którego chce się zapisać lub odczytać dane.

Tablice jako stałe

Wcześniej powiedzieliśmy, że tablice należy deklarować jako zmienne. Owszem, jednak jest możliwe deklarowanie tablic jako stałych. Można więc przyjąć, że tablice to ?specjalny? typ danych, który może być deklarowany zarówno jako zmienna, jak i stała.

Tak jak w przypadku ?zwykłych? stałych, dane także należy przypisać do tablicy podczas projektowania aplikacji:

program arrayConst;

const
  Tablica : array[0..1] of String = (
  ('Pierwszy element'), ('Drugi element')
  );

begin

end.

Także w tym przykładzie tablica składa się z dwóch elementów. Dodatkowe nawiasy zostały wprowadzone jedynie po to, aby zwiększyć czytelność kodu ? równie dobrze można by zapisać program w ten sposób:

program arrayConst;

const
  Tablica : array[0..1] of String = (
  'Pierwszy element', 'Drugi element');

begin

end.

Obowiązkowy jest jedynie jeden nawias, w którym wypisujemy elementy tablicy, oddzielając je przecinkami.

Należy uważać na przydział danych ? zgodnie z liczbą elementów, jakie zostały zadeklarowane w kodzie. Poniższy przykład:

program arrayConst;

const
  Tablica : array[0..2] of String = (
  'Pierwszy element', 'Drugi element');

begin

end.

nie będzie mógł zostać skompilowany ? zadeklarowano trzy elementy, a dane przydzielono jedynie do dwóch. Delphi wyświetli komunikat o błędzie: [Error] arrayConst.dpr(5): Number of elements differs from declaration.

Tablice rekordów jako stałe

Istnieje też możliwość deklarowania jako stałych tablic rekordów. Wszystko co zostało powiedziane dotychczas jest też prawdziwe dla stałych tablic rekordów ale inaczej wygląda przypisywanie wartości. Ponieważ kompilator nie wie, do których pól rekordu ma przypisać daną wartość musimy mu to wskazać. Spójrzmy na poniższy przykład:

type
  TOsoba = record
    Imie: string;
    Nazwisko: string;
    Wiek: Integer;
  end;

const
  Ludzie: array[1..3] of TOsoba = (
    (Imie: 'Adam'; Nazwisko: 'Kowalski'; Wiek: 20;),
    (Imie: 'Jan'; Nazwisko: 'Iksiński'; Wiek: 17;),
    (Imie: 'Zenon'; Nazwisko: 'Kuternoga'; Wiek: 56;));

Jak widać pojedyncza komórka takiej tablicy ma następujący format:

( identyfikator_pola_nr1_rekordu: przypisywana_wartość ; identyfikator_pola_nr2_rekordu: przypisywana_wartość ; identyfikator_pola_nrn_rekordu: przypisywana_wartość ;)

Tablice wielowymiarowe

Delphi umożliwia także deklarowanie tablic tzw. wielowymiarowych. Po zadeklarowaniu takich tablic, do konkretnego elementu możemy odwołać się w następujący sposób:

Tablica[0][0] := 'Przypisanie danych';

lub

Tablica[0, 0] := 'Przypisanie danych';

W powyższym przypadku skorzystałem jedynie z tablic dwuwymiarowych, których deklaracja wygląda tak:

var
  Tablica : array[0..1, 0..1] of String;

Deklaracja jest także specyficzna ? polega bowiem na wypisywaniu indeksów w nawiasie kwadratowym, przy czym poszczególne elementy są oddzielone przecinkami. W przedstawionej wyżej deklaracji mamy aż 4 elementy! Przydział danych odbywa się w następujący sposób:

program arrayarray;

var
  Tablica : array[0..1, 0..1] of String;

begin
  Tablica[0][0] := 'Element 1';
  Tablica[0][1] := 'Element 2';
  Tablica[1][0] := 'Element 3';
  Tablica[1][1] := 'Element 4';
end.

Istotę działania tablic dwuwymiarowych można zrozumieć lepiej, przeglądając listing poniżej.

program arrayarray;

var
  Tablica : array[0..1, 0..2] of String;

begin
  Tablica[0][0] := 'Fiat';
  { marka samochodu }

    Tablica[0][1] := 'Uno';
    Tablica[0][2] := 'Punto';
    { modele samochodów }

  Tablica[1][0] := 'Audi';

    Tablica[1][1] := 'A4';
    Tablica[1][2] := 'A8';

end.

W tym przypadku nastąpiła deklaracja tablicy 2x3. Dwa główne elementy to element Fiat oraz element Audi. Kolejne dwa ?podpola? określają modele samochodów.

Powyższy program poza przydzieleniem danych do tablicy nie wykonuje niczego konkretnego ? prezentuje jedynie, jak należy przypisywać dane do tablicy wielowymiarowej.
Opisując tablice wielowymiarowe, mówiłem tylko o dwóch wymiarach. Istnieje jednak możliwość zadeklarowania tablic, które będą miały wiele wymiarów.

program arrayx3;

var
  Tablica : array[0..1, 0..1, 0..1] of String;

begin
  Tablica[0][0][0] := 'Wartość';
  { itd. }
end.

W tym przypadku nasza tablica to tablica 3x2 typu String. W jaki sposób dane są przydzielane do tej tablicy? Odpowiedni przykład znajduje się w powyższym kodzie źródłowym.

Tablice dynamiczne

Nieraz podczas pracy z Delphi będzie wymagane zadeklarowanie w programie tablicy o niewiadomej liczbie elementów. Znaczy to, że programista w trakcie pisania programu nie jest w stanie określić, ilu elementów tablicy będzie potrzebował. W tym celu w Delphi 4 zaimplementowano możliwość tworzenia tablic dynamicznych. Tablice dynamiczne deklaruje się bez podania liczby elementów:

program dynArray;

var
  Tablica : array of String;

begin

end.

Przy tej okazji opiszę nowe polecenie ? SetLength. Służy ono do określania liczby elementów tablicy podczas działania programu. Pierwszym parametrem tego polecenia jest nazwa tablicy dynamicznej, natomiast drugim ? ilość elementów, z których tablica ma się składać. Parametry przekazywane do polecenia muszą być oddzielane przecinkami:

program dynArray;

var
  Tablica : array of String;

begin
  SetLength(Tablica, 3);
end.

Elementy tablic dynamicznych są zawsze indeksowane od 0

Od tej pory po uruchomieniu programu tablica będzie się składała z trzech elementów. Wypełnianie elementów danymi odbywa się tak samo jak w przypadku zwykłych tablic:

program dynArray;

var
  Tablica : array of String;

begin
  SetLength(Tablica, 3);
  Tablica[0] := 'Wartość 1';
  Tablica[1] := 'Wartość 2';
  Tablica[2] := 'Wartość 3';
end.

W chwili tworzenia programu nie jest możliwe określenie przez kompilator, z ilu elementów ostatecznie będzie składać się tablica. Stąd próba odczytu elementu spoza tablicy w trakcie działania programu może skończyć się komunikatem o błędzie, a nawet zawieszeniem działania aplikacji.

Możliwe jest deklarowanie tablic dynamicznych wielowymiarowych:

program dynArray;

var
Tablica :  array of array of String; // deklaracja tablicy dynamicznej dwuwymiarowej

begin
  SetLength(Tablica, 2, 3); // podanie rozmiaru tablicy
  Tablica[0, 1] := 'Wartość 1';
  Tablica[1, 0] := 'Wartość 2';
  Tablica[2, 3] := 'Wartość 3';
  //do poszczególnych komórek wrzucamy wartości
end.


Polecenia Low i High

Oba polecenia ? Low i High ? użyte w połączeniu z tablicami zwracają najmniejszy (Low) indeks tablicy oraz indeks największy (High). Warto je znać, gdyż czasem mogą się przydać. Należy jednak zauważyć, że owe funkcje nie zwracają wartości najmniejszego oraz największego elementu w tablicy. Najlepiej wytłumaczyć to na przykładzie.

Deklaracja tablicy może, na przykład, wyglądać następująco:

Tablica : array[10..100] of Integer;

Wywołanie polecenia Low(Tablica) spowoduje, że funkcja zwróci wartość 10, natomiast funkcja High zwróci wartość 100.

Odczytywanie wartości o najmniejszym i największym indeksie

Skoro Low i High wyznaczają najmniejszy oraz największy indeks z tablicy, to pytanie brzmi: jak odczyać wartości o najmniejszym i największym indeksie?

Rozwiązanie tego problemu nie jest trudne, ale również wymaga zastosowania funkcji Low oraz High. Otóż elementem o najmniejszym indeksie tablicy może być element o indeksie 0, ale równie dobrze może to być indeks 20. Trzeba więc pobrać numer elementu, a dopiero później go odczytać:

Tablica[Low(Tablica)]; // zwraca wartość elementu o najmniejszym indeksie

Przykładowy program zaprezentowany został poniżej:

program P3_6;

{$APPTYPE CONSOLE}

var
  Tablica : array[10..100] of String;

begin
  Tablica[10] := 'Wartość 10';
  Tablica[100] := 'Wartość 100';

  Writeln('Wartość pierwszego elementu tablicy: ' + Tablica[Low(Tablica)]);
  Writeln('Wartość ostatniego elementu tablicy: ' + Tablica[High(Tablica)]);
  Readln;

end.

Tablice o indeksie typowanym

Często może się zdarzyć, że koniecznym jest zdefiniowanie tablicy, której indeksy odpowiadają całemu zakresowi określonego typu, na przykład chcielibyśmy policzyć w danym tekście, ile jest wystąpień jakiego znaku.

Przy założeniu, że znak nie wystąpi więcej niż 65535 razy (typ Word), definicja tablicy z licznikami dla poszczególnych znaków mogłaby wyglądać tak:

Liczniki : array[0..255] of Word;

Wygodniejszym mogłoby być napisanie:

Licznik : array[Char] of Word;

Definicja taka oznacza, że w tablicy Licznik istnieje element dla każdego indeksu z zakresu Char. Zakres indeksów w postaci podanego typu danych może zostać wyrażony za pomocą każdego typu kardynalnego, również Boolean.

Podczas odwoływania się do elementów tablicy, musimy używać wartości dostępnych w ramach określonego typu. Taką sytuację pokazuje poniższy przykład:

function Foo : String;
var
  arrCB : array[Char, Boolean, 1..2] of String;
begin
  arrCB[#0, False, 1] := 'False';
  arrCB[#0, True,  1] := 'True';
//..
  Result := arrCB[#0, FALSE, 1];
end;

Przy korzystaniu z tablic o indeksie typowanym bardzo wygodnym staje się korzystanie z funkcji Low oraz High. Dzięki nim można analizować pełen zakres wartości tablicy, bez względu na to, jakiego typu użyto w jej definicji.

type
  Znak = Char;

function MaleIDuzeLitery(const Litera : znak; const Duza : Boolean) : Char;
var
  arrC:array[Znak, Boolean] of Char;
  literka : znak;
begin
  for literka := Low(arrC) to High(arrC) do
  begin
    arrC[literka, TRUE] := UpperCase(literka)[1];
    arrC[literka, FALSE] := LowerCase(literka)[1];
  end;
  result := arrC[Litera, Duza];
end;

// Użycie funkcji - znak 'a' (mała litera)
MaleIDuzeLitery(#65, False);

W powyższym przykładzie przedstawiona została funkcja, która tworzy dla każdego znaku dwa elementy w tablicy: jego wersję w postaci litery wielkiej oraz małej. Podając znak oraz wartość logiczną, czy chcemy wielką czy małą literę, w funkcji z tablicy jest pobierana określona wartość. Z punktu widzenia logiki programowania taka funkcja nie jest przydatna, jednak doskonale przedstawia ideę tablic indeksowanych typem: wystarczy zmienić definicję typu znak na WChar, aby funkcja działała tak samo dla znaków dwubajtowych.

Warto zauważyć, że w Delphi istnieje ograniczenie typów danych do 2GB, stąd stworzenie tablicy takiej, jak w poniższym przykładzie, nie jest możliwe:

//[Error] Data type too large: exceeds 2GB
T:array[integer] of byte;

Zobacz też:

9 komentarzy

@siekierzyński
Tablice dynamiczne, to tablice początkowym rozmiarze równym zero (rozmiarze w sensie ilości pól), więc bez SetLength raczej się nie obejdzie.

@wojmysz
Sprawdza się to dość łatwo. Dajmy na przykład taką tablicę:
mojaTablica: array of array of string; (czyli mamy tablicę tablic typu string)
Załóżmy, że ma ona jakieś wymiary x (pierwszy wymiar) i y (drugi wymiar). W takiej sytuacji pierwszy wymiar z tablicy wyciągamy poleceniem:
Length(mojaTablica);
By wydobyć drugi wymiar, musimy odwołać się do pierwszego elementu tej tablicy (choć wcale nie musi to być akurat pierwszy) w ten sposób:
Length(mojaTablica[0]);

Tak jeszcze słów kilka odnośnie artykuły - dodałbym informację o tym, że tablica dynamiczna wcale nie musi być kwadratowa (jak to jest w przypadku klasycznych tablic dwu-wymiarowych).

Dodatkowo nie zauważyłem wzmianki o wykorzystaniu SetLength do ustawiania wymiarów dynamicznych tablic wielowymiarowych. Otóż, SetLength pobiera dwa parametry podstawowe - modyfikowaną tablicę oraz pierwszy jej wymiar. Można jednak przekazać tej procedurze więcej parametrów (w zależności od ilości wymiarów tablicy). Weźmy dla przykładu przedstawioną wcześniej tablicę (mojaTablica), która ma 2 wymiary. Ustawimy teraz jej wymiary na x i y (liczby naturalne):
SetLength(mojaTablica,x,y);
I od tego momentu nasza tablica ma już pożądane przez nas wymiary. Analogicznie jest w przypadku wyższej liczby wymiarów - po prostu określamy kolejne wymiary.

I na zakończenie odniosę się do modyfikowania wymiarów tablicy w trakcie wykonywania programu. Otóż nie raz pracując z dynamicznymi wielowymiarowymi tablicami nawiedzi nas potrzeba modyfikacji, czy to powiększenia, czy też uszczuplenia jej rozmiarów. Jak to się robi? Wystarczy skorzystać ze znanej nam procedury SetLength i funkcji Length. Połączenie to jest dość intuicyjne, gdyż pierwsza z nich służy do ustalania rozmiaru, a druga do jego zwracania. Powróćmy po raz kolejny do naszej przykładowej tablicy (mojaTablica). Powiedzmy, że chcemy zwiększyć jej pierwszy wymiar o 2, a drugi o 3. Będzie nam do tego potrzebna wiedza na temat aktualnych wymiarów tablicy. W tym też momencie z pomocą przychodzi funkcja Length. A całość wyglądała by tak:
SetLength(mojaTablica,Length(mojaTablica)+2,Length(mojaTablica[0])+3);
Jak widać pobieramy pierwszy i drugi wymiar tablicy, a następnie zwiększamy je do żądanych przez nas wymiarów.

W jaki sposób w tablicy dwuwymiarowej poleceniem High możana sprawdzić oba indeksy

Czy da się zadeklarować tablicę dynamiczną, ale bez SetLength?

sesef -> bezpośrednio, automatycznie usunąć się nie da. Musisz samemu elementy od 6 do 10 przepisać na miejsca od 5 do 9, a następnie skrócić tablicę.

A jak usunąć jakiś element z tablicy dynamicznej po środku jej?? Np wiem ze tablica ma 10 elementów a ja chce usunąć 5. I czy wtedy Indexy tablic się zmniejszą?? że 6 będzie teraz 5 a 10 będzie 9??

Format - po co wprowadzasz sztuczne poprawki?

Należy cofnąć do ostatniej wersji Misiekd, Format w jednej poprawce wprowadził znak, by w kolejnej poprawce go usunąć...

Dobrze byłoby dodać informację o deklarowaniu dynamicznych tablic wielowymiarowych i wykorzystaniu przy tym SetLength

i dodałem wątek "Tablice rekordów jako stałe"

zmieniłem z "Odczytywanie najmniejszej i największej wartości" na "Odczytywanie wartości o najmniejszym i największym indeksie" w tytule oraz treści ponieważ odczytywanie najmniejszej kojarzy mi się z szukaniem najmniejszej wartości w tablicy (analogicznie największej).