Witam. W tym artykule przedstawiam używanie tablic jedno-, dwu- i wielowymiarowych, statycznych jak i dynamicznych, przesyłanie ich jako argumenty do procedur/funkcji itd.
Po co nam tablice?
Załóżmy, że w programie będziesz musiał operować na dziesiątkach zmiennych np. typu Integer.
Co wtedy? Czy trzeba definiować każdą zmienną po kolei? Nie, jeśli posłużymy się tablicami, która pozwalają na grupowanie obiektów danego typu. Dzięki nim praca na dziesiątkach/setkach/tysiącach zmiennych jest banalnie prosta, definiując tablicę dostajemy jakby paczkę uporządkowanych elementów danego typu, których używamy jak zwykłych zmiennych.
Trochę teorii.
Zacznijmy od wyjaśnienia: czym są tablice?
Tablice reprezentują uporządkowane, ponumerowane elementy tego samego typu:
1 2 3 |
var Tablica : array [0..10] of Integer ; |
Spójrzmy powyżej jeszcze raz. Co tam widzimy? Definicję tablicy, którą można uogólniając zapisać w następujący sposób:
1 |
nazwaTablicy : array [dolnyZakres..gornyZakres] of Integer ; |
Po kolei. Na początku, nazwa tablicy(niesłychane!), następnie znany nam dwukropek, po którym widnieje słowo kluczowe array, które informuje kompilator, że ma do czynienia z definicją tablicy. Dalej, w nawiasach klamrowych, pomiędzy dwoma kropkami umieszczamy dolny oraz górny zakres tablicy. Potem słowo kluczowe of a po nim, typ, którego mają być elementy tej tablicy. OK, wiemy jak definiować tablicę.
Wiemy już, że tablica zawiera elementy, na których operuje się tak jak na zwykłych zmiennych. Znowu spójrzmy na znaną nam już definicję:
1 2 3 |
var Tablica : array [0..10] of Integer ; |
Ile elementów zawiera powyższa tablica? No właśnie, ile? Koderzy znający C++ zapewne od razu odpowiedzieliby: 10! Ale to nie C++ 😉 W tym przypadku tablica ta ma 11 elementów: 0, 1, 2,…,10. Czyli mamy 11 elementów typu Integer. Ilość elementów można policzyć odejmując dolny zakres od górnego i dodając 1.
No tak, tak, ale jak operuje na poszczególnych elementach? Jak się odwołać do np. 5 elementu? Używając operatora []:
Tablica[4] := 12345 ;
Pewnie pomyślałeś, że popełniłem błąd, bo przecież miał być 5 element, a ja podałem jako indeks 4! Błędu nie ma, gdyż pierwszy elementem ma indeks 0, a nie 1-więc wszystko jasne.
Cztery sprawy o których powinieneś wiedzieć.
Po pierwsze, czy zakres tablicy([0..10]) może być dowolnie duży? Dowolnie duży nie-zasięg każdego zakresu(w naszym przypadku jest tylko jeden zakres-0..10, ale wkrótce poznamy tablice wielowymiarowe, a w nich będzie dowolna wręcz ilość zakresów) nie może przekraczać dwóch gigabajtów.
Po drugie: czy zakres zawsze musi zaczynać się od 0(tak jak w C++) ? Nie, nie musi, może to być dowolna liczba całkowita, np:
1 2 3 4 5 6 7 8 9 |
Tablica : array [-2000...-5] of Jakas_moja_klasa ; Tablica2 : array [-2..12345] of String ; Tablica3 : array [56..57] of Jakis_moj_typ_rekordu ; Tablica4 : array [0..0] of Char ; // tylko jeden element! o indeksie 0 Tablica5 : array [0..-4] of Integer ; // BŁĄD! oczywiście górny zakres nie może być mniejszy od dolnego |
Po trzecie, jak widzimy powyżej, tablica może być zbiorem elementów dowolnego typu(typ elementów danej tablicy nazywamy typem bazowym)-nawet typu zdefiniowanego przez użytkownika.
Po czwarte, zakresami tablic wcale nie muszą być liczby całkowite, mogą to być na przykład…litery czy identyfikatory prawdy(true) i fałszu(false), czy też nazwy typów. Bo przecież, tak naprawdę, pod wszystkimi znakami kryją się liczby całkowite, a pod prawdą-1 a fałszem-0, a co do nazw typów-to tak, jakbyśmy zapisali dolny zakres jako najmniejszą wartość jaką może przyjąć zmienna tego typu i analogicznie, górny zakres jako wartość największą, więc nie ma w tym dziwnego:
1 2 3 4 5 6 7 |
Tablica : array [Boolean] of String ; Tablica2 : array [false..true] of Char ; Tablica3 : array [‘a’..’z’] of Real ; Tablica4 : array [Char] of Integer ; |
Tylko w takim przypadku, nie odnosimy się do poszczególnych elementów poprzez zwykłe liczby całkowite, ale identyfikatory odpowiednie dla danego ‘typu’ zakresu tablicy:
1 2 3 4 5 6 7 |
Tablica[false] := ‘abc’ ; // czyli Tablica[0] Tablica2[true] := ‘t’ ; // czyli Tablica2[1] Tablica3[‘b’] = 3.14 ; // czyli Tablica3[98]! a nie Tablica3[1], ponieważ kod znkau ‘a’ to 97, znaku b-98, więc to tak jakbyśmy zdefiniowali tablicę Tablica3 : array [98..122] Tablica4[‘.’] = ‘10’ ; // czyli Tablica4[46] |
Poza tym, zakresami mogą być też typy wyliczeniowe oraz typy okrojone:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
type TMiesiace = (Styczen, Luty, Marzec, Kwiecien, Maj, Czerwiec, Lipiec, Sierpien, Wrzesien, Pazdziernik, Listopad, Grudzien) ; TDni = 1..7 ; var Tablica : array [TMiesiace] of String ; Tablica2 : array [TDni] of String ; begin Tablica[Styczen] = ‘Styczen’ ; Tablica2[6] = ‘Sobota’ ; |
—
Nieco więcej o używania i definiowaniu tablic.
Elementy tablicy mogą być wykorzystywane jak normalne zmienne typu np. Integer, to znaczy, że można je mnożyć, dodawać itp. Mogą też być argumentami funkcji i procedur(o przesyłaniu całych tablic powiemy sobie później):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var Tablica : array [0..3] of Integer ; i : Integer ; begin Tablica[0] := 5; Tablica[1] := 2 ; Tablica[2] := Tablica[0] * Tablica[1] ; i := Tablica[0] div tablica[1] ; Tablica[3] := i ; ShowMessage(IntToStr(Tablica[3])) ; |
Żadnych rewelacji, na elementach tablicy operujemy tak samo jak na zwykłych zmiennych. Mamy do dyspozycji 3 funkcje, które ułatwiają pracę z tablicami: Low, High oraz Length.
Low zwraca indeks pierwszego elementu, High-ostatniego a Length zwraca ilość elementów w tablicy:
1 2 3 4 5 |
var Tab : array [4..17] of Integer ; Label1.Caption := ‘Indkes pierwszego elementu to: ‘ + IntToSt(Low(Tab)) + ‘, indeks ostatniego to ‘ + IntToStr(High(Tab)) + ‘, ilość elementów w tablicy = ‘ + IntToStr(Length(Tablica)) ; |
W Label1 pojawi się tekst: ‘Indkes pierwszego elementu to: 4, indeks ostatniego to 17, ilość elementów w tablicy = 14
Funkcje te przydają się podczas np. pracy z tablicą w pętli:
1 2 3 |
for i := Low(Tab) to High(Tab) do Tab := i * 10 ; |
Dzięki nim nie musimy pamiętać ile elementów ma dana tablica.
—
Jeżeli definiujemy tablice jako lokalne, to nie są one wstępnie niczym inicjalizowane i zawierają śmieci-całkowicie losowe wartości. Jeżeli zdefiniujemy je jako globalne-to zostaną wstępnie zainicjalizowane.
Jeżeli wiemy, że będziemy w programie korzystać częściej z tablicy o określonym typie i zakresie, to możemy utworzyć nowy typ tablicy i potem definiować nowe tablice jako tablice tego naszego nowo stworzonego typu:
1 2 3 |
type TablicaInteger = array [0..9] of Integer ; |
Teraz możemy w programie definiować nasze tablice w ten sposób:
1 2 3 |
var Tab : TablicaInteger ; |
Co zaoszczędza nam pisania, poza tym, może się też przydać, gdy na przykład chcemy wszystkie tablice naszego typu TablicaInteger powiększyć o 5-wystarczy zmienić zakres w naszym typie i wszystkie tablice zdefiniowane jako TablicaInteger będą większe o 5 elementów! To nie wszystkie przypadki, gdy typ tablicowy jest przydatny, o następnych niebawem.
—
Jeżeli chcemy, aby jedna tablica była identyczna jak druga, to znaczy zawierała takie same elementy, to po prostu przypisujemy jedną do drugiej:
1 |
Tab2 := Tab1 ; |
Wszystkie elementy z Tab1 zostają skopiowane do Tab2. Oczywiście, muszą one mieć jednakowe rozmiary oraz ich elementy muszą być tego samego typu, ale, poza tym, muszą spełniać jeszcze jeden warunek. Spójrz:
1 2 3 |
Tab1 : array [0..4] of Integer ; Tab2 : array [0..4] of Integer ; |
Te tablice są takie same, prawda? A jednak, próba kompilacji tego kodu:
1 |
Tab1 := Tab2 ; |
zakończy się błędem ‘Incompatible types’. Jak to?! Przecież one są identyczne-no tak, spełniają dwa z wyżej wymienionych warunków, ale jest jeszcze jeden, otóż, Delphi używa nazwy podczas sprawdzania poprawności typów. Jeżeli chcemy móc przypisać jedną tablicę do drugiej, musimy je to zrobić w ten sposób:
1 2 3 |
var Tab1 , Tab2 : array [0..4] of Integer ; |
lub
1 2 3 4 5 6 7 8 9 10 |
type TabInt = array [0..4] of Integer ; // kolejne zastosowanie typu tablicowego var Tab1 , Tab2 : TabInt ; Tab1 := Tab2 ; // teraz już poprawnie |
Tablice dwuwymiarowe.
Znamy już obsługę tablic jednowymiarowych, często jednak przydałoby się mieć tablicę dwuwymiarową, np. gdy tworzymy grę i mapy chcemy przechowywać w tablicach-używanie takiej mapy w jednowymiarowej tablicy byłoby kompletną porażką. W tym przypadku możemy użyć tablicy dwuwymiarowej:
1 2 3 |
var Mapa : array [0..4] of array [0..4] of Integer ; |
Jak widzisz, tablicę dwuwymiarową definiuje się poprzez podanie następnego zakresu. Pierwszy zakres to ilość kolumn, a drugi to ilość wierszy. Można by to zapisać również w krótszy sposób:
1 2 3 |
var Mapa : array [0..4 , 0..4] of Integer ; |
Ułożenie elementów wygląda następująco(założyłem, że tablicę zdefiniowaliśmy jako globalną, czyli została ona wypełniona zerami):
1 2 3 4 5 6 7 8 9 |
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
Pięć kolumn i pięć rzędów. Jeżeli chcemy odwołać się do danego elementu, np. 2 w 3 kolumnie, to postępujemy następująco:
1 |
Mapa[2][1] := 5 ; |
lub
1 |
Mapa[2 , 1] := 5 ; |
Praca z dwuwymiarowymi tablicami jest tak samo łatwa jak z jednowymiarowymi. Na razie nie zamieszczam żadnych przykładowych procedur/funkcji operujących na tablicach-znajdziecie je w podpunkcie o przesyłaniu tablic do proc/fun.
Tablice wielowymiarowe.
Tablice wymiarowe definiuje się poprzez dopisywanie kolejnych zakresów w definicji:
1 |
TablicaWieloWym : array [0..4] of array [0..9] of array [0..14] of ... of array [0..9] of Integer ; |
lub
1 2 3 4 5 6 7 8 9 |
TablicaWieloWym : array [0..4 , 0..9 , 0..14 , ... , 0..9] of Integer ;[delphi] a ogólnie: [delphi]Tab : array of [zakres] of array [zakres] of ... of array [zakres] of Typ ; Tab : array [zakres , zakres , ... , zakres] of Typ ; |
To, co napisałem odnośnie tablic jednowymiarowych jak najbardziej odnosi się do tablic dwu- i wielowymiarowych, więc można np. napisać taką definicje:
1 |
Urozmaicona : array [0..2 , 'a'..'b' , Char , false..true] of TMojTyp ; |
—
Jeżeli chcemy dowiedzieć się, tak jak w przypadku tablic jednowymiarowych, jaki jest górny i dolny zakres danej tablicy lub ile posiada elementów to postępuje w ten sposób:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var Tablica : array [0..3] of array [-5..5] of Integer ; Low(Tablica) ; // otrzymamy indeks pierwszego wiersza: 0 High(Tablica) ; // otrzymamy indeks ostatniego wiersza: 3 Low(Tablica[0]) ; // otrzymamy indeks pierwszej kolumny: -5 High(Tablica[0]) ; // otrzymamy indeks ostatniej kolumny: 5 Length(Tablica) ; // zwróci liczbę wierszy: 4 Length(Tablica[0]) ; // zwróci ilość elementów w pojedynczej kolumnie: 11 |
Mnożąc dwa ostatnie wyniki przez siebie otrzymamy liczbę wszystkich elementów w tablicy.
Tak samo postępujemy z tablicami o większej ilości wymiarów.
Stałe tablice.
Czasami zachodzi potrzeba użycia tablicy, której wartości elementów są już przez nas znane na etapie pisania aplikacji. Możemy wtedy zdefiniować tablicę jako stałą:
1 2 3 4 5 6 7 8 9 |
const TabStalaInt : array [0..5] of Integer = (0 , 10 , 20 , 30 , 40 , 50) ; TabStalaStr : array [0..1] of array [0..1] of String = ((‘abc’ , ‘abc2’) , (‘tekst’ , ‘tekst2’)) ; TabStalaPoint : array [0..2] of TPoint = ((X:10 ; Y:10) , (X:100 ; Y:100) , (X:0 ; Y:0)) ; |
Oczywiście, należy od razu zainicjalizować tablicę.
Tablice dynamiczne.
Bardzo często zdarza się w naszym programie nie wiemy dokładnie, ile elementów powinna zawierać nasza tablica np. tworzymy program-książkę adresową i za każdym dodaniem nowego wpisu tworzymy nowy element tablicy. Gdybyś używał tablicy statycznej, to ile elementów powinna zawierać tablica przechowująca wpisy? 100? 1000? 10000? Może na zapas 1000000, bo chcemy mieć pewność, że dojdzie do przekroczenia zakresu tablicy. Takie rozwiązanie jest jednak bardzo nieeleganckie, przez cały czas trwania programu w pamięci trzymana jest ogromna tablica. Dzięki tablicom dynamicznym, możemy za zawołanie zmieniać jej wielkość wedle naszych potrzeb. Popatrz:
1 2 3 |
var TablicaDyn : array of Integer ; |
To właśnie definicja tablicy dynamicznej-nie podajemy zakresu. Na razie nie możemy korzystać z naszej tablicy, gdyż nie ma ona żadnych elementów. Powiększanie tablicy dynamicznej odbywa się za pomocą procedury SetLength:
1 |
SetLength(TablicaDyn , 10) ; |
Teraz tablica ta ma 10 elementów ponumerowanych od 0 do 9-zakres tablic dynamicznych zawsze zaczyna się od zera, poza tym, indeksami są zawsze tylko liczby całkowite (Integer):
1 2 3 4 5 6 7 8 |
Low(TablicaDyn) ; // zawsze 0 High(TablicaDyn) ; // 9 Length(TablicaDyn) ; // 10 TablicaDyn[5] := 10 ; // zawsze TablicaDyn[5], nigdy np. TablicaDyn[‘a’] co było możliwe przy pracy z tablicami statycznymi. |
Nowe elementy tablicy dynamicznej zawsze inicjalizowane są zerami(o ile jej elementy są typu Integer), znakiem #0 w przypadku Charów czy ‘’(pustym ciągiem) w przypadku Stringów podczas używania procedury SetLength:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var TabDyn : array of Integer ; begin SetLength(TabDyn , 1) ; TabDyn[0] := 5 ; // przed przypisaniem-TabDyn[0] = 0 SetLength(TabDyn , 2) ; // teraz ustalamy, że rozmiar tablicy TabDyn ma być równy 2; TabDyn[0] nadal jest równy 0, a nowy element, TabDyn[1] = 0 SetLength(TabDyn , 1) ; // z powrotem 1 element |
Aby zwolnić pamięć zajmowaną przez tablicę dynamiczną, można albo przypisać jej wartość nil lub ustalić jej zakres jako 0:
1 2 3 |
TabDyn := nil ; SetLength(TabDyn , 0) ; |
Tablicy TabDyn nadal można używać, nadając jej nowy rozmiar:
1 |
SetLength(TabDyn , 100) ; |
—
Podczas omawiania tablic statycznych, przedstawiłem problem przypisywania do siebie dwóch tablic. Obowiązuje on także w przypadku tablic dynamicznych, jednak, przypisywanie do siebie tablic dynamicznych to nie to samo, co tablic statycznych.
W tym przypadku, wartości z tablicy a zostaną skopiowane do tablicy b:
1 2 3 4 5 6 7 8 9 10 11 |
var A , B : array [0..1] of Integer ; begin A[0] := 4 ; B := A ; // kopiowanie B[0] := 5 ; // A[0] nadal jest równe 4, ponieważ linijkę wyżej nastąpiło kopiowanie elementów |
W przypadku tablic dynamicznych jest inaczej:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var C , D : array of Integer ; begin SetLength(C , 1) ; SetLength(D , 1) ; C[0] := 10 ; D := C ; D[0] := 20 ; ShowMessage(IntToStr(C[0]))) ; |
Zagadka: co zostanie pokazane w wiadomości? 10? Nie!-20. Dzieje się tak dlatego, że przy przypisywaniu do siebie dwóch tablic dynamicznych, nie występuje kopiowanie. Tablice są wskaźnikami to uporządkowanych w pamięci elementów i w tym przypadku, poprzez d := c każemy wskaźnikowi d pokazywać na to samo, na co pokazuje wskaźnik c. Dlatego nie ważne od tej pory którego wskaźnika będziemy używać-oba pokazują na to samo miejsce w pamięci.
Jeżeli chcemy przekopiować wartości tablicy, tak jak w wypadku tablic statycznych, musi posłużyć się funkcją Copy:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var C , D : array of Integer ; begin SetLength(C , 1) ; C[0] := 1; D := Copy(C) ; D[0] := 2; // C[0] <> D[0] |
Jak widać nie trzeba używać procedury SetLength w stosunku do tablicy D.
Jeżeli porównujemy dwie tablice dynamiczne:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var C , D : array of Integer ; begin SetLength(C , 1) ; SetLength(D , 1) ; C := 1 ; D := 1 ; if C = D then ShowMessage(‘Takie same’) ; |
To nie są porównywane wartości elementów, lecz to, czy oba wsakźniki pokazują na ten sam blok pamięci, więc w powyższym przykładzie nie pojawi się komunikat Takie same.
—
Pracując z tablicami dynamicznymi, którym ustalono już jakąś ilość elementów, mamy do dyspozycji funkcje Length, która zwraca ilość elementów w tablicy, Low, która zawsze zwraca 0 oraz funkcję High, która zwraca indeks ostatniego elementu(w przypadku tablic dynamicznych-High = Length -1), jeżeli tablica nie ma żadnego elementu-High zwróci -1. Jeżeli chcemy zmienić rozmiar tablicy, użyamy funkcji Copy lub SetLength (ta jest szybsza).
—
Wielowymiarowe tablice dynamiczne.
Oczywiście, tablice dynamiczne również mogą mieć wiele wymiarów.
1 2 3 4 5 6 7 8 9 |
var TabDyn2d : array of array of Integer ; begin SetLength(TabDyn2d , 10 , 15) ; TabDyn2d[9][14] := 10 ; |
Jak widać, w procedurze SetLength podajemy kolejną ilość elementów (gdyby tablica miała więcej wymiarów, to po kolei dodalibyśmy ilość elementów).
Jednakże, co odróżnia tablice dynamiczne od statycznych, nie muszą być one prostokątne:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var TabDyn2d : array of array of Integer ; begin SetLength(TabDyn2d , 3) ; // ustalamy, że mają być 3 rzędy, ale o kolumnach nie wspominamy SetLength(TabDyn2d[0] , 1) ; SetLength(TabDyn2d[1] , 2) ; SetLength(TabDyn2d[2] , 3) ; TabDyn2d[0][0] := 2 ; TabDyn2d[0][1] := 5 ; // BŁĄD! nie istnieje element [0][1] TabDyn2d[1][1] := 10 ; // ale element [1][1]-tak |
—
To tyle, jeśli chodzi o dynamiczne i statyczne. Zostało jeszcze do omówienia przesyłania i odbieranie tablic statycznych/dynamicznych w funkcjach i procedurach.
Przesyłanie tablic.
Jeżeli chcemy przesłać do procedury/funkcji tablicę pięcioelementową, to możemy to zrobić na dwa sposoby:
1 2 3 |
type TTab5El = array [0..4] of Integer ; |
1 2 3 |
procedure Proc1(T : TTab5El) ; procedure Proc2(T : array of Integer) ; |
Niemożliwe jest napisanie takiej deklaracji:
1 |
procedure Proc3(T : array [0..4] of Integer) ; |
Do Proc1 możemy przesłać jedynie tablice o ilości elementów równej pięć typu Integer, co więcej, muszą one być typu TTab5El, inaczej nie będziemy mogli jej przesłać.
1 2 3 4 5 6 7 8 9 10 11 |
var TabPoprawna : TTab5El ; TabZla : array [0..4] of Integer ; begin Proc1(TabPoprawna) ; // OK Proc1(TabZla) ; // błąd! musi być typu TTab5El |
W drugim przypadku mamy do czynienia z tak zwanymi otwartymi tablicami, możemy do Proc2 przesłać każdą tablicę typu Integer, nie ważne czy jest ona dynamiczna lub statyczna, ma 5 czy 100 elementów, jej typem indeksu jest Char czy Boolean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var Tab5El : TTab5El ; TabDyn : array of Integer ; TabStat10 : array [0..9] of Integer ; TabStatBool : array [Boolean] of Integer ; begin // wszystkie poprawne Proc2(Tab5El) ; SetLength(TabDyn , 100) ; Proc2(TabDyn) ; Proc2(TabStat10) ; Proc2(TabStatBool) ; |
W obu przypadkach(Proc1, Proc2), przesyłając tablicę, kompilator tworzy kopię naszej tablicy i to na niej operujemy w procedurze/funkcji. Co za tym idzie, zmieniając w procedurze tablicę nie zmieniamy jej oryginału lecz kopię, więc po wykonaniu procedury nasza tablica jest nietknięta. Poza tym, tworzenie kopii tablicy jest czasochłonne i w przypadku dużych tablic jest to bardzo nie korzystne:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure Proc1(T : TTab5El) ; begin T[0] := 12345 ; end ; var Tab : TTab5El ; begin Tab[0] := 10 ; Proc1(Tab) ; ShowMessage(IntToStr(Tab[0])) ; // nadal 10! |
Jeżeli jednak chcemy, aby nie kopiowano naszej tablicy lecz pracowano na oryginalne, co spowoduje, że wszelkie zmiany będą widoczne w przesyłanej tablicy oraz całość będzie się wykonywać szybciej, musimy posłużyć się słowem kluczowym var:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure Proc1(var T : TTab5El) ; begin T[0] := 12345 ; end ; var Tab : TTab5El ; begin Tab[0] := 10 ; Proc1(Tab) ; ShowMessage(IntToStr(Tab[0])) ; // 12345! |
—
O ile posługiwanie się tablicą przesłaną do 1. procedury(Proc1) jest intuicyjne i nie potrzeba żadnych wyjaśnień, o tyle w Proc2, z racji tego że parametr to array of Typ, jest trochę inaczej.
1. Indeksowanie przesyłanych tablic zawsze zaczyna się od 0, a typ indeksu to zawsze Integer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure Proc2(T : array of Integer) ; begin T[0] := 5 ; // tablica TabStatBool: T[0] a nie false; w przypadku TabStat: T[0] a nie T[-5] end ; var TabStatBool : array [Boolean] of Integer ; TabStat : array [-5..5] of Integer ; begin Proc2(TabStatBool) ; Proc2(TabStat) ; |
2. Nie wolno nam działać na przesłanej tablicy jako całości-nie można zmieniać jej rozmiaru procedurą SetLength, przypisywać danych z innej tablicy itp.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure Proc2(T : array of Integer) ; begin --- end ; var a : array of Integer ; begin a := T ; // błąd! SetLength(T , 10) ; // błąd! end ; |
3. Możemy ją przesyłać dalej, do innych procedur/funkcji o ile ich parametrem jest (w naszym przypadku) Nazwa : array of Integer lub var Nazwa : array of Integer ;
4. Zamiast przesyłać do naszej procedury/funkcji tablicę, możemy przesłać zmienną typu takiego jak tablica-wtedy będzie to traktowane jak przesłanie tablicy o jednym elemencie o wartości przesłanej zmiennej:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure Proc2(T : array of Integer) ; begin ShowMessage(IntToStr(Low(T))) ; end ; var Z : Integer ; begin Z := 10 ; Proc2(z) ; |
—
W przypadku, gdy parametrem jest array of Typ tak jak w Proc2, możemy posłużyć się konstruktorem, który tworzy tablicę podczas uruchomiania proc/fun:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure Proc2(T : array of Integer) ; begin --- end ; var a : Integer ; begin a := 5 ; Proc2([1 , 5 , a + 100 , a]) ; |
co jest równoważne do:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var Tab : array [0..3] of Integer ; begin Tab[0] := 1 ; Tab[1] := 5 ; Tab[2] := 105 ; Tab[3] := 5 ; Proc2(Tab) ; |
Konstruktorów tablic otwartych używać można jedynie w parametrach przesyłanych przez wartość lub jako stałe parametry (const).
—
Pozostaje jedno pytanie: co zrobić, jeżeli chcemy mieć możliwość zmiany rozmiaru przesłanej do proc/fun tablicy dynamicznej? Należy utworzyć nowy typ tablicowy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
type TDynArray = array of Integer ; procedure Proc(Tab : TDynArray) ; begin SetLength(Tab , 10) ; Tab[0] := 5 ; end ; var DynTab : TDynArray ; begin SetLength(DynTab , 2) ; Proc(DynTab) ; // po wyjściu z procedury, DynTab ma nadal 2 elementy end ; |
Mamy możliwość zmieniać rozmiar kopii przesłanej tablicy. Jeżeli chcielibyśmy, aby to ‘oryginalna’ była zmieniana, musimy dodać słowo kluczowe var:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
procedure Proc(var Tab : TDynArray) ; begin SetLength(Tab , 10) ; Tab[0] := 5 ; end ; var DynTab : TDynArray ; begin SetLength(DynTab , 2) ; Proc(DynTab) ; // teraz DynTab ma 10 elementów end ; |
—
W przypadku zwracania tablicy przez funkcję, nie może się posłużyć zapisami:
1 2 3 |
function Fun1 : array [0..5] of Integer ; function Fun2 : array of Integer ; |
Musimy utworzyć nowy typ tablicowy i obiekt tego typu zwrócić:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
type TTab5El = array [0..4] of Integer ; TDynArray = array of Integer ; function FunDyn : TDynArray ; var i : Integer ; begin SetLength(Result , 10) ; for i := Low(Result) to High(result) do Result := i ; end ; function FunStat : TTab5El ; begin Result[0] := 2 ; end ; var Dyn : TDynArray ; Stat : TTab5El ; begin Dyn := FunDyn ; Stat := FunStat ; end; |
—
To, można by pomyśleć, nareszcie koniec 😉 Starałem się wyczerpać temat, jednak i tak zostaje jeszcze to i owo, na przykład praca na tablicach używając wskaźników, ale na to zostawiam miejsce w artykule o wskaźnikach.
W razie jakichkolwiek uwag/sugestii proszę o maile lub PW, a w razie pytań-na forum.