8.1 构造函数
构造函数(constructor)是一种比较特殊的函数,它在合约部署的时候被调用一次,之后不会再被调用。构造函数一般用于初始化一些变量。在Solidity 0.4.22之前,构造函数为合约名同名函数;Solidity 0.4.22之后,构造函数统一使用constructor
作为函数名,例如,在构造函数中初始化合约owner:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract ConstructorDemo {
// 定义合约拥有者变量
address public owner;
// 构造函数,只在合约部署时调用一次
constructor() {
// 在构造函数中初始化合约拥有者
owner = msg.sender;
}
}
8.2 函数修改器
函数修改器是一种实现重复代码简化的语法。形式上分为基本类型、带参数的类型、三明治类型。
8.2.1 基本函数修改器
如果我们要实现可暂停合约,那么需要有一个合约是否暂停的状态变量pauesd:
// 合约暂停变量
bool public paused;
同时,需要有一个设置合约暂停的函数:
// 设置合约暂停、开启函数
function setPaused(bool _paused) external {
paused = _paused;
}
在所有业务函数中,都需要对paused变量进行判断,以确定合约是否暂停。此时,可以使用函数修改器进行代码简化,并提高代码可读性。
函数修改器定义代码:
// 函数修改器的定义
modifier notPaused() {
require(!paused, "Contract is Paused!");
_; // 代表使用该函数修改器的函数的其他代码在此处运行
}
使用该函数修改器的函数,只能在函数未暂停时可用,否则会抛出异常。函数修改器中的_;
代表使用该函数修改器的函数的其他代码在此处运行。
完整的实现代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract ModifierDemo {
// 合约暂停变量
bool public paused;
// 计数变量
uint256 public count;
// 函数修改器的定义
modifier notPaused() {
require(!paused, "Contract is Paused!");
_; // 代表使用该函数修改器的其他代码在此处运行
}
// 设置合约暂停、开启函数
function setPaused(bool _paused) external {
paused = _paused;
}
// 计数器加一,在合约非暂停状态可用。使用函数修改器notPaused
function inc() external notPaused {
count += 1;
}
// 计数器减一,在合约非暂停状态可用。使用函数修改器notPaused
function dec() external notPaused {
count -= 1;
}
}
只需要在需要使用函数修改器的函数定义中添加函数修改器的名称,就可以实现函数修改器的功能。
8.2.2 带参数的函数修改器
函数修改器可以带参数,使用方法和基本类型的函数修改器类似:
modifier cap(uint _x) {
require(_x<100, "x can not >=100");
_;
}
function incBy(uint _x) external notPaused cap(_x) {
count += _x;
}
8.2.3 三明治函数修改器
通过改变_;
的位置,可以实现三明治类型的函数修改器,使用三明治类型的函数修改器的函数代码运行顺序为:
- 运行函数修改器;
- 运行函数代码;
- 运行函数修改器;
示例:
modifier sandwich() {
count += 2;
_;
count *= 2;
}
function foo() external notPaused sandwich {
count *= 3;
}
上述示例中,如果count为初始值0,则count变化顺序为:
- count + 2 = 2;
- count * 3 = 6;
- count * 2 =12;
即count最终值为12。
8.3 常量
当合约中的变量有固定值,这些值不允许在合约部署之后被任何的函数修改,就可以将其定义为常量,比如:合约拥有者地址。定义为常量的变量可以节省gas fee。
8.3.1 constant
constant变量必须在声明的时候初始化,之后再也不能改变。尝试改变的话,编译不通过。constant声明和初始化方式如下:
contract ConstantDemo {
address public constant MY_ADDRESS = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
}
contract VarDemo {
address public MY_ADDRESS = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
}
在上述示例中,MY_ADDRESS在ConstantDemo合约中声明为constant变量,在VarDemo合约中声明为变量,通过对两个值进行读取,发现消耗的gas fee(Cost only applies when called by a contract)相差较大:
- constant变量读取消耗gas fee:356 gas
- 变量读取消耗gas fee:2489 gas
注意:读取gas fee只有在读取者为合约时才会收取。
8.3.1 immutable
immutable变量可以在声明时、构造函数中进行初始化,因此更加灵活。immutable变量只能被赋值(初始化)一次。例如:
contract ImmutableDemo {
address public immutable MY_ADDRESS;
constructor() {
// 在构造函数中初始化immutable变量
MY_ADDRESS = _getMyAddress();
}
function _getMyAddress() view internal returns(address) {
return msg.sender;
}
}
注意:经过测试,读取immutable变量消耗的gas fee和constant变量相同,均为356 gas。读取gas fee只有在读取者为合约时才会收取。
网友评论