文章目录
表达式与控制结构
赋值
结构化赋值与返回多个值
Solidity 内部支持元组类型(tuple types),即由多个可能类型不同、数量在编译时为常数的对象组成的列表。元组可用于一次性返回多个值。这些返回值可以赋值给新声明的变量,也可以赋值给已有变量(或广义上的左值 LValues)。
元组在 Solidity 中并不是一种正式的类型,它们只能用于表达式的语法组合。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
uint index;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
}
function g() public {
// 用类型声明变量并从返回的元组中赋值,
// 不需要指定所有元素(但数量必须一致)。
(uint x, , uint y) = f();
// 交换变量值的常见技巧 —— 不适用于非值类型的存储变量。
(x, y) = (y, x);
// 可以省略部分组件(也适用于变量声明)。
(index, , ) = f(); // 设置 index 为 7
}
}
不允许混合变量声明与非声明赋值,例如以下写法是无效的:(x, uint y) = (1, 2);
注意:当涉及引用类型时(如数组、结构体等),同时对多个变量赋值可能会出现意外的复制行为,需要小心使用。
数组和结构体的赋值复杂性
对于非值类型(如数组和结构体,包括 bytes
和 string
),赋值语义更为复杂。
在下面这个例子中,对 g(x)
的调用对 x
无效,因为它在内存中创建了一个存储值的独立副本。然而,h(x)
可以成功修改 x
,因为它传递的是引用而非副本。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract C {
uint[20] x;
function f() public {
g(x);
h(x);
}
function g(uint[20] memory y) internal pure {
y[2] = 3;
}
function h(uint[20] storage y) internal {
y[3] = 4;
}
}
g(x)
:传入的是memory
类型的副本,对y[2]
的修改不会影响到原始的x
。h(x)
:传入的是storage
引用,对y[3]
的修改将会直接改变合约状态变量x[3]
。
这是 Solidity 中引用类型(如数组、结构体)赋值和传参行为的重要概念,memory 会复制,storage 会引用原始数据。
作用域和声明
声明的变量将具有初始默认值,其字节表示为全零。变量的“默认值”是该类型的典型“零状态”。例如,bool
的默认值是 false
,uint
或 int
类型的默认值是 0
。对于静态大小的数组和 bytes1
到 bytes32
,每个单独的元素将初始化为对应类型的默认值。对于动态大小的数组、bytes
和 string
,默认值是空数组或空字符串。对于 enum
类型,默认值是它的第一个成员。
Solidity 中的作用域遵循 C99(以及许多其他语言)广泛使用的作用域规则:变量从其声明后的立即可见点开始,直到包含声明的最小 { }
块的结束。作为这一规则的例外,for
循环初始化部分声明的变量仅在 for
循环的结束之前可见。
类似参数的变量(函数参数、修饰符参数、catch
参数等)在后续的代码块中可见 —— 对于函数和修饰符参数,它们在函数体和修饰符体内可见;对于 catch
参数,它们在 catch
块内可见。
在代码块外声明的变量和其他项目,例如函数、合约、用户定义的类型等,即使在声明之前,也可以可见。这意味着我们可以在声明之前使用状态变量,并递归调用函数。
因此,以下示例将编译而不会产生警告,因为这两个变量具有相同的名称,但作用域是互不重叠的。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
function minimalScoping() pure public {
{
uint same;
same = 1;
}
{
uint same;
same = 3;