文章目录
合约
继承
Solidity 支持多重继承,包括多态性。
多态性意味着函数调用(无论是内部的还是外部的)总是会执行继承体系中最底层合约中具有相同名称(和参数类型)的函数。这必须在继承体系中的每一个函数上显式启用,使用 virtual
和 override
关键字。
可以通过显式指定合约名调用继承体系中更高层的函数,例如:ContractName.functionName()
,或者使用 super.functionName()
调用继承结构中上一级的函数(参考下文中“扁平化继承结构”)。
当一个合约继承其他合约时,只会在区块链上创建一个合约实例,所有父合约的代码会被编译进这个创建的合约中。这意味着对父合约函数的所有内部调用只是普通的内部函数调用(例如 super.f(...)
会使用 JUMP
而不是消息调用)。
状态变量遮蔽(shadowing)会被视为错误:如果任何一个父合约中已经声明了变量 x
,子合约中就不能再声明同名变量。
Solidity 的继承系统整体上类似 Python 的继承,尤其是对多重继承的处理,但也存在一些差异。
具体示例如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Owned {
address payable owner;
constructor() { owner = payable(msg.sender); }
}
// 使用 `is` 关键字派生自另一个合约。
// 派生合约可以访问所有非 private 的成员,包括 internal 函数和状态变量。
// 这些成员无法通过 `this` 关键字从外部访问。
contract Emittable is Owned {
event Emitted();
// 使用 `virtual` 关键字表示该函数可以被子类重写。
function emitEvent() virtual public {
if (msg.sender == owner)
emit Emitted();
}
}
// 这些抽象合约只是为了让编译器知道接口。注意没有函数体的声明。
// 如果一个合约未实现所有函数,就只能作为接口使用。
abstract contract Config {
function lookup(uint id) public virtual returns (address adr);
}
abstract contract NameReg {
function register(bytes32 name) public virtual;
function unregister() public virtual;
}
// 支持多重继承。注意:`Owned` 同时是 `Emittable` 的父类,
// 但在最终合约中只会有一个 `Owned` 实例(类似 C++ 的虚继承)。
contract Named is Owned, Emittable {
constructor(bytes32 name) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
// 函数可以通过相同的名称和参数类型进行重写。
// 如果返回值类型不一样,将导致编译错误。
// 无论是内部调用还是消息调用,都会考虑重写关系。
// 如果你要重写函数,必须使用 `override` 关键字。
// 如果这个函数还需要被其他合约重写,则还需要再次声明 `virtual`。
function emitEvent() public virtual override {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
// 仍然可以调用被重写的父类函数。
Emittable.emitEvent();
}
}
}
// 如果构造函数需要参数,则必须在派生合约的构造器中提供,
// 可以在构造函数声明中传参,也可以通过修饰器风格传参。
contract PriceFeed is Owned, Emittable, Named("GoldFeed") {
uint info;
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
// 这里只声明 `override` 而不是 `virtual`,
// 意味着继承自 PriceFeed 的