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` ループで配列の要素を繰り返し処理できる。
- ストレージ配列のループ処理はガス代に注意が必要!
配列はデータを効率的に管理するための重要なツールです。特に動的配列は柔軟性が高いですが、ガス消費量を意識することが大切です。
次は、データをキーと値のペアで管理する「マッピング」と、関連データをまとめる「構造体」に進む前に、非常に重要な「ストレージとメモリの違い」と「ガス最適化の基本」について深く掘り下げます。これらは効率的なスマートコントラクト開発に不可欠な知識となります。