英語版
このページの英語版を見る

CプログラマーのためのD言語プログラミング

経験豊富なCプログラマーは、自然に使えるようになる一連のイディオムやテクニックを蓄積している。 新しい言語を学習する際、それらの イディオムが非常に使いやすいため、新しい言語で同等のことを行う方法がわかりにくいことがある。 そこで、一般的なC言語のテクニックと、 対応するタスクをD言語で実行する方法を

Cにはオブジェクト指向の機能がないため、 オブジェクト指向に関する問題については 別のセクションを設けている。 C++C++

Cプリプロセッサについては、 「Cプリプロセッサ vs D」で取り上げている。


型のサイズを取得する

C言語の方法

sizeof(int)
sizeof(char *)
sizeof(double)
sizeof(struct Foo)

Dの方法

sizeof プロパティを使用する:

int.sizeof
(char *).sizeof
double.sizeof
Foo.sizeof

型の最大値と最小値を取得する

C言語の方法

#include <limits.h>
#include <math.h>

CHAR_MAX
CHAR_MIN
ULONG_MAX
DBL_MIN

Dの方法

xml-ph-0000@deepl.internal
char.max
char.min
ulong.max
double.min

プリミティブ型

CからDへの型変換

bool               =>        bool
char               =>        char
signed char        =>        byte
unsigned char      =>        ubyte
short              =>        short
unsigned short     =>        ushort
wchar_t            =>        core.stdc.stddef.wchar_t
int                =>        int
unsigned           =>        uint
long               =>        core.stdc.config.c_long
unsigned long      =>        core.stdc.config.c_ulong
long long          =>        long
unsigned long long =>        ulong
float              =>        float
double             =>        double
long double        =>        real
_Imaginary long double =>    ireal
_Complex long double   =>    creal

char は符号なし8ビット型であり、wchar は符号なし16ビット型であるが、 オーバーロードと型安全性を確保するために、それぞれ別個の型が用意されている。

C言語のintとunsigned intはサイズが異なるが、D言語ではそうではない。


特殊な浮動小数点値

Cの方法

#include <fp.h>

NAN
INFINITY

#include <float.h>

DBL_DIG
DBL_EPSILON
DBL_MANT_DIG
DBL_MAX_10_EXP
DBL_MAX_EXP
DBL_MIN_10_EXP
DBL_MIN_EXP

Dの方法

xml-ph-0000@deepl.internal
double.nan
double.infinity
double.dig
double.epsilon
double.mant_dig
double.max_10_exp
double.max_exp
double.min_10_exp
double.min_exp

浮動小数点数の除算の余り

Cの方法

#include <math.h>

float f = fmodf(x,y);
double d = fmod(x,y);
long double r = fmodl(x,y);

Dの方法

Dは浮動小数点演算子に対して余り演算子(% )をサポートしている。
float f = x % y;
double d = x % y;
real r = x % y;

浮動小数点数の比較におけるNANの処理

C言語の方法

Cでは、比較演算子のオペランドが NAN の場合に何が起こるのかは定義されておらず、また、それをチェックするCコンパイラはほとんどない(Digital Mars Cコンパイラは例外であり、DMのコンパイラはNAN オペランドをチェックする)。
#include <math.h>

if (isnan(x) || isnan(y))
    result = false;
else
    result = (x < y);

Dの方法

Dは、NAN引数で動作する比較と演算子をすべて提供している。
result = (x < y);        // xまたはyがnanの場合はfalse

アサートは、優れた防御的コーディング戦略において必要不可欠な要素である

Cの方法

C言語では、言語自体ではアサートを直接サポートしていないが、 標準ライブラリヘッダーのassert.hでマクロを定義している。このマクロは、 パラメータとして与えられた条件が真でない場合に、stderrに診断メッセージを書き込む。メッセージには、 __FILE__、__LINE__、__func__(C99)を使用して、失敗したアサートを特定する。

#include <assert.h>

assert(e == 0);

Dの方法

D は単にassertを言語に組み込む。
assert(e == 0);

配列のすべての要素を初期化する

Cの方法

#define ARRAY_LENGTH        17
int array[ARRAY_LENGTH];
for (i = 0; i < ARRAY_LENGTH; i++)
    array[i] = value;

Dの方法

xml-ph-0000@deepl.internal
int[17] array;
array[] = value;

配列をループする

Cの方法

配列の長さは別途定義されているか、または、不器用でエラーを起こしやすい sizeof() 式が長さの取得に使用されている。

#define ARRAY_LENGTH        17
int array[ARRAY_LENGTH];
for (size_t i = 0; i < ARRAY_LENGTH; i++)
    func(array[i]);
または:
int array[17];
for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); i++)
    func(array[i]);

Dの方法

配列の長さは、length プロパティを通じてアクセスできる。
int[17] array;
foreach (i; 0 .. array.length)
     func(array[i]);
あるいは、さらに良い方法(値へのアクセス)として、
int[17] array;
foreach (int value; array)
    func(value);
Dでは、明示的な型修飾子を使用しなくても要素の型を推論できる。
int[17] array;
foreach (value; array)
    func(value);
または、ref キーワード(参照アクセス用)を介して:
int[17] array;
foreach (ref value; array)
    value += 42;

可変サイズの配列の作成

C言語の方法

C言語では配列でこれを行うことはできない。 長さの変数を別に作成し、配列のサイズを明示的に管理する必要がある。
#include <stdlib.h>

int array_length;
int *array;
int *newarray;

newarray = realloc(array, (array_length + 1) * sizeof(int));
if (!newarray)
    error("out of memory");
array = newarray;
array[array_length++] = x;

D言語では

Dは、簡単にサイズ変更できる動的配列をサポートしている。Dは 必要なメモリ管理をすべてサポートしている。
int[] array;
int x;
array.length = array.length + 1;
array[array.length - 1] = x;

文字列連結

C言語の方法

解決すべきいくつかの問題がある。例えば、 ストレージをいつ解放できるか、ヌルポインタの処理、 文字列の長さの取得、メモリ割り当てなどである。
#include <string.h>

char *s1;
char *s2;
char *s;

// s1とs2を連結し、結果をsに入れる
s = malloc((s1 ? strlen(s1) : 0) +
           (s2 ? strlen(s2) : 0) + 1);
if (!s)
    error("out of memory");
if (s1)
    strcpy(s, s1);
else
    *s = 0;
if (s2)
    strcpy(s + strlen(s), s2);

// sに"hello"を追加する
char hello[] = "hello";
size_t lens = s ? strlen(s) : 0;
char *news = realloc(s, lens + sizeof(hello) + 1);
if (!news)
    error("out of memory");
s = news;
memcpy(s + lens, hello, sizeof(hello));

Dの方法

Dは、charとwchar配列に対して、それぞれ連結と追加を意味する~~= という演算子をオーバーロードする。
string s1;
string s2;
string s;

s = s1 ~ s2;
s ~= "hello";

フォーマット印刷

C言語の方法

printf() は汎用の書式付き印刷ルーチンである。
#include <stdio.h>

printf("Calling all cars %d times!\n", ntimes);

Dの方法

何と言えばいいだろうか。printf()がルールだ。
printf("Calling all cars %d times!\n", ntimes);
writefln() は、型を認識し、型安全である点で printf() を改善している。
import std.stdio;

writefln("Calling all cars %s times!", ntimes);

前方参照関数

C言語の方法

関数は前方参照できない。したがって、 ソースファイルでまだ遭遇していない関数を呼び出すには、 呼び出しの前に()()的に関数宣言を挿入する必要がある。
void forwardfunc();

void myfunc()
{
    forwardfunc();
}

void forwardfunc()
{
    ...
}

Dの方法

プログラムは全体として見られるため、 前方宣言をコーディングする必要はないばかりか、許可さえされていない! Dは、前方参照関数宣言を2度書くことに関連する煩わしさやエラーを回避する 。 関数は任意の順序で定義できる。
void myfunc()
{
    forwardfunc();
}

void forwardfunc()
{
    ...
}

引数を持たない関数

C言語の方法

void foo(void);

Dの方法

Dは厳格に型付けされた言語であるため、関数に引数がないことを明示的に 宣言する必要はない。ただ、引数があると宣言しないように すること。
void foo()
{
    ...
}

breakとcontinue文にラベルを付けた

C言語の方法

breakとcontinue文は、最も内側のネストされたループまたはswitchにのみ適用されるので、 複数のレベルのbreakを使用する場合はgotoを使用する必要がある。
    for (i = 0; i < 10; i++)
    {
        for (j = 0; j < 10; j++)
        {
            if (j == 3)
                goto Louter;
            if (j == 4)
                goto L2;
        }
        L2:
        ;
    }

Louter:
    ;

Dの方法

break文とcontinue文にはラベルを付けることができる。ラベルは 、囲むループまたはswitchのラベルであり、breakはそのループに適用される 。
Louter:
    for (i = 0; i < 10; i++)
    {
        for (j = 0; j < 10; j++)
        {
            if (j == 3)
                break Louter;
            if (j == 4)
                continue Louter;
        }
    }
    // ルーターはここにある

Goto 文

C言語の方法

悪評高いgoto文は、プロのCコーダーにとっては欠かせないものだ。 それは 、時に不十分な制御フロー文を補うために必要である。

Dの方法

C言語のgoto文の多くは、ラベル付きの breakおよびcontinue文のD機能によって 排除することができる。しかし、Dは、 ルールを破る必要がある場合を知っている実用的なプログラマーのための実用的な言語である。そのため、当然ながらDは goto文をサポートしている。

構造体タグの名前空間

C言語の方法

型を指定するたびにstruct キーワードを使用しなければならないのは煩わしいので、 よく使われる慣用句は次のとおりである。
typedef struct ABC { ... } ABC;

Dの方法

構造体タグ名は別の名前空間ではなく、通常の名前と同じ名前空間にある。 したがって、
struct ABC { ... }

文字列の検索

C言語の方法

文字列が与えられた場合、その文字列を可能な値のリストと比較し、 一致する値に基づいて処理を行う。この典型的な使用例は、 コマンドライン引数の処理である。
#include <string.h>
void dostring(char *s)
{
    enum Strings { Hello, Goodbye, Maybe, Max };
    static char *table[] = { [Hello]  ="hello",
                             [Goodbye]="goodbye",
                             [Maybe]  ="maybe" };
    int i;

    for (i = 0; i < Max; i++)
    {
        if (strcmp(s, table[i]) == 0)
            break;
    }
    switch (i)
    {
        case Hello:   ...
        case Goodbye: ...
        case Maybe:   ...
        default:      ...
    }
}
この方法の問題点は、3つの並列データ構造、つまり"列挙型"、"テーブル"、"switch case"を維持しなければならないことである。 値の数が非常に多い場合、 メンテナンス時に3つの間の関連性が明白ではなくなる可能性があるため、 バグが発生しやすい状況となる。C99で導入された指定初期化子を使用すると、 3つのデータ構造のうち2つを正しくリンクさせることができるが、その代償として 多くのタイピングが必要となる。 さらに、値の数が多くなると、バイナリまたはハッシュ検索によって 単純な線形探索よりも大幅なパフォーマンスの向上が期待できる。しかし、これらのコーディングには時間がかかり、 デバッグも必要となる。このようなことは通常、まったく行われない。

Dの方法

Dは、switch文の概念を拡張し、 文字列と数値の両方を処理できるようにする。すると、文字列の検索をコード化する方法は 単純明快になる。
void dostring(string s)
{
    switch (s)
    {
        case "hello":   ...
        case "goodbye": ...
        case "maybe":   ...
        default:        ...
    }
}
新しいケースを追加することも簡単になる。コンパイラは 高速なルックアップスキームを生成してくれるので、 手作業でコーディングする際に発生するバグや時間を

構造体のメンバーのアラインメントの設定

Cの方法

これは、プログラム全体に影響を与えるコマンドラインスイッチによって行われる。 モジュールやライブラリが再コンパイルされていない場合、悲惨な結果となる。 これを解決するために、#pragmaが使用される。
#pragma pack(1)
struct ABC
{
    ...
};
#pragma pack()
しかし、#pragmaは、理論上および実際上、コンパイラによって移植できない。

Dの方法

Dには、すべてのDコンパイラに共通するアラインメント設定用の構文がある。 実際に行われるアラインメントは、 ABI互換性を保つために、対応するCコンパイラのアラインメントと互換性がある。 アーキテクチャをまたいで特定のレイアウトに一致させるには、align(1) を使用し、手動で指定する。 DD

struct ABC
{
    int z;              // zはデフォルトにアライメントされる

    align (1) int x;    // xはバイト揃え
    align (4)
    {
        ...             // {}内の宣言は32ビットアライメントになる
    }
    align (2):          // ここから16ビットアライメントに切り替える

    int y;              // yは16ビットアライメント
}

匿名構造体および共用体

時には、ネストされた構造体や共用体を使用して構造体のレイアウトを制御できると便利です。

C言語の方法

C11以前のCでは、匿名構造体や共用体を使用することができず、 ダミーのメンバ名が必要であった。
struct Foo
{
    int i;
    union
    {
        struct { int x; long y; } abc;
        char *p;
    } bar;
};

#define x bar.abc.x
#define y bar.abc.y
#define p bar.p

struct Foo f;

f.i;
f.x;
f.y;
f.p;
それは不器用であるだけでなく、マクロを使用すると、シンボリックデバッガが何をやっているのか理解できず、 マクロは構造体のスコープではなくグローバルスコープを持つことになる。

Dの方法

匿名構造体および共用体は、より自然な方法でレイアウトを制御するために使用される。
struct Foo
{
    int i;
    union
    {
        struct { int x; long y; }
        char* p;
    }
}

Foo f;

f.i;
f.x;
f.y;
f.p;

構造体型と変数の宣言

C言語の方法

セミコロンで終わる1つの文で行うことである。

struct Foo { int x; int y; } foo;

または、2つを区切る:

struct Foo { int x; int y; };   // note terminating ;
struct Foo foo;

Dの方法

構造体の定義と宣言は、同じ文では行うことができない。

struct Foo { int x; int y; }    // 終端がないことに注意;
Foo foo;
つまり、終了の ; は省略可能であり、 セミコロンが使用される方法における struct {} と関数ブロック {} の間の混乱を招く違いを解消する 。

つまり、終了の ; は省略可能であり、 セミコロンが使用される方法における "構造体 {} と関数ブロック {} の間の混乱を招く違いを解消できる。


構造体メンバのオフセットを取得する

C言語の方法

当然、別のマクロが使用される。
#include <stddef>
struct Foo { int x; int y; };

off = offsetof(Foo, y);

Dの方法

オフセットは単なる別のプロパティである。
struct Foo { int x; int y; }

off = Foo.y.offsetof;

共用体の初期化

C言語の方法

共用体は「最初の要素」ルールに従って初期化される。
union U { int a; long b; };
union U x = { 5 };                // メンバー'a'を5に初期化する
union U y = { .b = 42l };         // メンバー'b'を42に初期化する (C99)

共用体の要素を追加したり並べ替えたりすると、 初期化子に悲惨な結果をもたらす可能性がある。C99で指定された初期化子は、この問題を修正する。

Dの方法

Dでは、どのメンバーが初期化されているかが明示的に示されるため、
union U { int a; long b; }
U x = { a:5 };
混乱やメンテナンス上の問題を回避できる。

構造体の初期化

C言語の方法

メンバは、{ }内の位置によって初期化される。
struct S { int a; int b; };
struct S x = { 5, 3 };
struct S y = { .b=3, .a=5  };   /* C99 */
小さな構造体ではそれほど問題にならないが、 多数のメンバがある場合、初期化子をフィールド宣言と慎重に一致させるのは面倒になる。 メンバが追加または再配置された場合、すべての初期化子を見つけ出し、 適切に修正しなければならない。これはバグの温床となる。 C99の指定初期化子は、この問題を修正する。

Dの方法

メンバーの初期化は明示的に行うことができる。
struct S { int a; int b; }
S x = { b:3, a:5 };
意味は明確であり、位置依存性はもはや存在しない。

配列の初期化

Cの方法

C言語では、位置依存によって配列が初期化される。C99ではこの問題が修正されている。
int a[3] = { 3,2,1 };
int a[3] = { [2]=1, [0]=3, [1]=2 };  /* C99指定初期化子 */
int a[3] = { [2]=1, [0]=3, 2 };      /* C99指定初期化子 */
ネストされた配列には{}が必要な場合と不要な場合がある。
int b[3][2] = { 2,3, {6,5}, 3,4 };

Dの方法

Dも位置依存によって行うが、インデックスも使用できる。 Dの構文はC99の指定初期化子よりも簡潔である。 以下のすべては同じ結果を生む。
int[3] a = [ 3, 2, 0 ];
int[3] a = [ 3, 2 ];            // C言語と同じように、未提供の初期化子は0である
int[3] a = [ 2:0, 0:3, 1:2 ];
int[3] a = [ 2:0, 0:3, 2 ];     // 提供されない場合、インデックスは
                                // 前の値に1を足した値になる。
これは、配列が列挙型でインデックス付けされ、列挙型の順序が変更されたり追加されたりする場合に便利です。
enum color { black, red, green }
int[3] c = [ black:3, green:2, red:5 ];
ネストした配列の初期化は、明示的で配列の型と一致していなければならない。
int[2][3] b = [ [2,3], [6,5], [3,4] ];

int[2][3] b = [[2,6,3],[3,5,4]];            // エラー

エスケープされた文字列リテラル

Cの方法

C言語では、文字列中の「¥」がエスケープ文字として解釈されるため、DOSファイルシステムとの間で問題が生じる。 ファイルc:¥root¥file.cを指定するには、次のようにする。
char file[] = "c:\\root\\file.c";
これは、正規表現を使用するとさらに厄介になる。 引用符で囲まれた文字列に一致させるためのエスケープシーケンスを考えてみよう。
/"[^\\]*(\\.[^\\]*)*"/

C言語では、この恐ろしさは次のように表現される。

char quoteString[] = "\"[^\\\\]*(\\\\.[^\\\\]*)*\"";

Dの方法

D言語には、エスケープ文字を使用できる"C言語のスタイル"の文字列リテラルと、 WYSIWYG(見たままが得られる)の生の文字列があり、`foo`r"bar" の構文で使用できる。
string file = r"c:\root\file.c";  // c:\root\file.c
string quotedString = `"[^\\]*(\\.[^\\]*)*"`;  // "[^\\]*(\\.[^\\]*)*"
有名な「Hello World」の文字列は次のようになる。
string hello = "hello world\n";

ASCII文字とワイド文字の比較

最近のプログラミングでは、プログラムの国際化に対応するために、"wchar"文字列が容易にサポートされることが求められている。

C言語の方法

C言語では、文字列にwchar_tとL接頭辞を使用する。
#include <wchar.h>
char foo_ascii[] = "hello";
wchar_t foo_wchar[] = L"hello";
コードがASCIIとwcharの両方に対応するように書かれていると、さらに状況は悪くなる。 ASCIIからwcharに文字列を切り替えるためにマクロが使用される。
#include <tchar.h>
tchar string[] = TEXT("hello");
さらに、実際にはwchar_t は、そのサイズが実装に依存するため、移植可能なコードでは使用できない。 POSIX準拠のマシンでは、一般的に UTF-32のコード単位を表し、WindowsではUTF-16のコード単位を表す。C11では、 この問題を克服するために、C++11型char16_tとchar32_tが導入された。

Dの方法

文字列の型は意味解析によって決定されるため、 文字列をマクロ呼び出しでラップする必要はない。 あるいは、型推論を使用する場合は、 文字列にcwd の接尾辞を付けることができる。これらはそれぞれ、UTF-8、 UTF-16、UTF-32のエンコードを表す。 接尾辞を使用しない場合、型は UTF-8文字列と推論される。
string  utf8  = "hello";     // UTF-8文字列
wstring utf16 = "hello";     // UTF-16文字列
dstring utf32 = "hello";     // UTF-32文字列

auto str    = "hello";       // UTF-8文字列
auto _utf8  = "hello"c;      // UTF-8文字列
auto _utf16 = "hello"w;      // UTF-16文字列
auto _utf32 = "hello"d;      // UTF-32文字列

列挙型に対応する配列

Cの方法

考えてみよう:
enum COLORS { red, blue, green, max };
char *cstring[max] = {"red", "blue", "green" };
char *cstring[max] = {[red]="red", [blue]="blue", [green]="green" };  /* C99 */
これは、要素数が少ないので、正しく作成するのは比較的簡単である。 しかし、要素数がかなり多くなると、 新しい要素を追加する際に正しく維持するのが難しくなる。C99では、 その問題を解決するために指定初期化子が追加された。

Dの方法

enum COLORS { red, blue, green }

string[COLORS.max + 1] cstring =
[
    COLORS.red   : "red",
    COLORS.blue  : "blue",
    COLORS.green : "green",
];
完璧ではないが、より良い。

typedefで新しい型を作成する

C言語の方法

C言語における型定義は弱いものであり、つまり、実際には新しい型を導入するものではない。 コンパイラは型定義と その基礎となる型を区別しない。
typedef void *Handle;
void foo(void *);
void bar(Handle);

Handle h;
foo(h);         // コーディングバグが修正されない
bar(h);         // OK
C言語での解決策は、 新しい型での型チェックとオーバーロードを得ることを唯一の目的としたダミーの構造体を作成することである。
struct Handle__ { void *value; }
typedef struct Handle__ *Handle;
void foo(void *);
void bar(Handle);

Handle h;
foo(h);         // 構文エラー
bar(h);         // OK
型にデフォルト値を設定するには、マクロの定義、 命名規則、そしてその規則に厳密に従うことが必要となる。
#define HANDLE_INIT ((Handle)-1)

Handle h = HANDLE_INIT;
h = func();
if (h != HANDLE_INIT)
    ...
構造体による解決策では、さらに複雑になる。
struct Handle__ HANDLE_INIT;

void init_handle(void)  // 起動時にこの関数を呼び出す
{
    HANDLE_INIT.value = (void *)-1;
}

Handle h = HANDLE_INIT;
h = func();
if (memcmp(&h,&HANDLE_INIT,sizeof(Handle)) != 0)
    ...
覚えるべき名前は4つある。Handle, HANDLE_INIT, struct Handle__, value

D Way

Dには強力なメタプログラミング機能があり、typedef をライブラリ機能として実装できる。std.typecons をインポートし、Typedef テンプレートを使用するだけだ。
import std.typecons;

alias Handle = Typedef!(void*);
void foo(void*);
void bar(Handle);

Handle h;
foo(h);  // 構文エラー
bar(h);  // OK
デフォルト値を処理するには、Typedefテンプレートに初期化子を第2引数として渡し、 .init プロパティで参照する。
alias Handle = Typedef!(void*, cast(void*)-1);
Handle h;
h = func();
if (h != Handle.init)
    ...
覚える必要があるのはHandle だけだ。

構造体の比較

C言語の方法

C言語では、構造体の代入はシンプルで便利な方法で定義されているが、
struct A x, y;
...
x = y;
構造体の比較については定義されていない。そのため、2つの構造体インスタンスを比較して等価であるかどうかを判断するには、
#include <string.h>

struct A x, y;
...
if (memcmp(&x, &y, sizeof(struct A)) == 0)
    ...

この方法の鈍感さに加え、 型チェックに関する言語からのあらゆる種類のヘルプが欠如していることに注目してほしい。

memcmp() には厄介なバグが潜んでいる。 構造体のレイアウトは、アラインメントの関係で「穴」ができる可能性がある。 C言語では、その穴に値が割り当てられることは保証されていないため、

memcmp() には厄介なバグが潜んでいる。 構造体のレイアウトは、アラインメントの関係で「穴」が生じる可能性がある。 C言語では、これらの穴に値が割り当てられることは保証されていないため、 2つの異なる構造体インスタンスが各メンバに同じ値を持つことがあり得るが、 穴には異なるガベージが含まれているため、比較結果は異なる。

Dの方法

Dは、明白で単純な方法でそれを実行する。
A x, y;
...
if (x == y)
    ...

文字列を比較する

C言語の方法

ライブラリ関数 strcmp() を使用する。
char str[] = "hello";

if (strcmp(str, "betty") == 0)  // 文字列は一致するか?
    ...
C言語では0で終端する文字列を使用するため、C言語の方法には 終端する0を常にスキャンするという本質的な非効率性がある。

Dの方法

なぜ== 演算子を使用しないのか?
string str = "hello";

if (str == "betty")
    ...
D文字列は文字列とは別に長さを保持している。 そのため、文字列比較の実装は Cよりもはるかに高速化できる(この違いは、 Cのmemcmp()とstrの速度差に相当する

D文字列は文字列とは別に長さを格納している。 そのため、文字列比較の実装は Cよりもはるかに高速化できる (この違いは、Cのmemcmp()とstrcmp()の速度差に相当する)。

Dは文字列の比較演算子もサポートしている。

これは、ソートや検索に役立つ。 xml-ph-0000@deepl.internal
string str = "hello";

if (str < "betty")
    ...
これは、ソートや検索に役立つ。

配列のソート

C言語の方法

多くのCプログラマーはバブルソートを何度も何度も再実装する傾向にあるが、 C言語でソートを行う正しい方法はqsort()を使用することである。
int compare(const void *p1, const void *p2)
{
    type *t1 = (type *)p1;
    type *t2 = (type *)p2;

    return *t1 - *t2;
}

type array[10];
...
qsort(array, sizeof(array)/sizeof(array[0]),
        sizeof(array[0]), compare);
各型に対してcompare()を記述する必要があり、 誤字の多いコードを慎重に記述しなければならない。各比較に必要な間接関数 呼び出しにより、qsort() ルーチンの達成可能なパフォーマンスが制限される。

D Way

Dには、最適化されたソートルーチンを備えた強力なstd.algorithm モジュールがあり、 比較可能な組み込み型またはユーザー定義型であれば、どのような型でも動作する。
import std.algorithm;
type[] array;
...
sort(array);      // 配列をインプレースでソートする

文字列リテラル

C言語の方法

C言語の文字列リテラルは複数行にまたがることができないため、 テキストのブロックを作成するには、行連結文字(¥)を使用する必要がある。
"This text spans\n\
multiple\n\
lines\n"
C言語の文字列リテラル連結では、問題は解決しない。
"This text spans\n"
"multiple\n"
"lines\n"
テキストの量が多い場合、これは面倒な作業になる。

Dの方法

文字列リテラルは複数行にまたがることができる。例えば:
"This text spans
multiple
lines
"
テキストのブロックをDソースにカット&ペーストするだけで済む。

データ構造の走査

C言語の方法

再帰的なデータ構造を走査する関数を考えてみよう。 この例では、文字列のシンプルなシンボルテーブルがある。 データ構造は二分木の配列である。 コードは、その網羅的な検索を行う必要がある。

再帰的なデータ構造を走査する関数を考えてみよう。 この例では、文字列のシンプルなシンボルテーブルがある。 データ構造は二分木の配列である。 コードは、その中にある特定の文字列を見つけ、それが唯一無二のインスタンスであるかどうかを判断するために、その網羅的な検索を行う必要がある 。

これを実現するには、再帰的にツリーを巡回するヘルパー関数membersearchx が必要である。 このヘルパー関数は、ツリー外のコンテキストを読み書きする必要があるため、 カスタム関数struct Paramblockを作成し、そのポインタを使用することで効率性を最大限に高めている。 "function""関数"

struct Symbol
{
    char *id;
    struct Symbol *left;
    struct Symbol *right;
};

struct Paramblock
{
    char *id;
    struct Symbol *sm;
};

static void membersearchx(struct Paramblock *p, struct Symbol *s)
{
    while (s)
    {
        if (strcmp(p->id,s->id) == 0)
        {
            if (p->sm)
                error("ambiguous member %s\n",p->id);
            p->sm = s;
        }

        if (s->left)
            membersearchx(p,s->left);
        s = s->right;
    }
}

struct Symbol *symbol_membersearch(Symbol *table[], int tablemax, char *id)
{
    struct Paramblock pb;
    int i;

    pb.id = id;
    pb.sm = NULL;
    for (i = 0; i < tablemax; i++)
    {
        membersearchx(pb, table[i]);
    }
    return pb.sm;
}

Dの方法

これはDの同じアルゴリズムであり、劇的に縮小される。 ネストされた関数は、()()的に囲い込む関数の変数にアクセスできるため、 パラブロックやその詳細な管理処理は必要ない。 ネストされたヘルパー関数は、 それを必要とする関数内に完全に含まれ、 局所性と保守性が向上する。

2つのバージョンのパフォーマンスは区別できない。

xml-ph-0000@deepl.internal
class Symbol
{
    string id;
    Symbol left;
    Symbol right;
}

Symbol symbol_membersearch(Symbol[] table, string id)
{
    Symbol sm;

    void membersearchx(Symbol s)
    {
        while (s)
        {
            if (id == s.id)
            {
                if (sm)
                    error("ambiguous member %s\n", id);
                sm = s;
            }

            if (s.left)
                membersearchx(s.left);
            s = s.right;
        }
    }

    for (int i = 0; i < table.length; i++)
    {
        membersearchx(table[i]);
    }

    return sm;
}

符号なし右シフト

C言語の方法

右シフト演算子>>>>= は、 左オペランドが符号付き整数型の場合は符号付きシフトとなり、 左オペランドが符号なし整数型の場合は符号なし右シフトとなる。 intで符号なし右シフトを行うには、 キャストが必要である。
int i, j;
...
j = (unsigned)i >> 3;
iint の場合、これはうまくいく。しかし、i が typedef で作成された型の場合、
myint i, j;
...
j = (unsigned)i >> 3;
およびmyintlong int の場合、unsigned へのキャストは 最も重要なビットを 無言でスローし、結果を破損させる。

D言語では、

Dには右シフト演算子>>>>= があり、 Cと同様に動作する。しかし、Dには明示的な符号なし 右シフト演算子>>>>>>= もあり、 左オペランドの符号に関係なく符号なし右シフトを行う。 したがって、
myint i, j;
...
j = i >>> 3;
は 安全でないキャストを回避し、あらゆる整数型で期待通りに動作する。

動的クロージャー

C言語の方法

再利用可能なコンテナ型について考えてみよう。再利用可能であるためには、 コンテナの各要素に任意のコードを適用する方法をサポートする必要がある 。これは、関数ポインタを受け取る適用関数を作成することで実現される 。

再利用可能なコンテナ型について考えてみよう。再利用可能であるためには、 コンテナの各要素に任意のコードを適用する方法をサポートしなければならない 。これは、コンテナの内容の各要素が渡される関数ポインタを受け取る適用関数を作成することで実現される 。

また、一般的なコンテキストポインタも必要であり、ここではvoid *p で表される。この例では、単純なコンテナ クラスが整数の配列を保持し、そのコンテナのユーザーが それらの整数の最大値を計算する。

void apply(void *p, int *array, int dim, void (*fp)(void *, int))
{
    for (int i = 0; i < dim; i++)
        fp(p, array[i]);
}

struct Collection
{
    int array[10];
};

void comp_max(void *p, int i)
{
    int *pmax = (int *)p;

    if (i > *pmax)
        *pmax = i;
}

void func(struct Collection *c)
{
    int max = INT_MIN;

    apply(&max, c->array, sizeof(c->array)/sizeof(c->array[0]), comp_max);
}

これは動作するが、柔軟性に欠ける。

Dの方法

Dバージョンでは、適用関数のコンテキスト情報を伝達するためにデリゲートを使用し、 コンテキスト情報を取得し局所性を向上させるためにネストした関数を使用する。
class Collection
{
    int[10] array;

    void apply(void delegate(int) fp)
    {
        for (int i = 0; i < array.length; i++)
            fp(array[i]);
    }
}

void func(Collection c)
{
    int max = int.min;

    void comp_max(int i)
    {
        if (i > max)
            max = i;
    }

    c.apply(&comp_max);
}
ポインタは排除され、キャストや汎用ポインタも 使用されない。Dバージョンは完全に型安全である。 Dの代替方法では、関数リテラルを使用する。
void func(Collection c)
{
    int max = int.min;

    c.apply(delegate(int i) { if (i > max) max = i; } );
}
無関係な関数名を作成する必要がなくなる。

可変長引数関数パラメータ

課題は、可変個数の引数を取る関数を書くことである。 例えば、引数の合計を求める関数などである。

C言語の方法

wayway
#include <stdio.h>
#include <stdarg.h>

int sum(int dim, ...)
{
    int i;
    int s = 0;
    va_list ap;

    va_start(ap, dim);
    for (i = 0; i < dim; i++)
        s += va_arg(ap, int);
    va_end(ap);
    return s;
}

int main()
{
    int i;

    i = sum(3, 8,7,6);
    printf("sum = %d\n", i);

    return 0;
}
これには2つの問題がある。まず、xml-ph-0000@deepl.internal関数は、引数がいくつ渡されたかを知る必要がある。これは明示的に記述しなければならず、 これには2つの問題がある。まず、sum 関数は、引数がいくつ渡されたかを知る必要がある。 これは明示的に記述しなければならず、 実際に記述された引数の数と同期が取れなくなる 可能性がある。 2つ目は、 引数の型が本当にint型であり、double型、string型、構造体型などではないことを確認する方法がない

Dの方法

配列パラメータ宣言の後に続く... は、 後続の引数がまとめて配列を構成することを意味する。引数は配列の型に対して型チェックされ 、引数の数は配列のプロパティとなる。
import std.stdio;

int sum(int[] values ...)
{
    int s = 0;

    foreach (int x; values)
        s += x;
    return s;
}

int main()
{
    int i;

    i = sum(8,7,6);
    writefln("sum = %d", i);

    return 0;
}