企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 继承(Inheritance) Solidity通过复制包括多态的代码来支持多重继承。 所有函数调用是`虚拟(virtual)`的,这意味着最远的派生方式会被调用,除非明确指定了合约。 当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。 基本的继承体系与`python`有些类似,特别是在处理多继承上面。 下面用一个例子来详细说明: ``` pragma solidity ^0.4.0; contract owned { function owned() { owner = msg.sender; } address owner; } // Use "is" to derive from another contract. Derived // contracts can access all non-private members including // internal functions and state variables. These cannot be // accessed externally via `this`, though. contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } // These abstract contracts are only provided to make the // interface known to the compiler. Note the function // without body. If a contract does not implement all // functions it can only be used as an interface. contract Config { function lookup(uint id) returns (address adr); } contract NameReg { function register(bytes32 name); function unregister(); } // Multiple inheritance is possible. Note that "owned" is // also a base class of "mortal", yet there is only a single // instance of "owned" (as for virtual inheritance in C++). contract named is owned, mortal { function named(bytes32 name) { Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); NameReg(config.lookup(1)).register(name); } // Functions can be overridden by another function with the same name and // the same number/types of inputs. If the overriding function has different // types of output parameters, that causes an error. // Both local and message-based function calls take these overrides // into account. function kill() { if (msg.sender == owner) { Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970); NameReg(config.lookup(1)).unregister(); // It is still possible to call a specific // overridden function. mortal.kill(); } } } // If a constructor takes an argument, it needs to be // provided in the header (or modifier-invocation-style at // the constructor of the derived contract (see below)). contract PriceFeed is owned, mortal, named("GoldFeed") { function updateInfo(uint newInfo) { if (msg.sender == owner) info = newInfo; } function get() constant returns(uint r) { return info; } uint info; } ``` 上面的例子的`named`合约的`kill()`方法中,我们调用了`motal.kill()`调用父合约的`销毁函数(destruction)`。但这样可能什么引发一些小问题。 ``` pragma solidity ^0.4.0; contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { function kill() { /* do cleanup 1 */ mortal.kill(); } } contract Base2 is mortal { function kill() { /* do cleanup 2 */ mortal.kill(); } } contract Final is Base1, Base2 { } ``` 对`Final.kill()`的调用只会调用`Base2.kill()`,因为派生重写,会跳过`Base1.kill`,因为它根本就不知道有`Base1`。一个变通方法是使用`super`。 ``` pragma solidity ^0.4.0; contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { function kill() { /* do cleanup 1 */ super.kill(); } } contract Base2 is mortal { function kill() { /* do cleanup 2 */ super.kill(); } } contract Final is Base2, Base1 { } ``` 如果`Base1`调用了函数`super`,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,所以会调用`Base2.kill()`。需要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这类似于普通虚函数查找`(ordinary virtual method lookup)` ## 基类构造器的方法(Arguments for Base Constructors) 派生的合约需要提供所有父合约需要的所有参数,所以用两种方式来做,见下面的例子: ``` pragma solidity ^0.4.0; contract Base { uint x; function Base(uint _x) { x = _x; } } contract Derived is Base(7) { function Derived(uint _y) Base(_y * _y) { } } ``` 或者直接在继承列表中使用`is Base(7)`,或像`修改器(modifier)`使用方式一样,做为派生构造器定义头的一部分`Base(_y * _y)`。第一种方式对于构造器是常量的情况比较方便,可以大概说明合约的行为。第二种方式适用于构造的参数值由派生合约的指定的情况。在上述两种都用的情况下,第二种方式优先(一般情况只用其中一种方式就好了)。 ## 多继承与线性化(Multiple Inheritance and Linearization) 实现多继承的编程语言需要解决几个问题,其中之一是`菱形继承问题`又称`钻石问题`,如下图。<br/> <img src="media/14825855458212/14826257952604.png" alt=""/> Solidity的解决方案参考`Python`,使用[C3_linearization](https://en.wikipedia.org/wiki/C3_linearization)来强制将基类合约转换一个有向无环图(DAG)的特定顺序。结果是我们希望的单调性,但却禁止了某些继承行为。特别是基类合约在`is`后的顺序非常重要。下面的代码,Solidity会报错`Linearization of inheritance graph impossible`。 ``` pragma solidity ^0.4.0; contract X {} contract A is X {} contract C is A, X {} ``` 原因是`C`会请求`X`来重写`A`(因为继承定义的顺序是`A,X`),但`A`自身又是重写`X`的,所以这是一个不可解决的矛盾。 一个简单的指定基类合约的继承顺序原则是从`most base-like`到`most derived`。 ## 继承有相同名字的不同类型成员 当继承最终导致一个合约同时存在多个相同名字的修改器或函数,它将被视为一个错误。同新的如果事件与修改器重名,或者函数与事件重名都将产生错误。作为一个例外,状态变量的`getter`可以覆盖一个`public`的函数。