В Solidity array
- это упорядоченный список элементов, который индексируется численно, начиная с 0.
Типы массивов
В этом разделе мы предоставляем два типа категоризации массивов:
- против фиксированного размера массивов с динамическим размером
- Одномерные и многомерные массивы.
и фиксированный размер массива с динамическим размером
В документации Solidity проводится различие между массивами фиксированного размера и динамического размера. В приведенных ниже примерах T
- это тип элемента, а k
- длина / размер массива. Если k = 5
, массив может содержать максимум 5 значений.
- Фиксированный размер:
T[k]
// array that can hold 5 unsigned integers maximum uint[5] my_array;
- Динамический размер:
T[]
// array containing an undefined number of strings string[] my_array;
Одномерные и многомерные массивы
Одномерные и многомерные массивы могут быть как фиксированного, так и динамического размера. Одномерные массивы по сути являются примерами, приведенными выше. Вот определения
T[k]
: одномерный, фиксированный размерT[]
: одномерный, динамический размер
Многомерные массивы - это, по сути, вложенные массивы (массив, содержащий другие массивы). Однако в Solidity они бывают трех форм. В первом примере мы используем двумерный массив ...
T[k][k]
: двумерный, фиксированный размерT[][]
: двухмерный, динамический размерT[][k]
илиT[k][]
: двумерные, смешанные.
… Но вы увидите, что многомерные массивы могут иметь любой уровень вложенности! Вот несколько примеров.
T[2][2][2]
: трехмерное изображение фиксированного размера (всеk
одинаковы)T[2][8][4][12]
: четырехмерные, фиксированные размеры (k
имеют разные значения)T[][][][][]
: пятимерный, динамический размерT[][3][2][][9][]
: шестимерные, смешанные
Идея здесь состоит в том, чтобы продемонстрировать, что массивы могут быть вложенными и стать действительно сложными. Как видите, во вложенном массиве можно смешивать динамический и фиксированный размер. Однако следует упомянуть два важных замечания о вложенном массиве.
У вас не может быть разных типов внутри вложенных массивов
Максимальный уровень вложенности для вложенных массивов - 15. Если вы попытаетесь создать переменную с 16 вложенными массивами, вы получите ошибку «слишком глубокий стек». Это связано с тем, что указатель стека в EVM не может получить доступ к слоту в стеке, который глубже, чем 16-й элемент (считая сверху вниз). Попробуйте вставить эти фрагменты кода ниже в Remix, и вы увидите сообщение об ошибке:
// 15 level of nesting : OK works uint[][][][][][][][][][][][][][][] public nesting_limit; // 'Stack too deep' error uint[][][][][][][][][][][][][][][][] public over_limit;
Как определять массивы?
Определение одномерных массивов
Вы определяете одномерный массив в Solidity, задав переменную T
, за которой следует квадратные скобки []
. Если это массив фиксированного размера, вы должны указать максимальное количество элементов между []
.
// (One-Dimensional array, fixed size) // Name of players in a 4 players online game room string[4] players_room; //(One-Dimensional array, Dynamic Size) // Names of people who have voted string[] have_voted;
Определение двумерных массивов
В Solidity нотация многомерного массива перевернута ТОЛЬКО ПРИ ОПРЕДЕЛЕНИИ по сравнению с другими языками программирования.
В этом случае мы собираемся сравнить 2 массива
string[2][] crypto_names; string[][6] names_A_to_F;
В первом примере crypto_names
у нас есть динамический массив, но его элементы представляют собой массивы фиксированного размера (2). Во втором случае names_A_to_F
- это массив фиксированного размера, состоящий из 6 элементов, каждый из которых является динамическим массивом.
Давайте посмотрим на первом примере:
string[2][] crypto_names;
В приведенном выше примере 2 фиксированного размера относится ко второму уровню вложенности, не к первому.
pragma solidity ^0.5.0; contract CryptoNames { string[2][] public crypto_names; constructor() public { crypto_names.push([“Alice”, “Bob”]); // generic characters crypto_names.push([“Carol”, “Dave”]); // 3rd and 4th party crypto_names.push([“Eve”, “Frank”]); // Bad guys crypto_names.push([“Grace”, “Heidi”]); // others } }
Переменная crypto_names
может содержать неограниченное количество массивов, но каждый из этих массивов может содержать максимум 2 значения. Если мы попытаемся протолкнуть массив с 3 именами (см. Функцию ниже), мы получим ошибку в Remix.
function invalidPush() public { crypto_names.push(["Ivan", "Judy", "Mallory"]); } // Error in Remix : /* Invalid type for argument in function call. Invalid implicit conversion from string memory[3] memory to string storage ref[2] storage ref requested. crypto_names.push(["Ivan", "Judy", "Mallory"]); ^-------------------------^ */
Чтобы получить абстрактный обзор, массив crypto_names
хотел бы в PHP:
// In PHP code $crypto_names = array( 0 => array( 0 => "Alice", 1 => "Bob", // No more values allowed ! ), 1 => array( 0 => "Carol", 1 => "Dave", // No more values allowed ! ), 2 => array( 0 => "Eve", 1 => "Frank", // No more values allowed ! ), 3 => array( 0 => "Grace", 1 => "Heidi", // No more values allowed ! ), // ... etc (can add more values) );
Давайте теперь посмотрим на противоположный пример, где фиксированный размер указан наоборот.
string[][6] names_A_to_F;
В этом примере предположим, что мы хотим сохранить список имен, перечисленных в алфавитном порядке, от буквы A
до буквы F
. Таким образом, индекс 0
содержит имена, начинающиеся с A
, индекс 1
содержит имена, начинающиеся с B
, до names_alphabet[5]
, который будет содержать массив имен, начинающихся с F
(F - 7-я буква алфавита, но поскольку индексы массива начинаются с нуля, индекс будет 7 — 1 = 6
).
Посмотрим на реализацию.
pragma solidity ^0.5.0; contract NamesAlphabet { string[][6] public names_A_to_F; constructor() public { names_A_to_F[0] = ["Alice"]; names_A_to_F[1] = ["Bob"]; names_A_to_F[2] = ["Carol", "Carlos", "Charlie", "Chuck", "Craig"]; names_A_to_F[3] = ["Dan", "Dave", "David"]; names_A_to_F[4] = ["Erin", "Eve"]; } }
Следует отметить два момента:
- Каждый индекс содержит различное количество значений.
- Индекс
0
и1
(буквыA
иB
) содержат только по одному имени каждый. - Индекс
2
(букваC
) содержит пять наименований. - Индекс
3
(букваD
) содержит три наименования. - Индекс
4
(букваE
) содержит по два имени.
2. Имена добавляются путем доступа к индексу (names_A_to_F[index] = ["value1", "value2", ...]
), а не с использованием .push(value1, value2, ...)
), использовавшегося ранее.
Вы не можете использовать метод .push
в массиве фиксированной длины. Вы должны указать индекс.
function addNamesF() public { names_A_to_F[5] = ["Faythe", "Frank"]; } function notWorking() public { names_A_to_F.push(["Faythe", "Frank"]); } // Solidity error: /* TypeError: Member "push" not found or not visible after argument-dependent lookup in string storage ref[] storage ref[26] storage ref. names_A_to_F.push(["Faythe", "Frank"]); ^---------------^ */
3. Наконец, вы не можете добавлять имена, начинающиеся сG
(то есть 7-й массив по индексу 6th
), поскольку ваша переменная определена для хранения не более 6 массивов (string[][6] names_A_to_F
). Скопируйте и вставьте приведенный ниже фрагмент кода в Remix, и вы увидите следующую ошибку:
function addNamesG() public { names_A_to_F[6] = ["Grace"]; } // Error in Remix: /* TypeError: Out of bounds array access. names_A_to_F[6] = ["Grace"]; ^-------------^ */
Чтобы получить абстрактный обзор, массив names_A_to_F
хотел бы в PHP:
// In PHP code $names_A_to_F = array( 0 => array( 0 => "Alice" // etc... (can add more values) ), 1 => array( 0 => "Bob" // etc... (can add more values) ), 2 => array( 0 => "Carol", 0 => "Carlos", 0 => "Charlie", 0 => "Chuck", 0 => "Craig", // etc... (can add more values) ), 3 => array( 0 => "Dan", 0 => "Dave", 0 => "David", // etc... (can add more values) ), 4 => array( 0 => "Erin", 0 => "Eve", // etc... (can add more values) ), 5 => array( 0 => "Faythe", 0 => "Frank", // etc... (can add more values) ), // No more values allowed ! );
Доступ к элементам в массивах фиксированного и динамического размера.
Как и во многих других языках программирования, вы должны указать индекс в квадратных скобках.
Доступ к элементам в многомерных массивах.
В отличие от определения массива с обратной записью, доступ к массиву осуществляется в порядке справа налево. Индексы всегда равны нулю.
Чтобы вернуться к нашему names_A_to_F
примеру:
pragma solidity ^0.5.0; contract NamesAlphabet { string[][6] public names_A_to_F; constructor() public { names_A_to_F[0] = ["Alice"]; names_A_to_F[1] = ["Bob"]; names_A_to_F[2] = ["Carol", "Carlos", "Charlie", "Chuck", "Craig"]; names_A_to_F[3] = ["Dan", "Dave", "David"]; names_A_to_F[4] = ["Erin", "Eve"]; } function getAliceName() public view returns (string memory) { return names_A_to_F[0][0]; } function getBobName() public view returns (string memory) { return names_A_to_F[1][0]; } function getEveName() public view returns (string memory) { return names_A_to_F[4][1]; } function getChuckName() public view returns(string memory) { return names_A_to_F[2][3]; } }
Литералы массивов (встроенные массивы)
Литералы массивов - это массивы, написанные как выражение. Они не сразу присваиваются переменной.
Литералы массива могут быть только фиксированного размера. Вы объявляете их с помощью синтаксиса ниже. Обратите внимание, что первое значение в объявлении массива должно иметь приведенный тип (type(value_1
).
// Here if k = 5, the array will contain 5 values. T[k] memory array_literal = [type(value_1), value_2, ... , value_k]; // example string[3] memory rgb_colours = [string("red"), "green", "blue"];
Вот еще один пример встроенных массивов.
// Base type (common types of the given elements) // Here the type would be uint8[3] function func1() { // Conversion of the 1st element to uint256 is necessary because // the type of every constant memory is uint8 by default. func2([uint(1), 2, 3]); } // memory type array that has a fixed size (uint3) function func2(uint[3] _data) { // Some code here }
Массивы памяти внутри функций
Массив памяти - это массив, созданный внутри функции. Поскольку он является частью памяти, он не является постоянным, поэтому массив стирается после выполнения функции. Чтобы создать массив памяти, используйте ключевое слово new
.
function memoryArray(uint len) { // memory array of length 7, containing uint // x.length == 7 uint[] memory x = new uint[](7); // memory array of length `len`, containing bytes // y.length == len bytes[] memory y = new bytes(len); }
Примером массива памяти может быть массив, содержащий x
простых чисел, которые вы хотите уменьшить (накопить).
function reduceArray(uint _length) public pure returns (uint) { uint[] memory _array = new uint[](_length); uint cumul; uint value = 2; for (uint i = 0; i < _array.length; i++ ) { _array[i] = value; cumul += _array[i]; value += 2; } return cumul; }
Обратите внимание, что нельзя назначить массив памяти фиксированного размера массиву памяти динамического размера. Для иллюстрации вот пример, который не компилируется.
function dontCompile() public { uint[] memory x = [uint(1), 3, 4]; } // Remix Error: /* TypeError: Type uint256[3] memory is not implicitly convertible to expected type uint256[] memory. uint[] memory x = [uint(1), 3, 4]; ^-------------------------------^ */
Обратите внимание, что вы не можете изменить размер массивов памяти, присвоив значения члену
.length
. Вы можете изменить.length
массива, только если он указан какstorage
.
Члены ссылочного типа массивов
Есть две встроенные функции, которые вы можете использовать с массивами в Solidity. Эти методы также являются общими для других языков программирования, использующих массивы.
.length
можно использовать для:
- вернуть количество элементов, содержащихся в массиве.
- изменять размер массива динамического размера (
T[]
) только вstorage
.
Как упоминалось ранее, можно изменять размер только массивов динамического размера, на которые есть ссылки в хранилище контракта.
Массивы памяти нельзя изменить, изменив их член длины, потому что их длина фиксируется после создания. В документации Solidity приводится следующее объяснение:
Это не произойдет автоматически при попытке доступа к элементам, внешним по отношению к текущей длине. Размер массива памяти фиксированный (но динамический, поскольку он может зависеть от параметра времени выполнения) после их создания.
.push(new_element)
, при выполнении:
- добавить новый элемент в конец массива
- возвращает новую длину массива.
Эта функция-член доступна только для массивов динамического размера и байтов.
Примечание: вы не можете использовать
push
для добавления нового элемента в строку
Массив как возвращаемые значения в функциях
Solidity позволяет создавать функции, возвращающие массивы (как фиксированного, так и динамического размера). Поскольку мы различаем три типа массивов (одномерные, двухмерные и многомерные), мы представим, как Solidity может возвращать эти три разных типа массивы через функции.
Если функция возвращает массив, указанное местоположение данных всегда должно быть memory
.
NB: вы не можете вернуть массив сопоставлений в Solidity.
Возвращать одномерные массивы
Вот простой пример, который возвращает массив фиксированного размера всех однозначных чисел (здесь массив представляет собой литерал массива, как описано ранее).
function returnOneDigitNumbers() public pure returns (uint[9] memory) { return [uint(1), 2, 3, 4, 5, 6, 7, 8, 9]; }
Чтобы продолжить наш пример crypto_names
, давайте представим массив динамического размера (расширяемый), который содержит адреса некоторых известных криптографов. Вы можете просто return
динамический массив, указав returns (type[] memory)
в определении функции. Картинка стоит тысячи слов.
address[] cryptographer_addresses; function addCryptographerAddress(address _address) public { cryptographer_addresses.push(_address); } function getAllCryptographersAddresses() public view returns (address[] memory) { return cryptographer_addresses; }
Чтобы увидеть это в действии, скопируйте / вставьте приведенный ниже код в Remix, разверните контракт, добавьте несколько адресов в список с помощью функции addCryptographerAddress()
, а затем нажмите getAllCryptographerAddresses (синим цветом на левой боковой панели). Вы должны увидеть следующее.
В таблице ниже перечислены различные типы значений для одномерных массивов, которые могут быть возвращены.
Чего нельзя делать в Arrays with Solidity?
- Невозможно использовать массивы массивов во внешних функциях.
- Https://ethereum.stackexchange.com/questions/11870/create-a-two-dimensional-array-in-solidity