W Turbo Pascalu można używać różnych formatów danych, takich jak liczba, napis, znak. Istnieją różne typy, niektóre zajmują mniej pamięci i są mniej dokładne, inne zajmują więcej pamięci, za co mają większą precyzję. Te dłuższe zazwyczaj wymagają też więcej czasu pracy procesora. Podstawowe typy danych zostały opisane w rozdziale Pierwsze programy, Zmienne.
Struktury danych pozwalają komputerowi w łatwiejszy sposób przetwarzać informacje, szybciej zapisywać do pliku, lepiej organizować w pamięci, szybciej wyszukiwać i ułatwić pracę programiście.
W bibliotece znajdują się szafki z kartotekami skatalogowanych książek. W każdej szafce są takie kartoteki. Tych szafek jest dość dużo. Są poustawiane obok siebie. Każda szuflada ma swój numer, czy literę. Są w nich tylko kartoteki. Jeśli biblioteka jest duża, regałów z kartotekami może być wiele.
Tablice są podobne do szuflad bibliotecznych. Tablica składa się z wielu 'schowków', poustawianych obok siebie w pamięci. W tablicy znajdują się tylko zmienne i to zmienne jednego typu. Każdy element tablicy ma swój indeks. Tablice mogą być wielowymiarowe, nie tylko w poziomie, ale i w pionie a nawet wgłąb.
Do czego służą? Przechowują grupy danych. Mogą zawierać listę liczb. Mogą zawierać kartoteki pracowników -ich nazwiska, imiona, bądź całe rekordy z danymi.
Aby je zadeklarować używa się w sekcji var wyrażenia: Array[{min}..{max}] of {...}
var tablica : Array[0..30] of Integer;
tablica_imion : Array[1..10] of string;
tab2 : Array[0..30, 1..3] of Integer; {tablica dwuwymiarowa}
A jak się odwoływać w programie do tablic?
Tablica[1] := 1; {zwykłe podstawienie}
for i:= 0 to 30 do {w pętli...}
tablica[i] := 0; {wyzerowanie całej tablicy}
tab2[i][1] := x; {dwuwymiarowa}
tab2[i, 1] := x; {drugi sposób}
{*****************************************}
{wczytanie i wyświetlenie 10 imion w pętli}
for i:= 1 to 10 do {w pętli...}
begin
Write('Podaj ', i, ' imię: ');
ReadLn(tablica_imion[i]);
end;
WriteLn('Wpisane imiona:');
for i:= 1 to 10 do {w pętli...}
begin
Write(tablica_imion[i]);
end;
Łatwo zauważyć, że do tablicy można się odwoływać za pomocą indeksu. I właściwie o to chodzi w używaniu tablic. Pozwalają znacznie zaoszczędzić czasochłonne wklepywanie kodu.
Przypuśćmy, że w tablicy liczb Integer, o nazwie tablica chcesz znaleźć wartość największą. Jak to zrobić? Trzeba przejść przez całą tablicę i sprawdzić, czy kolejny element jest większy nich dotychczas największy. Uwaga, na początku elementem dotychczas największym musi być ten, który jest pierwszym elementem tablicy, nie 0. Dlaczego? Ponieważ gdy tablica będzie zawierała jedynie liczby ujemne, program uważałby, że największą dotychczasową wartością jest 0, a tak nie będzie.
var tablica : Array[0..30] of Integer;
i, max : Integer;
begin
{tutaj można wypełnić tablicę jakimiś wartościami, np. losowymi}
Randomize; {Inicjowanie losowania}
for i:= 0 to 30 do {w pętli...}
tablica[i] := Random(100) - Random(100); {losowanie w zakresie -99..+99}
max := tablica[0];
for i:= 1 to 30 do {w pętli...}
begin
{Jeśli największy dotychczas element jest mniejszy niż badany...}
if max < tablica[i] then
max := tablica[i];{zmień dotychczas największy}
end;
{tu możesz wyświetlić max}
WriteLn('Max: ', max);
end.
Czasami zachodzi potrzeba utworzenia własnego typu -tzw. rekordu, który będzie przechowywał wymyślone przez nas dane. Przypuśćmy że chciałbyś zbudować prostą bazę danych zawierającą imiona i nazwiska swoich klientów. Zamiast przechowywać wszelkie dane w osobnych tablicach np.
const
MaxDanych = 200;
var
{imiona - tablica o rozmiarze MaxDanych elementów }
imiona : array[0..MaxDanych-1] of string[20];
nazwiska : array[0..MaxDanych-1] of string[30];
telefony : array[0..MaxDanych-1] of string[20];
{...}
{odwoływanie}
imiona[10]:='Moje Imię';
nazwiska[10]:='Moje Nazwisko';
telefony[10]:='0700 00 000';
Można pogrupować je blisko siebie. W taki sposób by znajdowały się obok siebie w pamięci (jest bardziej fachowo).
Można to zrobić choćby tak:
const
MaxDanych = 200;
type
TDaneOsobowe = record
imie : string[20];
nazwisko : string[30];
telefon : string[20];
rok_ur : integer;
end;
var
Dane : array[0..MaxDanych-1] of TDaneOsobowe;
{…}
{odwoływanie}
Dane[0].imie := 'Moje imie';
Dane[0].nazwisko := 'Moje nazwisko';
Dane[0].telefon := '0 700 00 000';
Dane[0].rok_ur := 1999;
{lub za pomocą with – uproszczenie}
with Dane[0] do
begin
imie := 'Moje imie';
rok_ur := 1999;
end;
W przykładzie zademonstrowano korzystanie z polecenia with - które dla każdego elementu wewnątrz dodaje przedrostek, w typ wypadku Dane[0]. Dzięki temu poleceniu możemy znacznie skrócić kod.
Do tablicy własnego rekordu można odnosić się jak do każdej innej tablicy. Można przeglądać tą tablicę za pomocą pętli i zmiennej iterowanej (zwiększanej).
Wskaźniki przypominają spis treści w grubej książce. Gdybyś podczas rozmowy telefonicznej chciał wskazać koledze konkretne miejsce w jakiejś publikacji, co zrobisz? Czy powiesz mu, otwórz swoją książę na zdaniu... I tu zaczniesz cytować, czy raczej podasz numer strony? Oczywiście numery stron są lepsze, bo ułatwiają szybie odnajdywanie miejsc. Tak samo wskaźniki w programowaniu -pozwalają przeszukiwać spore ilości danych bez wertowania całej pamięci. Podobnie jak przy rozmowie przez telefon, tak samo przy porozumiewaniu się między procedurami w programie potrzeba jak najściślejszych, ale jak najszybszych informacji.
Wskaźniki to adresy pamięci, które pokazują komputerowi miejsce gdzie znajdują się jakieś dane. Możesz przykładowo przechowywać w pamięci wskaźnik na obrazek bitmapowy, podczas gdy przechowywanie obrazu w tablicy byłoby niezbyt praktyczne. Pamięć do której odnoszą się wskaźniki można w każdej chwili zwalniać, deklarować i zmieniać.
Bez wskaźników nie byłoby możliwe nieograniczone tworzenie nowych danych, np. w słowniku. Gdyby nie wskaźniki mielibyśmy z góry określoną liczbę słów, które słownik byłby w stanie zapamiętać. W grach komputerowych, liczba ludzików, zbudowanych elementów w strategii, zawsze byłaby z góry ograniczona. Nie moglibyśmy w systemie uruchomić większej liczby programów niż zaplanowano.
Wskaźniki mogą wskazywać na dowolny obszar pamięci, a adres ten można zmieniać. Są pomocne podczas alokacji pamięci. Kiedy chcemy ją zadeklarować w czasie trwania programu, funkcja GetMem zwraca nam właśnie wskaźnik -czyli miejsce gdzie będziemy mogli wpisywać swoje dane. Gdybyśmy spróbowali wspisać swoje dane bez wcześniejszego zarezerwowania pamięci, w najlepszym wypadku zakończyłby się nasz program, w gorszym zawiesilibyśmy komputer.
Budowa wskaźników*
Ze względu na ryzoko zawieszenia programu wskaźniki są traktowane jako typy, które należy umiejętnie stosować. Wskaźniki na ogół zbudowane są z 4 bajtów. Pierwsze 2 określają segment danych, kolejne 2 ofset.
Pamięć konwencjonalna (pierwszy 1MB pamięci komputera) podzielony jest na segmenty po 16KB każdy. Tak więc 1 segment ma adres 0, drugi 1, trzeci 2, itd... Ofset to przemieszczenie względem segmentu. Dzięki ofsetowi możemy dojść do każdego jednego bajtu pamięci. Adres typu pointer składa się z dwóch bajtów segmentu, dwóch ofsetu i jest najczęściej zapisywany w kodzie szesnastkowym, dzięki czemu jest bardziej czytelny, np.
$a000:00, $b800:00
Typ pointer
Typ wskaźników nazywa się pointer. Gdy jakąś zmienną zadeklarujesz jako poiner, będzie ona wskaźnikiem. Wskaźniki mogą adresować różne typy zmiennych. Mogą adresować liczby, napisy, tablice, itd. Gdy adresują konkretny typ, nie deklaruje się ich jako pointer, tylko tworzy swój własny typ -o tym przeczytasz za moment.
Podstawianie wskaźników
Żeby wskaźnik wskazywał adres jakiejś zmiennej, możemy napisać:
var
wskaznik : pointer; {lub podobny typ}
{...}
wskaznik := @nazwa_zmiennej;
znacznik @ oznacza pobranie adresu zmiennej. Gdybyśmy chcieli teraz coś zapisać pod wskazanym miejscem wystarczy wywołać wskazywane miejsce, za pomocą znaku ^
wskaznik^ := nowa_wartosc;
W podanym przykładzie trzeba by wskaźnik był zadeklarowany jako wskaźnik na liczbę. Gdybyśmy bez rzutowania i bez określenia rodzaju wkaźnika próbowali mu podstawić wartość wystąpiłby błąd "illegal assigment".
Jak zadeklarować wskaźnik na liczbę?
np.
type p_liczba = ^integer; {utworzenie nowego typu - wskaźnika na liczbę}
var liczba : p_liczba;
i : integer;
begin
liczba := @i;
i:=2;
liczba^:=4;
end.
{teraz i = 4 a nie 2!}
Można ewentualnie potraktować typ pointer jako wskaźnik na liczbę co nazywa się rzutowaniem -dzięki temu unikniemy błędu a powiemy kompilatorowi, że wpisujemy w niego liczbę Integer
var liczba : pointer;
i : integer;
begin
liczba := @i;
i:=2;
integer(liczba^):=4;
end.