Skip to content

Instantly share code, notes, and snippets.

@repeatedly
Last active June 8, 2023 06:20
Show Gist options
  • Star 90 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save repeatedly/2470712 to your computer and use it in GitHub Desktop.
Save repeatedly/2470712 to your computer and use it in GitHub Desktop.
D言語基礎文法最速マスター

他の言語をある程度知っている人はこれを読めばD言語の基礎をマスターでき,D言語の氷山の一角くらいは知ることができると思います.対象バージョンはdmd 2.059です.

1. 基礎

ソースファイル

ASCIIかUTFしか受け付けません.それ以外の文字コードで書くとコンパイルエラーになります.

main

D言語のmainはCとは違い以下のようなシグネチャです.

void main();
void main(string[] args);

リターンコードについては処理系がよしなにやってくれます.

表示 (write(ln), writef(ln))

標準ライブラリにあるstd.stdioを使います.

import std.stdio;
...
write(1);          // 改行無し
writeln(1);        // 改行有り
writef("%02d", 1); // フォーマット指定

変数の宣言

D言語では"型 変数名"というように宣言します.

int a;        // デフォルト値で初期化される.intは0
int a = void; // 初期化されず,Cなどと同じくゴミが入っている

また,初期化子から型を推論するためのautoという予約語があります.

auto a = "auto";   // aはimmutable(char)[]型

他の修飾子がある場合も型推論が効きます.

const a = "const";

データ型

値型と参照型があります.以下は値型の一例です.

// ブール値
bool flag;

// 符号なし8bitのUTF-8文字(マルチバイトじゃないので注意)
char c;

// 数値はCとは違いサイズ固定です.
byte   num1;  // 8bit
short  num2;  // 16bit
int    num3;  // 32bit
double num4;  // 64bit

// 静的配列
int[5] arr;

// 構造体
Random random;

参照型は複合的なデータ構造などが当てはまります.

// 動的配列 (スライス)
int[] arr;

// オブジェクト
Object obj;

// デリゲート
void delegate(void) action;

参照型と言っても実はポインタの値渡しみたいなものです.

コメント

4種類あります.

// 一行コメント

/*
   複数行コメント
 */

/**
   DDocコメント
 */

/+ /+
   ネスト可能複数行コメント
 +/ +/

プログラムの実行

コンパイル.

$ dmd foo.d
$ ./foo

runオプション使うと実行ファイルなどを生成せずに実行できます.

$ dmd -run foo.d

-runの依存関係解決などを強化したrdmdも同梱されています.また,--evalで簡単にコードを試せたりもします.

$ rdmd foo.d
$ rdmd --eval="writeln(10);"

2. 数値

整数.uがつくと符号なしになります.

long  num = -1;          // 符号つき
ulong num = 100_000_000; // 符号なし

浮動小数点数.

double num = 1.234;
real   num = 5.678;  // ハードウェア依存(x86 CPUなら80bit)

複素数も使えたりします.

creal num = 1 + 2i;
num += 3i;
writeln(num.re, num.im);  // reが実部(1),imが虚部(5)

この組み込み複素数はstd.complexで置き換えられる予定です(まだ期日は決まってません).

四則演算

// numはintとする
num = 1 + 1;
num = 1 - 1;
num = 1 * 2;
num = 5 / 2;  // 2
num = 5 % 2;  // 1

演算子のどちらかが浮動小数点数の場合,結果も浮動小数点数になります.

// numはdoubleとする
num = 5.0 / 2;  // 2.5(numがintなどの整数型だとコンパイルエラー)

インクリメントとデクリメント

勿論あります.

i++;
--i;

3. 文字列

文字列はダブルクォートで囲みます.ダブルクォートの中では\t(タブ)や\n(改行)などの特殊文字を利用することができます.

string str1 = "abc";
string str2 = "a\tbc\n";

D言語での文字列は4で述べる配列の一種に過ぎません(stringはimmutable(char)[]のaliasです).またポストフィックスをつけることでリテラルの型を指定できます.

string str3 = "hello"c  // 各文字はchar型
wstring str4 = "hello"w  // 各文字はwchar型
dstring str5 = "hello"d  // 各文字はdchar型

文字列操作

// 結合
auto str = "aaa" ~ "bbb";

// 長さ(バイト)
auto length = "abcdef".length;

// 切り出し
auto substr = "abcd"[0..2];  // "ab"

/* これ以降のものはstd.stringが必要です */

// 分割
auto record = "aaa,bbb,ccc".split(",");  // ["aaa", "bbb", "ccc"]

// 検索
auto idx = "abcd".indexOf("bc");  // 見つかった場合はその位置,見つからなかった場合は-1

4. 配列

配列は「[]」を使います.静的配列と動的配列がありますが,よく使われる動的配列について書きます(動的配列は実はスライスだったりします.詳しくはこの記事を参照).

int[] arr = [100, 200, 300];

宣言ではCとは違い前置形式となります.

要素の参照と代入

// 参照
a = arr[0];
b = arr[1];
c = arr[5];  // Error! 要素数より多いと例外が投げられる

// 代入
arr[0] = 1;
arr[1] = 2;
arr[5] = 5;  // 参照と同じく

要素の個数

len = arr.length;

// 要素を増やす(増えた分はデフォルト値で埋められる)
arr.length += 10;

配列の操作

std.arrayを使うとD言語での標準的なインターフェイス(Range)が利用できます.

import std.array;

auto arr = [1, 2, 3];

// 先頭を取得
auto a = arr.front;  // aは1

// 先頭を削除
arr.popFront();  // arrは[2, 3]

// 先頭に追加(push系がないのでinsertで)
arr.insert(0, 5);  // arrは[5, 2, 3]

// 末尾を取得
auto b = arr.back;  // bは3

// 末尾を削除
arr.popBack();  // arrは[5, 2]

// 末尾に追加
arr ~= 9;  // arrは[5, 2, 9]

popFrontやpopBackで値が返らないのは例外安全のためです.

ベクトル演算

いちいちループとか使う必要ありません.[]を使うことでベクトル演算できます.

auto a = [1, 2, 3];
a[] += 10;  // [11, 12, 13]

5. 連想配列

連想配列も「[]」を使います.キーと値を:で区切ります.

int[string] hash = ["a" : 1, "b" : 2];

要素の参照と代入

// 参照
hash["a"]  // 1
hash["b"]  // 2
hash["z"]  // 配列と同じく例外が投げられる

// 代入
hash["c"] = 5
hash["d"] = 7

連想配列の操作

// キーの取得
hash.keys;  // ["a", "b", "c", "d"]

// 値の取得
hash.values; // [1, 2, 5, 7]

// キーの存在確認
auto val = "a" in hash;  // valには1へのポインタ,なければnull

// ハッシュのペアの削除
hash.remove("a");

6. 制御文

if文

if (cond) {
    // do something
}

if ~ else文

if (cond) {
    // do something
} else {
    // do something
}

if ~ else if 文

if (cond) {
    // do something
} else if (cond) {
    // do something
}

switch文

Cとは違い,文字列が使えたりcaseを並べて書くことが出来ます.

switch (command) {
  case "foo", "bar":
    // do something
    break;
  case "baz":
    // do something
    break;
  default:
}

while文

uint i;
while (i < 5) {
    // do something
    ++i;
}

for文

for (uint i; i < 5; i++) {
    // do something
}

foreach文

配列や,opApply/Rangeインターフェイスを実装しているオブジェクトを処理出来ます.

foreach (elem; arr) {
    // do something
}

要素を弄りたかったらrefをつけます.

foreach (ref elem; arr) {
    // do something
}

7. 関数/デリゲート

関数はCと同じようなものですが色々と指定できます.xはconstな値として入力を表し,refは参照として操作することを表します.lazyは遅延評価(zが関数内で使われるまで評価を遅延させる)を行います.

nothrow void foo(in int x, ref int y, lazy int z = 0)
{
    // do something
}

デリゲートはネストされた関数などが該当します.これはクロージャの働きもします.

uint delegate() createCounter()
{
  uint count;
  return { return ++count; };  // {}はdelegateリテラル(引数がないので()は省略)
}

auto counter = createCounter();
writeln(counter());  // 1

2.059から以下のようなラムダシンタックスがあります.

void f(uint delegate(uint) d)
{
    writeln(d(10));
}

void main()
{
    f((x) { return x + 2; });
    f(x => x + 2);  // ラムダシンタックス(上のと等価)
}

CTFE(Compile Time Function Excecute)

D言語ではコンパイル時に色々処理をすることが出来ます.クラスの生成も出来ますし,例外も投げれますし,もちろん数値計算や配列も操作出来ます.そのため,コンパイル時に定数を渡せば,それなりに色々な関数が動作します. たとえば,以下のような乱数生成も普通にコンパイル時に動きます.

ulong gen()
{
    Random r;
    popFrontN(r, 1000);
    return r.front;
}

8. ファイル入出力

std.stdioにあるFileを使います.以下はコピーの例です.

auto fin  = File("orig.txt");       // デフォルトは読み込み
auto fout = File("copy.txt", "w");  // 書き込みモードでオープン

foreach (line; fin.byLine)
    fout.write(line);

// Fileは参照カウントで管理されているので明示的なcloseは不要

知っておいた方がよい文法

D言語の真偽値

null,false,数値型の0,まだ割り当てられていない動的配列,は偽となります.最後の配列の挙動はどうにも怪しいため,配列関係はemptyを使って評価することをオススメします.

クラス

Javaと同等の機能を提供しています(単一継承です).

class Person
{
  private:  // 以降はprivate
    string name_;

  public:  // 以降はpublic
    @property
    {
        // @properyをつけるとname()ではなくnameで呼び出せるようになる
        // p.name
        string name() const { return name_; }
        // p.name = newName
        void name(string name) { name_ = name; }
    }

    this(string name)  // コンストラクタはthis
    {
        name_ = name;
    }

    ...
}  // Cなどと違い;はいらない

その他にもinterface,PODとして扱えるstructやunion,演算子オーバーロードなどもあります.また,D言語はGCを使ってメモリ管理しているため,newした後deleteする必要はありません(自らmallocなどした場合は勿論freeは必要です).

テンプレート

C++よりかはすっきり書けるようになってます.以下は階乗を計算するテンプレートです.

template factorial(int n)
{
  static if (n == 1)
    enum factorial = 1;
  else
    enum factorial = n * factorial!(n - 1);
}

/* C++などとは違い<>を使わず!()を使う */
factorial!(5) // 120

このようにD言語ではコンパイル時用のstatic ifがあったりします.

勿論クラスや関数(UFCSの項参照)でも使えます.

class Hoge(T)
{
    T value;
}

テンプレートミックスイン

単一継承のD言語では,クラス間で実装を共有する時はテンプレートを使います.

template HogeImpl()
{
    uint hoge_;
    uint hogeTwice() { return hoge_ * hoge_; }
}

class A
{
    mixin HogeImpl;  // Aで定義したかのように使える
}

class B
{
    mixin HogeImpl;  // 同様
}

UFCS (Uniform Function Call Syntax)

第一引数をオブジェクトのように扱えるシンタックスシュガーです.2.058までは配列のみでしたが,2.059からintとかでも使えます.

void foo(T)(T obj) {}

// 本来はfoo([1, 2, 3])とか
[1, 2, 3].foo();
5.foo();
2.5f.foo();

std.arrayやstd.stringを使った関数がvar.methodのように呼べたのはこのためです.

例外処理

Mutex(変数m)を例に.まずはよく知られているtry - catch - finally.

lock(m);
try {
    // do something
} finally {
    unlock(m);
}

スコープガード文を使った方法もあります.解放処理がとても近くなり,コードが見やすくなります.

lock(m);
scope(exit) unlock(m);  // どんな理由であれ,スコープを出る時に呼ばれる
// do something

RAIIのような方法も可能です.scopeで宣言された変数はスコープを抜ける時に破棄され,デストラクタを呼び出します.

class Lock
{
    Mutex m_;

    this(Mutex m) { m_ = m; lock(m_); }

    ~this() { unlock(m_); }  // デストラクタは~this
}

scope myLock = new Lock(m);
// do something

Range

D言語の標準ライブラリは今このコンセプトを中心に開発されています.核となるstd.rangeに満たすべきインターフェイスが定義されています.以下はstd.rangeベースのアルゴリズムの例です.

import std.algorithm;

auto arr = [1, 2, 3, 4, 5];

// 条件に合うものだけを選ぶ
filter!("a % 2 == 0")(arr);  // [2, 4]
// '"a % 2 == 0"'の代わりに'a => a % 2 == 0'でもOK.delegateと文字列両方受け付ける
filter!(a => a % 2 == 0)(arr);  // [2, 4]

// 条件に合うものを除く
remove!("a % 2 == 0")(arr);  // [1, 3, 5]

// 加工結果をRangeで返す
map!("a * a")(arr);  // [1, 4, 9, 16, 25]

// 降順にソートする
sort!("a > b")(arr);  // [5, 4, 3, 2, 1]

基本的に,これらのアルゴリズムが返すのはarrの型ではなく,それぞれの計算を隠蔽したRangeオブジェクトです.これによって,Rangeは基本的には計算を遅延し,それぞれのRangeをつなげても,なるべく中間のオブジェクトを作らないようにしています. 配列として結果を返して欲しい時はstd.array.arrayを使います

import std.array;

array(map!("a * a")(arr));

8で上げたFileなども含め,他のモジュールもRangeベースとなっています.

単体テスト

unittestで囲った所にテストをかけます.クラス内でも構わないので,より近くに書くのがD言語の作法です.

unittest 
{
    bool foo() { /* do something */ return flag; }
    assert(foo());
}

これはコンパイル時にunittestオプションを渡すことで実行されるようになります.

終わりに

template/契約/TLS/型システムあたり含め,全然魅力を書けてない気もしますが基本ということでまぁこんな感じで許して下さい.他に詳しく知りたい方はTwitterで #dlang をつけてつぶやくか,以下のサイトを参考にしてみてください.この記事を必要であれば随時アップデートしていきます.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment