D/Typy złożone
Typy złożone
[edytuj]Język D zawiera w sobie kilka typów prostych, dzięki którym możemy reprezentować wartości logiczne, liczby oraz znaki. W większości wypadków jednak to zdecydowanie za mało. Dlatego w praktycznie wszystkich językach programowania dostępne są typy złożone, które umożliwiają reprezentację bardziej złożonych danych, jak na przykład współrzędnych punktów, adresów, wymiarów itd. Jednym z rodzajów typów złożonych są tablice, z których korzystałeś już wcześniej. W tym dziale poznasz inne, jak na przykład struktury, unie czy typy wyliczeniowe. Dowiesz się również, jak definiować własne typy oraz aliasy.
Definiowanie typów
[edytuj]Do definiowania własnych typów służy słowo kluczowe typedef:
typdef int mojint; mojint a = 4;
Typ zdefiniowany instrukcją typedef jest przez kompilator uważany za inny, niż typ, który przyporządkowaliśmy nowemu. Dzięki temu możemy zdefiniować dwie funkcje:
void funkcja(int a){ /* funkcja ze zwykłym intem */ }
void funkcja(mojint a){ /* funkcja z moim intem */ }
int i = 4;
funkcja(i) // wykona się funkcja ze zwykłym intem
mojint j = 4;
funkcja(j) // wykona się funkcja z moim intem
Dla typów zdefiniowanych instrukcją typedef możemy ustalać wartości domyślne:
typedef int mojint = 7; mojint a; // a otrzymuje wartość 7
Aliasy do typów
[edytuj]Aliasy z pozoru są łudząco podobne do typów zdefiniowanych przy użyciu typedef. Jest jednak zasadnicza różnica - słowo kluczowe alias tworzy jedynie odnośnik, a nie nowy typ. Dlatego taka sytuacja:
alias int mojInt;
void funkcja(int a){ /* funkcja ze zwykłym intem */ }
void funkcja(mojint a){ /* funkcja z moim intem */ }
jest nie możliwa, ponieważ kompilator poinformuje nas o dwóch identycznych funkcjach. Aliasem jest na przykład string, pod którym kryje się typ invariant(char)[].
Aliasy do funkcji i zmiennych
[edytuj]Aliasy możemy też tworzyć do funkcji i zmiennych, np.:
alias std.math.sqrt pierwiastek; real x = pierwiastek(2.0);
Bywa to dużym ułatwieniem, kiedy często korzystamy z funkcji lub zmiennych o skomplikowanych nazwach.
Typy wyliczeniowe
[edytuj]Typy wyliczeniowe (ang. enum, enumeration - wyliczenie) to typy, które mają zdefiniowaną pewną liczbę stałych wartości. Używa się ich najczęściej wtedy, gdy chcemy zawęzić zakres wartości zmiennej do kilku możliwości. Typy wyliczeniowe deklarujemy w następujący sposób:
enum Kolor {
CZARNY,
BIAŁY,
CZERWONY,
ZIELONY,
NIEBIESKI,
ZOLTY
};
Kolor kolor = Kolor.CZARNY;
| Stosowanie dużych liter jest jedynie konwencją, nie jest wymagane. Jest to tradycja z czasów C, ponieważ stałe podobne do enumów, tworzyło się przy użyciu makr preprocessora, i aby podkreślić fakt bycia makrem, stosowano konwencje dużych litery |
| W wersji 2 języka D, można pominąć przyrostek Kolor. w wyrażeniu Kolor.CZARNY. Można też pominąć, to w wielu innych miejscach, jeśli kompilator jest w stanie wydedukować, że oczekiwana wartość ma być jedną z wartości enum, np. w switch, lub w argumencie funkcji |
Typy wyliczeniowe często są stosowane w konstrukcji switch:
switch (kolor) {
case Kolor.CZARNY:
writefln("Czarny");
break;
// pozostałe wartości
}
Typy wyliczeniowe oparte są domyślnie na typach liczbowych, dlatego wartościom typu wyliczeniowego można przypisywać wartości liczbowe (domyślnie każda wartość, której my nie przypisaliśmy liczby, otrzymuje liczbę o jeden większą od poprzedniej):
enum Kolor {
CZARNY = 4,
BIAŁY = 21,
CZERWONY, // 22
ZIELONY = 1
};
Standardowo zmienne typu enum, będą miały taki sam rozmiar jak int, tj. 4 bajty. Możliwa też jest konwersja z enumów do intów i z powrotem. Czasami możemy zarządać innej reprezentacji enumów, np. 1 bajtowej:
enum Kolor : ubyte {
CZARNY,
BIAŁY,
CZERWONY,
ZIELONY
}
Jeżeli chcemy, możemy stworzyć typ wyliczeniowy, którego wartości będą przechowywane w innym typie, niż liczbowy, np.
enum Kolor : string {
CZARNY = "czarny",
BIAŁY = "biały",
};
W takim wypadku musimy każdej opcji przyporządkować wartość określonego typu, ponieważ np. do ciągu znaków "czarny" nie można dodać 1.
Struktury
[edytuj]Struktura jest to typ danych umożliwiający przechowywanie wielu wartości różnych typów w jednej zmiennej. Typ strukturalny tworzymy za pomocą słowa kluczowego struct:
struct Struktura {
int a;
byte b;
ushort c = 4; // Jeżeli przy tworzeniu zmiennej typu Struktura nie zainicjujemy c, ustawi się ono na 4.
}
Struktura s = {1, 4, 2};
Struktura s2 = {a:1, c:3, b:5}; // Możemy ustawiać parametry także w ten sposób
Struktura s3 = {1} // Nie musimy podawać wszystkich wartości - tutaj a=1, b=0 (nie podaliśmy) a c=4 (wartość domyślna).
Unie
[edytuj]Unie z pozoru są bardzo podobne do struktur, ale różnią się jedną zasadniczą cechą - unia może przechowywać tylko jedną wartość jednocześnie. Zmienne unii zachodzą na siebie w pamięci. Ilustruje to przykład:
import std.stdio;
union Unia {
byte liczba;
char znak;
}
void main() {
Unia unia;
unia.liczba = 0x24;
writefln(unia.znak); // Wypisze nam znak o kodzie 0x24, czyli $
}
Zwróćmy uwagę na to, że inicjowaliśmy liczbę, a wypisaliśmy znak. Na ekranie pojawił się znak o kodzie równym liczbie. Powodem tego zjawiska jest właśnie to, że liczba i znak znajdują się na tym samym miejscu w pamięci. Ta własność unii czasami bywa przydatna - umożliwia na przykład sprytną konwersję adresu IP do postaci szesnastkowej:
import std.stdio;
struct ByteAddress {
ubyte a;
ubyte b;
ubyte c;
ubyte d;
};
union IP {
ByteAddress byteaddress;
int fourByteAddress;
};
void main() {
IP ip;
ip.byteaddress.a = 142;
ip.byteaddress.b = 21;
ip.byteaddress.c = 2;
ip.byteaddress.d = 255;
writefln("%X", ip.fourByteAddress); // Wypisze: FF02158E
}
Wprowadzone do struktury byteaddress cztery liczby składające się na adres IP zostały odczytane w formie jednej zmiennej całkowitej i wypisane w systemie szesnastkowym na ekran. Jako, że liczba całkowita zajmuje w pamięci te same cztery bajty, co zmienne ubyte, to na ekran wypisana została zmienna złożona z tych czterech bajtów.
Użycie unii jest szeroko uważane za mało przejrzyste. Możne prowadzić do subtelnych błędów. W wielu wypadkach powoduje generacje bardzo nieoptymalnego kodu, oraz może powodować nie prawidłowe działanie garbage collectora. Używaj unii jedynie w absolutnej konieczności. W wielu wypadkach można użyć rzutowań (cast).