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).