Tablice
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.
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:
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:
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:
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.
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:
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:
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:
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.
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:
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ść ;)
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:
lub
W powyższym przypadku skorzystałem jedynie z tablic dwuwymiarowych, których deklaracja wygląda tak:
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:
Istotę działania tablic dwuwymiarowych można zrozumieć lepiej, przeglądając listing poniżej.
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.
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.
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:
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:
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:
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.
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:
Wywołanie polecenia Low(Tablica) spowoduje, że funkcja zwróci wartość 10, natomiast funkcja High zwróci wartość 100.
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ć:
Przykładowy program zaprezentowany został poniżej:
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:
Wygodniejszym mogłoby być napisanie:
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:
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.
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:
Zobacz też:
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:
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:
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.
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.
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.
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.
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;));
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:
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.
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.
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.
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:
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:
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.
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.
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:
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ć:
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.
{$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:
Wygodniejszym mogłoby być napisanie:
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;
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);
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:
Zobacz też:



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