[Solidityのはじめ方] Part14: 配列とイテレーション

Solidityでデータをまとめて扱うための「配列」とその繰り返し処理「イテレーション」を学びましょう!

配列とは? 🤔

配列は、同じ型のデータを複数まとめて格納するためのデータ構造です。スマートコントラクト内で、ユーザーリスト、投票結果、設定値など、様々な情報を整理するのに役立ちます。

Solidityには大きく分けて2種類の配列があります。

  • 固定長配列 (Fixed-size Array): 作成時に要素数を決定し、後から変更できない配列。
  • 動的配列 (Dynamic Array): 要素数を後から変更できる配列。

配列の宣言と初期化 ✍️

固定長配列

型名の後に `[要素数]` をつけて宣言します。初期化も同時に行えます。

// 5つの符号なし整数を格納する固定長配列
uint[5] public fixedNumbers = [10, 20, 30, 40, 50];

// 3つのアドレスを格納する固定長配列(初期値はゼロアドレス)
address[3] public addresses;

動的配列

型名の後に `[]` をつけて宣言します。要素数は最初は0ですが、後から追加できます。ストレージに動的配列を宣言する場合、`new` キーワードを使って初期化することもできますが、通常は要素を追加していく形になります。メモリ内の動的配列は `new` でサイズを指定して作成します。

// 符号なし整数の動的配列(ストレージ)
uint[] public dynamicNumbers;

// 文字列の動的配列(ストレージ)
string[] public names;

function addData() public {
    // ストレージの動的配列に要素を追加
    dynamicNumbers.push(100);
    dynamicNumbers.push(200);
    names.push("Alice");
    names.push("Bob");

    // メモリ内に動的配列を作成 (サイズ指定)
    uint[] memory memoryNumbers = new uint[](3);
    memoryNumbers[0] = 1;
    memoryNumbers[1] = 2;
    memoryNumbers[2] = 3;
}
💡 メモリ配列とストレージ配列:
関数内で `memory` キーワードを使って宣言された配列は一時的なもので、関数実行終了後に消えます。ガス代が安価です。
コントラクトのトップレベルで宣言された配列(状態変数)は `storage` に永続的に保存され、ガス代が高くなります。

配列の操作 ⚙️

要素へのアクセス

インデックス(0から始まる番号)を使って特定の要素にアクセスします。

uint[5] public fixedNumbers = [10, 20, 30, 40, 50];
uint[] public dynamicNumbers;

function accessElements() public returns (uint, uint) {
    dynamicNumbers.push(100);
    dynamicNumbers.push(200);

    uint firstFixed = fixedNumbers[0]; // 10
    uint secondDynamic = dynamicNumbers[1]; // 200

    // 要素の値を変更
    fixedNumbers[0] = 15;
    dynamicNumbers[1] = 250;

    return (firstFixed, secondDynamic); // 元の値を返す
}
⚠️ 注意: 存在しないインデックスにアクセスしようとするとエラーが発生します。

要素の追加(動的配列)

動的配列(ストレージ配列のみ)の末尾に要素を追加するには `push()` メソッドを使います。`push()` は新しい要素を追加し、配列の `length` を1増やします。要素を指定して `push(要素)` とすることも可能です(Solidity 0.6.0以降)。

uint[] public dynamicNumbers;

function addElement(uint _number) public {
    dynamicNumbers.push(_number); // 配列の末尾に _number を追加
}

// Solidity 0.8.0 以降では push() で追加後の新しい長さを返さなくなりました。

要素の削除(動的配列)

動的配列(ストレージ配列のみ)の末尾の要素を削除するには `pop()` メソッドを使います。`pop()` は配列の `length` を1減らします。

uint[] public dynamicNumbers = [10, 20, 30];

function removeLastElement() public {
    if (dynamicNumbers.length > 0) {
        dynamicNumbers.pop(); // 末尾の要素 (30) を削除
        // dynamicNumbers は [10, 20] になる
    }
}

特定のインデックスの要素を削除したい場合、その要素を最後の要素で上書きし、`pop()` を使うという方法が一般的ですが、要素の順序が変わる点に注意が必要です。順序を保ちたい場合は、削除したい要素以降の全要素を一つ前にずらす必要がありますが、ガス代が高くなる可能性があります。

配列の長さ

配列の要素数を取得するには `length` プロパティを使います。固定長配列では `length` は常に一定ですが、動的配列では変化します。

uint[5] public fixedNumbers = [10, 20, 30, 40, 50];
uint[] public dynamicNumbers;

function getLengths() public view returns (uint, uint) {
    uint fixedLength = fixedNumbers.length; // 5
    uint dynamicLength = dynamicNumbers.length; // 要素数によって変わる

    return (fixedLength, dynamicLength);
}

動的配列(ストレージ配列のみ)の長さを直接変更することもできますが、推奨されません。要素がゼロで埋められたり、削除されたりします。

イテレーション(繰り返し処理) 🔄

配列のすべての要素に対して同じ処理を行いたい場合、ループ(繰り返し処理)を使います。Solidityでは主に `for` ループが使われます。

uint[] public numbers = [1, 2, 3, 4, 5];
uint public sum = 0;

function calculateSum() public {
    sum = 0; // 計算前に合計をリセット
    for (uint i = 0; i < numbers.length; i++) {
        sum += numbers[i];
    }
    // ループ後、sum は 1 + 2 + 3 + 4 + 5 = 15 になる
}

// 配列の要素を2倍にする関数 (メモリ配列の例)
function doubleValues(uint[] memory _data) public pure returns (uint[] memory) {
    uint[] memory result = new uint[](_data.length);
    for (uint i = 0; i < _data.length; i++) {
        result[i] = _data[i] * 2;
    }
    return result;
}
⛽️ ガス代に関する注意:
ストレージ配列に対するループ処理は、要素数が増えるほどガス代が非常に高くなります。ループ内でストレージへの書き込みを行う場合は特に注意が必要です。
巨大な配列全体を一度のトランザクションで処理するのは避け、複数回に分ける、またはオフチェーンで処理するなどの工夫が求められます。

まとめと次のステップ 🚀

今回はSolidityにおける配列の基本的な使い方と、`for`ループを使ったイテレーションについて学びました。

  • 配列には固定長配列動的配列がある。
  • インデックスを使って要素にアクセス、変更できる。
  • 動的配列では `push()` で要素を追加、`pop()` で末尾要素を削除できる。
  • `length` で配列の長さを取得できる。
  • `for` ループで配列の要素を繰り返し処理できる。
  • ストレージ配列のループ処理はガス代に注意が必要!

配列はデータを効率的に管理するための重要なツールです。特に動的配列は柔軟性が高いですが、ガス消費量を意識することが大切です。

次は、データをキーと値のペアで管理する「マッピング」と、関連データをまとめる「構造体」に進む前に、非常に重要な「ストレージとメモリの違い」と「ガス最適化の基本」について深く掘り下げます。これらは効率的なスマートコントラクト開発に不可欠な知識となります。