Solidity 支持多重繼承,包括多態(tài)性。
多態(tài)性意味著函數(shù)調(diào)用(內(nèi)部和外部)總是在繼承層次結(jié)構(gòu)中最派生的合約中執(zhí)行同名(和參數(shù)類型)的函數(shù)。這必須使用virtualandoverride關(guān)鍵字在層次結(jié)構(gòu)中的每個(gè)函數(shù)上顯式啟用。有關(guān)更多詳細(xì)信息,請(qǐng)參閱函數(shù)覆蓋。
如果您想在扁平繼承層次結(jié)構(gòu)中調(diào)用更高一級(jí)的函數(shù)(見下文),則可以通過(guò)顯式指定合約 usingContractName.functionName()或 using在內(nèi)部調(diào)用繼承層次結(jié)構(gòu)中的函數(shù)。super.functionName()
當(dāng)一個(gè)合約繼承自其他合約時(shí),區(qū)塊鏈上只創(chuàng)建一個(gè)合約,所有基礎(chǔ)合約的代碼都編譯到創(chuàng)建的合約中。這意味著對(duì)基礎(chǔ)合約函數(shù)的所有內(nèi)部調(diào)用也只使用內(nèi)部函數(shù)調(diào)用(super.f(..)將使用 JUMP 而不是消息調(diào)用)。
狀態(tài)變量遮蔽被視為錯(cuò)誤。一個(gè)派生合約只能聲明一個(gè)狀態(tài)變量x,前提是在它的任何基礎(chǔ)中都沒(méi)有同名的可見狀態(tài)變量。
通用的繼承系統(tǒng)和Python 的非常相似 ,尤其是在多重繼承方面,但也有一些區(qū)別。
以下示例中給出了詳細(xì)信息。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract Owned { constructor() { owner = payable(msg.sender); } address payable 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 Destructible is Owned { // The keyword `virtual` means that the function can change // its behaviour in derived classes ("overriding"). function destroy() virtual public { 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. 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; } // Multiple inheritance is possible. Note that `Owned` is // also a base class of `Destructible`, yet there is only a single // instance of `Owned` (as for virtual inheritance in C++). contract Named is Owned, Destructible { constructor(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. // If you want the function to override, you need to use the // `override` keyword. You need to specify the `virtual` keyword again // if you want this function to be overridden again. function destroy() public virtual override { if (msg.sender == owner) { Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).unregister(); // It is still possible to call a specific // overridden function. Destructible.destroy(); } } } // 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, Destructible, Named("GoldFeed") { function updateInfo(uint newInfo) public { if (msg.sender == owner) info = newInfo; } // Here, we only specify `override` and not `virtual`. // This means that contracts deriving from `PriceFeed` // cannot change the behaviour of `destroy` anymore. function destroy() public override(Destructible, Named) { Named.destroy(); } function get() public view returns(uint r) { return info; } uint info; }
請(qǐng)注意,上面我們調(diào)用Destructible.destroy()“轉(zhuǎn)發(fā)”銷毀請(qǐng)求。這樣做的方式是有問(wèn)題的,如下例所示:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract owned { constructor() { owner = payable(msg.sender); } address payable owner; } contract Destructible is owned { function destroy() public virtual { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is Destructible { function destroy() public virtual override { /* do cleanup 1 */ Destructible.destroy(); } } contract Base2 is Destructible { function destroy() public virtual override { /* do cleanup 2 */ Destructible.destroy(); } } contract Final is Base1, Base2 { function destroy() public override(Base1, Base2) { Base2.destroy(); } }
調(diào)用Final.destroy()將調(diào)用Base2.destroy,因?yàn)槲覀冊(cè)谧罱K覆蓋中明確指定它,但此函數(shù)將繞過(guò) Base1.destroy. 解決這個(gè)問(wèn)題的方法是使用super:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract owned { constructor() { owner = payable(msg.sender); } address payable owner; } contract Destructible is owned { function destroy() virtual public { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is Destructible { function destroy() public virtual override { /* do cleanup 1 */ super.destroy(); } } contract Base2 is Destructible { function destroy() public virtual override { /* do cleanup 2 */ super.destroy(); } } contract Final is Base1, Base2 { function destroy() public override(Base1, Base2) { super.destroy(); } }
如果Base2調(diào)用 的函數(shù)super,它不會(huì)簡(jiǎn)單地在其基礎(chǔ)合約之一上調(diào)用此函數(shù)。相反,它會(huì)在最終繼承圖中的下一個(gè)基礎(chǔ)合約上調(diào)用此函數(shù),因此它將調(diào)用Base1.destroy()(請(qǐng)注意,最終繼承順序是 - 從最衍生的合約開始:Final、Base2、Base1、Destructible、owned)。使用 super 時(shí)調(diào)用的實(shí)際函數(shù)在使用它的類的上下文中是未知的,盡管它的類型是已知的。這與普通的虛擬方法查找類似。
如果標(biāo)記為 ,則可以通過(guò)繼承合同來(lái)更改其行為來(lái)覆蓋基本功能virtual
。然后,覆蓋函數(shù)必須override
在函數(shù)頭中使用關(guān)鍵字。重寫函數(shù)只能將重寫函數(shù)的可見性從 更改external
為public
??勺冃钥梢园凑找韵马樞蚋臑楦鼑?yán)格的: nonpayable
可以被view
and覆蓋pure
。view
可以被
覆蓋pure
。 payable
是一個(gè)例外,不能更改為任何其他可變性。
以下示例演示了不斷變化的可變性和可見性:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract Base { function foo() virtual external view {} } contract Middle is Base {} contract Inherited is Middle { function foo() override public pure {} }
對(duì)于多重繼承,定義相同函數(shù)的最衍生的基礎(chǔ)合約必須在override
關(guān)鍵字之后顯式指定。換句話說(shuō),您必須指定所有定義相同功能且尚未被另一個(gè)基礎(chǔ)合約覆蓋的基礎(chǔ)合約(在通過(guò)繼承圖的某個(gè)路徑上)。此外,如果合約從多個(gè)(不相關(guān)的)基礎(chǔ)繼承相同的功能,它必須顯式覆蓋它:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract Base1 { function foo() virtual public {} } contract Base2 { function foo() virtual public {} } contract Inherited is Base1, Base2 { // Derives from multiple bases defining foo(), so we must explicitly // override it function foo() public override(Base1, Base2) {} }
如果函數(shù)是在通用基礎(chǔ)合約中定義的,或者如果通用基礎(chǔ)合約中有一個(gè)唯一函數(shù)已經(jīng)覆蓋了所有其他函數(shù),則不需要顯式覆蓋說(shuō)明符。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract A { function f() public pure{} } contract B is A {} contract C is A {} // No explicit override required contract D is B, C {}
更正式地說(shuō),如果有一個(gè)基礎(chǔ)合約是簽名的所有覆蓋路徑的一部分,并且(1)該基礎(chǔ)實(shí)現(xiàn)該函數(shù)并且沒(méi)有來(lái)自多個(gè)基礎(chǔ)的路徑,則不需要重寫從多個(gè)基礎(chǔ)繼承的函數(shù)(直接或間接)當(dāng)前與基礎(chǔ)的合約提到了具有該簽名的函數(shù),或者 (2) 該基礎(chǔ)沒(méi)有實(shí)現(xiàn)該功能,并且在從當(dāng)前合約到該基礎(chǔ)的所有路徑中最多有一次提及該功能。
從這個(gè)意義上說(shuō),簽名的覆蓋路徑是通過(guò)繼承圖的路徑,該路徑從所考慮的合同開始,到提及具有該簽名的未覆蓋功能的合同結(jié)束。
如果您不將覆蓋的函數(shù)標(biāo)記為virtual
,則派生合約將無(wú)法再更改該函數(shù)的行為。
筆記
具有private
可見性的函數(shù)不能virtual
。
筆記
沒(méi)有實(shí)現(xiàn)的函數(shù)必須virtual
在接口之外標(biāo)記。在接口中,所有功能都被自動(dòng)考慮virtual
。
筆記
從 Solidity 0.8.8 開始,override
重寫接口函數(shù)時(shí)不需要關(guān)鍵字,除非函數(shù)在多個(gè)基中定義。
如果函數(shù)的參數(shù)和返回類型與變量的 getter 函數(shù)匹配,則公共狀態(tài)變量可以覆蓋外部函數(shù):
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract A { function f() external view virtual returns(uint) { return 5; } } contract B is A { uint public override f; }
筆記
雖然公共狀態(tài)變量可以覆蓋外部函數(shù),但它們本身不能被覆蓋。
函數(shù)修飾符可以相互覆蓋。這與函數(shù)覆蓋的工作方式相同 (除了修飾符沒(méi)有重載)。virtual
必須在覆蓋修飾符上使用關(guān)鍵字 ,并且override
必須在覆蓋修飾符中使用關(guān)鍵字:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract Base { modifier foo() virtual {_;} } contract Inherited is Base { modifier foo() override {_;} }
在多重繼承的情況下,必須明確指定所有直接基礎(chǔ)合約:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract Base1 { modifier foo() virtual {_;} } contract Base2 { modifier foo() virtual {_;} } contract Inherited is Base1, Base2 { modifier foo() override(Base1, Base2) {_;} }
構(gòu)造函數(shù)是使用constructor
關(guān)鍵字聲明的可選函數(shù),在創(chuàng)建合約時(shí)執(zhí)行,您可以在其中運(yùn)行合約初始化代碼。
在執(zhí)行構(gòu)造函數(shù)代碼之前,如果內(nèi)聯(lián)初始化狀態(tài)變量,則將其初始化為其指定值,否則將其初始化為默認(rèn)值。
構(gòu)造函數(shù)運(yùn)行后,合約的最終代碼將部署到區(qū)塊鏈。代碼的部署成本與代碼長(zhǎng)度成線性關(guān)系。此代碼包括作為公共接口一部分的所有函數(shù)以及可通過(guò)函數(shù)調(diào)用從那里訪問(wèn)的所有函數(shù)。它不包括僅從構(gòu)造函數(shù)調(diào)用的構(gòu)造函數(shù)代碼或內(nèi)部函數(shù)。
如果沒(méi)有構(gòu)造函數(shù),合約將假定默認(rèn)構(gòu)造函數(shù),相當(dāng)于. 例如:constructor() {}
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; abstract contract A { uint public a; constructor(uint a_) { a = a_; } } contract B is A(1) { constructor() {} }
您可以在構(gòu)造函數(shù)中使用內(nèi)部參數(shù)(例如存儲(chǔ)指針)。在這種情況下,必須將合約標(biāo)記為abstract,因?yàn)檫@些參數(shù)不能從外部分配有效值,而只能通過(guò)派生合約的構(gòu)造函數(shù)分配。
警告
在 0.4.22 版本之前,構(gòu)造函數(shù)被定義為與合約同名的函數(shù)。此語(yǔ)法已被棄用,并且在 0.5.0 版中不再允許使用。
警告
在 0.7.0 版本之前,您必須將構(gòu)造函數(shù)的可見性指定為 internal
或public
。
所有基礎(chǔ)合約的構(gòu)造函數(shù)都將按照下面解釋的線性化規(guī)則進(jìn)行調(diào)用。如果基本構(gòu)造函數(shù)有參數(shù),則派生合約需要指定所有參數(shù)。這可以通過(guò)兩種方式完成:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract Base { uint x; constructor(uint x_) { x = x_; } } // Either directly specify in the inheritance list... contract Derived1 is Base(7) { constructor() {} } // or through a "modifier" of the derived constructor... contract Derived2 is Base { constructor(uint y) Base(y * y) {} } // or declare abstract... abstract contract Derived3 is Base { } // and have the next concrete derived contract initialize it. contract DerivedFromDerived is Derived3 { constructor() Base(10 + 10) {} }
一種方法是直接在繼承列表 ( ) 中。另一個(gè)是作為派生構(gòu)造函數(shù)()的一部分調(diào)用修飾符的方式。如果構(gòu)造函數(shù)參數(shù)是一個(gè)常量并定義合約的行為或描述它,那么第一種方法會(huì)更方便。如果 base 的構(gòu)造函數(shù)參數(shù)依賴于派生合約的參數(shù),則必須使用第二種方法。參數(shù)必須在繼承列表或派生構(gòu)造函數(shù)的修飾符樣式中給出。在這兩個(gè)地方指定參數(shù)是錯(cuò)誤的。is Base(7)
Base(y * y)
如果派生合約沒(méi)有為其所有基礎(chǔ)合約的構(gòu)造函數(shù)指定參數(shù),則必須將其聲明為抽象的。在這種情況下,當(dāng)另一個(gè)合約派生自它時(shí),該其他合約的繼承列表或構(gòu)造函數(shù)必須為所有未指定其參數(shù)的基類提供必要的參數(shù)(否則,該其他合約也必須聲明為抽象)。例如,在上面的代碼片段中,請(qǐng)參見Derived3
和DerivedFromDerived
。
允許多重繼承的語(yǔ)言必須處理幾個(gè)問(wèn)題。一是鉆石問(wèn)題。Solidity 類似于 Python,因?yàn)樗褂谩?nbsp;C3 線性化”來(lái)強(qiáng)制基類的有向無(wú)環(huán)圖 (DAG) 中的特定順序。這導(dǎo)致了理想的單調(diào)性屬性,但不允許某些繼承圖。特別是,指令中基類的順序is
很重要:您必須按照從“最基類”到“最衍生”的順序??列出直接基類合約。請(qǐng)注意,此順序與
Python 中使用的順序相反。
解釋這一點(diǎn)的另一種簡(jiǎn)化方法是,當(dāng)調(diào)用在不同合約中多次定義的函數(shù)時(shí),以深度優(yōu)先的方式從右到左(在 Python 中從左到右)搜索給定的堿基,在第一次匹配時(shí)停止. 如果已經(jīng)搜索了基本合約,則跳過(guò)它。
在下面的代碼中,Solidity 將給出錯(cuò)誤“繼承圖的線性化不可能”。
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.0 <0.9.0; contract X {} contract A is X {} // This will not compile contract C is A, X {}
這樣做的原因是C
請(qǐng)求X
覆蓋A
(通過(guò)按此順序指定),但本身請(qǐng)求覆蓋,這是無(wú)法解決的矛盾。A, X
A
X
由于您必須顯式覆蓋從多個(gè)基類繼承的函數(shù)而無(wú)需唯一覆蓋,因此 C3 線性化在實(shí)踐中并不太重要。
繼承線性化特別重要但可能不太清楚的一個(gè)領(lǐng)域是在繼承層次結(jié)構(gòu)中有多個(gè)構(gòu)造函數(shù)時(shí)。構(gòu)造函數(shù)將始終以線性化順序執(zhí)行,而不管繼承合約的構(gòu)造函數(shù)中提供它們的參數(shù)的順序如何。例如:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract Base1 { constructor() {} } contract Base2 { constructor() {} } // Constructors are executed in the following order: // 1 - Base1 // 2 - Base2 // 3 - Derived1 contract Derived1 is Base1, Base2 { constructor() Base1() Base2() {} } // Constructors are executed in the following order: // 1 - Base2 // 2 - Base1 // 3 - Derived2 contract Derived2 is Base2, Base1 { constructor() Base2() Base1() {} } // Constructors are still executed in the following order: // 1 - Base2 // 2 - Base1 // 3 - Derived3 contract Derived3 is Base2, Base1 { constructor() Base1() Base2() {} }
一個(gè)函數(shù)和一個(gè)修飾符
一個(gè)函數(shù)和一個(gè)事件
事件和修飾符
作為一個(gè)例外,狀態(tài)變量 getter 可以覆蓋外部函數(shù)。
更多建議: