函数 Returns 和 Return
函数 Returns 和 Return
Solidity 中与函数输出相关的有两个关键字:return
和returns
。它们的区别在于:
returns
:跟在函数名后面,用于声明返回的变量类型及变量名。return
:用于函数主体中,返回指定的变量。
// 返回多个变量
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){
return(1, true, [uint256(1),2,5]);
}
在上述代码中,我们利用 returns
关键字声明了有多个返回值的 returnMultiple()
函数,然后我们在函数主体中使用 return(1, true, [uint256(1),2,5])
确定了返回值。
这里uint256[3]
声明了一个长度为3
且类型为uint256
的数组作为返回值。因为[1,2,3]
会默认为uint8(3)
,因此[uint256(1),2,5]
中首个元素必须强转uint256
来声明该数组内的元素皆为此类型。数组类型返回值默认必须用 memory 修饰,在下一个章节会细说变量的存储和作用域。
数据位置
Solidity 数据存储位置有三类:storage
,memory
和calldata
。不同存储位置的gas
成本不同。storage
类型的数据存在链上,类似计算机的硬盘,消耗gas
多;memory
和calldata
类型的临时存在内存里,消耗gas
少。整体消耗gas
从多到少依次为:storage
> memory
> calldata
。大致用法:
storage
:合约里的状态变量默认都是storage
,存储在链上。
memory
:函数里的参数和临时变量一般用memory
,存储在内存中,不上链。尤其是如果返回数据类型是变长的情况下,必须加 memory 修饰,例如:string, bytes, array 和自定义结构。
calldata
:和memory
类似,存储在内存中,不上链。与memory
的不同点在于calldata
变量不能修改(immutable
),一般用于函数的参数。例子:
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
//参数为calldata数组,不能被修改
// _x[0] = 0 //这样修改会报错
return(_x);
}
Example:
数据位置和赋值规则
在不同存储类型相互赋值时候,有时会产生独立的副本(修改新变量不会影响原变量),有时会产生引用(修改新变量会影响原变量)。规则如下:
赋值本质上是创建引用指向本体,因此修改本体或者是引用,变化可以被同步:
storage
(合约的状态变量)赋值给本地storage
(函数里的)时候,会创建引用,改变新变量会影响原变量。例子:uint[] x = [1,2,3]; // 状态变量:数组 x function fStorage() public{ //声明一个storage的变量 xStorage,指向x。修改xStorage也会影响x uint[] storage xStorage = x; xStorage[0] = 100; }
Example:
memory
赋值给memory
,会创建引用,改变新变量会影响原变量。
其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方。这有时会涉及到开发中的问题,比如从storage
中读取数据,赋值给memory
,然后修改memory
的数据,但如果没有将memory
的数据赋值回storage
,那么storage
的数据是不会改变的。
事件
event
是智能合约告诉外部世界“刚才我干了什么”的方式,用来打日志、提供链上行为的追踪线索。
Solidity
中的事件(event
)是EVM
上日志的抽象,它具有两个特点:
- 响应:应用程序(
ethers.js
)可以通过RPC
接口订阅和监听这些事件,并在前端做响应。 - 经济:事件是
EVM
上比较经济的存储数据的方式,每个大概消耗 2,000gas
;相比之下,链上存储一个新变量至少需要 20,000gas
。