オフィスアワーがそろそろ始まるよ!()

配列

配列について解説します。

可変長配列ArrayListについては、9章 containerに説明を記載します。

配列型

Zenの配列型では、要素数は型の一部であり、その要素数はコンパイル時に計算可能である必要があります。

examples/ch02-primitives/src/arrays.zen:4:10

test "array basics" {
    const array = [4]i32{ 1, 2, 3, 4 };
    ok(@TypeOf(array) == [4]i32);
    ok(array.len == 4);
    ok(array[0] == 1);
    ok(array[3] == 4);
}

上の例では、変数arrayの型は、[4]i32です。配列リテラル[4]i32{ 1, 2, 3, 4 }により、変数を初期化しています。変数の型は、配列リテラルから型推論が可能なため、変数arrayの型は省略しています。

.lenで配列の長さを取得することができ、[インデックス]で個別の要素にインデックスアクセスすることが可能です。インデックスの型はusizeである必要があります。

次のコードはコンパイルエラーになります。

test "array index" {
    const array = [4]i32{ 1, 2, 3, 4 };
    var index: i32 = 0;
    ok(array[index] == 1);
}
error[E02046]: expected 'usize', found 'i32'
    ok(array[index] == 1);
             ~

このような場合、@intCastを利用して、indexusizeに変換する必要があります。

examples/ch02-primitives/src/arrays.zen:12:16

test "array index" {
    const array = [_]i32{ 1, 2, 3, 4 };
    var index: i32 = 0;
    ok(array[@intCast(usize, index)] == 1);
}

ノート: ただし、インデックスの値がコンパイル時計算可能であれば、暗黙の型変換が行われるため、@intCastによる明示的な変換は不要です。

繰り返しになりますが、要素数が型の一部であることに注意して下さい。要素数4の配列と要素数5の配列は、違う型になります

examples/ch02-primitives/src/arrays.zen:18:23

test "array types" {
    const array1 = [4]i32{ 1, 2, 3, 4 };
    const array2 = [5]i32{ 1, 2, 3, 4, 5 };

    ok(@TypeOf(array1) != @TypeOf(array2));
}

配列をvarで定義すると、各要素の値を更新することができます。

examples/ch02-primitives/src/arrays.zen:25:29

test "var array" {
    var array = [4]i32{ 1, 2, 3, 4 };
    array[0] = 5;
    ok(array[0] == 5);
}

undefinedを代入することで、配列の値を不定値で初期化することができます。

examples/ch02-primitives/src/arrays.zen:31:34

test "array containing undefined values" {
    var array: [100]i32 = undefined;
    ok(array.len == 100);
}

配列リテラル

配列リテラルとは、配列型に対してプログラム内に直接記述した値を意味します。構文は[要素数]要素型{ 値, ... }です。

配列要素数を明示的に記述する方法と、配列リテラルの{ 値, ... }から要素数を推論させる記述方法があります。要素数を推論させる場合は、[_]要素型{ 値, ... }のように、要素数をアンダースコア (_) にします。

examples/ch02-primitives/src/arrays.zen:36:41

test "array literals" {
    const array1 = [4]i32{ 1, 2, 3, 4 };
    const array2 = [_]i32{ 1, 2, 3, 4 };
    // `array1`と`array2`は同じ型
    ok(@TypeOf(array1) == @TypeOf(array2));
}

配列のイテレーション

配列の全要素を順番に取得したい場合、forを利用します。for (配列) | キャプチャ変数名 |という構文を利用します。forの包括的な利用方法については、4章 繰り返しを参照して下さい。

examples/ch02-primitives/src/arrays.zen:43:51

test "iterate over an array" {
    var array = [_]i32{ 1, 2, 3, 4 };
    var sum: i32 = 0;
    // 各要素は`elem`として取得可能
    for (array) |elem| {
        sum += elem;
    }
    ok(sum == 10);
}

array自体がvarなのにも関わらず、上記の例ではelemconstな値になります。そのため、elemの値を変更することはできません。

次のコードはコンパイルエラーになります。

    for (array) |elem| {
        elem = 0;
    }
error[E02030]: cannot assign to constant variable
        elem = 0;
               ~

配列の要素自体を更新したい場合、各要素の参照を取得し (*をつけて配列要素をキャプチャし)、参照外し (デリファレンス) 演算子 (.*) を使用します。

examples/ch02-primitives/src/arrays.zen:53:60

test "update an array in for loop" {
    var array = [_]i32{ 1, 2, 3, 4 };
    // 各要素は`elem.*`として読み書き可能
    for (array) |*elem| {
        elem.* = elem.* * elem.*;
    }
    ok(array[3] == 16);
}

forブロック内で取得している要素のインデックスを使用したい場合、for (配列) | キャプチャ変数名, インデックス |という構文を利用します。

examples/ch02-primitives/src/arrays.zen:62:72

test "get index of an array in for loop" {
    var array = [_]usize{ 1, 2, 3, 4 };
    // `i`に`usize`型のインデックスが格納される
    for (array) |*elem, i| {
        elem.* = i + i;
    }
    ok(array[0] == 0);
    ok(array[1] == 2);
    ok(array[2] == 4);
    ok(array[3] == 6);
}

iusize型であることに注意して下さい。

C言語を経験している方は、インデックスをインクリメントしながら配列要素をアクセスする方法に馴染みがあるかもしれません。しかしそのような配列のアクセス方法は間違いを犯しやすいため、Zenでは非推奨です。

インデックスアクセス時の境界チェック

要素数が型の一部であるため、次のように、インデックスアクセス時に要素数以上のインデックスを指定すると、コンパイルエラーになります。

    const array = [4]i32{ 1, 2, 3, 4 };
    ok(array[4] != 4);
error[E04028]: index is out of bounds
    ok(array[4] != 4);
            ~

ただし、コンパイルエラーとなるのは、インデックスがコンパイル時計算可能な場合です。インデックスが実行時にしか計算できない場合、安全性保護付き未定義動作により、パニックが発生します。

test "invalid index at runtime" {
    var array = [_]i32{ 1, 2, 3, 4 };
    var index: usize = 4;
    array[index] = 0;
}
index out of bounds
    array[index] = 0;
         ^

配列の連結と配列の積

コンパイル時計算可能な配列に対しては、配列の連結をしたり、配列の積を計算することができます。

配列を連結するときは、++演算子を使用します。連結する配列は、双方ともコンパイル時計算可能である必要があります。

examples/ch02-primitives/src/arrays.zen:74:82

const equalSlices = std.testing.equalSlices;
test "concatenates arrays" {
    const array1 = [_]i32{ 1, 2 };
    const array2 = [_]i32{ 3, 4 };
    const concatenated = array1 ++ array2;

    const expected = [_]i32{ 1, 2, 3, 4 };
    equalSlices(i32, &concatenated, &expected);
}

equalSlicesは、2つの配列が一致比較を行うテスト用関数です。正確にはスライスの一致比較を行います。スライスについては、次の節で説明しますので、ここでは単純に2つの配列を比較している、と考えて下さい。

配列の積を計算するときは、**演算子を使用します。array ** numで、配列arraynum回連結した配列を生成します。連結する配列は、双方ともコンパイル時計算可能である必要があります。

examples/ch02-primitives/src/arrays.zen:84:89

test "multiplies array" {
    const multiplied = [_]i32{ 1, 2 } ** 2;

    const expected = [_]i32{ 1, 2, 1, 2 };
    equalSlices(i32, &multiplied, &expected);
}

配列の積は配列の要素を0でクリアする場合にも利用できます。

examples/ch02-primitives/src/arrays.zen:91:97

test "zero clear array" {
    const zeros = [_]i32{0} ** 100;
    ok(zeros.len == 100);
    for (zeros) |zero| {
        ok(zero == 0);
    }
}

配列の連結と配列の積は、コンパイル時計算可能な場合に限り利用できます。実行時に長さが変化する可変長配列は、標準ライブラリのArrayListを使用します。ArrayListについては、9章 containerを参照して下さい。

多次元配列

多次元配列は配列をネストすることで作成できます。

examples/ch02-primitives/src/arrays.zen:99:114

test "two-dimensional arrays" {
    const matrix = [3][4]i32{
        [4]i32{ 0, 1, 2, 3 },
        [4]i32{ 4, 5, 6, 7 },
        [4]i32{ 8, 9, 10, 11 },
    };

    ok(@TypeOf(matrix) == [3][4]i32);
    // `matrix`は`[4]i32`の配列を3つ持つ
    ok(matrix.len == 3);
    // インデックス1つで中の`[4]i32`配列にアクセス
    ok(@TypeOf(matrix[0]) == [4]i32);
    ok(matrix[0].len == 4);
    // インデックス2つで`i32`にアクセス
    // インデックスは外側から順番に書く
    ok(matrix[1][2] == 6);

forをネストすることで、全要素に順番にアクセスできます。

examples/ch02-primitives/src/arrays.zen:116:122

    var sum: i32 = 0;
    for (matrix) |array| {
        for (array) |elem| {
            sum += elem;
        }
    }
    ok(sum == 66);

多次元配列においても要素数の推論が可能です。

examples/ch02-primitives/src/arrays.zen:125:133

test "inference of two-dimensional arrays" {
    // 一番外の配列要素数は推論可能
    const matrix = [_][4]i32{
        // 内側の配列要素定義では推論可能
        [_]i32{ 0, 1, 2, 3 },
        [_]i32{ 4, 5, 6, 7 },
        [_]i32{ 8, 9, 10, 11 },
    };
}

次節で説明するスライスを使って、次のような多次元配列を作成できます。

examples/ch02-primitives/src/arrays.zen:135:144

test "array of slice" {
    const matrix = [_][]i32{
        &[_]i32{ 0, 1, 2 },
        &[_]i32{ 3, 4, 5, 6, 7 },
        &[_]i32{ 8, 9 },
        &[_]i32{ 10, 11, 12, 13, 12, 15, 16 },
    };

    ok(@TypeOf(matrix) == [4][]i32);
}

終端値付き配列

配列型の特殊な型として配列の終端値を指定することができます。配列型を指定する際に [] の中に : に続けて終端値を指定します。 例えば、要素数が 5 、終端値が 10、要素型が u32 の配列の型は [5:10]u32 となります。

特に Zen の文字列リテラルは 0 を終端値とした u8 の配列となります。厳密には *[5:0]u8 のようなポインタとなります。これにより、C言語のライブラリ関数に文字列を渡す場合には変換をせずに使用することができます。

終端値付き配列の要素数 len は、終端値の分を含まない値となります。メモリ上は要素数 + 1 の位置に終端値が格納されます。

終端値には基本的にアクセスできません。例えば、var x: [5:10]u32 の配列に対して x[5] にアクセスするようなコードを書くとコンパイルエラーとなります。また、実行時に終端値にアクセスした場合は安全性保護付き未定義動作となります。

これらについて次のコードで説明します。

examples/ch02-primitives/src/arrays.zen:146:160

test "sentinel-terminated arrays" {
    const x100 = [_:100]u32{ 1, 2, 3 };
    ok(@TypeOf(x100) == [3:100]u32);
    ok(x100.len == 3);

    const string = "hello";
    ok(@TypeOf(string) == *[5:0]u8);

    // error[E04028]: index is out of bounds
    //ok(x100[x100.len] == 100);

    // 実行時に安全性保護付き未定義動作
    //var index = x100.len;
    //ok(x100[index] == 100);
}

第2章 文字列第14章 Cとのインタフェース も参照してください。

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.