配列
種類
配列には4つの種類がある:
Syntax | Description |
---|---|
type[integer](整数型 | 静的配列 |
type[](型 | 動的配列 |
型*配列 | ポインタ配列 |
型[型] | 連想配列 |
静的配列
int[3] s;
静的配列はコンパイル時に長さが固定される。
静的配列の合計サイズは16MBを超えることはできない。
0次元の静的配列は許されるが、そのための領域は確保されない。 の領域は割り当てられない。
静的配列は値型である。 それらは値によって関数に渡され、関数によって返される。
- 大きな配列には動的配列を使う。
- 要素数が0の静的配列は、可変長構造体の最後のメンバとして、あるいは、可変長構造体の縮退ケースとして有用である。 可変長構造体の最後のメンバとして、あるいはテンプレート展開の縮退ケースとして有用である。 テンプレート展開の縮退ケースとして有用である。
- 静的配列は値によって関数に渡される、 大きな配列はスタック領域を大量に消費する。代わりに動的配列 を使う。
ダイナミック・アレイ
int[] a;
動的配列は、長さと配列データへのポインタから構成される。 複数の動的配列は、配列データのすべてまたは一部を共有することができる。
- ポインタ配列の代わりに動的配列をできるだけ使用する。 動的配列のインデックスは境界チェックされ、バッファのアンダーフローやオーバーフローの問題を避けることができる。 オーバーフロー問題を回避する。
ポインタ配列
int* p;
ポインタ は、メモリ上の連続した複数の値のブロックを操作することができる。 複数の値にアクセスすることはできない。 @safeそれは ポインタ演算を必要とするからである。 これは、C言語とのインターフェイスや、特殊なシステム作業のためにサポートされている。 特殊なシステム作業のためにサポートされている。 ポインタは長さを持たないので、コンパイラやランタイムがポインタに対して境界チェックなどを行うことはできない。 そのため、コンパイラやランタイムはポインタに対して境界チェックなどを行うことができない。
配列の宣言
宣言は宣言される識別子の前に現れ、右から左に読む。 宣言は宣言される識別子の前に現れ、右から左に読む:
int[] a; // intの動的配列 int[4][3] b; // 4つのint型からなる3つの配列の配列 int[][5] c; // intの動的配列5個の配列 int*[]*[3] d; // intへの動的配列への3つのポインタの配列 int[]* e; // intの動的配列へのポインタ
配列リテラル
auto a1 = [1,2,3]; // 型はint[]で、要素1、2、3を持つ auto a2 = [1u,2,3]; // 型はuint[]で、要素は1u、2u、3uである int[2] a3 = [1,2]; // 型はint[2]で、要素は1、2である
[] は空の配列リテラルである。
配列リテラルを参照のこと。
アレイ割り当て
動的配列とポインタ配列に対して行う操作には、大きく分けて2種類ある。 配列のハンドルに影響を与えるもの、 配列のハンドルに影響を与えるものと、配列の内容に影響を与えるものである。代入は これらの型ではハンドルにのみ影響する。
.ptr 静的配列と動的配列の"@property"プロパティは、配列の最初の要素のアドレスを示す。 を与える:
int* p; int[3] s; int[] a; p = s.ptr; // pは配列sの最初の要素を指す。 p = a.ptr; // pは配列aの最初の要素を指す。 // pが指す配列の長さは不明なので、エラーとなる //s = p; //a = p; // エラー,長さが不明である a = s; // aはsの要素を指す assert(a.ptr == s.ptr); int[] b; a = b; // aはbと同じ配列を指す assert(a.ptr == b.ptr); assert(a == []);
静的配列は動的配列から代入することができる。 データはコピーされる:
int[3] s; int[] a; //s = [1, 2]; // エラー s = [1, 2, 3]; // OK //s = [1, 2, 3, 4]; // エラー a = [4, 5, 6]; s = a; // OK assert(s.ptr != a.ptr); a = [1, 2]; //s = a; // RangeError、長さの不一致 a = s; assert(a.ptr == s.ptr); //s = a; // RangeError、オーバーラップ
ダイナミック・アレイのデータは、スタティック・アレイのメモリと重なってはならない。 動的配列データは静的配列メモリと重なってはならない。 コピーも参照のこと。
インデックス作成
インデックスは、配列の要素へのアクセスを可能にする:
auto a = [1,2,3]; assert(a[0] == 1); assert(a[2] == 3); a[2] = 4; assert(a[2] == 4); assert(a == [1,2,4]); //writeln(a[3]); // 実行時エラー(bounds checksをオフにしない限り) int[2] b = [1,2]; assert(b[1] == 2); //writeln(b[2]); // コンパイル時エラー、インデックスが範囲外
IndexOperationも参照のこと。
ポインタ演算
ポインタはインデックスを付けることもできるが、境界チェックは行われない。 配列とは異なり、ポインタ値は特定の算術式で使用することもできる。 算術式で使用することもできる:
int[] a = [1,2,3]; int* p = a.ptr; p[2] = 4; assert(a[2] == 4); writeln(p[3]); // 未定義の動作 assert(p == &a[0]); p++; // a[1]を指す assert(*p == 2);
詳細はAddExpressionを参照のこと。
スライシング
配列のスライスとは、配列の部分配列を指定することである。 これは、2つのインデックス式を指定することで行われる。 開始インデックスから終了インデックスまでの要素が選択される。 終了インデックスにある要素は含まれない。
配列のスライスは、データをコピーするのではなく、そのデータへの別の参照に過ぎない。 参照に過ぎない。スライスは動的配列を生成する。
int[3] a = [4, 5, 6]; // 3つのintの静的配列 int[] b; b = a[1..3]; // a[1..3]は // a[1]とa[2]からなる2要素の動的配列である assert(b == [5, 6]); assert(b.ptr == a.ptr + 1); a[2] = 3; assert(b == [5, 3]); b = b[1..2]; assert(b == [3]);
式[] は、配列全体のスライスを表す略記法である。
スライス は、他の配列の一部を参照するのに便利なだけではない、 ポインターを境界チェックされた配列に変換するのにも便利だ:
int[10] a = [ 1,2,3,4,5,6,7,8,9,10 ]; int* p = &a[2]; writeln(p[7]); // 10 writeln(p[8]); // 未定義の動作 int[] b = p[0..8]; // ポインタ要素を動的配列に変換する assert(b is a[2..10]); writeln(b); writeln(b[7]); // 10 //writeln(b[8]); // ランタイムエラー(bounds checksをオフにしない限り)
SliceOperationも参照のこと。
配列の長さ
静的配列や動的配列のインデックス付けやスライスを行う場合、記号 は配列の長さを表す、 シンボル$ は配列の長さを表す。
int[4] foo; int[] bar = foo; // これらの式は等価である: bar = foo; bar = foo[]; bar = foo[0 .. 4]; bar = foo[0 .. $]; bar = foo[0 .. foo.length]; int* p = foo.ptr; //bar = p[0 .. $]; // エラー、'$'が定義されていない、pは配列ではないので int i; //i = foo[0]+$; // エラー、'$'が定義されていない、[ ]の範囲外 i = bar[$-1]; // 配列の最後の要素を取り出す
アレイコピー
スライス演算子が代入式の左辺として現れる場合、それは配列の内容がコピーされることを意味する。 スライス演算子が代入式の左辺として現れる場合、それは配列への参照ではなく、配列の内容が代入の対象となることを意味する。 であることを意味する。 配列のコピーは、左辺がスライスで、右辺がその配列へのポインタである場合に起こる。 右辺が同じ型の配列またはポインタであるときに起こる。
int[3] s, t; int[] a; s = t; // tの3つの要素がsにコピーされる s[] = t; // tの3つの要素がsにコピーされる s[] = t[]; // tの3つの要素がsにコピーされる s[1..2] = t[0..1]; // s[1] = t[0]と同じ s[0..2] = t[1..3]; // s[0] = t[1], s[1] = t[2]と同じである //s[0..4] = t[0..4]; // エラー、sとtには3つの要素しかない //s[0..2] = t; // エラー、オペランドの長さが異なる a = [1, 2]; s[0..2] = a; assert(s == [1, 2, 0]); //a[] = s; // RangeError、長さが一致しない a[0..2] = s[1..3]; assert(a == [2, 0]);
重複コピー
重複コピーはエラーである:
void main() { int[3] s; s[0..2] = s[1..3]; // エラー、重複コピー s[1..3] = s[0..2]; // エラー、重複コピー }
オーバーラップを禁止することで、シリアルの場合よりも積極的な並列コードの最適化が可能になる。 並列コードの最適化が可能になる。 より積極的な並列コードの最適化が可能になる。
オーバーラップが必要な場合は std.algorithm.mutation.copy:
import std.algorithm; int[] s = [1, 2, 3, 4]; copy(s[1..3], s[0..2]); assert(s == [2, 3, 3, 4]);
アレイセッティング
スライス演算子が代入式の左辺として現れ、右辺の型が代入式の右辺の型と同じである。 スライス演算子が代入式の左辺として現れ、右辺の型が左辺の要素型と同じである場合、配列の内容は左辺の要素型と同じになる。 右辺の型が左辺の型と同じ場合、左辺の配列内容は右辺に設定される。 の配列内容が右辺に設定される。
int[3] s; int[] a; int* p; s[] = 3; assert(s == [3, 3, 3]); a = s; a[] = 1; assert(s == [1, 1, 1]); p = s.ptr; p[0..2] = 2; assert(s == [2, 2, 1]);
配列の連結
二項演算子~ はcat演算子である。これは 配列の連結に使われる:
int[] a = [1, 2]; assert(a ~ 3 == [1, 2, 3]); // 配列を1つの値で連結する int[] b = a ~ [3, 4]; assert(b == [1, 2, 3, 4]); // 2つの配列を連結する
多くの言語では、連結のために+ 演算子をオーバーロードしている。 これは混乱を招くジレンマにつながる:
"10" + 3 + 4
数値17 、文字列"1034" 、または文字列"107" 。 を生成するのだろうか?それは明らかではない。 曖昧さをなくすために、言語設計者は注意深くルールを書くことになる。 曖昧さをなくすためのルールを注意深く書くことになる。それよりも + 。 を連結する。
連結は、オペランドの一方が長さ0の配列であっても、常にオペランドのコピーを作成する。 オペランドの一方が長さ0の配列であってもである:
auto b = [7]; auto a = b; // aはbを参照する assert(a is b); a = b ~ []; // aはbのコピーを参照する assert(a !is b); assert(a == b);
is 。
配列の追加
同様に、~= 演算子は、次のように追加を意味する:
a ~= b; // aはaとbの連結になる
詳細は動的配列長の設定を参照のこと。
ベクトルの操作
多くの配列操作 は、ループとしてではなく、高レベルで表現することができる。 例えば、ループである:
T[] a, b;
...
for (size_t i = 0; i < a.length; i++)
a[i] = b[i] + 4;
の要素をa の要素に割り当てている。b の要素に4 。これは次のように表すこともできる。 ベクトル表記で表すこともできる:
T[] a, b; ... a[] = b[] + 4;
ベクトル演算は、代入またはオペアサインメント式の左辺として現れるスライス演算子で示される。 によって示される。 右辺は以下の組み合わせとなる:
- 左辺と同じ長さと型の配列SliceOperation 左辺と同じ長さと型のスライス操作
- 左辺と同じ要素型のスカラー式
以下の操作がサポートされている:
- 単項演算:-,~
- 加算:+ 、-
- ムル:* /,% 、
- ビット演算子:^ & 、|
- パウ:^^
int[3] a = 0; int[] b = [1, 2, 3]; a[] += 10 - (b[] ^^ 2); assert(a == [9, 6, 1]);
左側のスライスと右側のスライスは重なってはならない。 すべてのオペランドは、たとえ配列スライスの要素がゼロであっても、正確に1回評価される。 の要素がゼロであっても、すべてのオペランドは正確に1回評価される。
要素型がマッチするオーバーロード演算子を定義している場合、それらのメソッドは でなければならない、 それらのメソッドはpure nothrow @nogc でなければならない。
配列要素が計算される順番は、実装によって定義される。 配列要素が計算される順番は実装で定義され、並列に行われることもある。 アプリケーションはこの順番に依存してはならない。
Implementation Note: 多くのベクトル演算は 多くのベクトル演算は、ターゲット・コンピュータで利用可能なベクトル演算命令を利用することが期待される。 を利用することが期待される。
矩形配列
経験豊富なFORTRAN数値演算プログラマーは、行列演算などのために多次元「長方形」配列が非常に高速であることを知っている。 「配列へのポインタの配列」セマンティクスから生じるポインタへのポインタを介してアクセスするよりも、はるかに高速であることを知っている。 配列へのポインタの配列」セマンティクスから生じるポインタへのポインタを介してアクセスするよりもはるかに高速であることを知っている。 例えば、D構文である:
double[][] matrix;
は行列を配列へのポインタの配列として宣言している。(動的配列は へのポインタとして実装される)。配列はさまざまなサイズを持つことができるため(動的にサイズが変更される)、これは「ギザギザ」配列と呼ばれることがある。 動的配列は配列データへのポインタとして実装される)。コードの最適化にとってさらに悪いのは 配列の行が互いを指すこともある!幸いなことに、D静的配列は、同じ構文を使いながら D静的配列は、同じ構文を使いながら、連続したメモリ・ブロックに固定された矩形レイアウトとして実装されている。 メモリの連続したブロックに固定された矩形レイアウトとして実装されている:
import std.stdio : writeln; double[6][3] matrix = 0; // すべての要素を0にする。 void main() { writeln(matrix); // [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]] }
ディメンジョンとインデックスは逆の順序で表示されることに注意。宣言の 宣言の次元は右から左に読まれるのに対し、インデックスは左から右に読まれる。 左から右に読まれる:
import std.stdio : writeln; void main() { double[6][3] matrix = 0; matrix[2][5] = 3.14; // 右下の要素に割り当てる。 writeln(matrix); // [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 3.14]] static assert(!__traits(compiles, matrix[5][2])); // 配列のインデックスが範囲外である。 }
詳細はDlang Wiki - Dense Multidimensional Arraysを参照のこと。
配列のプロパティ
静的配列プロパティは以下の通りである:
Property | Description |
---|---|
.init | .init 配列リテラルを返し、そのリテラルの各要素は配列要素型の"@property"プロパティである。 |
.sizeof | 配列の長さに を乗じた値を返す。 |
.length | 配列の要素数を返す。 これは静的配列では固定数である。型はsize_t 。 |
.ptr | 配列の最初の要素へのポインタを返す。 |
.dup | 同じサイズの動的配列を作成し、そこに配列の内容をコピーする。コピーされた配列は、不変量やconstが取り除かれる。この変換が無効な場合、呼び出しはコンパイルされない。 |
.idup | 同じサイズの動的配列を作成し、その中に配列の内容をコピーする。コピーは不変であると型付けされる。この変換が無効な場合、呼び出しはコンパイルされない。 |
.tupleof | 配列の各要素のl値シーケンスを返す:
void foo(int, int, int) { /* ... */ } int[3] ia = [1, 2, 3]; foo(ia.tupleof); // `foo(1, 2, 3);`と同じ float[3] fa; //fa = ia; // エラー fa.tupleof = ia.tupleof; assert(fa == [1F, 2F, 3F]); |
動的配列のプロパティは以下の通りである:
Property | Description |
---|---|
.init | null を返す。 |
.sizeof | 動的配列参照のサイズを返す、 32ビットでは8、64ビットでは16である。 |
.length | 配列の要素数を取得/設定する。 配列の要素数を取得/設定する。これはsize_t 型である。 |
.capacity | 再割り当てせずに配列に追加できる要素数を返す。 詳細はこちらを参照のこと。 |
.ptr | 配列の最初の要素へのポインタを返す。 |
.dup | 同じサイズの動的配列を作成し、そこに配列の内容をコピーする。コピーされた配列は、不変量やconstが取り除かれる。この変換が無効な場合、コールはコンパイルされない。 |
.idup | 同じサイズの動的配列を作成し、その中に配列の内容をコピーする。コピーは不変であると型付けされる。この変換が無効な場合、呼び出しはコンパイルされない。 |
例:
int* p; int[3] s; int[] a; p.length; // エラー、ポインタの長さが不明 s.length; // コンパイル時の定数は3 a.length; // 実行時の値 p.dup; // エラー、長さが不明 s.dup; // 3要素の配列を作成し、 // そこにsの要素をコピーする a.dup; // a.length要素の配列を作成し、 // aの要素をそこにコピーする
動的配列長を設定する
動的配列の.length "プロパティは、 演算子の左辺として設定することができる。 = の左辺として設定できる:
array.length = 7;
これにより、配列はその場で再割り当てされ、既存の内容は新しい配列にコピーされる。 の内容が新しい配列にコピーされる。新しい配列の長さが より短い場合は、配列は再割り当てされず、データはコピーされない。 これは 配列をスライスするのと同じである:
array = array[0..7];
新しい配列の長さの方が長い場合は、必要に応じて配列を再割り当てする、 既存の要素を保持する。新しい要素は デフォルトのイニシャライザで埋められる。
配列を成長させる
効率を最大化するために、ランタイムは常に配列のサイズを変更しようとする。 のサイズを変更しようとする。新しいサイズ が大きく、かつ
- アレイがGCによって割り当てられていない。
- アレイの予備容量がない。
- その場でサイズを変更すると、別のスライスでまだアクセス可能な有効なデータが上書きされる。
char[] a = new char[20]; char[] b = a[0..10]; char[] c = a[10..20]; char[] d = a; b.length = 15; // その場で拡張するとaの他のデータが上書きされるため、 // 常に再割り当てを行う。 b[11] = 'x'; // a[11]とC[1]は影響を受けない assert(a[11] == char.init); d.length = 1; assert(d.ptr == a.ptr); // 変更されない d.length = 20; // これを実行するとaとcが上書きされるため、再割り当てを行う。 assert(d.ptr != a.ptr); c.length = 12; // cの後には何も割り当てられていないため、領域が許すのであれば、 // その場で再割り当てしてもよい。 c[5] = 'y'; // aの内容に影響を与えるかもしれないが、 // 再割り当てされたためbやdには影響しない。 a.length = 25; // これは常に再割り当てされる。 // なぜなら、cがその場で拡張された場合、aを拡張するとcを上書きしてしまうからである。cがその場で、 // 再割り当てされなかった場合十分なスペースがなかったことを意味する、 // これは、aにとっては依然としてtrueである。 a[15] = 'z'; // aまたはcのどちらかが再割り当てされたため、cには影響しない。
コピー動作を保証するには、.dup "プロパティを使用する。 サイズを変更できるユニークな配列であることを保証する。
動的配列のサイズ変更は、比較的コストのかかる操作である。 そこで、以下のような方法で配列を埋めていく:
void fun() { int[] array; while (1) { import core.stdc.stdio : getchar; auto c = getchar; if (!c) break; ++array.length; array[array.length - 1] = c; } }
void fun() { int[] array; array.length = 100; // 推測 int i; for (i = 0; ; i++) { import core.stdc.stdio : getchar; auto c = getchar; if (!c) break; if (i == array.length) array.length *= 2; array[i] = c; } array.length = i; }
初期サイズの選択は、予想される一般的なユースケースに基づいて行う。 コードのインスツルメンテーションによって決定することができる、 あるいは単に適切な判断を下す。 例えば、コンソールからユーザーの入力を収集する場合、80より長くなることはないだろう。 コンソールからユーザーの入力を収集する場合、80より長くなることはないだろう。
capacity そしてreserve
capacity "プロパティは、動的配列が再割り当てされずに伸びる最大長を示す プロパティは、動的配列が再割り当てされずに成長できる最大長を与える。配列が を指していない場合、容量はゼロになる。 配列aの予備容量はa.capacity - a.length である。
デフォルトでは、スライスの後に要素が格納された場合、capacity はゼロになる。
int[] a; assert(a.capacity == 0); a.length = 3; // 予備の容量も割り当てる可能性がある assert(a.capacity >= 3); auto b = a[1..3]; assert(b.capacity >= 2); // aまたはbのどちらかが、空き容量に追加することができる b = a[0..2]; assert(b.capacity == 0);
このreserve 関数は配列の容量を拡張する。 .length で使用するために配列の容量を拡張する。
int[] array; const size_t cap = array.reserve(10); // リクエスト assert(cap >= 10); // 割り当てられた容量はリクエストより多いかもしれない assert(array.ptr != null); int[] copy = array; assert(copy.capacity == cap); // 配列とコピーは同じ容量を持つ array ~= [1, 2, 3, 4, 5]; // そのまま成長する assert(cap == array.capacity); // 配列のメモリは再割り当てされなかった assert(copy.ptr == array.ptr); assert(copy.capacity == 0); copy ~= 0; // 新たに割り当てられる assert(copy.ptr != array.ptr);
上記では、copy の長さはゼロのままだが、 のコールで確保された同じメモリーを指している。 reserve を指す。array 。 に追加されるため、copy.ptr + 0 はもはや未使用メモリを指していない。 array[0] copy.capacity のアドレスとなる。 copy 、array の要素が上書きされるのを防ぐため、 はゼロになる。
予備容量を持つ配列が、その長さを減らされたり、前の最後の要素より前に終了する スライスが割り当てられる、 はゼロになる。
@system関数 assumeSafeAppend関数を使えば しかし、より長いスライスに存在する可能性のある不変要素を上書きしないように注意しなければならない。 を上書きしないように注意しなければならない。
int[] a = [1, 2, 3]; a.length--; assert(a.capacity == 0); a.assumeSafeAppend(); assert(a.capacity >= 3);
配列プロパティとしての関数
Uniform Function Call Syntax (UFCS)を参照のこと。
配列の境界チェック
配列のインデックスが0より小さいか、配列の長さ以上であることはエラーである。 0より小さいか、配列の長さ以上である。インデックスが が範囲外の場合、ArrayIndexError がスローされる。 がスローされ、コンパイル時に検出された場合はエラーが発生する。 が発生する。プログラムは配列の境界チェックに頼ってはならない。 例えば、以下のプログラムは正しくない:
void main() { import core.exception; try { auto array = [1, 2]; for (auto i = 0; ; i++) { array[i] = 5; } } catch (ArrayIndexError) { // ループを終了する } }
void main() { auto array = [1, 2]; for (auto i = 0; i < array.length; i++) { array[i] = 5; } }
Implementation Note: コンパイラはコンパイル時に コンパイラは、例えば、コンパイル時に配列境界のエラーを検出しようとするはずである:
int[3] foo; int x = foo[3]; // エラー、範囲外
実行時に配列の境界をチェックするコードを挿入すべきである。 コンパイル時のスイッチで をコンパイル時に切り替えるべきである。
安全関数」も参照のこと。
配列の境界チェックを無効にする
実行時に配列境界チェックコードを挿入することは、コンパイラのスイッチでオフにすることができる。 コンパイラ・スイッチでオフにできる。 -boundscheck.
@system 、@trusted 、境界チェックが無効になっている、 コードの正しさは、コード作成者によって保証されなければならない。
一方、@safe 、境界チェックを無効にすることは、コンパイラが保証しているメモリー安全性を壊すことになる。 コンパイラが保証しているメモリ安全性を壊してしまう。これは推奨されない。 速度測定に動機: "がない限り、推奨されない。
アレイの初期化
デフォルトの初期化
- ポインターはnull に初期化される。
- 静的配列の内容は、配列要素型のデフォルト初期化子である に初期化される。
- 動的配列は0要素に初期化される。
- 連想配列は0要素に初期化される。
長さの初期化
new 式を使用すると、型を指定し、次にその型を使用することで、指定した長さの動的 配列を割り当てることができる。 を確保するために使うことができる。 (size) 構文を使用する:
int[] i = new int[](5); //i.length == 5 int[][] j = new int[][](10, 5); //j.length == 10, j[0].length == 5
ボイドの初期化
ボイド初期化は、配列のイニシャライザーが。 がvoid である場合に起こる。つまり、配列の中身は未定義となる。 つまり、配列の中身は未定義になる。 これは効率の最適化として最も有用である。 Void初期化は高度なテクニックであり、プロファイリングによってそれが重要であると判断された場合にのみ使用すべきである。 プロファイリングによってそれが重要であることが示された場合にのみ使用されるべきである。
動的配列の要素をボイド初期化するには std.array.uninitializedArray.
静的に割り当てられた配列の静的初期化
静的初期化は、 で囲まれた配列要素値のリストで指定する。 [ ] 要素の値のリストで与えられる。オプションで、値の前にインデックスと。 : を付けることができる。 インデックスが与えられない場合、前のインデックスに1を足した値が設定される。 に1を足した値に設定される。
int[3] a = [ 1:2, 3 ]; // a[0] = 0, a[1] = 2, a[2] = 3 assert(a == [0, 2, 3]);
これは、配列のインデックスが列挙型で与えられている場合に便利である:
enum Color { red, blue, green } int[Color.max + 1] value = [ Color.blue :6, Color.green:2, Color.red :5 ]; assert(value == [5, 6, 2]);
静的配列のすべての要素は、特定の値で初期化することができる:
int[4] a = 42; // aのすべての要素を42に設定する assert(a == [42, 42, 42, 42]);
これらの配列は、グローバル・スコープに現れたときに静的に割り当てられる。 そうでない場合は、const またはstatic でマークする必要がある。
特殊な配列型
文字列
文字列は (読み取り専用)の配列である。文字列リテラルは本質的に 文字配列リテラルを書く簡単な方法である。
char[] arr; //arr = "abc"; // エラー、`string`型の式`"abc"`を暗黙的に`char[]`型に変換することはできない arr = "abc".dup; // OK、変更可能なコピーを作成する string str1 = "abc"; // OK、同じ型 //str1 = arr; // エラー、`char[]`型の式`arr`を暗黙的に`string`型に変換できない str1 = arr.idup; // OK、要素の不変のコピーを確保する assert(str1 == "abc"); string str2 = str1; // OK、同じ不変の配列の内容の変更可能なスライス
string という名前はimmutable(char)[] のエイリアスである。 immutable(char)[] 型はimmutable charの配列を表す。 は変更可能である。
immutable(char)[] s = "foo"; s[0] = 'a'; // エラー、s[0]は不変 s = "bar"; // OK、s自身は不変ではない
文字列への参照も不変である必要がある場合は、次のように宣言することができる。immutable char[] またはimmutable string :
immutable char[] s = "foo"; s[0] = 'a'; // エラー、sは不変のデータを参照している s = "bar"; // エラー、sは不変である
文字列はコピー、比較、連結、追加ができる:
string s1; immutable s2 = "ello"; s1 = s2; s1 = "h" ~ s1; if (s1 > "farro") s1 ~= " there"; assert(s1 == "hello there");
を配列のセマンティクスで使用する。生成されたテンポラリは ガベージコレクタによって(あるいはalloca() )クリーンアップされる。 それだけでなく、これは特殊な文字列配列だけでなく、あらゆる 配列で動作する。
文字列リテラル型
文字列リテラルの"型 の型はコンパイルのセマンティックフェーズで決定される。型は暗黙の変換規則によって決定される。 暗黙の変換規則によって決定される。 等しく適用可能な暗黙的変換が2つある場合、結果はエラーとなる、 はエラーとなる。このような これらのケースを区別するために、キャストまたはc 、 w またはd の接尾辞を使うことができる:
cast(immutable(wchar)[]) "abc" // これはwchar文字の配列である "abc"w //
接尾辞を持たず、かつキャストされていない文字列リテラルは、暗黙のうちに文字列間で変換できる。 の間で暗黙的に変換できる。 string wstring およびdstring の間で暗黙的に変換できる(以下を参照)。
void fun() { char c; wchar w; dchar d; c = 'b'; // cには文字'b'が代入される w = 'b'; // wには文字 'b'が代入される //w = 'bc'; // エラー - 一度に1つのwchar文字しか割り当てられない w = "b"[0]; // wにはwchar文字'b'が割り当てられる w = "\r"[0]; // wにはキャリッジリターンwchar文字が代入される d = 'd'; // dには文字'd'が代入される }
文字列とユニコード
文字列データは以下のようにエンコードされる:
Alias | Type | Encoding |
---|---|---|
string | immutable(char)[] | UTF-8 |
wstring | immutable(wchar)[] | UTF-16 |
dstring | immutable(dchar)[] | UTF-32 |
組み込みの比較演算子はコード単位で動作することに注意されたい。 コード単位で動作する。 有効な文字列に対する最終結果は コード・ポイント と同じである。 の比較と同じである。 正規化される。 正規化はコストのかかる操作であり、言語プリミティブには適さない。 プリミティブには適さない高価な操作であるため、ユーザーによって強制されるものと仮定される。
標準ライブラリは、エンコーディングが混在する文字列を比較するために手を貸す。 (透過的にデコードすることにより std.algorithm.cmp), 大文字小文字を区別しない比較と正規化。
最後になるが、望ましい文字列のソート順は文化や言語によって異なる。 文化や言語によって異なる。 のようなものではない。文字列の自然な並び順は ユニコードの照合順序アルゴリズム を適用することで得られる。
文字ポインタとC文字列
文字へのポインタを生成することができる:
string str = "abcd"; immutable(char)* p = &str[3]; // 4番目の要素へのポインタ assert(*p == 'd'); p = str.ptr; // 1番目の要素へのポインタ assert(*p == 'a');
Dでは文字列リテラルだけがゼロ終端である。 一般に、文字列データへのポインタを をCに転送する場合、終端'\0' を付加する:
string str = "ab"; assert(str.ptr[2] == '\0'); // OK str ~= "cd"; // strがゼロ終端でなくなった str ~= "\0"; assert(str[4] == '\0'); // OK str.length = 2; // strが正しくゼロ終端されなくなった assert(str.ptr[2] != '\0');
例:printf
core.stdc.stdio.printfはCの関数であり、Dの一部ではない。printf() は0終端のC文字列を表示する。D文字列で D文字列でprintf() 。ひとつは を追加することである:
str ~= "\0"; printf("the string is '%s'\n", str.ptr);あるいは、そのどちらかだ:
import std.string; printf("the string is '%s'\n", std.string.toStringz(str));
文字列リテラルにはすでに0が付加されている。 を直接使うことができる:
printf("the string is '%s'\n", "string literal".ptr);
ではなぜ、printf への最初の文字列リテラルは必要ないのか? .ptr 最初のパラメータはconst(char)* としてプロトタイプ化されている。 文字列リテラルは暗黙的にconst(char)* に変換できる。 しかし、printf の残りの引数は可変長である。 ...(で指定される)、 で指定)であり、immutable(char)[] で型付けされた文字列リテラルを変種パラメータに渡すことはできない。 を可変長引数に渡すことはできない。
つ目の方法は、精度指定子を使う方法である。 長さが最初に来て、その後にポインタが来る:
printf("the string is '%.*s'\n", cast(int)str.length, str.ptr);
最良の方法は std.stdio.writeflnを使うことである。 D文字列を扱うことができる:
import std.stdio; writefln("the string is '%s'", str);
ボイド配列
ワイルドカードとして機能する特別な型の配列がある。 void[] として宣言された配列を格納できる。ボイド配列は ボイド配列は、何らかの配列データが扱われるが、配列要素の正確な型は重要でない低レベルの操作に使用される。 配列要素の正確な型は重要ではない。の.length 。 の は、本来の型の要素数ではなく、データのバイト長である。 である。インデックス付けやスライス操作における配列のインデックスは、バイトインデックスとして解釈される。 演算における配列インデックスはバイトインデックスとして解釈される。
どの型の配列も、暗黙のうちにvoid配列に変換することができる。 .length コンパイラは適切な計算を挿入する。 のサイズが要素数ではなくバイト数になるように、コンパイラは適切な計算を挿入する。ボイド 配列は、キャストを使わずに元の型に戻すことはできない、 また、要素サイズがvoid配列の長さを均等に分割しない配列型に変換するのはエラーである。 要素サイズが void 配列の長さを均等に分割しない配列型に変換するのはエラーである。
void main() { int[] data1 = [1,2,3]; long[] data2; void[] arr = data1; // OK、int[]は暗黙的にvoid[]に変換される。 assert(data1.length == 3); assert(arr.length == 12); // 長さは暗黙のうちにバイト数に変換される。 //data1 = arr; // 不正: void[]は暗黙のうちにint[]に変換しない // int[]に変換されない。 int[] data3 = cast(int[]) arr; // OK、明示的キャストで変換できる。 data2 = cast(long[]) arr; // ランタイムエラー: long.sizeof == 8、これは // 12バイトであるarr.lengthを // 分割しない。 }
コンパイル時に長さがわかっていれば、空配列も static にできる。 も可能である。長さはバイト数で指定する:
void main() { byte[2] x; int[2] y; void[2] a = x; // OK、長さが一致する void[2] b = y; // エラー: int[2]は8バイト長で、2バイトに収まらない。 }
ボイド配列は、単に ubyte[] には微妙な違いがある。ガベージ・コレクタは は通常、ubyte[] 配列のポインタをスキャンしない、ubyte[] ポインタではなく、純粋なバイト・データだけが格納されていると考えられるからだ。しかし は void[] 配列のポインタをスキャンする。 そのような配列は、ポインタの配列や、ポインタを含む要素の配列から暗黙のうちに 変換されている可能性があるからである。 要素から暗黙のうちに変換されている可能性があるからだ。 ポインタを含む配列を。 ポインターを含む配列をubyte[] 。 ポインタを含む配列を。
暗黙の変換
ポインターT* は暗黙のうちに次のいずれかに変換される。 に変換できる:
- void*
静的配列T[dim] は、暗黙的に次のように変換できる。 に暗黙的に変換できる。 U はT の基底クラスである):
- T[]
- const(U)[]
- const(U[])
- void[]
動的配列T[] は、暗黙のうちに以下のいずれかに変換できる。 U はT の基底クラスである):
- const(U)[]
- const(U[])
- void[]
配列リテラルは、暗黙的に静的配列型に変換することもできる。 型に暗黙的に変換することもできる。詳細は配列リテラル を参照のこと。
文字列リテラルは、暗黙的に静的配列型や文字ポインタ型に変換することもできる。 型や文字ポインタ型に暗黙的に変換することもできる。詳細は文字列リテラルを参照のこと。
DEEPL APIにより翻訳、ところどころ修正。
このページの最新版(英語)
このページの原文(英語)
翻訳時のdmdのバージョン: 2.108.0
ドキュメントのdmdのバージョン: 2.109.1
翻訳日付 :
HTML生成日時:
編集者: dokutoku