В 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"];
    }
}

Следует отметить два момента:

  1. Каждый индекс содержит различное количество значений.
  • Индекс 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?