Wprowadzanie dla programistów C/C++ edytuj

Jeśli jesteś doświadczonym programistą, albo po prostu czujesz się swobodnie w językach C lub C++, ten rozdział jest dla Ciebie. Może być on również przydatny, dla osób, znających inne języki, a którzy nie chcą marnować czasu na wstęp do programowania. Łatwo się odnajdą tutaj osoby znające składnie języków podobnych do C, np. Java, C#. Również osoby programujące w takich językach jak Python, Pascal, PHP, nie powinny mieć większych problemów, ponieważ podstawowe elementy (zmienne, typy, kompilacja/uruchomienie, instrukcje warunkowe, pętle, funkcje) są bardzo podobne.

Do osób sceptycznie nastawionych do różnych pomysłów, szczególnie w kwestii wydajność różnych aspektów języka, apelujemy o powstrzymanie się tymczasowo od krytyki, ponieważ, większość decyzji projektowych języka ma swoje uzasadnienie, a fakt, iż język D jest jednym z najszybszych (drugi w popularnym teście Shootout Debiana) języków na świecie (wbrew intuicji) świadczy jedynie o niepełnym zrozumieniu pewnych aspektów (np. automatycznego odśmiecania pamięci, funkcji wirtualnych, optymalizacji kompilatora, obsługi tablic, stosu, wyjątków itp, itd).

Start edytuj

Najprostszym możliwym programem na rozpoczęcie jest oczywiście Hello World.

import std.stdio;
void main() {
   writefln("Hello World");
}

Aby skompilować program kompilatorem dmd:

$ dmd hello.d

I uruchamiamy:

$ ./hello
Hello World
$

Można też wykonać kompilacje i uruchomienie w jednej komendzie:

$ dmd -run ./hello.d
Hello World
$

Przydaje się to przy pisaniu skryptów, wtedy w pierwszej linii skryptu możemy dodać:

#!/usr/bin/dmd -run

aby po prostu stosować normalne uruchomienie (pamiętaj o dodaniu atrybuty wykonywalności: chmod +x ./hello.d):

$ ./hello.d

Pełna sygnatura funkcji main to: int main(char[][] args).

Proste IO edytuj

Funkcja writef/writefln znajdująca się w module std.stdio, jest bardzo podobna do printf z C, może przyjmować wiele argumentów, jest jednak trochę wygodniejsza i bezpieczniejsza jeśli chodzi o typy (funkcja printf jest dostępna w module std.c.stdio, jednak nie radzi się jej używać o czym za chwilę). Trochę przykładów

writef("Hello %s\n", "World");
writefln("Mam %d lat i %f m wzorstu", 21, 1.76);
int a = 5, b = 66;
writefln("a=", a, " b=", b, " a+b=", a+b);

Oba sposoby można mieszać. writefln w trakcie wykonania sprawdza typy argumentów, i podejmuje różne działania, jeśli typy są sprzeczne, to funkcja rzuca wyjątek czasu wykonania.

writefln("%d", 1.2); // rzuci wyjątek: Error: std.format floating

Format %s oprócz wypisywania łańcuchów służy za meta format, można go zastosować do dowolnego typu, i zostanie użyty domyślny format dla tego typu, a w przypadku obiektów, zostanie użyta metoda char[] toString(). W przypadku tablic zostaną wypisane wszystkie składniki, oddzielone przecinkami (z [ i ] odpowiednio na początku i końcu), a obiekty złożone zostaną obsłużone rekurencyjnie.

Podstawowa arytmetyka edytuj

Podstawowa arytmetyka jest zgodna ze standardową notacją C i innych języków:

int a, b, c, d, e;
a = 11;
b = a*2;
c = b - a / 3;
b -= c+a;

Priorytety operatorów są praktycznie identyczne jak w C, zawsze można wymusić priorytety nawiasami. Aktualnie nie ma operatora potęgowania.

Zmienne edytuj

Zmienne w D, można definiować w praktycznie dowolnym miejscu programu, a nie jak w C, tylko na początku bloku. Zmienne są automatycznie inicjalizowane (dla zmiennych całkowitych jest to 0, dla zmiennoprzecinkowych jest to wartość NaN, a dla bool: false, dla char: 0xFF).

import std.stdio;
int main() {
  writefln("Trochę zmiennych:");
  int a = 6;
  int b;
  double c, d;
  d = 55.1
  writefln(a, " ", b, " ", c, " ", d);
  return 0;
}

Da:

Trochę zmiennych:
6 0 nan 55.1

Również w przypadku tablic, wszystkie elementy tablicy są inicjalizowane. Można nie inicjalizować zmiennych przy pomocy przypisania im void przy inicjalizacji.

void f() {
   int duza[1000] = void;
}

Należy używać tego tylko w bardzo wyjątkowych sytuacjach (prowadzi to do błędów, nawet jeśli jest bezpiecznie używane to może mylić garbage collector [choć bieżąca implementacja już powinna być na to odporna], i prowadzić do innych przykrych błędów, nawet jeśli jest prawidłowo używana). Używać tylko przy krytycznie ważnych funkcjach i jeśli tablica/zmienna jest zaraz wypełniania wartościami.

Typy edytuj

Podstawowymi typami zmiennych są: liczby ze znakiem: int (32 bity), short (16), byte (8), long (64), cent (128, zarezerwowane); typy bez znaku uint, ushort, ubyte, ulong, ucent; zmiennoprzecinkowe: float (pojedyncza precyzja), double (podwójna precyzja), real (największa precyzja, 80(128) bitów na i387); typy urojone i zespolone: ifloat, idouble, ireal, cfloat, cdouble, creal. Typ logiczny: bool (zajmuje 8 bitów). Oraz typy znakowe: char (8 bitów), dchar (16), wchar (32).

Właściwości edytuj

Typy mają pewne właściwości (properties) (jak również zmienne tych typów, ale są one naprawdę atrybutami typów). Są one tylko do odczytu. Mianowicie .init - inicjalizator typu. .sizeof - wielkość zmiennej w bajtach, .alignof - rozmiar wyrównania, .mangleof - string reprezentujący tzw. mangled (wewnętrzną) reprezentacje typu (używaną przez kompilator, linker i specjalne narzędzia, np. do refleksji), .stringof - reprezentacja łańcuchowa źródła typu lub wartości.

Dodatkowo:

Dla typów całkowitych: .init - 0, .min, .max - wartości minimalne i maksymalne.

Dla typów zmiennoprzecinkowych: .init - NaN, .infinity - nieskończoność, .nan - wartość NaN, .dig - ilość cyfr dziesiętnych precyzji, .epsilon - najmniejszy przyrost do liczby 1, .mant_dig - ilość bitów w mantysie, .max_10_exp - maksymalny int taki że 10^10max_10_exp jest reprezentowalny, .max_exp - maksymalny int taki że 2^max_exp-1 jest reprezentowalny, .min_10_exp - minimalny int taki że 10^min_10_exp jest reprezentowalny jako liczba znormalizowana, .min_exp - minimalny int taki że 2^min_exp-1 jest reprezentowalny jako liczba znormalizowana, .max - największa liczba reprezentowalna różna od nieskończoności, .min najmniejsza znormalizowana wartość różna od 0, .re - część rzeczywista, .im - część urojona.

Literały edytuj

Literały odpowiadające tym typom są podobne do tych w innych językach, jednak umożliwiono na wiele udogodnień:

int a = 123;
int duza = 123_444_555;  // można rozdzielać cyfry podkreśleniem
int b = 0xb1;            // szesnastkowo 177
int c = 0660;            // ósemkowo
int d = 0b0101001010;    // dwójkowo (binarnie)
float x = 1.FFA2g11;     // liczba zmiennoprzecinkowa zapisana szesnastkowo,
                         // po g jest wykładnik przy potędze 2

Literały typu bool (logiczne) to: true i false.

Literały urojone kończą się literą i:

ifloat i = 1.0i;

Literały zespolone są tworzone w fazie semantycznej z sąsiadujących literałów rzeczywistego i urojonego połączonych znakiem plus lub minus:

cdouble phi = 0.55 + 0.15i;

Logika edytuj

Zmienne logiczne (typu bool) mają 1 bajt i mogą przechowywać tylko true albo false. Jedynymi dozwolonymi operatorami są: & | ^ &= |= ^= ! && || ?:. Wartość typu bool mogą być automatycznie skonwertowane na typ całkowity (false -> 0, true -> 1). Podobnie w drugą stronę: (0 -> false, 1 -> true, inne -> błąd!). Jawne rzutowanie wyrażenia oznacza porównywanie z 0 (tzn == 0, albo != 0) dla typów arytmetycznych, albo null lub != null dla wskaźników i referencji.

Typy złożone edytuj

Typami złożonymi są: wskaźniki, tablice, tablice asocjacyjne, funkcje oraz delegaty.

Tablice edytuj

Tablice (zbiór zmiennych tego samego typu, indeksowany liczbami) tworzy się przy pomocy składni podobnej do tej z C.

int t[100];               // tworzy tablicę 100 liczb typu int i je wypełnia 0

Dozwolona (a wręcza zalecana) jest odwrotna składnia:

int[100] t;

Przy tworzeniu kilku zmiennych wszystkie muszą być tego samego typu:

int a[100], b[100];

Co jest bardziej oczywiste przy zapisie lewostronnym:

int[100] a, b;                 // a i b są tablicami 100 intów

Błędem jest:

int a[100], b[101];             // BŁĄD: różne typy!

Tablice obsługuje się standardowo:

int x = a[37];
a[37] = 4;

W wypadku tablic wielowymiarowych:

int c[10][100];                    // 10 tablic każda o 100 elementach int

albo:

int[100][10] c;                    // UWAGA: Odwrotna kolejność

ewentualnie:

int[100] c[10];

Dostęp do takiej tablicy jest standardowy dla C (indeksuje się od 0):

int l = c[56][5];

Ponieważ rozmiary tablic są znane w czasie kompilacji, tak naprawdę zmienna tablicowa to nie tylko wskaźnik, ale para wskaźnik oraz rozmiar. Jest to bardzo pomocne (w C i tak zwykle tworzy się dodatkową zmienną na pamiętanie rozmiaru) przy przekazywaniu jej do funkcji. Dzięki temu również jest sprawdzana poprawność indeksów:

int l2 = c[56][10];                 // BŁĄD: tablica c[56] ma tylko elementy 0..9

Tego typu błąd powinien zostać zauważony już na etapie kompilacji, a na pewno w czasie wykonania programu (chyba, że skompilowany z opcją -release, która wyłącza takie sprawdzania, ponieważ spowalniają one oczywiście program).

Tablice mają dzięki temu dodatkową właściwość .length, zawierającą rozmiar tablicy (ilość elementów). .length można zmieniać, jednak należy się liczyć z konsekwencjami. Tablice mają również właściwość .ptr, która jest wskaźnikiem jak w C, na początek tablicy. (kiedyś przy rzutowaniu na typ wskaźnikowy, był pobierany właśnie .ptr).

Literały tablicowe edytuj

Tablice można inicjalizować przy pomocy następującej składni:

int[5] a = [1,5,1,77,66];

Można pominąć rozmiar, wtedy zostanie on dobrany automatycznie:

int[] a = [1,5,1,77,66];      // a jest zmienną int[5]

Tak więc a nie jest tzw. tablicą dynamiczną (o czym za chwilę). Typy elementów muszą się zgadzać, w wypadku niejednoznaczności używany jest typ pierwszego elementu, a reszta jest rzutowana (o ile automatyczne rzutowanie jest dozwolone).

Stringi edytuj

Podobnie:

char[10] a;

To para wskaźnik oraz długość. Dzięki temu nie jest potrzebne dodatkowe sprawdzanie długości stringów, i co ważne stringi takie nie mają wbudowanego na końcu znaku '\0'. Dlatego używanie funkcji printf jest niebezpieczne (która poza tym i tak musi przyjąć a.ptr, a nie a).

char[] a;
// .. stwórz a
printf("%s\n", a.ptr);             // wykrzaczy się, ponieważ a nie ma 0 na końcu

Literały stringowe mają jednak go wbudowanego (a "literal".lenght jest oszukany i jest o jeden mniejszy, choć w pamięci zabiera o jeden bajt więcej. Pozostawiono to celem współpracy w C. Literały takie są automatycznie rzutowane na char*). Dlatego to jest poprawne:

printf("Test\n");              // ponieważ literał ten zawiera 0 na końcu.

Stringi w D są więc zwykłymi tablicami. Stringi używają kodowania UTF8. W wypadku zwykłych znaków ASCII nie zmienia to niczego, jednak umożliwia łatwiejsze używanie znaków międzynarodowych: znaki wtedy są te kodowane przy pomocy dwóch bajtów (na szczęście są one używane w miarę rzadko i nie powoduje to dużego wzrostu zużycia pamięci). Wiążą się z tym problemy: .length niekoniecznie jest teraz ilością liter w stringu (poza tym Unicode obsługuje specjalne meta znaki i akcent, co jeszcze bardziej komplikuje sprawę). Warto jedynie wiedzieć, że większość funkcji z std.string radzi sobie z UTF-8 (ponieważ UTF-8 został zaprojektowany, aby w większości kwestii nie trzeba było modyfikować algorytmów). A jeśli chcemy możemy rzutować taki string na tablicę wchar[], wtedy można indeksować znak po znaku (albo iterować pętlą foreach).

W literałach tekstowych można używać znaków Unicode (ponieważ kod źródłowy jest zapisany zwykle w UTF-8, domyślnie - inne zachowanie można przełączyć tzw. BOM).

writefln("Zażółć gęślą jaźń");

Również dowolne identyfikatory mogą mieć znaki Unicode:

float prędkość = 55.6;

Nie powinno się używać zmiennych rozpoczynających się od znaku podkreślenia.

Np. prezentowana dalej pętla foreach:

char[] tekst = "Zażółć gęslą jaźń"
foreach (char znak; tekst) {
  writefln(znak);
}

nie wyświetli znak po znaku tego tekstu, ponieważ znak nie potrafi przechowywać np. 'ż', i na ekranie będzie trochę śmieci. Prawidłowo:

char[] tekst = "Zażółć gęslą jaźń"
foreach (dchar znak; tekst) {  // dchar ma 16 bitów i polskie znaki obsłuży
  writefln(znak);
}

Dynamiczne tablice edytuj

Tablice dynamiczne to tablice których rozmiar możemy zmieniać.

int[] a;               // tworzy tablicę o długości 0 (nie podaje się rozmiaru)
a.length = 5;          // zmienia rozmiar na 5, inicjalizuje nowe miejsca zerami
a[3] = 11;
a.length = 8;          // realokuje
a[6] = 33;
a.length = 5;          // realokuje (być może niszczy obiekty i przenosi w inne miejsce)
a[6] = 44;             // błąd

Ważne jest iż:

int[] a = [1,5,7,8];  // to też tworzy tablicę dynamiczną

to to samo co:

int[] a;
a = [1,5,7,8];

natomiast

int[4] b = [1,5,7,8];

tworzy tablicę statyczną (chociaż inicjalizacja wartościami zostanie przeprowadzona dopiero przy wykonaniu tej linijki). Np. w drugim wypadku możemy zniszczyć tablicę prosto:

a = [66,88];              // zmieniamy na inną

albo dodać (a dokładniej dopisać) prosto element:

a ~= 101;                 // teraz mamy [66,88,101];

Tablice takie łatwo możemy inicjalizować też:

a[] = 5;                     // cała tablica piątek
a = b;                       // kopiuje tablicę

Wycinanie edytuj

Ponieważ tablice są parą wskaźnik oraz długość, można łatwo wycinać tablice, zmieniając końce (a więc te pary).

int[] a = [1,2,3,4,5];       // zwykła tablica
int[] b = a[1..3];           // b będzie [2,3]

Drugi argument wskazuje za ostatni element. Zmienna b wskazuje na te same elementy! A więc zmiana w jednej z nich zmienia drugie. Można używać specjalnej zmiennej length (lub jej synonimu $) wewnątrz nawiasów. Jeśli zależy nam na innej zmiennej length, należy dodać kropkę.

int[] a;
a = [1,2,5,7,8,1,23,4];
int[] b = a[4..$];                // [8,1,23,4]
int length = 5;
int[] b2 = a[4..length];       // to samo co b
int[] b3 = a[4 .. .length];    // [8]


Możemy również w ten sposób konwertować wskaźniki (np. z C) na tablice dynamiczne (ze sprawdzaniem indeksów):

int *p = a.ptr;
int[] c = p[0..8];
char *temp = funkcje_z_c();
char[] s = temp[0 .. strlen(temp)];

Albo przypisywać pewnemu zakresowi tablicy wartości grupowo:

a[1..3] = 100;                   // da [1,100,100,100,8,1,23,4]

Tablice haszujące edytuj

Są to tablice dynamiczne indeksowane innymi typami (np. stringami).

float[char[]] ceny;
ceny["czapka"] = 15.5;
ceny["mleko"] = 2.1;

Aktualna implementacja tablic asocjacyjnych znajduje się w pliku Aa.d w bibliotece standardowej. Aktualnie tablice te nie zachowują żadnego porządku, można indeksować dowolnymi obiektami (można udostępnić metodę uint toHash() oraz int opCmp(KeyType* s)).

Literały dla tablic haszujących mają następującą składnię (dostępne od DMD 1.014):

 float[char[]] ceny;
 ceny = ["mleko": 2.1, "chleb": 1.9];

Typy klucza i wartości są wnioskowane na podstawie pierwszego elementu, a reszta jest rzutowana niejawnie na te typy (tutaj char[5], float). W przypadku łańcuchów znaków więc trzeba dodatkowo zrobić rzutowanie:

 ceny = [cast(char[])"mleko": 2.1, "chleb": 1.9, "sok": 3.4];
// bo "sok" jest char[3] i nie można rzutować do char[5] ("mleko")

Własne tablice edytuj

Operatory [] używane do przypisywania, wczytywania, oraz wycinania można zaimplementować we własnej klasie (np. w celu stworzenia "kontentera", zbiorów różnego typu, czy innych typów dynamicznych, np. drzew, list, itp).

Iterowanie edytuj

Poza pętlą for, while, do..while taką jak w C:

for (int i = 0; i < 10; i++) {
  writefln("Haha");
}

Dostępna jest również pętla foreach, która ułatwia iterowanie po kolekcjach:

int[] a = [1,5,6];
foreach (int i, int v; a) {               // i to indeks, v to wartość
  writefln(i, " element tablicy a to ", v);
}

Co można skrócić:

foreach (i, v; a) {          // typy i oraz v zostaną automatycznie dobrane
  writefln(i, " element tablicy a to ", v);
}

Albo jeśli nie potrzebujemy indeksu:

foreach (v; a) {          // typy i oraz v zostaną automatycznie dobrane
  writefln("w a jest ", v);
}

Jeśli chcemy modyfikować elementy podczas iteracji należy użyć atrybutu inout (indeksów nie wolno modyfikować, jak również nie powinno się modyfikować długości tablicy, ani np. dodawać elementów do kontenera):

foreach (i, inout v; a) {
  writefln("w a było a[%d]", i,  v);
  v = i;
}

Istnieje również foreach_revese który iteruje w przeciwnym kierunku. foreach można zaimplementować w własnej klasie przy pomocy opApply (która przyjmuje delegata o różnej ilości argumentów). Bardzo to ułatwia pisanie różnego kodu.

Kompilator sam się zatroszczy o wybranie optymalnego sposobu iteracji (liczbami, a może wskaźnikami, itp).

W przypadku tablic haszujących foreachem można iterować po parach klucz, wartość (porządek iteracji nie jest zachowany!, przynajmniej w bieżącej implementacji):

char[][char[]] telefony;
telefony["Witek"] = "123-123";
telefony["Jacek"] = "165-163";
telefony["Marcin"] = "667-121";
foreach(kto, t; telefon) {            // wnioskowanie typów
  writefln("Osoba ", kto, " ma numer ", t);
}

Typy definiowane edytuj

Służą do tego słowa kluczowe: alias, typedef, enum, struct, union oraz class. W celu dowiedzenia się o nich więcej zajrzyj do rozdziału Typy złożone. Najważniejsze fakty to to, że enum używa się jak w C.

enum Karty {
  PIK, TREFL, KARO, KIER             // duże litery to tylko konwencja
}

alias tworzy inną nazwę typu (np. krótszą):

alias char[][char[]] mapa;            // tablica haszujaca stringów w stringi

typdef z kolei tworzy nowy typ:

typedef int cena = -1;

Możemy redefiniować nowy .init dla takiego typu. Przypisywanie pomiędzy typem int i cena jest niedozwolone bez jawnego rzutowania:

int a = 6;
cena b = a;         // BŁĄD
cena c;             // -1

Przeładowanie funkcji działa dla nich oddzielnie:

f(int a) ... 
f(cena a)...

to dwie różne funkcje.

Atrybuty, wnioskowanie typu edytuj

Atrybuty dostępu: public, private, protected.

Atrybuty inne: volatile (ulotne, ważne przy wielowątkowych aplikacjach), const (nie wolno zmieniać), static (składniki statyczne).

Automatyczne wnioskowanie typu:

char[][][char[]] x;
x["as"] = ["vv", "gv", "nn"];
auto copy = x;         // kopia będzie takiego samego typu co x

Automatyczne wnioskowanie jest również używane bez auto, jeśli pominiemy typ przy innym atrybucie (np. const, static, scope):

const x = 5;               // to samo, co const int x
static t = [4,5,6];        // to samo, co static int t[3]

Ma ono również zastosowanie w foreach (częściowo) oraz w szablonach.

Atrybut zasięgu:

 { 
 scope Plik t = new Plik("dane.txt");         // 
 ...
 }        // po wyjściu t zostanie zniszczony

możemy pominąć nazwę typu: wnioskowanie

 {
 scope t = new Plik("dane.txt");
 ...
 }

Atrybut zasięgu wyrażenia:

 {
   stworzy_plik_tymczasowy();
   scope(failure) writefln("Wystapil nieprzewidziany blad, usuwam pliki");
   scope(exit) usuń_plik_tymczasowy();
   ...
   ...
   zróbmy_błąd(); // np. wyjątek, albo break, albo goto, albo return, coś co spowoduje opuszczenie zasięgu
   ...
   ...
 } // to co przy scope zostanie wykonane, jeśli nastąpi wyjście lub błąd

(oraz scope(failure), scope(success), do wyjątków), ułatwiają cofanie różnych transakcji oraz umieszczają kod obsługi błędu w jednym miejscu.

Funkcje edytuj

Funkcje się jedynie definiuje, nie są potrzebne deklaracje, nie ma potrzeby używania plików nagłówkowych, funkcje i zmienne globalne, można używać przed ich deklaracją. Przykłady:

import std.stdio;
void main() {
  writefln(cos());
}
int cos() {
  return 42;
}

Zmienne wbudowane, enumy oraz struktury są przekazywane przez wartość. Tablice oraz obiekty klas są przekazywane przez referencję. W przypadku tablic domyślna jest semantyka COW (kopiowanie przy zapisie) mówiąca, że nie jest robiona kopia dopóki nie jest to ostatecznie wymagane (przynajmniej tak robią funkcje biblioteki standardowej ze względu na wydajność). Kopię tablicy można zrobić poprzez "metodę" .dup zmiennej tablicowej.

Parametry inout edytuj

W celu eliminacji użycia wskaźników do modyfikowania zmiennych spoza funkcji używa się parametrów inout (oraz out, in, ten ostatni jest domyślny):

import std.stdio;
void main() {
  int a = 55;
  cos(a);
  writefln("a=%d", a);
}
void cos(inout int x) {
  x += 10;
}

Wyświetli 65. W nowej wersji istnieje tez synonim inout: ref.

Zagnieżdżanie edytuj

Funkcje można zagnieżdżać w funkcjach (funkcje zagnieżdżone mają dostęp do zmiennych i parametrów formalnych funkcji na wyższym poziomie).

import std.stdio;
void main() {
  writefln(cos(55, 66));
}
int cos(int x, int y) {
  int roznicarazy(int r) { return r*(x-y); }        // możemy używać x, y
  return roznicarazy(5) + x + y;
}

Funkcje z void edytuj

W funkcjach void wychodzi się przez return bez argumentów (lub z, wtedy to co jest przy return jest obliczane, ale nie jest zwracane).

Funkcję z C edytuj

Deklaracje funkcji z C, Pascal, Fortrana czy Win32 obejmuje się atrybutem extern(C) itp.

Jeśli chce się aby dana funkcja zdefiniowana w D, mogła być wywoływana z C należy dodać jej taki atrybut (zmienia się wtedy między innymi konwencja przekazywania parametrów, zachowanie funkcji zmiennoargumentowych).

"Inlinowanie" edytuj

Kompilator sam decyduje kiedy "inlainować" funkcję lub metodę.

Funkcje czasu kompilacji edytuj

Jeśli funkcja jest używana w kontekście inicjalizacji zmiennnej statycznej lub const, albo przekazywana jako wyrażenie do szablonu. Funkcja jest wykonywana w czasie kompilacji.

 int sqr(int a) { return a*a; }
 int main() {
    static x = sqr(5);                // wykona się w trakcie kompilacji
    int x = sqr(5);                   // w trakcie wykonania
    writefln(sqr(5));                 // w trakcie wykonania
    writefln(eval!(sqr(5)));          // w trakcie kompilacji
    const y = 6;
    const x2 = sqr(y);                // w trakcie kompilacji
    int y2 = 6;
    const x3 = sqr(y2);               // w trakcie wykonania
 }
 template eval(E) { alias E eval; }

Funkcje czasu kompilacji mogą używać jedynie stałych parametrów (literałów albo innych stałych znanych w czasie kompilacji zmiennych stałych czy statycznych), mogą używać tablic, literałów, struktur, pętli, oraz innych funkcji nienaruszających tych zasad. Nie wolno używać klas, plików, instrukcji IO (poza pragma(msg, "Komunikat")).

Pozwala to na przykład na prekalkulowanie pewnych stałych czy ich stablicowanie już na etapie kompilacji, albo na łatwe tworzenie kodu do wykorzystania przez mixiny.


< powrót do spisu treści