Typy danych - systematyka
Dryobates
Object Pascal ma bardzo wiele typów i dosyć ostro przestrzega zasad zgodności typów. To umożliwia kompilatorowi wyszukiwanie błędów popełnionych przez użytkownika (np. nie przekaże do funkcji, która wymaga łańcucha znaków, rekordu czy liczby, co powodowałoby błędy).
W OP istnieje wiele predefiniowanych typów, które rozpoznaje kompilator. Typy dzielą się na ogólne (zależne od platformy) i podstawowe (niezależne od platformy). Operacje na typach ogólnych są szybsze, ale mogą się one zmieniać w zależności od procesora i systemu operacyjnego (np. Integer w systemach 16-bitowych ma wartość maksymalną 32767, w systemach 32-bitowych 2147483647).
Aby sprawdzić ilość miejsca zajmowanego przez dany typ w pamięci należy użyć funkcji SizeOf
Podział typów:
I. Typy proste, w skład, których wchodzą typy porządkowe i rzeczywiste stanowią uporządkowane zbiory
1. Typy porządkowe to takie uporządkowane zbiory, w których każdy element oprócz pierwszego ma swojego poprzednika i każdy element oprócz ostatniego ma swojego następcę.
Każdy element ma swój numer porządkowy:
- dla liczb typu całkowitego jest to wartość tej liczby
- dla typów okrojonych są to wartości odpowiadających im typów bazowych
- dla typów wyliczeniowych jest to numer elementu zbioru zaczynając od 0
Funkcje operujące na typach porządkowych:
- Ord - zwraca wartość porządkową (nie działa na typ Int64)
- Pred - zwraca wartość poprzednią (nie używać na właściwościach, które mają procedurę write)
- Succ - zwraca wartość następną (nie używać na właściwościach, które mają procedurę write)
- High - zwraca wartość największą
- Low - zwraca wartość najmniejszą
Dokładne opisy tych funkcji znajdują się w Encyklopedii Delphi
A. Typ całkowity odpowiada liczbom całkowitym z odpowiednich przedziałów. Są dwa typy ogólne:
- Integer (w systemach 32-bitowych przyjmuje wartość -2147483648..2147483647),
- Cardinal (w systemach 32-bitowych przyjmuje wartość 0..4294967295),
i siedem podstawowych:
- Shortint (-128..127)
- Smallint (-32768..32767)
- Longint (-2147483648..2147483647)
- Int64 (-263..263-1)
- Byte (0..255)
- Word (0..65535)
- Longword (0..4294967295)
B. Typ znakowy gromadzi znaki w kilku standardach (ASCII, ANSI i Unicode)
Podstawowymi typami są: - ANSIChar (zapisywany za pomocą 8 bitów. Zawiera 256 znaków w danym kodowaniu)
- WideChar (zapisywany za pomocą 16 bitów. Zawiera 65536 znaków. Jest tylko jedno kodowanie. Pierwsze 256 znaków odpowiada znakom ANSIChar)
Ogólnym typem jest:
- Char (odpowiada ANSIChar w kompilatorze Object Pascal z Delphi 6)
Mając kod znaku można uzyskać znak używając funkcji Chr (patrz Encyklopedia Delphi)
C. Typy boolowskie reprezentują wartości "prawda" (True) i "fałsz" (False) - Boolean (1 bajt)
- ByteBool (1 bajt)
- WordBool (2 bajty)
- LongBool (4 bajty)
Podstawowym typem boolowskim w Delphi jest Boolean. Pozostałe typy: ByteBool, WordBool i LongBool są wprowadzone w celu zgodności z innymi językami. Typy boolowskie można traktować jako typy wyliczeniowe
Jeżeli zdefiniujmy typ:
a następnie zadeklarujemy
To otrzymamy typ okrojony 5..10, którego wartości 5, 7 i 10 mają określone nazwy, ale istnieje też dostęp do pozostałych wartości (6, 8, 9).
E. Typy okrojone to typy postaci:
Typ okrojony zajmuje tyle samo miejsca w pamięci co typ bazowy, z którego powstał. Np. type Cyfry = 0..9; zajmuje 1 bajt.
Typem bazowym może być dowolny typ porządkowy.
Typy okrojone są wygodne przy określaniu zakresu danych, które może przyjąć funkcja/procedura.
2. Typy rzeczywiste reprezentują liczby zmiennoprzecinkowe (lub liczby zapisywane za pomocą cechy i mantysy).
Typy podstawowe:
Typ | Zakres |
Real48 | 2.9 x 10-39 .. 1.7 x 1038 |
Single | 1.5 x 10-45 .. 3.4 x 1038 |
Double | 5.0 x 10-324 .. 1.7 x 10308 |
Extended | 3.6 x 10-4951 .. 1.1 x 104932 |
Comp | -263+1 .. 263 -1 |
Currency | -922337203685477.5808.. 922337203685477.5807 |
Typ ogólny (w kompilatorze Delphi 6)
Real = Double (5.0 x 10-324 .. 1.7 x 10308)
Comp jest specyficznym typem całkowitym. Należy do typów rzeczywistych tylko dlatego, że jest zapisywany w postaci cechy i mantysy i nie zachowuje się jak typ prządkowy ( nie można na nim zastosować Pred i Succ).
Currency jest typem stałoprzecinkowym w prowadzonym w celu dokładnych obliczeń na pieniądzach. Przyporządkowując wartości tego typu innym typom rzeczywistym wartość jest automatycznie mnożona lub dzielona przez 10000.
II. Typy łańcuchowe reprezentują zbiory typów znakowych.
Predefiniowane typy:
- ShortString - maksymalnie 255 znaków ANSIChar(zajmuje od 2 do 256 bajtów)
- AnsiString - maksymalnie ~2^31 znaków ANSIChar (zajmuje od 4 bajtów do 2GB)
- WideString - maksymalnie ~2^30 znaków WideChar (zajmuje od 4 bajtów do 2GB)
III. Elementami typów strukturalnych mogą być dowolne inne typy (wyjątek stanowią zbiory, których elementami mogą być jedynie typy porządkowe). W celu zaoszczędzenia miejsca (ale też spowolnienia dostępu do danych) przy deklaracji typu można użyć słowa kluczowego packed np.:
Są dwa rodzaje tablic:
- statyczne:
Rozmiar tablicy dynamicznej ustawia się przez SetLength w trakcie działania programu.
Można również definiować tablice tablic, czyli tablice wielowymiarowe np.:
Ustawianie rozmiarów tablicy wielowymiarowej jest podobne:
SetLength(Talbica, LiczbaWierszy, LiczbaKolumn);
Co jednak zrobić, jeżeli potrzebujemy tablicy trójkątnej? Można zadeklarować tablicę kwadratową, ale zużywamy wówczas prawie dwa razy więcej pamięci. Może więc zadeklarujemy taką tablicę:
Czyli mam hybrydę: tablicę statyczną, której elementami są tablice dynamiczne. Ponieważ są to tablice dynamiczne, to bez problemu każdej z nich możemy nadać rozmiar:
Otrzymaliśmy więc taką tablicę:
K
KK
KKK
KKKK
.
.
.
KKKKKKKKKKK
K - komórka tablicy typu Byte;
No tak, ale jak nie wiemy jak wiele wierszy tablicy będziemy potrzebować? Może zadeklarujmy tablicę dynamiczną, której elementami będą tablice dynamiczne:
var
Tab: TTab;
i : Byte;
begin
{Możemy ustalić liczbę wierszy:}
SetLength(Tab, 20);
{Jak też liczbę kolumn w każdym wierszu:}
for i := 0 to 10 do
SetLength(Tab[i], Random(10));
end;
W ten sposób możemy tworzyć tablice w kształcie trapezu krzywoliniowego.
Specyficznym rodzajem tablic są tablice otwarte. Są przekazywane wyłącznie w procedurach i funkcjach. Nie mogą być definiowane przy użyciu type.
np:
Niestety tablice do tak zdefiniowanych funkcji mogą być przekazywane jedynie jako const. Co zrobić, jeżeli mamy za dużą tablicę statyczną?
Z pomocą przychodzi funkcja Slice, która przycina naszą tablicę do odpowiednich rozmiarów.
3. Rekordy to zbiory różnych pól.
Co zrobić, jeżeli nie wszystkie dane w rekordach wykorzystujemy? Po co zużywać cenne miejsce? Możemy wówczas użyć rekordów z częścią wariantową:
W takim rekordzie mamy zawsze dostęp do Pole1, ale już z pozostałymi polami jest różnie. Możemy mieć albo dostęp do Pole2 i Pole3 albo do Pole4, Pole5, Pole6 albo do Pole7. Nigdy naraz. Pole2 i Pole3 zajmują te same miejsce w pamięci co Pole4, Pole5 i Pole6, a także Pole7. Obszar rezerwowanej pamięci jest równy większej grupie. Możemy wykorzystać to, że pola zajmują to samo miejsce w pamięci, aby mieć wygodny dostęp do niektórych danych:
Jak widać pominęliśmy Pole0. Jeżeli nie ma potrzeby nie musiymy go wykorzystywać (jest to pole zwykłe - zawsze dostępne). Nie wykorzystalićmy także wszystkich elementów typu Byte - nie musimy. Za to otrzymaliśmy bardzo wygodny dostęp do młodszej i starszej cześci zmiennej typu comp.
Jednym z większych ograniczeń w stosowaniu części wariantowych jest to, że można je umieszczać jedynie na końcu rekordu. Jednak i na to są sposoby - rekordy w rekordach:
4. Pliki to uporządkowane zbiory elementów dowolnego typu, za wyjątkiem typów wskaźnikowych, dynamicznych tablic, długich łańcuchów (ANSIString i WideString), klas, variantów i rekordów zawierających takie typy.
Niepoprawne linki do "SetLength" i "Slice".
świetny artykuł. bardzo użyteczny.
//dop.
chociaż przydałoby się napisać trochę więcej o zbiorach.
Dodać należy, że rekord zajmuje tyle miejsca w pamięci, ile jego największy (pod względem zajętości pamięci) wariant + rozmiar części niewariantowej.
dry... artykuł cienki jak barszcz :PPPP ;)
O tablicach otwartych jest troszke niescisle, bo do procedury z (otwartym) parametrem tablicowym mozna rowniez przekazac tablice dynamiczna. Vide http://www.4programmers.net/forum/viewtopic.php?id=30914 .
No tak! Postarales sie!
Ode mnie też 6 :]
No, to sie nazywa artykul. Ode mnie 6!
Świetny, systematyczny artykuł. Ale paru drobiazgów zabrakło:
tablice otwarte
nieprostokątne tablice dynamiczne
packed record i jego znaczenie przy operacjach plikowych
rekordy wariantowe
może jak będę miał czas do dopiszę, ale i tak jest rewelacyjnie!!!!