【易錯概念】Solidity語法的重載祝钢,繼承的定義

1,摘要

以太坊智能合約語言Solitidy是一種面向?qū)ο蟮恼Z言若厚,本文結(jié)合面向?qū)ο笳Z言的特性拦英,講清楚Solitidy語言的多態(tài)(Polymorphism)(重寫,重載)盹沈,繼承(Inheritance)等特性龄章。

2,合約說明

Solidity 合約類似于面向?qū)ο笳Z言中的類乞封。合約中有用于數(shù)據(jù)持久化的狀態(tài)變量做裙,和可以修改狀態(tài)變量的函數(shù)。 調(diào)用另一個合約實例的函數(shù)時肃晚,會執(zhí)行一個 EVM 函數(shù)調(diào)用锚贱,這個操作會切換執(zhí)行時的上下文,這樣关串,前一個合約的狀態(tài)變量就不能訪問了拧廊。
面向?qū)ο?Object Oriented,OO)語言有3大特性:封裝,繼承晋修,多態(tài)吧碾,Solidity語言也具有著3中特性。
面向?qū)ο笳Z言3大特性的說明解釋如下:

  • 封裝(Encapsulation)
    封裝墓卦,就是把客觀事物封裝成抽象的類倦春,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對象操作,對不可信的進(jìn)行信息隱藏落剪。一個類就是一個封裝了數(shù)據(jù)以及操作這些數(shù)據(jù)的代碼的邏輯實體睁本。在一個對象內(nèi)部,某些代碼或某些數(shù)據(jù)可以是私有的忠怖,不能被外界訪問呢堰。通過這種方式,對象對內(nèi)部數(shù)據(jù)提供了不同級別的保護(hù)凡泣,以防止程序中無關(guān)的部分意外的改變或錯誤的使用了對象的私有部分枉疼。

  • 繼承(Inheritance)
    繼承皮假,指可以讓某個類型的對象獲得另一個類型的對象的屬性的方法。它支持按級分類的概念往衷。繼承是指這樣一種能力:它可以使用現(xiàn)有類的所有功能钞翔,并在無需重新編寫原來的類的情況下對這些功能進(jìn)行擴(kuò)展。 通過繼承創(chuàng)建的新類稱為“子類”或“派生類”席舍,被繼承的類稱為“基類”、“父類”或“超類”哮笆。繼承的過程来颤,就是從一般到特殊的過程。要實現(xiàn)繼承稠肘,可以通過 “繼承”(Inheritance)和“組合”(Composition)來實現(xiàn)福铅。繼承概念的實現(xiàn)方式有二類:實現(xiàn)繼承與接口繼承。實現(xiàn)繼承是指直接使用 基類的屬性和方法而無需額外編碼的能力项阴;接口繼承是指僅使用屬性和方法的名稱滑黔、但是子類必須提供實現(xiàn)的能力。

  • 多態(tài)(Polymorphism)
    多態(tài)环揽,是指一個類實例的相同方法在不同情形有不同表現(xiàn)形式略荡。多態(tài)機(jī)制使具有不同內(nèi)部結(jié)構(gòu)的對象可以共享相同的外部接口。這意味著歉胶,雖然針對不同對象的具體操作不同汛兜,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以調(diào)用通今。
    另外也解釋一下重載和重寫粥谬。
    重載(Override)是多態(tài)的一種形式,是一個類的內(nèi)部辫塌,方法中多個參數(shù)漏策,根據(jù)入?yún)⒌膫€數(shù)不同,會返回不同的結(jié)果臼氨。
    重寫(Overwrited)掺喻,是子類繼承父類,重寫父類的方法一也。
    多態(tài)性是允許你將父對象設(shè)置成為一個或更多的他的子對象相等的技術(shù)巢寡,賦值之后,父對象就可以根據(jù)當(dāng)前賦值給它的子對象的特性以不同的方式運(yùn)作椰苟。簡單的說抑月,就是一句話:允許將子類類型的指針賦值給父類類型的指針。多態(tài)性在Object Pascal和C++中都是通過虛函數(shù)的舆蝴。

3谦絮,函數(shù)重載(Override)

合約可以具有多個不同參數(shù)的同名函數(shù)题诵。這也適用于繼承函數(shù)。以下示例展示了合約 A 中的重載函數(shù) f层皱。

pragma solidity ^0.4.16;

contract A {
    function f(uint _in) public pure returns (uint out) {
        out = 1;
    }

    function f(uint _in, bytes32 _key) public pure returns (uint out) {
        out = 2;
    }
}

重載函數(shù)也存在于外部接口中性锭。如果兩個外部可見函數(shù)僅區(qū)別于 Solidity 內(nèi)的類型而不是它們的外部類型則會導(dǎo)致錯誤。

// 以下代碼無法編譯
pragma solidity ^0.4.16;

contract A {
    function f(B _in) public pure returns (B out) {
        out = _in;
    }

    function f(address _in) public pure returns (address out) {
        out = _in;
    }
}

contract B {
}

以上兩個 f 函數(shù)重載都接受了 ABI 的地址類型叫胖,雖然它們在 Solidity 中被認(rèn)為是不同的草冈。

3.1 重載解析和參數(shù)匹配

通過將當(dāng)前范圍內(nèi)的函數(shù)聲明與函數(shù)調(diào)用中提供的參數(shù)相匹配,可以選擇重載函數(shù)瓮增。 如果所有參數(shù)都可以隱式地轉(zhuǎn)換為預(yù)期類型怎棱,則選擇函數(shù)作為重載候選項。如果一個候選都沒有绷跑,解析失敗拳恋。

pragma solidity ^0.4.16;

contract A {
    function f(uint8 _in) public pure returns (uint8 out) {
        out = _in;
    }

    function f(uint256 _in) public pure returns (uint256 out) {
        out = _in;
    }
}

調(diào)用 f(50) 會導(dǎo)致類型錯誤,因為 50 既可以被隱式轉(zhuǎn)換為 uint8 也可以被隱式轉(zhuǎn)換為 uint256砸捏。 另一方面谬运,調(diào)用 f(256) 則會解析為 f(uint256) 重載,因為 256 不能隱式轉(zhuǎn)換為 uint8垦藏。

注解

  • 返回參數(shù)不作為重載解析的依據(jù)梆暖。

4,繼承

通過復(fù)制包括多態(tài)的代碼膝藕,Solidity 支持多重繼承式廷。

所有的函數(shù)調(diào)用都是虛擬的,這意味著最遠(yuǎn)的派生函數(shù)會被調(diào)用芭挽,除非明確給出合約名稱滑废。

當(dāng)一個合約從多個合約繼承時,在區(qū)塊鏈上只有一個合約被創(chuàng)建袜爪,所有基類合約的代碼被復(fù)制到創(chuàng)建的合約中蠕趁。

總的來說,Solidity 的繼承系統(tǒng)與 Python的繼承系統(tǒng) 辛馆,非常 相似俺陋,特別是多重繼承方面。

下面的例子進(jìn)行了詳細(xì)的說明昙篙。

pragma solidity ^0.4.16;

contract owned {
    function owned() { owner = msg.sender; }
    address owner;
}

// 使用 is 從另一個合約派生腊状。派生合約可以訪問所有非私有成員,包括內(nèi)部函數(shù)和狀態(tài)變量苔可,
// 但無法通過 this 來外部訪問缴挖。
contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

// 這些抽象合約僅用于給編譯器提供接口。
// 注意函數(shù)沒有函數(shù)體焚辅。
// 如果一個合約沒有實現(xiàn)所有函數(shù)映屋,則只能用作接口苟鸯。
contract Config {
    function lookup(uint id) public returns (address adr);
}

contract NameReg {
    function register(bytes32 name) public;
    function unregister() public;
 }

// 可以多重繼承。請注意棚点,owned 也是 mortal 的基類早处,
// 但只有一個 owned 實例(就像 C++ 中的虛擬繼承)。
contract named is owned, mortal {
    function named(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }

    // 函數(shù)可以被另一個具有相同名稱和相同數(shù)量/類型輸入的函數(shù)重載瘫析。
    // 如果重載函數(shù)有不同類型的輸出參數(shù)砌梆,會導(dǎo)致錯誤。
    // 本地和基于消息的函數(shù)調(diào)用都會考慮這些重載颁股。
    function kill() public {
        if (msg.sender == owner) {
            Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
            NameReg(config.lookup(1)).unregister();
            // 仍然可以調(diào)用特定的重載函數(shù)么库。
            mortal.kill();
        }
    }
}

// 如果構(gòu)造函數(shù)接受參數(shù),
// 則需要在聲明(合約的構(gòu)造函數(shù))時提供甘有,
// 或在派生合約的構(gòu)造函數(shù)位置以修飾器調(diào)用風(fēng)格提供(見下文)。
contract PriceFeed is owned, mortal, named("GoldFeed") {
   function updateInfo(uint newInfo) public {
      if (msg.sender == owner) info = newInfo;
   }

   function get() public view returns(uint r) { return info; }

   uint info;
}

注意葡缰,在上邊的代碼中亏掀,我們調(diào)用 mortal.kill() 來“轉(zhuǎn)發(fā)”銷毀請求。 這樣做法是有問題的泛释,在下面的例子中可以看到:

pragma solidity ^0.4.0;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* 清除操作 1 */ mortal.kill(); }
}

contract Base2 is mortal {
    function kill() public { /* 清除操作 2 */ mortal.kill(); }
}

contract Final is Base1, Base2 {
}

調(diào)用 Final.kill() 時會調(diào)用最遠(yuǎn)的派生重載函數(shù) Base2.kill滤愕,但是會繞過 Base1.kill, 主要是因為它甚至都不知道 Base1 的存在怜校。解決這個問題的方法是使用 super:

pragma solidity ^0.4.0;

contract owned {
    function owned() public { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is mortal {
    function kill() public { /* 清除操作 1 */ super.kill(); }
}


contract Base2 is mortal {
    function kill() public { /* 清除操作 2 */ super.kill(); }
}

contract Final is Base1, Base2 {
}

如果 Base2 調(diào)用 super 的函數(shù)间影,它不會簡單在其基類合約上調(diào)用該函數(shù)。 相反茄茁,它在最終的繼承關(guān)系圖譜的下一個基類合約中調(diào)用這個函數(shù)魂贬,所以它會調(diào)用 Base1.kill() (注意最終的繼承序列是——從最遠(yuǎn)派生合約開始:Final, Base2, Base1, mortal, ownerd)。 在類中使用 super 調(diào)用的實際函數(shù)在當(dāng)前類的上下文中是未知的裙顽,盡管它的類型是已知的付燥。 這與普通的虛擬方法查找類似。

4.1 基類構(gòu)造函數(shù)的參數(shù)

派生合約需要提供基類構(gòu)造函數(shù)需要的所有參數(shù)愈犹。這可以通過兩種方式來完成:

pragma solidity ^0.4.0;

contract Base {
    uint x;
    function Base(uint _x) public { x = _x; }
}

contract Derived is Base(7) {
    function Derived(uint _y) Base(_y * _y) public {
    }
}

一種方法直接在繼承列表中調(diào)用基類構(gòu)造函數(shù)(is Base(7))键科。 另一種方法是像 修飾器modifier 使用方法一樣, 作為派生合約構(gòu)造函數(shù)定義頭的一部分漩怎,(Base(_y * _y))勋颖。 如果構(gòu)造函數(shù)參數(shù)是常量并且定義或描述了合約的行為,使用第一種方法比較方便勋锤。 如果基類構(gòu)造函數(shù)的參數(shù)依賴于派生合約饭玲,那么必須使用第二種方法。 如果像這個簡單的例子一樣怪得,兩個地方都用到了咱枉,優(yōu)先使用 修飾器modifier 風(fēng)格的參數(shù)卑硫。

4.2 多重繼承與線性化

編程語言實現(xiàn)多重繼承需要解決幾個問題。 一個問題是 鉆石問題蚕断。 Solidity 借鑒了 Python 的方式并且使用“ C3 線性化 ”強(qiáng)制一個由基類構(gòu)成的 DAG(有向無環(huán)圖)保持一個特定的順序欢伏。 這最終反映為我們所希望的唯一化的結(jié)果,但也使某些繼承方式變?yōu)闊o效亿乳。尤其是硝拧,基類在 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聊训,無法解決這種沖突抱究。

可以通過一個簡單的規(guī)則來記憶: 以從“最接近的基類”(most base-like)到“最遠(yuǎn)的繼承”(most derived)的順序來指定所有的基類。

4.3 繼承有相同名字的不同類型成員

當(dāng)繼承導(dǎo)致一個合約具有相同名字的函數(shù)和 修飾器modifier 時带斑,這會被認(rèn)為是一個錯誤鼓寺。 當(dāng)事件和 修飾器modifier 同名,或者函數(shù)和事件同名時勋磕,同樣會被認(rèn)為是一個錯誤妈候。 有一種例外情況,狀態(tài)變量的 getter 可以覆蓋一個 public 函數(shù)挂滓。

5苦银,參考

(1)Solidity官網(wǎng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赶站,隨后出現(xiàn)的幾起案子幔虏,更是在濱河造成了極大的恐慌,老刑警劉巖亲怠,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件所计,死亡現(xiàn)場離奇詭異,居然都是意外死亡团秽,警方通過查閱死者的電腦和手機(jī)主胧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來习勤,“玉大人踪栋,你說我怎么就攤上這事⊥急希” “怎么了夷都?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長予颤。 經(jīng)常有香客問我囤官,道長冬阳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任党饮,我火速辦了婚禮肝陪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刑顺。我一直安慰自己氯窍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布蹲堂。 她就那樣靜靜地躺著狼讨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柒竞。 梳的紋絲不亂的頭發(fā)上政供,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音朽基,去河邊找鬼鲫骗。 笑死,一個胖子當(dāng)著我的面吹牛踩晶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枕磁,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼渡蜻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了计济?” 一聲冷哼從身側(cè)響起茸苇,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沦寂,沒想到半個月后学密,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡传藏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年腻暮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毯侦。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哭靖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侈离,到底是詐尸還是另有隱情试幽,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布卦碾,位于F島的核電站铺坞,受9級特大地震影響起宽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜济榨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一坯沪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腿短,春花似錦屏箍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钝诚,卻和暖如春颖御,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凝颇。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工潘拱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拧略。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓芦岂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垫蛆。 傳聞我的和親對象是個殘疾皇子禽最,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1,摘要 以太坊智能合約語言Solitidy是一種面向?qū)ο蟮恼Z言袱饭,本文清楚合約定義川无,以及派生的抽象合約,接口虑乖,庫的...
    筆名輝哥閱讀 2,181評論 0 51
  • 1.面向?qū)ο蟮某绦蛟O(shè)計思想是什么懦趋? 答:把數(shù)據(jù)結(jié)構(gòu)和對數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作的方法封裝形成一個個的對象。 2.什么是類疹味?...
    少帥yangjie閱讀 4,988評論 0 14
  • C++ 繼承 面向?qū)ο蟪绦蛟O(shè)計中最重要的一個概念是繼承佛猛。繼承允許我們依據(jù)另一個類來定義一個類惑芭,這使得創(chuàng)建和維護(hù)一個...
    蘇州丸子閱讀 3,221評論 0 5
  • 第一次看演唱會,沒想到是李宗盛大叔的幻锁,特別震撼凯亮。宗盛大叔牛仔褲白襯衫,雖然已經(jīng)是花甲之年哄尔,但是仍然有著青春的樣...
    只為滄海不為岸閱讀 317評論 0 0
  • [cp]#跟宇彤老師成為聲音萬人迷# 想起這幾天完成的作業(yè)假消,總感覺到吃力,越想的提升越找不到突破口岭接,雖然每次李子老...
    穆希樂閱讀 225評論 0 0