欢迎光临
我们一直在努力

从零开始的Web3学习 14 | Solidity 构造函数(Constructor)

1. Constructor

在Solidity中,构造函数(Constructor)是一个特殊的函数,它在合约部署时自动执行一次,且仅执行一次。构造函数主要用于初始化合约的状态变量、设置管理员地址、定义代币总量等部署时需确定的参数。

1.1 构造函数定义

  • 构造函数是一种特殊的函数,只在合约部署时调用一次。
  • 主要用途是初始化状态变量。

从 Solidity 0.4.22 开始,使用 constructor 关键字定义构造函数,取代了早期版本中与合约同名的函数写法。

contract MyContract {
address public owner;
uint256 public value;

// 构造函数
constructor(uint256 _value) {
owner = msg.sender; // 部署者设为所有者
value = _value; // 初始化状态变量
}
}

特点:

  • 构造函数不能有名称,只能使用 constructor 关键字。
  • 构造函数不能有可见性修饰符(如 public、private),因为它只在部署时被调用。
  • 构造函数可以是 payable 的,允许在部署时接收以太币。
  • 构造函数可以接收参数,这些参数在部署合约时提供。

1.2 构造函数的作用

  • 初始化状态变量:为状态变量赋予初始值,尤其是那些在部署后才能确定的值(如代币名称、符号、总量等)。
  • 设置权限:将部署者设为 owner,或初始化多重签名地址。
  • 执行一次性设置:例如注册到某个注册表、创建其他合约实例等。
  • 接收以太币:若构造函数标记为 payable,则部署时可向合约转入以太币。

1.3 继承中的构造函数

当合约继承自其他合约时,构造函数的调用顺序很重要。

1.3.1 基础规则
  • 如果基类合约定义了构造函数,派生合约必须提供基类构造函数所需的参数(如果基类构造函数有参数)。
  • 基类构造函数的初始化按照继承列表的顺序进行(线性化)。
1.3.2 传递参数的方式

方式一:在继承列表中直接传递

contract Base {
uint256 public x;
constructor(uint256 _x) {
x = _x;
}
}

contract Derived is Base(123) {
// 在继承时直接传递参数 123
}

方式二:通过派生合约的构造函数传递

contract Base {
uint256 public x;
constructor(uint256 _x) {
x = _x;
}
}

contract Derived is Base {
constructor(uint256 _x) Base(_x) {
// 通过 Base(_x) 调用基类构造函数
}
}

如果有多级继承,构造函数的执行顺序是:

  • 按照继承列表从最基类到最派生类依次执行构造函数。
  • 如果基类构造函数有参数,必须在派生合约的构造函数中显式提供。
  • 1.3.3 示例多重继承

    contract A {
    constructor() {
    // 最先执行
    }
    }

    contract B {
    constructor() {
    // 第二个执行
    }
    }

    contract C is A, B {
    constructor() {
    // 最后执行
    }
    }

    1.3.4 构造函数与immutable 变量

    immutable 变量可以在构造函数中赋值,且之后不能再修改。这是初始化不可变变量的标准方式。

    contract MyContract {
    uint256 public immutable CREATED_AT;
    address public immutable OWNER;

    constructor() {
    CREATED_AT = block.timestamp;
    OWNER = msg.sender;
    }
    }

    注意:immutable 变量在构造函数中赋值后,在运行时读取它们比读取普通状态变量更省 Gas(直接嵌入字节码)。

    1.4 注意事项与常见陷阱

    • 构造函数不能重入:由于构造函数只在部署时执行一次,无需担心重入攻击。
    • 构造函数不能返回值:它没有返回值,因为部署后返回的是合约地址。
    • 如果构造函数失败,合约部署将回滚,不会创建合约。
    • 构造函数中调用外部合约需谨慎:如果调用的外部合约回滚,整个部署也会失败。
    • 在继承中,如果基类构造函数需要参数而派生合约未提供,编译会报错。
    • 构造函数中不能使用 this 来引用自身,因为此时合约地址尚未确定(实际上地址已确定,但代码尚未部署完毕,某些操作可能受限)。

    1.5 示例:完整的带构造函数的 ERC20 合约

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;

    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";

    contract MyToken is ERC20, Ownable {
    uint256 public immutable CAP;

    constructor(string memory name, string memory symbol, uint256 cap)
    ERC20(name, symbol)
    {
    CAP = cap;
    _mint(msg.sender, cap); // 将全部代币铸造给部署者
    }

    // 可选:禁止额外铸币
    function mint(address to, uint256 amount) public onlyOwner {
    require(totalSupply() + amount <= CAP, "Cap exceeded");
    _mint(to, amount);
    }
    }

    在这个例子中,构造函数:

    • 接收代币名称、符号和总量上限。
    • 调用基类 ERC20 的构造函数。
    • 设置 CAP 为不可变变量。
    • 将全部上限数量的代币铸造给部署者。

    总结

    构造函数是 Solidity 合约部署时的初始化入口,负责设置初始状态。理解构造函数的定义、继承规则以及与 immutable 的配合,是编写安全、高效合约的基础。在使用 OpenZeppelin 等库时,通常需要正确调用父合约的构造函数,并根据业务需求设计合理的初始化逻辑。

    编程作业

    任务: 创建一个Solidity合约,其中包含一个构造函数,该构造函数接收一个整数作为参数并将其赋值 给一个公共状态变量。

    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.7;

    contract SqimpleStorage{
    uint public sotredData;

    constructor(uint initVal){
    sotredData= initVal;
    }
    }
    //作业说明:
    //1.使用RemixIDE创建和部署上述合约。
    //2.尝试传递不同的参数值进行部署,观察‘storedData‘的变化。
    //3.分析构造函数的调用时机和作用。

    赞(0)
    未经允许不得转载:171主机测评 » 从零开始的Web3学习 14 | Solidity 构造函数(Constructor)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址