Solidity 继承

文章详细介绍了 Solidity 中的继承机制,包括如何使用 virtualoverride 关键字实现函数重写,如何使用 super 关键字调用父合约的函数,以及如何处理多重继承和构造函数初始化。

从头实现一个 ERC20 合约无疑会让人感到疲惫。Solidity 的行为类似于面向对象的语言,并且允许继承。这里是一个最小的示例。


contract Parent {
    function theMeaningOfLife()
        public
        pure
        returns (uint256) {
            return 42;
    }
}

contract Child is Parent {

}

部署到 Remix 时,从下拉菜单中选择 Child 而不是 Parent 进行部署。

https://static.wixstatic.com/media/61a666_843d766cb40445de9d21657f94bfa237~mv2.png/v1/fill/w_939,h_610,al_c,q_95,enc_auto/Inheritance1.png

即使 Child 是空的,我们也能在 Child 中看到该函数。

https://static.wixstatic.com/media/61a666_fb428cb0a1aa4236950028fcf7a1db70~mv2.png/v1/fill/w_939,h_595,al_c,q_95,enc_auto/Inheritance2.png

当一个“合约” “另一个合约”时,它继承了所有的功能。

与其他面向对象编程语言一样,函数可以被重写。以下是更改值的构造。


contract Parent {
    function theMeaningOfLife()
        public
        pure
        virtual
        returns (uint256) {
            return 42;
    }
}

contract Child is Parent {
    function theMeaningOfLife()
        public
        pure
        override
        returns (uint256) {
            return 43;
    }
}

https://static.wixstatic.com/media/61a666_31b8d5b723244b1d91878f40900d7a97~mv2.png/v1/fill/w_939,h_434,al_c,q_95,enc_auto/Inheritance3.png

请注意,只有 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();
    }
}

让我们总结一下我们所学到的内容

  • 只有 virtual 函数可以被重写
  • 重写父类函数的函数必须具有 override 修饰符
  • 重写函数的名称、参数和返回类型必须完全匹配
  • 可以使用 super 关键字,而不是复制和粘贴父函数的代码
  • 你可以从多个合约中继承
  • 继承时必须显式调用父构造函数。

创造一个轻松的 ERC20 代币

继承结合 import 语句,使我们能够轻松利用其他人创建的库。将此合约部署到 Remix 中,你将看到所有 ERC20 函数都已为你实现。


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

contract Token is ERC20("SomeToken", "symbol") {

}

澄清说明

智能合约作为 Solidity 对象与部署在区块链上的智能合约之间有明显的区别。

你无法继承部署在区块链上的合约。

它们是生存于你之外的二进制块。由于模糊的术语,一些 Solidity 开发人员担心函数和变量可以被恶意合约继承和重写。这不可能发生。 尽管我们将部署代码称为“合约”,而将 Solidity 代码称为“合约”,但它们并不是相同的东西。

练习问题

InheritanceOverride

MultiInheritance

Super

AccessModifiers

学习更多

查看 Solidity 训练营 ,以了解更多关于智能合约开发和代币标准的内容。

  • 原文链接: rareskills.io/learn-soli...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/