文章详细介绍了 Solidity 中的继承机制,包括如何使用 virtual
和 override
关键字实现函数重写,如何使用 super
关键字调用父合约的函数,以及如何处理多重继承和构造函数初始化。
从头实现一个 ERC20 合约无疑会让人感到疲惫。Solidity 的行为类似于面向对象的语言,并且允许继承。这里是一个最小的示例。
contract Parent {
function theMeaningOfLife()
public
pure
returns (uint256) {
return 42;
}
}
contract Child is Parent {
}
部署到 Remix 时,从下拉菜单中选择 Child 而不是 Parent 进行部署。
即使 Child 是空的,我们也能在 Child 中看到该函数。
当一个“合约” 是 “另一个合约”时,它继承了所有的功能。
与其他面向对象编程语言一样,函数可以被重写。以下是更改值的构造。
contract Parent {
function theMeaningOfLife()
public
pure
virtual
returns (uint256) {
return 42;
}
}
contract Child is Parent {
function theMeaningOfLife()
public
pure
override
returns (uint256) {
return 43;
}
}
请注意,只有 virtual 函数才能被重写。如果你尝试重写一个不是 virtual 的函数,代码将无法编译。
此外,当一个函数进行重写时,它必须完全匹配,包括名称、参数和返回类型。
contract Parent {
function theMeaningOfLife()
public
pure
virtual
returns (uint256) {
return 42;
}
}
contract Child is Parent {
// 无效:有不同的参数
function theMeaningOfLife(uint256 x)
public
pure
override
returns (uint256) {
return 42 + x;
}
// 无效:有不同的返回类型
function theMeaningOfLife(uint256 x)
public
pure
override
returns (bool) {
return true;
}
// 无效:有不同的名称
function theMeaningOfLif3(uint256 x)
public
pure
override
returns (uint256) {
return 42;
}
}
Solidity 支持多重继承。
contract Parent1 {
function theMeaningOfLife()
public
pure
virtual
returns (uint256) {
return 42;
}
}
contract Parent2 {
function hackerFavoriteNumber()
public
pure
virtual
returns (uint256) {
return 1337;
}
}
contract Child is Parent1, Parent2 {
}
如果你想知道,如果两个父类有一个同名的函数,子类必须重写它,否则其行为将会产生歧义。如果你陷入这种情况,你可能在软件设计中做错了事情。 所以让我们不要走这条路。
私有 vs 内部
有两种方法可以使一个函数不从外部世界可访问:给予它们一个 private 或 internal 修饰符。区分非常简单。
私有函数(和变量)不能被子合约“看到”。
内部函数和变量可以。
contract Parent {
function foo()
internal
pure
virtual
returns (string memory) {
return "foo";
}
// 错误!私有函数不能被重写,
// 所以将其设置为 virtual 没有意义!
function bar()
private
pure
virtual
returns (string memory) {
return "bar";
}
}
super 关键字
super 关键字意味着“调用父类的函数。”以下是它如何使用:
contract Parent {
function foo()
internal
pure
virtual
returns (string memory) {
return "foo";
}
}
contract Child is Parent {
// 我们重写了 foo 使其为 public
function foo()
public
pure
override
returns (string memory) {
return super.foo();
}
}
如果我们在这里不包含 super 关键字,foo() 将调用自身并进入无限递归。尝试去掉 super 并在 Remix 中运行代码。交易将因无限递归而回退(以太坊不允许代码永远运行,它会强制终止它们。确切机制是一个后续讨论的中间主题)。
Super 意味着“调用父类的 foo,而不是我的。”这让我们可以获得 foo 的所有功能,而不必复制和粘贴代码。
调用父类的构造函数
Solidity 不允许你从父合约继承而不初始化其构造函数。考虑这种情况。
contract Parent {
string private name;
constructor(string memory _name) {
name = _name;
}
function getName() public view virtual returns (string memory) {
return name;
}
}
contract Child is Parent {
// 错误,name 尚未设置!
function getName() public view override returns (string memory) {
return super.getName();
}
}
解决方案是在继承时调用父类构造函数。
contract Parent {
string private name;
constructor(string memory _name) {
name = _name;
}
function getName()
public
view
virtual
returns (string memory) {
return name;
}
}
//修复
contract Child is Parent("The Beatles") {
function getName()
public
view
override
returns (string memory) {
return super.getName();
}
}
让我们总结一下我们所学到的内容
创造一个轻松的 ERC20 代币
继承结合 import 语句,使我们能够轻松利用其他人创建的库。将此合约部署到 Remix 中,你将看到所有 ERC20 函数都已为你实现。
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Token is ERC20("SomeToken", "symbol") {
}
澄清说明
智能合约作为 Solidity 对象与部署在区块链上的智能合约之间有明显的区别。
你无法继承部署在区块链上的合约。
它们是生存于你之外的二进制块。由于模糊的术语,一些 Solidity 开发人员担心函数和变量可以被恶意合约继承和重写。这不可能发生。 尽管我们将部署代码称为“合约”,而将 Solidity 代码称为“合约”,但它们并不是相同的东西。
练习问题
查看 Solidity 训练营 ,以了解更多关于智能合约开发和代币标准的内容。
- 原文链接: rareskills.io/learn-soli...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!