数组可以具有编译时固定大小或动态大小。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Array {
// 初始化数组的几种方式
uint[] public arr;
uint[] public arr2 = [1, 2, 3];
// 固定大小的数组,所有元素都初始化为 0
uint[10] public myFixedSizeArr;
function get(uint i) public view returns (uint) {
return arr[i];
}
// Solidity 可以返回整个数组。
// 但是,对于可以无限增长长度的数组,应该避免使用此函数。
function getArr() public view returns (uint[] memory) {
return arr;
}
function push(uint i) public {
// 追加元素到数组
// 这将使数组长度增加 1。
arr.push(i);
}
function pop() public {
// 从数组中删除最后一个元素
// 这将使数组长度减少 1。
arr.pop();
}
function getLength() public view returns (uint) {
return arr.length;
}
function remove(uint index) public {
// delete 不会改变数组长度。
// 它会将索引处的值重置为其默认值,
// 在本例中为 0。
delete arr[index];
}
function examples() external {
// 在内存中创建数组,只能创建固定大小的数组
uint[] memory a = new uint[](5);
}
}
arr 和 arr2 分别演示了两种初始化数组的方式。arr 是一个动态数组,因为它没有指定固定的大小。arr2 是一个静态数组,因为它的大小在声明时已经确定了。
myFixedSizeArr 是一个具有固定大小的数组,因为它的大小在声明时已经确定了。
get 函数演示了如何访问数组中的元素。在 Solidity 中,数组的索引从 0 开始。
getArr 函数演示了如何返回整个数组。但是,如果数组可能无限增长,则应避免使用此函数。
push 函数演示了如何向数组添加元素。这将使数组长度增加 1。
pop 函数演示了如何从数组中删除元素。这将使数组长度减少 1。
getLength 函数演示了如何获取数组的长度。
remove 函数演示了如何从数组中删除元素。使用 delete 关键字,将元素值重置为其默认值(在本例中为 0)。
examples 函数演示了如何在内存中创建数组,只能创建具有固定大小的数组。
通过从右向左移动元素来删除数组元素
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract ArrayRemoveByShifting {
// [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
// [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
// [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
// [1] -- remove(0) --> [1] --> []
uint[] public arr;
function remove(uint _index) public {
require(_index < arr.length, "index out of bound");
for (uint i = _index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
arr.pop();
}
function test() external {
arr = [1, 2, 3, 4, 5];
remove(2);
// [1, 2, 4, 5]
assert(arr[0] == 1);
assert(arr[1] == 2);
assert(arr[2] == 4);
assert(arr[3] == 5);
assert(arr.length == 4);
arr = [1];
remove(0);
// []
assert(arr.length == 0);
}
}
remove 函数删除数组中给定索引的元素,并将剩余元素从右向左移动。函数首先使用 require 语句检查索引是否在数组范围内,然后循环遍历从给定索引开始的数组,将每个元素向左移动一个位置。最后,使用 pop 函数从数组末尾删除一个元素。 test 函数为 remove 函数提供了几个用例,以检查函数是否正确删除数组中的元素,并更新数组长度。
代码中用到的词汇:
数组大小在运行时可以更改的数组。
一个指向数组元素的数字,从零开始计数。
Solidity 中的一个内置函数,用于在函数中检查条件,如果条件不满足,则抛出异常。
从数组末尾删除一个元素,并返回删除的元素。
pop()通常和push()一起使用,实现类似栈的后进先出结构。
uint[] public stack = [1, 2, 3];
// 移除并返回最后一个元素
uint removed = stack.pop();
// 添加一个新元素
stack.push(5);
//需要注意的是:pop()会消耗gas,因为它写入了状态变化。如果数组为空,调用pop()会触发错误。返回的值popped是被移除元素的一个副本,对它的修改不会影响原数组。
当然,我们也可以在内存中创建数组
function examples() external {
// 在内存中创建数组,只能创建固定大小,避免gas费,提高效率
uint[] memory a = new uint[](5);
//memory表示仅在内存中创建这个数组,而不是存储在区块链上。内存数组的生命周期仅限于一个函数的执行。
}
当然如果我们想要删除一个数组,在solidity中是没有删除数组的方法的,所以需要一段逻辑
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract ArrayRemoveByShifting {
// [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
// [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
// [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
// [1] -- remove(0) --> [1] --> []
uint[] public arr = [1,2,3,4,5,6,7,8,9];
function remove(uint _index) public {
require(_index < arr.length, "index out of bound");
for (uint i = _index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
arr.pop();
}
function get() public view returns(uint[] memory ) {
return arr;
}
}
对于返回值声明为 uint[] memory 的原因是:
Solidity 中,存储在状态变量中的数组是引用类型,直接返回这个状态变量arr会返回一个引用,external函数调用者可以修改状态。
为了避免这种修改状态的情况,我们通过返回一个内存数组副本来断开引用关系。
memory关键字表示这个返回的数组仅存在于内存中,外部调用不会影响到存储的原数组。
一些同学会将代码这样去写
function get() public view returns(uint[] ) {
return arr;
}
这样报错
Solidity不允许直接将存储引用转换为内存数组,因为会带来安全风险。所以我们需要显式把存储数组 copy 一份到内存中返回 !直接返回存储引用类型是不被允许的,我们需要通过内存返回副本。