A Digitalmars D programozási nyelv

Utasítások, vezérlési szerkezetek

A C és C++ programozók számára a D utasítások ismerősek lesznek, néhány érdekes újdonsággal kibővítve.

Blokk-, kifejezés- és deklarációs utasítások ugyanolyanok mint a C/C++-ban, az utasítások ;-vel vannak elválasztva egymástól.

A goto, continue és break utasítások ugyanúgy megtalálhatók a D nyelvben mint a C++-ban, ugyanazzal a jelentéssel.

A synchronize, try-catch-finally és throw megvalósításokat szintén megtartották ebben a nyelvben, működésük hasonló a C++-beli működéshez.

Kifejezések

A C és C++ programozók számára a D kifejezések a megszokottak lesznek néhány érdekes kiegészítéssel.

Kiértékelési sorrend

Hacsak nincs másképp specifikálva, az implementáció nincs megkötve abból a szempontból, hogy egy kifejezés komponenseit milyen sorrendben értékelje ki. Hiba akkor lép fel a kiértékelés sorrendjétől függően, ha az nincs specifikálva. Például a következő kifejezések illegálisak:

i = ++i; c = a + (a = b); func(++i, ++i);

Ha a fordító meg tudja határozni, hogy egy kifejezés eredménye illegális-e a kiértékelés sorrendjétől függően, akkor jelezheti a hibát.

Előbb a baloldali operandus, majd a jobboldali operandus értékelődik ki. A kifejezés típusa a jobboldali operandus típusa lesz, és az eredmény a jobboldali operandusban jelenik meg.

Assign kifejezések

A jobboldali operandus impliciten átkonvertálódik a baloldali operandus típusára, és hozzárendelődik. Az eredmény típusa a balérték típusa lesz, és az eredmény értéke a balérték lesz miután kiértékelődött.

A baloldali operandusnak egy balértéknek kell lennie.

Értékadó operátor kifejezések

+= -= *= /= %= &= |= ^= <<= >>= >>>=

Az értékadó operátor kifejezések alakja: a op= b, ami szemantikusan ekvivalens az a = a op b alakkal, azzal a különbséggel, hogy az a operandus csak egyszer értékelődik ki.

Feltételes kifejezések

OrOrExpression ? Expression : ConditionalExpression

Az első kifejezés boolean típusúra konvertálódik át, majd kiértékelődik. Ha ez igaz, akkor a második kifejezés értékelődik ki, majd ennek eredménye lesz az eredeti kifejezés eredménye. Ha az első kifejezés értéke hamis, akkor a harmadik kifejezés értékelődik ki, és ez lesz az eredmény. Ha a második vagy a harmadik kifejezés típusa void, akkor az eredmény típusa is void lesz. Egyébként a második és harmadik kifejezés típusa impliciten átkonvertálódik egy egyszerű típussá, és ez lesz az eredmény típusa.

VagyVagy kifejezések

AndAndExpression || AndAndExpression

Egy vagyvagy kifejezés eredményének típusa boolean lesz, kivéve ha a jobb operandus típusa void -> ekkor az eredmény is void lesz.

Először a baloldali operandus értékelődik ki, amelynek típusa boolean lesz. Ha ez igazra értékelődik ki, akkor a jobboldali operandus nem értékelődik ki, az eredmény true lesz feltéve, hogy az eredmény típusa boolean. ha a baloldali operandus hamis, akkor az eredmény a jobboldali operandus eredményének boolean-ra konvertált értéke lesz.

ÉsÉs kifejezések

OrExpression && OrExpression

Egy ésés kifejezés eredményének típusa boolean, de ha a jobboldali operandus típusa void, akkor az eredmény is void lesz.

Először a baloldali operandus értékelődik ki. Ha ennek booleanra konvertált eredménye false, akkor a jobboldali operandus nem értékelődik ki, az eredmény false lesz. Egyébként az eredmény a jobboldali operandus booleanra konvertált értéke lesz.

Bitwise kifejezések

A bitwise kifejezések bitenkénti műveleteket végeznek az operandusaikon. Az operandusok beépített típusúak kell hogy legyenek.

Vagy kifejezések

XorExpression | XorExpression

Xor kifejezések

AndExpression ^ AndExpression

És kifejezések

EqualExpression & EqualExpression

Egyenlőség kifejezések

RelExpression == RelExpression //egyenlőségvizsgálat
RelExpression != RelExpression // igaz, ha nem egyenlő

A C, C++-ban megszokott módon működik.

Lebegőpontos típus esetén bonyolultabb az egyenlőségvizsgálat. A -0 és a +0 egyenlők. Ha az egyik vagy mindkét operandus NaN, akkor az == és a != is false értéket ad vissza. Egyébként a szokásos módon történik az összehasonlítás.

Komplex számok esetén az egyenlőség a következőképpen van definiálva:

x.re == y.re && x.im == y.im

az egyenlőtlenség pedig:

x.re != y.re || x.im != y.im

Az osztály objektumokra az egyenlőség az Object.eq() meghívásával dönthető el. Két null objektum egyenlő, ha csak az egyik objektum null, akkor nem egyenlők.

A statikus és dinamikus tömböknél az egyenlőséget a tömbök hosszának egyenlősége és összes elemeinek egyenlősége adja.

Azonosság kifejezések

RelExpression === RelExpression
RelExpression !== RelExpression

A === azonosságot vizsgál, a !== a nem azonosságot nézi. Az eredmény típusa boolean.

Ha az operandusok típusa nem osztály objektum, statikus vagy dinamikus tömbök, akkor az azonosság ugyanazt jelenti, mint az egyenlőség.

Osztály objektum esetén az azonosság azt jelenti, hogy az objektum referenciák ugyanarra az objektumra mutatnak.

Statikus és dinamikus tömbök esetén az azonosság a tömbelemek azonosságára vonatkozó azonosságot jelenti.

Relációs kifejezések

< <= > >= !<>= !<> <> <>= !> !>= !< in

A beépített típusokra a szokásos módon működik.

Osztály objektumok esetén az Object.cmp()-t hívjuk meg a baloldali operandusra, 0-t adunk meg jobboldali operandusként. Például az (o1 op o2) relációs kifejezést a következő formában kell megadni:

(o1.cmp(o2) op 0)

Hibát ad, ha az egyik objektum null.

Statikus és dinamikus tömbök esetén az op reláció a tömbök első nem egyenlő elemét hasonlítja össze. Ha a két tömb egyenlő, de különböző hosszúságúak, akkor a rövidebb tömb "kevésbé" lesz összehasonlítva, mint a hosszabb tömb. (????)

Integerek összehasonlítása

Számok összehasonlítása a szokott módon a <, >, <=, >=, ==, != relációkkal történik. Hibát eredményez az összehasonlítás a <, <=, >, >= relációk esetén, ha az egyik operandus signed, a másik pedig unsigned.

Lebegőpontos számok összehasonlítása

Ha az egyik vagy mindkét operandus lebegőpontos, akkor lebegőpontos összehasonlítást végzünk.

14 lehetséges összehasonlítást mutat be az alábbi táblázat, ahol az unordered jelentése: az egyik vagy mindkét operandus NaN:

Operator Greater Than Less Than Equal Unordered Exception Relation
== F F T F no equal
!= T T F T no unordered, less, or greater
> T F F F yes greater
>= T F T F yes greater or equal
< F T F F yes less
<= F T T F yes less or equal
!<>= F F F T no unordered
<> T T F F yes less or greater
<>= T T T F yes less, equal, or greater
!<= T F F T no unordered or greater
!< T F T T no unordered, greater, or equal
!>= F T F T no unordered or less
!> F T T T no unordered, less, or equal
!<> F F T T no unordered or equal

Megjegyzés:

  1. Lebegőpontos összehasonlítások esetén az (a !op b) nem ugyanaz mint a !(a op b).
  2. "Unordered" jelentése: egyik vagy mindkét operandus egy NAN.
  3. "Exception" jelentése: Invalid Exception lép érvénybe, ha az operandusok közül az egyik NAN.

In kifejezések

ShiftExpression in ShiftExpression

Egy asszociatív tömb esetén ellenőrizni lehet, hogy egy elem eleme-e a tömbnek:

int foo[char[]]; . if ("hello" in foo)

Az in kifejezésnek ugyanolyan precedenciája van, mint a relációs kifejezéseknek: <, <= -nak, stb.

Shift kifejezések

AddExpression << AddExpression
AddExpression >> AddExpression
AddExpression >>> AddExpression

Az operandusoknak beépített típusúaknak kell lenniük. Az eredmény típusa a baloldali operandus típusa lesz. Az eredmény értéke a jobboldali operandus értékével való léptetés eredménye lesz.

<< // balra léptetés >> // egy signed jobbra léptetése >>> // egy unsigned jobbra léptetése.

Add kifejezés

MulExpression + MulExpression
MulExpression - MulExpression

Beépített típusok esetén szokásos.

Ha az egyik operandus lebegőpontos, akkor a másik operandus impliciten átkonvertálódik erre a típusra, végül az aritmetikai konverzió hajtódik végre ha szükséges.

Ha az első operandus egy pointer, a második pedig egy beépített típus, akkor az eredmény típusa az első operandus típusa lesz, az eredmény értéke pedig a pointer plusz (vagy mínusz) a második operandus szorozva az első operandus típusának méretével.

A + operátor esetén, ha mindkét operandus tömb kompatíbilis típussal, akkor az eredmény is tömb lesz kompatíbilis típussal, és az értéke a két tömb konkatenációja lesz.

Mul kifejezések

UnaryExpression * UnaryExpression
UnaryExpression / UnaryExpression
UnaryExpression % UnaryExpression

Aritmetikai típusokra a szokásos módon működik, az osztás vagy a modulusképzés a DivideByZeroException kivételt dobja, ha a jobboldali operandus 0.

Lebegőpontos operandusok esetén a műveletek megfelelnek az IEEE 754 lebegőpontos ekvivalenciának. A modulusképzés csak valós számok esetén alkalmazható, képzetes vagy komplex számokra nem.

Unáris kifejezések

& ++ -- * - + ! ~ delete new ( Type ) ( Type ) . Identifier

Cast kifejezések

A C-ben és a C++-ban a cast kifejezés alakja a következő:

(type) unaryexpression

Ez viszont kétértelmű, ugyanis:

(foo) - p;

Ez most a p negáltjának a dereferenciája lesz a foo típusra, vagy a p a foo-bóI van kivonva? Ezt csak úgy lehetne eldönteni, hogy a szimbólumtáblában megnézzük, hogy a foo egy típus vagy egy változó. Viszont a D tervezésekor az volt az egyik cél, hogy ne kelljen a szimbólumtáblához fordulni ilyen esetben. Ezért más szintaxisra volt szükség.

C++ ezt a következőképpen oldja meg:

dynamic_cast<Type>(expression)

ami nem szép megoldás. A D-ben bevezették a cast kulcsszót:

cast(foo) -p; // cast (-p) a foo típusra (foo) - p; // p kivonása a foo-ból

A D-nek nincs a Java-hoz hasonló instanceof operátora, mivel a cast operátor ugyanennek a funkciónak felel meg:

Java:

if (a instanceof B)

D:

if ((B) a)

Primary kifejezések

Identifier this super null true false NumericLiteral StringLiteral AssertExpression Type . Identifier

Assert kifejezések

AssertExpression: assert ( Expression )

Az assert-ek kiértékelik az expression-t. Ha az eredmény false, akkor egy AssertException kivétel lép fel. Ha az eredmény true, akkor nincs kivétel. Akkor lép fel hiba, ha az expression bármilyen mellékhatást tartalmaz, ami a programtól függ. Egy assert kifejezés eredményének típusa void lesz. Az assert-ek alapvető részei a Design by Contract-nak (DBC) amelyet a D támogat.

Cimkézett utasítások, GOTO

Az utasításokat el lehet látni címkével. A címke egy azonosító, ami megelőz egy utasítást.

LabelledStatement: Identifier ':' Statement

Bármilyen, még az üres utasítást is meg lehet címkézni, ezzel segítve a goto, break és continue utasításokat.

Goto utasítás

GotoStatement:
goto Identifier ;
goto default ;
goto case ;
goto case Expression ;

A goto hatására a vezérlés a goto után megadott cimkén folytatódik.

if (foo) goto L1; x = 3; L1: x++;

goto default; esetén a tartalmazó switch utasítás default ágához ugrik.

goto case; a tartalmazó switch utasítás következő case ágában lévő utasításhoz ugrik.

goto case expression; a tartalmazó switch utasítás Expression-t tartalmazó ágához ugrik.

switch (x) { case 3: goto case; case 4: goto default; case 5: goto case 4; default: x = 4; break; }

Minden közbeeső finally végrehajtásra kerül és minden szinkronizációs mutex felszabadul (release)

Goto-t nem lehetséges inicializáció kihagyására használni.

Elágazások

If utasítás

IfStatement:
if ( Expression ) Statement
if ( Expression ) Statement else Statement

A szokásos módon működik. Egy 'kallódó' else ág megegyezés szerint a legközelebbi if utasításhoz tartozik.

Switch Statement

SwitchStatement:
switch ( Expression ) BlockStatement

CaseStatement:
case Expression : Statement

DefaultStatement:
default: Statement

A switch feltétele kiértékelődik, melynek eredménytípusa beépített típus, char[] vagy wchar[] kell hogy legyen. Az ennek megfelelő case ág végrehajtódik. Ha nincs megfelelő ág, akkor a default ág hajtódik végre. Ha nincs default ág, akkor SwitchException kivétel lép fel. Csak egy default ág lehet.

A case ág utasítássorozatai és a default ág utasítássorozatai blokkba ágyazottak is lehetnek, nem kell a legkülső blokkban lenniük. Például a következő forma megengedett:

switch (i) { case 1: { case 2: } break; }

A C és C++-hoz hasonlóan a case utasítás nem állítja meg a "lefelé csordogálást", a break utasítással lehet kiugrani a blokkból. Például a következő utasítás 4-et ad eredményül, ha az i=1 volt:

switch (i) { case 1: x = 3; case 2: x = 4; break; case 3: x = 5; break; }

Megjegyzés: A C és C++-al ellentétben stringet is használhatunk a switch kifejezésében! Például:

char[] name; ... switch (name) { case "fred": case "sally": ... }

Ciklusok

While utasítás

WhileStatement: while ( Expression ) Statement

Addig fut, amíg az Expression hamis értéket nem ad. Egy break utasítás hatására kiugrunk a ciklusból. Egy continue utasítás hatására a ciklus az Expression kifejezés vizsgálatával folytatódik. Lásd C++-nál.

Do-While utasítás

DoStatement: do Statement while ( Expression )

A Statement egyszer biztosan végrehajtódik. Ha az Expression igazra értékelődik ki, újra végrehajtódik a ciklusmag.

A break -kel kiugrunk a ciklusból, a continue-val újra kiértékeljük a feltételt.

For utasítás

ForStatement: for (Initialize; Test; Increment) Statement

Hasonlóan működik mint a C++-beli for ciklus.

A break-ke kiugrunk, a continue-val az Increment-tel folytatódik a program.

Az Initializer-ben deklarálhatjuk a ciklusváltozót, amit a ciklusmagban is használhatunk:

for (int i = 0; i < 10; i++) foo(i);

A függvénytörzs nem lehet üres:

for (int i = 0; i < 10; i++) ; // hibás

Helyette használjunk üres blokk-utasítást:

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

Az Initializer és a Test elhagyható. Ha nincs megadva Test, akkor az olyan, mintha mindig a true érték állna ott.

Foreach utasitás

A foreach utasitás egy collection (gyűjtemény) elemein iterál

ForeachStatement:
Foreach (ForeachTypeList; Aggregate) NoScopeNonEmptyStatement

Foreach:
foreach
foreach_reverse

ForeachTypeList:
ForeachType
ForeachType , ForeachTypeList

ForeachType:
inout Type Identifier
Type Identifier
inout Identifier
Identifier

Aggregate:
Expression
Tupleu

A collection kiértékelődik. Statikus tömb, dinamikus tömb, asszociatív tömb, struktúra, osztály, delegate, vagy tuple(rendezett n-es) típusok valamelyikére kell kiértékelődnie.

A NoScopeNonEmptyStatement a collection minden elemére végrehajtódik egyszer. Minden iteráció elején a ForeachTypeList-ben megadott változók felveszik a csoport egyes elemeinek értékét.

Ha a változó inout típusú akkor referencia adódik át az elemre, különben másolás történik.

A collection a ciklus invariánsa kell hogy legyen, azaz egyetlen elem sem adható a csoporthoz illetve távolítható el belőle a NoScopeNonEmptyStatement-ben.

Ha a csoport statikus vagy dinamikus tömb, akkor egy vagy két változót deklarálhatunk.

Ha egyet, akkor a változó a tömbbeli elemek értékét veszi fel egyenként. Értelemszerűen a változó típusa meg kell hogy egyezzen a tömb elemeinek típusával (kivéve a lent vázolt esetben)

Ha kettőt, akkor az első változó az index, míg a második változó az érték. Az index csak int vagy uint típusú lehet, és nem lehet inout módú. Az index értéke a tömbelem aktuális elemének indexe lesz.

Pl.:

char[] a; ... foreach (int i, char c; a) { printf("a[%d] = '%c'\n", i, c); }

A foreach utasításnál a tömb a 0-s kezdőindextől iterálódik a tömb hosszáig (maximum), míg foreach_reverse esetén az iteráció fordított irányú.

Ha a collection char, wchar vagy dchar típusokból álló statikus vagy dinamikus tömb akkor az érték típusa bármelyik lehet a char, wchar, dchar közül. Eszerint bármelyik UTF tömböt átkonvertálhatjuk bármelyik másikká:

char[] a = "\xE2\x89\xA0"; // \u2260 mint 3 UTF-8 bytes foreach (dchar c; a) { printf("a[] = %x\n", c); // prints 'a[] = 2260' } dchar[] b = "\u2260"; foreach (char c; b) { printf("%x, ", c); // prints 'e2, 89, a0' }

A csoport lehet string literál is, és ebben az esetben char, wchar vagy dchar típusú tömbként érhetjük el:

void test() { foreach (char c; "ab") { printf("'%c'\n", c); } foreach (wchar w; "xy") { wprintf("'%c'\n", w); } }

Ami a következő kimenetet adja:

'a ' 'b' 'x' 'y'

Ha a csoport asszociatív tömb, akkor egy vagy két változó lehet definiálva:

Ha egy, akkor a változó értéke a tömb elemeinek értéke egyenként

Ha kettő, akkor az első változó az index, míg a második változó az érték. Az index típusa meg kell egyezzen az asszociatív tömb indexeinek típusával, és nem lehet inout módú. Az index értéke a tömbelem aktuális elemének indexe lesz.

Abban az esetben ha asszociatív tömb esetén az elemek sorrendje nem definiált a foreach_reverse nem értelmezett. (foreach viszont igen)

double[char[]] a; // index type is char[], value type is double ... foreach (char[] s, double d; a) { printf("a['%.*s'] = %g\n", s, d); }

Ha struct vagy class típusú, a foreach egy speciális opApply member függvény segítségével van definiálva. A foreach_reverse pedig a speciális opApplyReverse memberrel. Ha az osztályra szeretnénk használni a foreach, illetve foreach_reverse utasításokat, akkor ezekben a speciális függvényekben a típus meg kell egyezzen a ForeachType típussal.

int opApply(int delegate(inout Type [, ...]) dg); int opApplyReverse(int delegate(inout Type [, ...]) dg);

ahol

Tpye = ForeachTpye::Type (Identifier)

Több ForeachType megfelel több Type-nak az opApply vagy opApplayReverse függvényekben.

Több opApplay és opApplayReverse függvény is lehetséges (túlterhelés/polimorfizmus). A megfelelőt a dg típusával választja ki a fordító, úgy hogy megegyezzen a ForeachType-al az ForeachStatement-ben. A függvények törzse iterál a collection elemein, és átadja őket a dg függvénynek. Ha a dg 0-val tér vissza, akkor veszi a következő elemet (következő iterációs lépés) Ha dg nem 0-val tér vissza akkor az iteráció megáll és a függvény visszatér azzal az értékkel. Ha az iteráció befejeződött (végigment az összes elemen) a függvény 0-val visszatér.

Például vegyünk egy 2 elemet tároló osztályt:

class Foo { uint array[2]; int opApply(int delegate(inout uint) dg) { int result = 0; for (int i = 0; i < array.length; i++) { result = dg(array[i]); if (result) break; } return result; } }

Ezt használhatjuk pl az alábbi kódban:

void test() { Foo a = new Foo(); a.array[0] = 73; a.array[1] = 82; foreach (uint u; a) { printf("%d\n", u); } }

És a következő eredményt kapjuk:

73 82

Ha a csoport egy delegate, akkor a delegate típusa megegyezik az opApply típusával. Ez lehetővé teszi több különböző iterációs stratégia működését ugyanabban az osztályban vagy struktúrában.

Inout használatával felülírhatjuk az eredeti elemeket:

void test() { static uint[2] a = [7, 8]; foreach (inout uint u; a) { u++; } foreach (uint u; a) { printf("%d\n", u); } }

Eredmény:

8 9

De: az index nem lehet inout típusú

Ha a Types a ForeachType-ban nem definiált akkor meghatározható a csoport típusából.

A csoportot az iteráció alatt nem lehet átméretezni, újraallokálni, felszabadítani, vagy megsemmisíteni:

int[] a; int[] b; foreach (int i; a) { a = null; // error a.length += 10; // error a = b; // error } a = null; // ok

Ha a csoport egy tuple akkor egy vagy két változó deklarálható:

Ha egy, akkor a változó értéke a tuple elemeinek értéke egyenként. Ha a változó típusa adott akkor meg egyezzen a tuple elemeinek típusával. Ha nincs megadva a változó típusa automatikusan a tuple elemeinek típusa lesz amely iterációs lépésenként változhat.

Ha kettő, akkor az első változó az index, míg a második változó az érték. Az index típusa int vagy uint lehet, és nem lehet inout. Az index értéke a tuple aktuális elemének indexe lesz.

Ha a tuple egy típuslista akkor a foreach utasítás végrehajtódik egyszer minden típusra, és az érték egy alias lesz típusra.

import std.typelist; // for TypeList void main() { alias TypeList!(int, long, double) TL; foreach (T; TL) { writefln(typeid(T)); } }

Eredmény:

int long double

A foreach törzsében elhelyezett break utasítás hatására kilép a foreach-ből, míg continue utasításra a következő iterációs lépésbe kezd.

Continue utasítás

ContinueStatement:
continue;
continue Identifier ;

A continue abortálja a tartalmazó ciklus aktuális iterációját, és a következő iterációs lépésre ugrik.

Érvényes a while, for, do ciklusokra.

Ha a continue-t egy azonosító követi, akkor az egy címke egy tartalmazó while, for vagy do ciklusra, és a ciklus következő iterációja kerül végrehajtásra. Amennyiben a címke érvénytelen, hiba lép fel.

Minden közbeeső finally végrehajtódik, és minden közbeeső szinkronizációs objektum szabaddá válik (release)

Megjegyzés:

Ha egy finally egy return throw vagy goto utasítást hajt végre amely elhagyja a finally-t a continue célját nem lehet elérni!

for (i = 0; i < 10; i++) { if (foo(i)) continue; bar(); }

Break utasítás

BreakStatement:
break;
break Identifier ;

A break kilép az őt tartalmazó utasításból, és a soron következő utasítást hajtja végre. Érvényes a legbelső while, for, do, vagy switch utasításokra.

Ha a breaket egy azonosító követi akkor az egy címke a tartalmazó while, for, do, vagy switch utasításra, és ennek hatására abból az utasításból lép ki. Amennyiben a címke érvénytelen, hiba lép fel.

Minden közbeeső finally végrehajtódik, és minden közbeeső szinkronizációs objektum szabaddá válik (release)

Ha egy finally egy return throw vagy goto utasítást hajt végre amely elhagyja a finally-t akkor a break célját nem lehet elérni.

for (i = 0; i < 10; i++) { if (foo(i)) break; }

Operátor-túlterhelés (D 1.0)

Overloading tökéletes eszköz speciális egy, vagy kétoperandusú tagfüggvények implementálására.

Egyértékű Operátor Túlterhelés

Túlterhelhető Egyértékű Operátorok

        op         opfunc
- neg
~ com
e++ növelés
e-- Csökkentés

Egyértékű túlterhelést ad.

op : operátor ; opfunc : a megfelelő osztály vagy struktúra függvény neve. A szintaxis:

op a

ahol a egy osztály vagy struktúra referencia. Interpretációja a következőképpen történik:

a.opfunc()
++e és –e túlterhelése

Mióta ++e szemantikája úgy van definiálva, hogy szemantikája megegyezzen e+=1 -gyel, ++e kifejezés e+=1 alakra újraírható, és eztán az operátor túlterhelés ellenőrzése kész van. A helyzet –e-vel hasonló.

Például:

class A { int neg(); } A a; -a; // ekvivalens a.neg()-gel; class A { int neg(int i); } A a; -a; // ekvivalens a.neg()-gel, ami hiba

Bináris Operátor Túlterhelés

Túlterhelhető Bináris Operátorok

   op       kommutatív?       opfunc        opfunc_r
+ igen add -
- nem sub sub_r
* igen mul -
/ nem div div_r
% igen mod mod_r
& nem and -
| igen or -
^ igen xor -
<< nem shl shl_r
>> nem shr shr_r
>>> nem ushr ushr_r
~ nem cat cat_r
== igen eq -
!= igen eq -
< igen cmp -
<= igen cmp -
> igen cmp -
>= igen cmp -
+= nem addass -
-= nem subass -
*= nem mulass -
/= nem divass -
%= nem modass -
&= nem andass -
|= nem orass -
^= nem xorass -
<<= nem shlass -
>>= nem shrass -
>>>= nem ushrass -
~= nem catass -

Kétoperandusú túlterhelést tesz lehetővé, az op az operátor, az opfunc a megfelelő osztály vagy struktúra függvénye, a szintaxis a következő:

a op b

kiértékelése megegyezik, mintha a következőt írtuk volna:

a.opfunc(b)

vagy:

b.opfunc_r(a)

A következő szabály sorozatot alkalmazza, az alábbi sorrendben, annak meghatározására, hogy melyik formula van használva:

Ha a egy struktúra, vagy osztály referencia, amely opfunc-beli nevek valamelyikét tartalmazza, a kifejezés átírható:

a.opfunc(b)

Ha b egy struktúra vagy osztály referencia, amelyet az opfunc_r oszlop tartalmaz és az operátor nem kommutatív, a kifejezés átírható:

b.opfunc_r(a)

Ha b egy struktúra vagy osztály, amelyet az opfunc oszlop tartalmaz, és az operátor (op) kommutatív, a kifejezés átírható:

b.opfunc(a)

Abban az esetben, ha a vagy b egy struktúra vagy osztály, hiba.

Például:

class A { int add(int i); } A a; a + 1; // ekvivalens a.add(1)-gyel 1 + a; // ekvivalens a.add(1)-gyel class B { int div_r(int i); } B b; 1 / b; // ekvivalens b.div_r(1)-gyel
x
== and != túlterhelése

Mindkét operátor az eq() függvényt használja. (a == b) kifejezés úgy is leírható, mint a.equals(b), és (a != b) mint !a.equals(b).

Az eq() függvény is az Object osztály részeként van definiálva a következőképpen:

int eq(Object o);

vagyis minden osztálynak létezik eq() művelete.

Ha egy struktúrának nincs eq() függvény deklarálva akkor a két struktúrában lévő bitek összehasonlítása határozza meg az egyenlőséget, vagy a nem egyenlőséget.

<, <=, > and >= túlterhelése

Ezek az összehasonlító operátorok a cmp() függvényt használják. Az (a op a) kifejezés átírható (a.cmp(b) op 0) alakra. A kommutatív műveletek a (0 op b.cmp(a)) alakra írhatóak át.

A cmp() függvény az Object osztály műveleteként van meghatározva a következőképpen:

int cmp(Object o);

vagyis minden osztálynak létezik cmp() művelete.

Ha egy struktúrának nincs cmp() függvény deklarálva, akkor megkísérli a két struktúrát összehasonlítani, és ha nem sikerül hibaüzenetet küld.

Megjegyzés: Referencia és egy osztály objektum összehasonlítása lehetséges, mint:

if (a === null)

és nem mint:

if (a == null)

Az utóbbit átkonvertálja:

if (a.cmp(null))

alakra, amely hibaüzenetet küld, ha cmp virtuális függvény.

Alapok

Annak oka, hogy mind eq() és cmp() függvény is van definiálva az, hogy

Egyenlőség tesztelése néha sokkal eredményesebb, mint a nagyobb, vagy kisebb vizsgálat.

Néhány objektum esetében a nagyobb vagy kisebb összehasonlításnak nincs semmi értelme. Ezekben az esetekben a cmp() függvényt hatástalanítani lehet:

class A { int cmp(Object o) { assert(0); // az összehasonlításnak nincs értelme return 0; } }
Jövőbeli kilátások

Egyre több operátor válik túlterhelhetővé, de némelyek, mint ., &&, ||, ?:, és még néhány soha.

Egyéb utasítások

Return utasítás

ReturnStatement:
return;
return Expression ;

A return kilép az aktuális függvényből és annak visszatérési értékét szolgáltatja. Visszatérési érték akkor kell ha a függvény nem void. A kifejezés impliciten konvertálódik a visszatérési érték típusára.

Legalább egy return, throw utasítás vagy assert(0) kifejezés kell azokban a függvényekben amelyek visszatérési értéke nem void.

Akkor is adhatunk vissza egy kifejezést ha a függvény void típusú, ekkor a kifejezés kiértékelődik de nem adódik át visszatérési értékként.

Mielőtt a függvény ténylegesen visszatérne, minden automatikus objektum törlődik, minden finally és scope utasítás végrehajtásra kerül, és minden szinkronizációs objektum felszabadul (release)

A függvény nem tér vissza ha bármelyik finally-ben return goto vagy throw utasítás van amely elhagyja a finally-t.

Ha van kilépési postcondition (lásd szerződés), akkor az végrehajtódik miután a kifejezés kiértékelődött, de mielőtt a függvény visszatérne.

int foo(int x) { return x + 3; }

With utasítás

WithStatement: with ( Expression ) BlockStatement

A with jelentése ugyanaz, mint a C++-ban, az Expression egy objektum referenciát értékel ki. Ennek mezőire lehet hivatkozni a with -en belül minősítés nélkül.

Megjegyzés: az Expression csak egyszer értékelődik ki.

Synchronized utasítás

A synchronized utasítás a kritikus szakaszokat veszi körül, és ezzel szinkronizálja a szálak egyidejű hozzáférését a blokkhoz.

SynchronizedStatement:
synchronized ScopeStatement
synchronized ( Expression ) ScopeStatement

A szinkronizációval elérhető, hogy egyszerre csak egy szál hajthassa végre a szinkronizációs utasítást (ScopeStatement)

A szinkronizációs kifejezés -ahol az Expression egy objektum referencia- biztosítja, hogy egyszerre csak egy szál használhassa az objektumot a ScopeStatement végrehajtására. Ha az Expression egy Interface, akkor Object-re konvertálódik (cast) .

A szinkronizáció befejeződik ha a ScopeStatement végrehajtódik kivétellel, goto, vagy return utasítással.

Példa: synchronized { ... }

Volatile utasítás

VolatileStatement:
volatile Statement
volatile ;

Az utasítás kiértékelődik. Memóriába írás csak a Statement végrehajtása előtt, az olvasások előtt akár a Statement-ben akár utána. Olvasás ennek megfelelően a Statement után, és az összes írás után akár a Statement előtt vagy benne.

A volatile utasítás azonban nem garantálja az atomi végrehajtást. Ha azt szeretnénk, használjuk a Synchronized utasítást.

Asm utasítás

A magas szintű nyelvbe beépített assemblert támogatja a D nyelv az asm utasítással:

AsmStatement: asm { } asm { AsmInstructionList } AsmInstructionList: AsmInstruction ; AsmInstruction ; AsmInstructionList

Egy asm utasítás megengedi az assembly nyelv parancsainak közvetlen használatát. Egyszerűen lehet kezelni a speciális CPU felületet anélkül, hogy az assemblerhez kellene fordulnunk. A D fordító felügyel a függvényhívási konvenciókra, verem-beállításokra, stb.

Az utasítások formája természetesen erősen függ a CPU környezeti beállításaitól. De a következő formátumra vonatkozó konvenciók érvényesek:

Ezek a szabályok biztosítják, hogy a D forráskódot a szintaktikus és szemantikus elemzőtől függetlenül lehessen tokenizálni.

Például Intel Pentium-on:

int x = 3;
asm
{
mov EAX,x; // beolvassa az x tartalmát és elhelyezi az EAX regiszterbe.
}

A D-nek nincs volatile tároló típusa. A volatile-t tipikusan hardware regiszterek elérésére használják, így a magas szintű nyelvbe beépített assembler alkalmas ilyen esetekre, mint például:

int gethardware()
{
asm
{
mov EAX, dword ptr 0x1234;
}
}

Néhány D megvalósítás, mint például a D-ről C-re fordító, a magas szintű nyelvbe beépített assemblert nem érzékeli, ezért nem szükséges ezt implementálni. A version utasítást használják ennek bejegyzésére/igazolására:

version (InlineAsm) { asm { ... } } else { `... some workaround ... }

Pragma


Pragma:
    pragma ( Identifier )
    pragma ( Identifier , ExpressionList )

ExpressionList:
	ExpressionList , Expression
	Expression

A pragmák arra valók, hogy speciális információt adjunk át a fordítónak valamint implementáció-specifikus kiegészítéseket adjunk a D-hez. A pragmák önmagukban is állhatnak, ekkor ';'-val végződnek. De meghatározhatnak egy kifejezést, kifejezések blokkját, egy deklarációt vagy deklarációk egy blokkját.

pragma(ident); // simánönmagában pragma(ident) declaration; // hatókör: egy deklaráció pragma(ident): // összes utánna következő deklaráció declaration; declaration; pragma(ident) // deklarációk blokkja { declaration; declaration; } pragma(ident) statement; // egy kifejezés pragma(ident) // kifejezések blokkja { statement; statement; }

A pragma fajtáját az Identifier rész határozza meg. Az ExpressionList AssignExpressions részek (tehát kifejezések) vesszővel elválasztott listája. Szintaktikusan helyesnek kell lenniük, de a szemantikája a pragma fajtájától függ.

Minden implementációnak támogatnia kell a következő pragmákat (minimum figyelmen kívül kell hagynia):

msg
Fordítás alatt egy szöveget ír ki, az AssignExpressions-nak string literálnak kell lenni:

pragma(msg, "compiling...");

lib
Egy direktívát helyez el az object-fájlban, ezáltal a paraméterben megadott programkönyvtárat a szerkesztő csatolni fogja. Az AssignExpressions-nak string literálnak kell lenni:

pragma(lib, "foo.lib");

startaddress
Egy direktívát helyez el az object-fájlban, minek hatására a szerkesztőprogram a program kezdőcímét a paraméterben megadott függvényre állítja:

void foo() { ... } pragma(startaddress, foo);
Ez a megoldás nem felhasználó szintű, hanem rendszer szintű programok írásához használatos. Nem rendszerprogramnál ezt a kezdőcímet a futtatókörnyezet biztosítja.

Fordító-specifikus pragmák:

Fordító-specifikus pragmák definiálhatók, ha a fordítóprogram készítő szervezet vagy cég nevével kezdődnek (prefixe), hasonlóan, mint a verzió-azonosítóknál:

pragma(DigitalMars_funky_extension) { ... }

A fordítóprogramnak hibát kell jeleznie, ha ismeretlen pragmával találkozik, akkor is, ha ezek fordító specifikusak (nyilván egy másik fordítóról van szó, mert a sajátját csak felismeri). Ebből következik, hogy ezeket a pragmákat érdemes version blokkba ágyazni:

version (DigitalMars) { pragma(DigitalMars_funky_extension) { ... } }