Solidity語法(七)合約

可見性和訪問限制符(Visibility And Accessors)

因為Solidity可以理解兩種函數(shù)調(diào)用(“內(nèi)部調(diào)用”组哩,不創(chuàng)建一個真實的EVM調(diào)用(也稱為“消息調(diào)用”);“外部的調(diào)用”-要創(chuàng)建一個真實的EMV調(diào)用), 有四種的函數(shù)和狀態(tài)變量的可見性嘹黔。

函數(shù)可以被定義為external, public, internal or private骨杂,缺省是 public涂身。對狀態(tài)變量而言, external是不可能的,默認(rèn)是 internal。

external: 外部函數(shù)是合約接口的一部分,這意味著它們可以從其他合約調(diào)用, 也可以通過事務(wù)調(diào)用搓蚪。外部函數(shù)f不能被內(nèi)部調(diào)用(即 f()不執(zhí)行,但this.f()執(zhí)行)蛤售。外部函數(shù),當(dāng)他們接收大數(shù)組時,更有效悍抑。

public:公共函數(shù)是合約接口的一部分,可以通過內(nèi)部調(diào)用或通過消息調(diào)用。對公共狀態(tài)變量而言杜耙,會有的自動訪問限制符的函數(shù)生成(見下文)搜骡。

internal:這些函數(shù)和狀態(tài)變量只能內(nèi)部訪問(即在當(dāng)前合約或由它派生的合約),而不使用(關(guān)鍵字)this 。

private:私有函數(shù)和狀態(tài)變量僅僅在定義該合約中可見佑女, 在派生的合約中不可見记靡。

請注意

在外部觀察者中,合約的內(nèi)部的各項均可見团驱。用 private 僅僅防止其他合約來訪問和修改(該合約中)信息, 但它對blockchain之外的整個世界仍然可見摸吠。

可見性說明符是放在在狀態(tài)變量的類型之后,(也可以放在)參數(shù)列表和函數(shù)返回的參數(shù)列表之間嚎花。

contract c {

    function f(uint a) private returns (uint b) { return a + 1; }

    function setData(uint a) internal { data = a; }

    uint public data;

}

其他合約可以調(diào)用c.data()來檢索狀態(tài)存儲中data的值,但不能訪問(函數(shù))f寸痢。由c派生的合約可以訪問(合約中)setData(函數(shù)),以便改變data的值(僅僅在它們自己的范圍里)紊选。

訪問函數(shù)(Getter Functions)

編譯器為自動為所有的public的狀態(tài)變量創(chuàng)建訪問函數(shù)啼止。下面的合約例子中,編譯器會生成一個名叫data的無參兵罢,返回值是uint的類型的值data献烦。狀態(tài)變量的初始化可以在定義時完成。

pragma solidity ^0.4.0;

contract C{
    uint public c = 10;
}

contract D{
    C c = new C();
    
    function getDataUsingAccessor() returns (uint){
        return c.c();
    }
}

訪問函數(shù)有外部(external)可見性卖词。如果通過內(nèi)部(internal)的方式訪問巩那,比如直接訪問,你可以直接把它當(dāng)一個變量進(jìn)行使用此蜈,但如果使用外部(external)的方式來訪問即横,如通過this.,那么它必須通過函數(shù)的方式來調(diào)用舶替。

pragma solidity ^0.4.0;

contract C{
    uint public c = 10;
    
    function accessInternal() returns (uint){
        return c;
    }
    
    function accessExternal() returns (uint){
        return this.c();
    }
}

在acessExternal函數(shù)中令境,如果直接返回return this.c;,會出現(xiàn)報錯Return argument type function () constant external returns (uint256) is not implicitly convertible to expected type (type of first return variable) uint256.顾瞪。原因應(yīng)該是通過外部(external)的方式只能訪問到this.c作為函數(shù)的對象舔庶,所以它認(rèn)為你是想把一個函數(shù)轉(zhuǎn)為uint故而報錯。

函數(shù)修改器(Function Modifiers)

修改器(Modifiers)可以用來輕易的改變一個函數(shù)的行為陈醒。比如用于在函數(shù)執(zhí)行前檢查某種前置條件惕橙。修改器是一種合約屬性,可被繼承钉跷,同時還可被派生的合約重寫(override)弥鹦。下面我們來看一段示例代碼:

pragma solidity ^0.4.0;

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

    // This contract only defines a modifier but does not use
    // it - it will be used in derived contracts.
    // The function body is inserted where the special symbol
    // "_;" in the definition of a modifier appears.
    // This means that if the owner calls this function, the
    // function is executed and otherwise, an exception is
    // thrown.
    modifier onlyOwner {
        if (msg.sender != owner)
            throw;
        _;
    }
}

contract mortal is owned {
    // This contract inherits the "onlyOwner"-modifier from
    // "owned" and applies it to the "close"-function, which
    // causes that calls to "close" only have an effect if
    // they are made by the stored owner.
    function close() onlyOwner {
        selfdestruct(owner);
    }
}


contract priced {
    // Modifiers can receive arguments:
    modifier costs(uint price) {
        if (msg.value >= price) {
            _;
        }
    }
}


contract Register is priced, owned {
    mapping (address => bool) registeredAddresses;
    uint price;

    function Register(uint initialPrice) { price = initialPrice; }

    // It is important to also provide the
    // "payable" keyword here, otherwise the function will
    // automatically reject all Ether sent to it.
    function register() payable costs(price) {
        registeredAddresses[msg.sender] = true;
    }

    function changePrice(uint _price) onlyOwner {
        price = _price;
    }
}

修改器可以被繼承,使用將modifier置于參數(shù)后,返回值前即可彬坏。

特殊_表示使用修改符的函數(shù)體的替換位置朦促。

從合約Register可以看出全約可以多繼承,通過,號分隔兩個被繼承的對象栓始。

修改器也是可以接收參數(shù)的务冕,如priced的costs。

使用修改器實現(xiàn)的一個防重復(fù)進(jìn)入的例子幻赚。

pragma solidity ^0.4.0;
contract Mutex {
    bool locked;
    modifier noReentrancy() {
        if (locked) throw;
        locked = true;
        _;
        locked = false;
    }

    /// This function is protected by a mutex, which means that
    /// reentrant calls from within msg.sender.call cannot call f again.
    /// The `return 7` statement assigns 7 to the return value but still
    /// executes the statement `locked = false` in the modifier.
    function f() noReentrancy returns (uint) {
        if (!msg.sender.call()) throw;
        return 7;
    }
}

例子中禀忆,由于call()方法有可能會調(diào)回當(dāng)前方法,修改器實現(xiàn)了防重入的檢查落恼。

如果同一個函數(shù)有多個修改器箩退,他們之間以空格隔開,修飾器會依次檢查執(zhí)行佳谦。

需要注意的是戴涝,在Solidity的早期版本中,有修改器的函數(shù)吠昭,它的return語句的行為有些不同喊括。
在修改器中和函數(shù)體內(nèi)的顯式的return語句,僅僅跳出當(dāng)前的修改器和函數(shù)體矢棚。返回的變量會被賦值郑什,但整個執(zhí)行邏輯會在前一個修改器后面定義的"_"后繼續(xù)執(zhí)行。

修改器的參數(shù)可以是任意表達(dá)式蒲肋。在對應(yīng)的上下文中蘑拯,所有的函數(shù)中引入的符號,在修改器中均可見兜粘。但修改器中引入的符號在函數(shù)中不可見申窘,因為它們有可能被重寫。

常量(constant state variables)

狀態(tài)變量可以被定義為constant孔轴,常量剃法。這樣的話,它必須在編譯期間通過一個表達(dá)式賦值路鹰。賦值的表達(dá)式不允許:1)訪問storage贷洲;2)區(qū)塊鏈數(shù)據(jù),如now晋柱,this.balance优构,block.number;3)合約執(zhí)行的中間數(shù)據(jù)雁竞,如msg.gas钦椭;4)向外部合約發(fā)起調(diào)用。也許會造成內(nèi)存分配副作用表達(dá)式是允許的,但不允許產(chǎn)生其它內(nèi)存對象的副作用的表達(dá)式彪腔。內(nèi)置的函數(shù)keccak256侥锦,keccak256,ripemd160德挣,ecrecover捎拯,addmod,mulmod可以允許調(diào)用盲厌,即使它們是調(diào)用的外部合約。

允許內(nèi)存分配祸泪,從而帶來可能的副作用的原因是因為這將允許構(gòu)建復(fù)雜的對象吗浩,比如,查找表没隘。雖然當(dāng)前的特性尚未完整支持懂扼。

編譯器并不會為常量在storage上預(yù)留空間,每個使用的常量都會被對應(yīng)的常量表達(dá)式所替換(也許優(yōu)化器會直接替換為常量表達(dá)式的結(jié)果值)右蒲。

不是所有的類型都支持常量阀湿,當(dāng)前支持的僅有值類型和字符串。

pragma solidity ^0.4.0;

contract C {
    uint constant x = 32**22 + 8;
    string constant text = "abc";
    bytes32 constant myHash = keccak256("abc");
}

常函數(shù)(Constant Functions)

函數(shù)也可被聲明為常量瑰妄,這類函數(shù)將承諾自己不修改區(qū)塊鏈上任何狀態(tài)陷嘴。

pragma solidity ^0.4.0;

contract C {
    function f(uint a, uint b) constant returns (uint) {
        return a * (b + 42);
    }
}

訪問器(Accessor)方法默認(rèn)被標(biāo)記為constant。當(dāng)前編譯器并未強(qiáng)制一個constant的方法不能修改狀態(tài)间坐。但建議大家對于不會修改數(shù)據(jù)的標(biāo)記為constant灾挨。

回退函數(shù)(fallback function)

每一個合約有且僅有一個沒有名字的函數(shù)。這個函數(shù)無參數(shù)竹宋,也無返回值劳澄。如果調(diào)用合約時,沒有匹配上任何一個函數(shù)(或者沒有傳哪怕一點(diǎn)數(shù)據(jù))蜈七,就會調(diào)用默認(rèn)的回退函數(shù)秒拔。

此外,當(dāng)合約收到ether時(沒有任何其它數(shù)據(jù))飒硅,這個函數(shù)也會被執(zhí)行砂缩。在此時,一般僅有少量的gas剩余狡相,用于執(zhí)行這個函數(shù)(準(zhǔn)確的說梯轻,還剩2300gas)。所以應(yīng)該盡量保證回退函數(shù)使用少的gas尽棕。

下述提供給回退函數(shù)可執(zhí)行的操作會比常規(guī)的花費(fèi)得多一點(diǎn)喳挑。

  • 寫入到存儲(storage)
  • 創(chuàng)建一個合約
  • 執(zhí)行一個外部(external)函數(shù)調(diào)用,會花費(fèi)非常多的gas
  • 發(fā)送ether

請在部署合約到網(wǎng)絡(luò)前,保證透徹的測試你的回退函數(shù)伊诵,來保證函數(shù)執(zhí)行的花費(fèi)控制在2300gas以內(nèi)单绑。

一個沒有定義一個回退函數(shù)的合約。如果接收ether曹宴,會觸發(fā)異常搂橙,并返還ether(solidity v0.4.0開始)。所以合約要接收ether笛坦,必須實現(xiàn)回退函數(shù)区转。下面來看個例子:

pragma solidity ^0.4.0;

contract Test {
    // This function is called for all messages sent to
    // this contract (there is no other function).
    // Sending Ether to this contract will cause an exception,
    // because the fallback function does not have the "payable"
    // modifier.
    function() { x = 1; }
    uint x;
}


// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
    function() payable { }
}

contract Caller {
    function callTest(Test test) {
        test.call(0xabcdef01); // hash does not exist
        // results in test.x becoming == 1.

        // The following call will fail, reject the
        // Ether and return false:
        test.send(2 ether);
    }
}

事件(Event)

事件

在介紹事件前,我們先明確事件版扩,日志這兩個概念废离。事件發(fā)生后被記錄到區(qū)塊鏈上成為了日志〗嘎總的來說蜻韭,事件強(qiáng)調(diào)功能,一種行為柿扣;日志強(qiáng)調(diào)存儲肖方,內(nèi)容。

事件是以太坊EVM提供的一種日志基礎(chǔ)設(shè)施未状。事件可以用來做操作記錄俯画,存儲為日志。也可以用來實現(xiàn)一些交互功能司草,比如通知UI活翩,返回函數(shù)調(diào)用結(jié)果等。

當(dāng)定義的事件觸發(fā)時翻伺,我們可以將事件存儲到EVM的交易日志中材泄,日志是區(qū)塊鏈中的一種特殊數(shù)據(jù)結(jié)構(gòu)。日志與合約關(guān)聯(lián)吨岭,與合約的存儲合并存入?yún)^(qū)塊鏈中拉宗。只要某個區(qū)塊可以訪問,其相關(guān)的日志就可以訪問辣辫。但在合約中旦事,我們不能直接訪問日志和事件數(shù)據(jù)(即便是創(chuàng)建日志的合約)。下面我們來看看急灭,如何在Solidity中實現(xiàn)一個事件:

pragma solidity ^0.4.0;

contract Transfer{
  event transfer(address indexed _from, address indexed _to, uint indexed value);

  function deposit() payable {
    address current = this;
    uint value = msg.value;
    transfer(msg.sender, current, value);
  }


  function getBanlance() constant returns(uint) {
      return this.balance;
  }

  /* fallback function */
  function(){}
}

從上面的例子中姐浮,我們使用event關(guān)鍵字定義一個事件,參數(shù)列表中為要記錄的日志參數(shù)的名稱及類型葬馋。

監(jiān)聽事件

在web3.js中卖鲤,提供了響應(yīng)事件的方法肾扰,如下:

var event = myContract.transfer();

    // 監(jiān)聽
    event.watch(function(error, result){
    console.log("Event are as following:-------");
    
    for(let key in result){
     console.log(key + " : " + result[key]);
    }
    
    console.log("Event ending-------");
});

另外一種簡便寫法是直接加入事件回調(diào),這樣就不用再寫watch的部分:

var event = myContract.transfer(function(error, result){
    console.log("Event are as following:-------");
    
    for(let key in result){
     console.log(key + " : " + result[key]);
    }
    
    console.log("Event ending-------");
});

檢索日志

Indexed屬性

可以在事件參數(shù)上增加indexed屬性蛋逾,最多可以對三個參數(shù)增加這樣的屬性集晚。加上這個屬性,可以允許你在web3.js中通過對加了這個屬性的參數(shù)進(jìn)行值過濾区匣,方式如下:

var event = myContract.transfer({value: "100"});

上面實現(xiàn)的是對value值為100的日志偷拔,過濾后的返回。

如果你想同時匹配多個值亏钩,還可以傳入一個要匹配的數(shù)組莲绰。

var event = myContract.transfer({value: ["99","100","101"]});

增加了indexed的參數(shù)值會存到日志結(jié)構(gòu)的Topic部分,便于快速查找姑丑。而未加indexed的參數(shù)值會存在data部分钉蒲,成為原始日志。需要注意的是彻坛,如果增加indexed屬性的是數(shù)組類型(包括stringbytes),那么只會在Topic存儲對應(yīng)的數(shù)據(jù)的web3.sha3哈希值踏枣,將不會再存原始數(shù)據(jù)昌屉。因為Topic是用于快速查找的,不能存任意長度的數(shù)據(jù)茵瀑,所以通過Topic實際存的是數(shù)組這種非固定長度數(shù)據(jù)哈希結(jié)果间驮。要查找時,是將要查找內(nèi)容哈希后與Topic內(nèi)容進(jìn)行匹配马昨,但我們不能反推哈希結(jié)果竞帽,從而得不到原始值。

View

函數(shù)可以被聲明為view鸿捧,在這種情況下屹篓,它們承諾不修改狀態(tài)。

以下語句被認(rèn)為是修改狀態(tài):

  1. 寫入狀態(tài)變量匙奴。
  2. 發(fā)射事件堆巧。
  3. 創(chuàng)建其他合約。
  4. 使用selfdestruct泼菌。
  5. 通過調(diào)用發(fā)送Ether谍肤。
  6. 調(diào)用其他函數(shù)不被標(biāo)記view或者pure。
  7. 使用低等級調(diào)用哗伯。
  8. 使用包含某些操作碼的內(nèi)聯(lián)匯編荒揣。
contract C {
    function f(uint a, uint b) view returns (uint) {
        return a * (b + 42) + now;
    }
}
  • constant是view的別名。

  • Getter方法被標(biāo)記為view焊刹。

  • 編譯器并沒有強(qiáng)制執(zhí)行view方法不修改狀態(tài)系任。

Pure

函數(shù)可以聲明為pure恳蹲,在這種情況下,它們承諾不會從該狀態(tài)中讀取或修改該狀態(tài)赋除。

除了上述狀態(tài)修改語句的列表外阱缓,以下是從狀態(tài)讀取的:

  1. 從狀態(tài)變量讀取。
  2. 訪問this.balance或.balance举农。
  3. 訪問block荆针,tx,msg的任何成員(除了msg.sig和msg.data)颁糟。
  4. 調(diào)用任何未標(biāo)記為pure的功能航背。
  5. 使用包含某些操作碼的內(nèi)聯(lián)匯編。
contract C {
    function f(uint a, uint b) pure returns (uint) {
        return a * (b + 42);
    }
}

編譯器并沒有強(qiáng)制執(zhí)行pure方法不是從狀態(tài)中讀取的棱貌。

繼承(Inheritance)

pragma solidity ^0.4.0;

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

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

抽象(Abstract Contracts)

抽象函數(shù)是沒有函數(shù)體的的函數(shù)玖媚。如下:

pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
}

這樣的合約不能通過編譯,即使合約內(nèi)也包含一些正常的函數(shù)婚脱。但它們可以做為基合約被繼承今魔。

pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
    
    function getContractName() returns (string){
        return "Feline";
    }
}

contract Cat is Feline {
    function utterance() returns (bytes32) { return "miaow"; }
}

如果一個合約從一個抽象合約里繼承,但卻沒實現(xiàn)所有函數(shù)障贸,那么它也是一個抽象合約错森。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市篮洁,隨后出現(xiàn)的幾起案子涩维,更是在濱河造成了極大的恐慌,老刑警劉巖袁波,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓦阐,死亡現(xiàn)場離奇詭異,居然都是意外死亡篷牌,警方通過查閱死者的電腦和手機(jī)睡蟋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枷颊,“玉大人薄湿,你說我怎么就攤上這事⊥滴裕” “怎么了豺瘤?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長听诸。 經(jīng)常有香客問我坐求,道長,這世上最難降的妖魔是什么晌梨? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任桥嗤,我火速辦了婚禮须妻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泛领。我一直安慰自己荒吏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布渊鞋。 她就那樣靜靜地躺著绰更,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锡宋。 梳的紋絲不亂的頭發(fā)上儡湾,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音执俩,去河邊找鬼徐钠。 笑死,一個胖子當(dāng)著我的面吹牛役首,可吹牛的內(nèi)容都是我干的尝丐。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼衡奥,長吁一口氣:“原來是場噩夢啊……” “哼爹袁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杰赛,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矮台,沒想到半個月后乏屯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瘦赫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年辰晕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片确虱。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡含友,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出校辩,到底是詐尸還是另有隱情窘问,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布宜咒,位于F島的核電站惠赫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏故黑。R本人自食惡果不足惜儿咱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一庭砍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧混埠,春花似錦怠缸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至使套,卻和暖如春罐呼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侦高。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工嫉柴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奉呛。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓计螺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞧壮。 傳聞我的和親對象是個殘疾皇子登馒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

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