区块链学习第八期
solidity小细节【上篇】
[toc] ---1:internal、external、public、private 可见性?
没标明函数类型的,默认 internal。
- public: 内部外部均可见。(也可用于修饰状态变量,public 变量会自动生成 getter 函数,用于查询数值).
- private: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。
- external: 只能从合约外部访问(但是可以用 this.f()来调用,f 是函数名)
- internal: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)
2:解构式赋值
solidity 使用解构式赋值的规则,支持读取函数的全部或部分返回值。
- 读取所有返回值:声明变量,并且将要赋值的变量用,隔开,按顺序排列。
1 | uint256 _number; |
- 读取部分返回值:声明要读取的返回值对应的变量,不读取的留空。下面这段代码中,我们只读取_bool,而不读取返回的_number 和_array:
1 | (, _bool2, ) = returnNamed(); |
3:数据位置 data
solidity 数据存储位置有三类:storage,memory 和 calldata。不同存储位置的 gas 成本不同。storage 类型的数据存在链上,类似计算机的硬盘,消耗 gas 多;memory 和 calldata 类型的临时存在内存里,消耗 gas 少。大致用法:
storage:合约里的状态变量默认都是 storage,存储在链上。
memory:函数里的参数和临时变量一般用 memory,存储在内存中,不上链。
calldata:和 memory 类似,存储在内存中,不上链。与 memory 的不同点在于 calldata 变量不能修改(immutable),一般用于函数的参数。例子:
1 | function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){ |
赋值规则(这个一定要注意)
在不同存储类型相互赋值时候,有时会产生独立的副本(修改新变量不会影响原变量),有时会产生引用(**修改新变量会影响原变量**)
- storage(合约的状态变量)赋值给本地 storage(函数里的)时候,会创建引用,改变新变量会影响原变量。
1 | uint[] x = [1,2,3]; // 状态变量:数组 x |
- storage 赋值给 memory,会创建独立的复本,修改其中一个不会影响另一个;反之亦然
1 | uint[] x = [1,2,3]; // 状态变量:数组 x |
4:变量的作用域
Solidity 中变量按作用域划分有三种,分别是状态变量(state variable),局部变量(local variable)和全局变量(global variable
状态变量
状态变量是数据存储在链上的变量,所有合约内函数都可以访问 ,gas 消耗高。状态变量在合约内、函数外声明
1
2
3
4
5contract Variables {
uint public x = 1;
uint public y;
string public z;
}局部变量
局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,gas 低。局部变量在函数内声明
1
2
3
4
5
6function bar() external pure returns(uint){
uint xx = 1;
uint yy = 3;
uint zz = xx + yy;
return(zz);
}全局变量
全局变量是全局范围工作的变量,都是 solidity 预留关键字。他们可以在函数内不声明直接使用
1
2
3
4
5
6
7function global() external view returns(address, uint, bytes memory){
address sender = msg.sender;
uint blockNum = block.number;
bytes memory data = msg.data;
return(sender, blockNum, data);
}
5:delete 操作符
delete a 会让变量 a 的值变为初始值。
1 | // delete操作符 |
6:常数 constant、immutable
只有数值变量可以声明 constant 和 immutable;string 和 bytes 可以声明为 constant,但不能为 immutable
- constant 变量必须在声明的时候初始化,之后再也不能改变
- immutable 变量可以在声明时或构造函数中初始化
7:重写、多重继承
重写
virtual: 父合约中的函数,如果希望子合约重写,需要加上 virtual 关键字。
override:子合约重写了父合约中的函数,需要加上 override 关键字。
多重继承
继承时要按辈分最高到最低的顺序排。比如我们写一个 Erzi 合约,继承 Yeye 合约和 Baba 合约,那么就要写成contract Erzi is Yeye, Baba,而不能写成 contract Erzi is Baba, Yeye,不然就会报错。 如果某一个函数在多个继承的合约里都存在,比如例子中的 hip()和 pop(),在子合约里必须重写,不然会报错。 重写在多个父合约中都重名的函数时,override 关键字后面要加上所有父合约名字,例如 override(Yeye, Baba)。
1 | contract Erzi is Yeye, Baba{ |
修饰器的重写
Solidity 中的修饰器(Modifier)同样可以继承,用法与函数继承类似,在相应的地方加 virtual 和 override 关键字即可
1 | contract Base1 { |
1 | modifier exactDividedBy2And3(uint _a) override { |
构造函数的继承
1 | contract C is A { |
8:抽象合约、接口区别
抽象合约
如果一个智能合约里至少有一个未实现的函数,即某个函数缺少主体{}中的内容,则必须将该合约标为 abstract,不然编译会报错;另外,未实现的函数需要加 virtual,以便子合约重写
接口
接口类似于抽象合约,但它不实现任何功能。接口的规则:不能包含状态变量
- 不能包含构造函数
- 不能继承除接口外的其他合约
- 所有函数都必须是 external 且不能有函数体
- 继承接口的合约必须实现接口定义的所有功能