参照型とは、値そのものではなく、その値が格納されているメモリ位置を指す型です。値型では変数に直接データの値が格納されるのに対して、変数には格納されている位置への参照のみが保存されています。
参照型は、データのメモリ上の位置を保持するため、データの大きさに関係なくメモリの使用を最適化することができます。
マッピング型は、キーと値のペアを格納するデータ構造です。
配列型
array
(配列型)は、同じ型の要素を連続して格納するデータ構造です。Solidityのarray
には固定長と可変長の2種類があります。可変長array
は要素の追加や削除が可能で、固定長array
は要素数が固定されています。
次の例では、fruits
は可変長の配列、fixedNumbers
は固定長の配列です。配列は、push()
、pop()
という関数で要素を追加したり、削除したりすることができます。
string[] fruits = ["banana", "apple", "strawberry"]; // 可変長
uint[3] fixedNumbers = [1, 2, 3]; // 固定長
function processSomething() {
fruits.pop(); // popにより、最後に追加された要素が削除される
fruits.push("peach"); // 可変長は要素を追加することができる
uint length = fruits.length; // lengthにより要素の数が取得できる
uint secondElement = fixedNumbers[1]; // 配列[i]でi番目の要素が取得できる
}
構造体
struct
(構造体)は複数の異なる型の変数をまとめて管理するために使用されます。struct
は独自のデータ型を定義することができ、1つの変数で複数の値を扱えるようになります。
次の例では、複数の属性情報を持つ人物の情報をまとめて管理するために、Person
というstruct
型を定義しています。Person
は、name
とbalance
(残高)という異なるデータ型である2つの変数を持っており、これはメンバ変数と呼ばれます。Person
型の変数の例として、onePerson
とanotherPerson
の2つの変数が定義されていますが、それぞれ初期化方法が異なる点に注意しましょう(いずれも有効な初期化方法です)。
struct Person {
string name;
uint256 balance;
}
Person onePerson = Person("Tanaka", 1e18); // 1e18 = 1000000000000000000 (0が18個)
Person anotherPerson;
function processSomething() {
anotherPerson.name = "Sato";
anotherPerson.balance = 2e18;
}
マッピング型
mapping
(マッピング型)は、キーと値のペアを格納するデータ構造です。キーを指定することで速やかに値を検索(取得)することができます(データベースのテーブルのようなイメージで、キーはインデックスをイメージすると分かりやすいです)。
次の例のaddressToPerson
は、address
型とPerson
型(前述のstruct
の例)を紐付けるマッピング型の変数です。アドレスと人の情報を紐づけて保存するために使う変数です。
6, 7行目の記述によって、mapping
に紐づけが記録されます。
mapping(address => Person) addressToPerson;
address oneAddress = 0x551af3f6A226a62BCb27f08B42401bd5629f2E44;
address anotherAddress = 0x0a011ABbCc1Ba8f82eF2c7490f8FD91cb35dBF62;
function processSomething() {
addressToPerson[oneAddress] = onePerson;
addressToPerson[anotherAddress] = anotherPerson;
}
データ配置場所
“data location”(データ配置場所)は、配列、構造体などの参照型のデータについて、保存する場所を明示するための記述です。保存場所としては、storage
, memory
, calldata
の3つがあります。
変数を定義する際には、上記のいづれかを意識する必要があります。利用するデータ配置場所によって、ガス代も異なり、高い順に、storage
> memory
> calldata
となります(永続的にデータを保存するstorage
が最もコストが高い)。つまり、変数定義においては、ガス代を意識して定義する必要があるということになります。
※ EVMが管理している(アクセスできる)領域という意味では他にもスタック(関数実行の際に利用される一時領域)、ソースコード内やログ領域があります。例えば、後の章で記載するイベントはログ領域に保存されます。
storage
storage
は永続的なデータの保存場所であり、スマートコントラクトのストレージ領域に格納されます。storage
はブロックチェーン上で状態を保持し、トランザクション間でデータの永続性が確保されます(他の2つと違うのが永続性がある点です)。変数がstorage
に格納されている場合、その値はコントラクトの実行ごとに保持されます。
次の例では、PersonStorage
というコントラクトの中にaddressToPerson
というマッピング変数が定義されています。このaddressToPerson
はstorage
です。
contract PersonStorage {
...
mapping(address => Person) addressToPerson;
...
}
memory
memory
は一時的なデータの保存場所であり、関数の実行中に使用されます。memory
は関数の実行が終了すると破棄され、データは消失します。memory
は動的に割り当てられたデータや一時的な変数、関数のローカル変数など、実行時に必要なデータを保持するために使用されます。
次の例では、関数の引数(後述)として、numbers
という配列を定義していますが、これは関数内だけで使われる想定なので、memory
が適切です。memory
なので、値の変更等も必要であれば可能です。
contract PersonStorage {
...
function sumUp(uint[] memory numbers) {
uint sum = 0;
for (uint i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
...
}
}
calldata
calldata
は読み取り専用のデータ領域で、外部からスマートコントラクトに渡される引数や、外部からのトランザクションデータがcalldata
内に格納されます。calldata
内のデータは変更できません。
次の例では、引数にuserAddress
(ユーザーのアドレス)を定義し、アドレスから該当のユーザーの残高を取得し、それをbalance
というmemory
型の変数に入れています。
contract PersonStorage {
...
function getBalanceOf(address[] calldata addressArray) {
uint256 sum = 0;
// addressArray[0] = 0x551af3f6A226a62BCb27f08B42401bd5629f2E44; // 読み取り専用なのでこれはダメ
for (uint i = 0; i < addressArray.length; i++) {
sum += addressToPerson[addressArray[i]].balance;
}
...
}
}
ご意見をお聞かせください!
この記事、または、web3チュートリアル全体について、是非、あなたのご意見をお聞かせください。
アンケートはこちらからご回答いただけます。
無料相談承ります
オンラインでの無料相談を承っています。ご希望の方は、お問い合わせフォームよりご連絡ください。
ITの専門家があなたのご質問にお答えいたします。
Comments