他の言語をある程度知っている人はこれを読めばD言語の基礎をマスターでき,D言語の氷山の一角くらいは知ることができると思います.対象バージョンはdmd 2.059です.
ASCIIかUTFしか受け付けません.それ以外の文字コードで書くとコンパイルエラーになります.
D言語のmainはCとは違い以下のようなシグネチャです.
void main();
void main(string[] args);
リターンコードについては処理系がよしなにやってくれます.
標準ライブラリにある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);"
整数.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;
文字列はダブルクォートで囲みます.ダブルクォートの中では\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
配列は「[]」を使います.静的配列と動的配列がありますが,よく使われる動的配列について書きます(動的配列は実はスライスだったりします.詳しくはこの記事を参照).
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]
連想配列も「[]」を使います.キーと値を:で区切ります.
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");
if (cond) {
// do something
}
if (cond) {
// do something
} else {
// do something
}
if (cond) {
// do something
} else if (cond) {
// do something
}
Cとは違い,文字列が使えたりcaseを並べて書くことが出来ます.
switch (command) {
case "foo", "bar":
// do something
break;
case "baz":
// do something
break;
default:
}
uint i;
while (i < 5) {
// do something
++i;
}
for (uint i; i < 5; i++) {
// do something
}
配列や,opApply/Rangeインターフェイスを実装しているオブジェクトを処理出来ます.
foreach (elem; arr) {
// do something
}
要素を弄りたかったらrefをつけます.
foreach (ref elem; arr) {
// do something
}
関数は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); // ラムダシンタックス(上のと等価)
}
D言語ではコンパイル時に色々処理をすることが出来ます.クラスの生成も出来ますし,例外も投げれますし,もちろん数値計算や配列も操作出来ます.そのため,コンパイル時に定数を渡せば,それなりに色々な関数が動作します. たとえば,以下のような乱数生成も普通にコンパイル時に動きます.
ulong gen()
{
Random r;
popFrontN(r, 1000);
return r.front;
}
std.stdioにあるFileを使います.以下はコピーの例です.
auto fin = File("orig.txt"); // デフォルトは読み込み
auto fout = File("copy.txt", "w"); // 書き込みモードでオープン
foreach (line; fin.byLine)
fout.write(line);
// Fileは参照カウントで管理されているので明示的なcloseは不要
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; // 同様
}
第一引数をオブジェクトのように扱えるシンタックスシュガーです.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
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 をつけてつぶやくか,以下のサイトを参考にしてみてください.この記事を必要であれば随時アップデートしていきます.
- 公式サイト
- 公式サイトの日本語訳
- dusers: 日本でのポータルサイト
- わかったつもりになるD言語(Phobosなどに古い記述がありますが,templateなどの内容が参考になります)