solidity learning (1)

學習文檔筆記:
http://solidity-cn.readthedocs.io/zh/develop/layout-of-source-files.html

1.pragma solidity ^0.4.0;
這樣,意味着源文件將既不容許低於 0.4.0 版本的編譯器編譯, 也不容許高於(包含) 0.5.0 版本的編譯器編譯(第二個條件因使用 ^ 被添加)

2.導入
import * as symbolName from 「filename」;//或「.sol」文件
等同於import "filename" as symbolName;

3.註釋
單行(//)、多行註釋(/*…*/)和(/** ... */)在函數開頭或內部作的註釋
/** @title 形狀計算器。 */
contract shapeCalculator {
    /** @dev 求矩形代表面積與周長。
    * @param w 矩形寬度。
    * @param h 矩形高度。
    * @return s 求得表面積。
    * @return p 求得周長。
    */
    function rectangle(uint w, uint h) returns (uint s, uint p) {
        s = w * h;
        p = 2 * (w + h);
    }
}

4.二者差異是什麼啊?????
枚舉類型¶
枚舉可用來建立由必定數量的「常量值」構成的自定義類型
enum State { Created, Locked, Inactive }
枚舉下標定義從左至右從零開始。
Created=0, Locked=1, Inactive =2
訪問枚舉方式 State.Created 實際等於數字 0


結構類型¶
結構是能夠將幾個變量分組的自定義類型
    struct Voter { // 結構
        uint weight;
        bool voted;
        address delegate;
        uint vote;
    }

enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    ActionChoices choice;
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;

    function setGoStraight() public {
        choice = ActionChoices.GoStraight;
    }

    // 因爲枚舉類型不屬於 |ABI| 的一部分,所以對於全部來自 Solidity 外部的調用,
    // "getChoice" 的簽名會自動被改爲 "getChoice() returns (uint8)"。
    // 整數類型的大小已經足夠存儲全部枚舉類型的值,隨着值的個數增長,
    // 能夠逐漸使用 `uint16` 或更大的整數類型。
    function getChoice() public view returns (ActionChoices) {
        return choice;

我知道枚舉的差異在哪裏了,枚舉裏面的是常量,就像是咱們以前設值時,性別枚舉的值就有男和女,因此當你聲明一個枚舉類型的變量的時候,它的值要麼是男,要麼是女這樣

5.整型¶
int / uint :分別表示有符號和無符號的不一樣位數的整型變量。 支持關鍵字 uint8 到 uint256 (無符號,從 8 位到 256 位)以及 int8 到 int256,以 8 位爲步長遞增。 uint 和 int 分別是 uint256 和 int256 的別名

6.定長浮點型¶-如今還不用考慮怎麼使用它
警告
Solidity 尚未徹底支持定長浮點型。能夠聲明定長浮點型的變量,但不能給它們賦值或把它們賦值給其餘變量。。
fixed / ufixed:表示各類大小的有符號和無符號的定長浮點型。 在關鍵字 ufixedMxN 和 fixedMxN 中,M 表示該類型佔用的位數,N 表示可用的小數位數。 M 必須能整除 8,即 8 到 256 位。 N 則能夠是從 0 到 80 之間的任意數。 ufixed 和 fixed 分別是 ufixed128x19 和 fixed128x19 的別名。

7.地址類型¶
address:地址類型存儲一個 20 字節的值(以太坊地址的大小)
可使用 balance 屬性來查詢一個地址的餘額, 也可使用 transfer 函數向一個地址發送 以太幣
Ether
(以 wei 爲單位)

註解
若是 x 是一個合約地址,它的代碼(更具體來講是它的 fallback 函數,若是有的話)會跟 transfer 函數調用一塊兒執行(這是 EVM 的一個特性,沒法阻止)。 若是在執行過程當中用光了 gas 或者由於任何緣由執行失敗,以太幣Ether交易會被打回,當前的合約也會在終止的同時拋出異常。
to.transfer(msg.value)//to爲接受者的地址

8.call()\delegeteCall()
(1)爲了與不符合 應用二進制接口
Application Binary Interface(ABI)
的合約交互,因而就有了能夠接受任意類型任意數量參數的 call 函數(可是仍是不太理解它的用處)
call 返回的布爾值代表了被調用的函數已經執行完畢(true)或者引起了一個 EVM 異常(false)。 沒法訪問返回的真實數據
可使用 .gas() 修飾器
modifier
調整提供的 gas 數量相似地,也能控制提供的 以太幣
Ether
的值,每一個修改器出現的順序不重要
nameReg.call.gas(1000000).value(1 ether)("register", 「MyName");

(2)delegatecall 的目的是使用存儲在另一個合約中的庫代碼

⚠️這三個函數 call, delegatecall 和 callcode 都是很是低級的函數,須要謹慎使用。 具體來講,任何未知的合約均可能是惡意的。 你在調用一個合約的同時就將控制權交給了它,它能夠反過來調用你的合約, 所以,當調用返回時要爲你的狀態變量的改變作好準備


9.定長字節數組¶
關鍵字有:bytes1, bytes2, bytes3, ..., bytes32。byte 是 bytes1 的別名。
.length 表示這個字節數組的長度(只讀)
註解
能夠將 byte[] 看成字節數組使用,但這種方式很是浪費存儲空間,準確來講,是在傳入調用時,每一個元素會浪費 31 字節。 更好地作法是使用 bytes。

10.變長字節數組¶
bytes:
變長字節數組,參見 數組。它並非值類型。
string:
變長 UTF-8 編碼字符串類型,參見 數組。並非值類型。


11.字面常數-須要好好看看
Solidity 中是沒有八進制的,所以前置 0 是無效的
註解
數值字面常數表達式只要在非字面常數表達式中使用就會轉換成非字面常數類型。 在下面的例子中,儘管咱們知道 b 的值是一個整數,但 2.5 + a 這部分表達式並不進行類型檢查,所以編譯不能經過。
//在remix中編譯的時候的確是會報錯
browser/test.sol:4:17: TypeError: Operator + not compatible with types rational_const 5 / 2 and uint128
    uint128 b = 2.5 + a + 0.5;
                ^-----^
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;

意思應該就是說由於在字面上咱們能夠看見不進行類型檢查時a是非字面常數類型

十六進制字面常數以關鍵字 hex 打頭,後面緊跟着用單引號或雙引號引發來的字符串(例如,hex」001122FF")


12.函數
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]


與參數類型相反,返回類型不能爲空 —— 若是函數類型不須要返回,則須要刪除整個 returns (<return types>) 部分。

有兩種方法能夠訪問當前合約中的函數:一種是直接使用它的名字,f ,另外一種是使用 this.f 。 前者適用於內部函數internal,後者適用於外部函數external
請注意,當前合約的 public 函數既能夠被看成內部函數也能夠被看成外部函數使用。 若是想將一個函數看成內部函數使用,就用 f 調用,若是想將其看成外部函數,使用 this.f 。

13.library ArrayUtils {
  // internal內部函數能夠在內部庫函數中使用,
  // 由於它們會成爲同一代碼上下文的一部分
…}
contract Pyramid {
  using ArrayUtils for *;//使用,而後經過ArrayUtils.函數名()調用
}

14.數據位置——重點,一直沒有搞懂的地方
有三種類型,memory,storage和calldata,通常只有外部函數的參數(不包括返回參數)被強制指定爲calldata。這種數據位置是隻讀的,不會持久化到區塊鏈

storage存儲或memory內存
memory存儲位置同咱們普通程序的內存相似,即分配,即便用,動態分配,越過做用域即不可被訪問,等待被回收。
而對於storage的變量,數據將永遠存在於區塊鏈上。

總結¶
強制指定的數據位置:
    •    外部函數的參數(不包括返回參數): calldata,效果跟 memory 差很少
    •    狀態變量: storage
默認數據位置:
    •    函數參數(包括返回參數): memory
    •    全部其它局部變量: storage

舉例說明:
contract C {
    uint[] x; // x 的數據存儲位置是 storage,狀態變量

    // memoryArray 的數據存儲位置是 memory,函數參數
    function f(uint[] memoryArray) public {
        x = memoryArray; // 將整個數組拷貝到 storage 中,可行
        var y = x;  // 分配一個指針(其中 y 的數據存儲位置是 storage),可行,其餘局部變量
        y[7]; // 返回第 8 個元素,可行
        y.length = 2; // 經過 y 修改 x,可行
        delete x; // 清除數組,同時修改 y,可行
        // 下面的就不可行了;須要在 storage 中建立新的未命名的臨時數組
        // 但 storage 是「靜態」分配的:
        // y = memoryArray;




        // 下面這一行也不可行,由於這會「重置」指針,
        // 但並無可讓它指向的合適的存儲位置。
        // delete y;

    }
}

賦值行爲:

(1)當咱們把一個storage類型的變量賦值給另外一個storage時,咱們只是修改了它的指針,一個變,另外一個也變
struct S{string a;uint b;}
  S s;
    
  function convertStorage(S storage s) internal{
    S tmp = s;
    tmp.a = 「Test」;//s的a的值也改變了
  }

(2)memory轉爲memory,memory之間是引用傳遞,並不會拷貝數據,一個變,另外一個也跟着變

(3)memory轉換爲storage
由於局部變量和狀態變量的類型均可能是storage。因此咱們要分開來講這兩種狀況。
1. memory賦值給狀態變量
將一個memory類型的變量賦值給一個狀態變量時,實際是將內存變量拷貝到存儲中,後續二者不會有任何關係
  S s;

  function memoryToState(S memory tmp) internal{
    s = tmp;//從內存中複製到狀態變量中。

    //修改舊memory中的值,並不會影響狀態變量
    tmp.a = 「Test」;//s的就不會變了
  }

2.memory賦值給局部變量是不能夠的
因爲在區塊鏈中,storage必須是靜態分配存儲空間的。局部變量雖然是一個storage的,但它僅僅是一個storage類型的指針。若是進行這樣的賦值,實際會產生一個錯誤。
function assign(S s) internal{ //默認的變量是storage的指針
//error:Type struct MemoryToLocalVar.S memory is not implicitly convertible to expected type struct MemoryToLocalVar.S storage pointer.
S tmp = s; //修改變量爲memory類型S memory tmp = s;便可

}

(4)storage轉爲memory,會建立一份獨立的拷貝,兩兩互不影響



數組:
能夠在聲明時指定長度,也能夠動態調整大小。
對於 存儲storage的數組來講,元素類型能夠是任意的(即元素也能夠是數組類型,映射類型或者結構體)。
 對於 內存memory的數組來講,元素類型不能是映射類型,若是做爲 public 函數的參數,它只能是 ABI 類型。

(1)在memory中:一經建立,內存memory數組的大小就是固定的(但倒是動態的,也就是說,它依賴於運行時的參數
1.內存中的數組是不容許建立變長數組的
uint[] memory a;//wrong
2.數組字面常數是一種定長的 內存memory數組類型
在memory中,聲明定長數組:
uint[] memory a = new uint[](2);

在memory中,聲明定長數組並初始化:
uint[2] memory a = [uint(1),2];
uint[] memory a = [uint(1),2];
//wrong,定長的 內存memory數組並不能賦值給變長的 內存memory數組
爲何要在第一個元素一前面加上uint的類型,這是由於咱們前面聲明的是uint的數組,而在solidity中有uint8,uint248等多種不一樣位數的無符號int型常量,在不聲明的狀況下,默認使用uint256,而咱們初始化的數組元素是1和2,系統會斷定爲uint8,因此須要咱們在第一個元素1前面強制轉化爲uint型

固然,咱們也能夠以下的聲明方式:
uint8[2] memory a = [1,2];
但要注意,那麼a數組裏面就不能夠出現2進制裏面大於8位的數了

定長數組是沒法經過length和push方法更改長度或插入的

(2)在storage中

1.在storage中,聲明定長數組,並初始化變量:
uint[2] a = [1,2];

2.在storage中,聲明變長數組,並初始化變量:

uint[] a = [1,2];

3.二維數組初始化,行列是反過來定義的:
uint[2][3] a = [[1,1],[2,2],[3,3]];
但訪問是相同的:
訪問第三個動態數組的第二個元素,你應該使用 a[2][1]


(3)
bytes 和 string 類型的變量是特殊的數組。 bytes 相似於 byte[],但它在 calldata 中會被「緊打包」(譯者注:將元素連續地存在一塊兒,不會按每 32 字節一單元的方式來存放)。 string 與 bytes 相同,但(暫時)不容許用長度或索引來訪問。
(4)變長的 存儲storage數組以及 bytes 類型(而不是 string 類型)都有一個叫作 push 的成員函數

15.一個合約如何調用另外一個合約
contract MappingExample {
    mapping(address => uint) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

contract MappingUser {
    function f() public returns (uint) {
        MappingExample m = new MappingExample();
        m.update(100);
        return m.balances(this);//this是該合約的地址
    }
}

16.delete的做用:
delete a 的結果是將 a 的類型在初始化時的值賦值給 a。即對於整型變量來講,至關於 a = 0, 但 delete 也適用於數組,對於動態數組來講,是將數組的長度設爲 0,而對於靜態數組來講,是將數組中的全部元素重置。 若是對象是結構體,則將結構體中的全部屬性重置。
delete 對整個映射是無效的(由於映射的鍵能夠是任意的,一般也是未知的)。 所以在你刪除一個結構體時,結果將重置全部的非映射屬性,這個過程是遞歸進行的,除非它們是映射。 然而,單個的鍵及其映射的值是能夠被刪除的。

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() public {
        uint x = data;
        delete x; // 將 x 設爲 0,並不影響數據
        delete data; // 將 data 設爲 0,並不影響 x,由於它仍然有個副本
        uint[] storage y = dataArray;
//能夠經過改變dataArray來改變y,也能夠經過改變y來改變dataArray
        delete dataArray;
        // 將 dataArray.length 設爲 0,但因爲 uint[] 是一個複雜的對象,y 也將受到影響,
        // 由於它是一個存儲位置是 storage 的對象的別名。
        // 另外一方面:"delete y" 是非法的,引用了 storage 對象的局部變量只能由已有的 storage 對象賦值。
    }
}

17.基本類型之間的轉換
(1)隱式轉換
int8 不能轉換成 uint256(由於 uint256 不能涵蓋某些值,例如,-1)。 更進一步來講,無符號整型能夠轉換成跟它大小相等或更大的字節類型,但反之不能。 任何能夠轉換成 uint160 的類型均可以轉換成 address 類型。
(2)顯式轉換
但有些時候會出問題
int8 y = -3;
uint x = uint(y);
這段代碼的最後,x 的值將是 0xfffff..fd (64 個 16 進制字符),由於這是 -3 的 256 位補碼形式。

若是一個類型顯式轉換成更小的類型,相應的高位將被捨棄
uint32 a = 0x12345678;
uint16 b = uint16(a); // 此時 b 的值是 0x5678

類型推斷
爲了方便起見,沒有必要每次都精確指定一個變量的類型,編譯器會根據分配該變量的第一個表達式的類型自動推斷該變量的類型
uint24 x = 0x123;
var y = x;
這裏 y 的類型將是 uint24。不能對函數參數或者返回參數使用 var。

18.時間seconds、 minutes、 hours、 days、 weeks 和 years
years 後綴已經不推薦使用了,由於從 0.5.0 版本開始將再也不支持。

這些後綴不能直接用在變量後邊。若是想用時間單位(例如 days)來將輸入變量換算爲時間,你能夠用以下方式來完成:
function f(uint start, uint daysAfter) public {
    if (now >= start + daysAfter * 1 days) {
        // ...
    }
}

19.在一個私鏈上,你頗有可能碰到因爲 sha25六、ripemd160 或者 ecrecover 引發的 Out-of-Gas。這個緣由就是他們被當作所謂的預編譯合約而執行,而且在第一次收到消息後這些合約才真正存在(儘管合約代碼是硬代碼)。發送到不存在的合約的消息很是昂貴,因此實際的執行會致使 Out-of-Gas 錯誤。在你的合約中實際使用它們以前,給每一個合約發送一點兒以太幣,好比 1 Wei。這在官方網絡或測試網絡上不是問題。

20.
合約相關¶
this (current contract's type):
當前合約,能夠明確轉換爲 地址類型。
selfdestruct(address recipient):
銷燬合約,並把餘額發送到指定 地址類型。
suicide(address recipient):
與 selfdestruct 等價,但已不推薦使用。

21.solidity與 Javascript 和 C 不一樣的是,它們可能返回任意數量的參數做爲輸出。

例子:
    function arithmetics(uint _a, uint _b)
        public
        pure
        returns (uint o_sum, uint o_product)
    {
        o_sum = _a + _b;
        o_product = _a * _b;
    }


22.
JavaScript 中的大部分控制結構在 Solidity 中都是可用的,除了 switch 和 goto。 所以 Solidity 中有 if,else,while,do,for,break,continue,return,``? :``這些與在 C 或者 JavaScript 中表達相同語義的關鍵詞。
用於表示條件的括號 不能夠 被省略,單語句體兩邊的花括號能夠被省略。

注意,與 C 和 JavaScript 不一樣, Solidity 中非布爾類型數值不能轉換爲布爾類型,所以 if (1) { ... } 的寫法在 Solidity 中 無效 。

23.省略函數參數名稱¶
未使用參數的名稱(特別是返回參數)能夠省略。這些參數仍然存在於堆棧中,但它們沒法訪問

contract C {
    // 省略參數名稱
    function func(uint k, uint) public pure returns(uint) {
        return k;
    }

24.經過 new 建立合約¶
使用關鍵字 new 能夠建立一個新合約。待建立合約的完整代碼必須事先知道,所以遞歸的建立依賴是不可能的。

即一個合約中建立另外一個合約,另外一個合約必定要在該合約以前就已經聲明瞭


25.賦值¶
解構賦值和返回多值¶
Solidity 內部容許元組 (tuple) 類型,也就是一個在編譯時元素數量固定的對象列表,列表中的元素能夠是不一樣類型的對象。這些元組能夠用來同時返回多個數值,也能夠用它們來同時給多個新聲明的變量或者既存的變量(或一般的 LValues):
pragma solidity >0.4.23 <0.5.0;

contract C {
    uint[] data;

    function f() public pure returns (uint, bool, uint) {
        return (7, true, 2);
    }

    function g() public {
        //基於返回的元組來聲明變量並賦值
        (uint x, bool b, uint y) = f();
        //交換兩個值的通用竅門——但不適用於非值類型的存儲 (storage) 變量。
        (x, y) = (y, x);
        //元組的末尾元素能夠省略(這也適用於變量聲明)。
        (data.length,,) = f(); // 將長度設置爲 7
        //省略元組中末尾元素的寫法,僅能夠在賦值操做的左側使用,除了這個例外:
        (x,) = (1,);
        //(1,) 是指定單元素元組的惟一方法,由於 (1)
        //至關於 1。
    }
}

26.變量聲明後將有默認初始值,其初始值字節表示所有爲零。任何類型變量的「默認值」是其對應類型的典型「零狀態」,因此並不須要本身賦值,聲明便可
例如, bool 類型的默認值是 false 。 uint 或 int 類型的默認值是 0 。對於靜態大小的數組和 bytes1 到 bytes32 ,每一個單獨的元素將被初始化爲與其類型相對應的默認值。 最後,對於動態大小的數組, bytes 和 string 類型,其默認缺省值是一個空數組或字符串。


27.錯誤處理:
在下例中,你能夠看到如何輕鬆使用``require``檢查輸入條件以及如何使用``assert``檢查內部錯誤,注意,你能夠給 require 提供一個消息字符串,而 assert 不行。
pragma solidity ^0.4.22;

contract Sharer {
    function sendHalf(address addr) public payable returns (uint balance) {
        require(msg.value % 2 == 0, "Even value required.");
        uint balanceBeforeTransfer = this.balance;
        addr.transfer(msg.value / 2);
                    //因爲轉移函數在失敗時拋出異常而且不能在這裏回調,所以咱們應該沒有辦法仍然有一半的錢。
        assert(this.balance == balanceBeforeTransfer - msg.value / 2);
        return this.balance;
    }
}

下列狀況將會產生一個 assert 式異常:
    1    若是你訪問數組的索引太大或爲負數(例如 x[i] 其中 i >= x.length 或 i < 0)。
    2    若是你訪問固定長度 bytesN 的索引太大或爲負數。
    3    若是你用零當除數作除法或模運算(例如 5 / 0 或 23 % 0 )。
    4    若是你移位負數位。
    5    若是你將一個太大或負數值轉換爲一個枚舉類型。
    6    若是你調用內部函數類型的零初始化變量。
    7    若是你調用 assert 的參數(表達式)最終結算爲 false。
下列狀況將會產生一個 require 式異常:
    1    調用 throw 。
    2    若是你調用 require 的參數(表達式)最終結算爲 false 。
    3    若是你經過消息調用調用某個函數,但該函數沒有正確結束(它耗盡了 gas,沒有匹配函數,或者自己拋出一個異常),上述函數不包括低級別的操做 call , send , delegatecall 或者 callcode 。低級操做不會拋出異常,而經過返回 false 來指示失敗。
    4    若是你使用 new 關鍵字建立合約,但合約沒有正確建立(請參閱上條有關」未正確完成「的定義)。
    5    若是你對不包含代碼的合約執行外部函數調用。
    6    若是你的合約經過一個沒有 payable 修飾符的公有函數(包括構造函數和 fallback 函數)接收 Ether。
    7    若是你的合約經過公有 getter 函數接收 Ether 。
    8    若是 .transfer() 失敗。
在內部, Solidity 對一個 require 式的異常執行回退操做(指令 0xfd )並執行一個無效操做(指令 0xfe )來引起 assert 式異常。 在這兩種狀況下,都會致使 EVM 回退對狀態所作的全部更改。回退的緣由是不能繼續安全地執行,由於沒有實現預期的效果。 由於咱們想保留交易的原子性,因此最安全的作法是回退全部更改並使整個交易(或至少是調用)不產生效果。 請注意, assert 式異常消耗了全部可用的調用 gas ,而從 Metropolis 版本起 require 式的異常不會消耗任何 gas。
下邊的例子展現瞭如何在 revert 和 require 中使用錯誤字符串:
pragma solidity ^0.4.22;

contract VendingMachine {
    function buy(uint amount) payable {
        if (amount > msg.value / 2 ether)
            revert("Not enough Ether provided.");
        // 下邊是等價的方法來作一樣的檢查:
        require(
            amount <= msg.value / 2 ether,
            "Not enough Ether provided."
        );
        // 執行購買操做
    }
}
這裏提供的字符串應該是通過 ABI 編碼 以後的,由於它其實是調用了 Error(string) 函數。在上邊的例子裏,revert("Not enough Ether provided."); 會產生以下的十六進制錯誤返回值:
0x08c379a0                                                         // Error(string) 的函數選擇器
0x0000000000000000000000000000000000000000000000000000000000000020 // 數據的偏移量(32)
0x000000000000000000000000000000000000000000000000000000000000001a // 字符串長度(26)
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // 字符串數據("Not enough Ether





28.合約
contract OwnedToken {

    TokenCreator creator;

    function OwnedToken(bytes32 _name) public {
//這裏的意思就是顯示地將msg.sender這個地址轉換成了TokenCreator合約,這樣以後就能夠調用該合約中的函數了
        creator = TokenCreator(msg.sender);

       creator.isTokenTransferOK(owner, newOwner)

    }


contract TokenCreator {…}

29.可見性
contract C {
    uint private data;

    function f(uint a) private returns(uint b) { return a + 1; }//不可繼承
    function setData(uint a) public { data = a; }
    function getData() public returns(uint) { return data; }
    function compute(uint a, uint b) internal returns (uint) { return a+b; }//可繼承
}

contract D {//這能訪問public
    function readData() public {
        C c = new C();
        uint local = c.f(7); // error: member `f` is not visible
        c.setData(3);
        local = c.getData();
        local = c.compute(3, 5); // error: member `compute` is not visible
    }
}

contract E is C {//繼承至C,因此能夠訪問internal,public
    function g() public {
        C c = new C();
        uint val = compute(3, 5); // access to internal member (from derived to parent contract)
    }
}


30.getter函數
(1)就是在合約中聲明的狀態變量其實都自動地生成了getter函數,就是能夠像訪問函數同樣訪問它的值,在合約外訪問時就能夠直接 合約名.data()
pragma solidity ^0.4.0;

contract C {
    uint public data = 42;
}

contract Caller {
    C c = new C();
    function f() public {
        uint local = c.data();
    }
}
(2)若是是在合約內部訪問,它有兩種訪問的形式:internal和external
internal則是直接變量名訪問便可
external則是使用this.data()
pragma solidity ^0.4.0;

contract C {
    uint public data;
    function x() public {
        data = 3; // internal access
        uint val = this.data(); // external access
    }
}


31.
contract Complex {
//Note that the mapping in the struct is omitted because there is no good way to provide the key for the mapping.
    struct Data {
//若是在一個結構體中聲明瞭一個映射,通常賦值時都先省略,而後在賦值mapping,由於它的key是不固定的
        uint a;
        bytes3 b;
        mapping (uint => uint) map;
    }
    mapping (uint => mapping(bool => Data[])) public data;
//調用方法:data[arg1][arg2][arg3].a
}

32.修飾器modifier
繼承後能夠直接使用被繼承處的修飾器

使用修改器實現的一個防重複進入的例子。
pragma solidity ^0.4.0;
contract Mutex {
    bool locked;
    modifier noReentrancy() {
        if (locked) throw;
        locked = true;
        _;//函數體f()return前的內容執行的區域
        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()方法有可能會調回當前方法,修改器實現了防重入的檢查。
若是同一個函數有多個修改器,他們之間以空格隔開,修飾器會依次檢查執行。
須要注意的是,在Solidity的早期版本中,有修改器的函數,它的return語句的行爲有些不一樣。
在修改器中和函數體內的顯式的return語句,僅僅跳出當前的修改器和函數體。返回的變量會被賦值,但整個執行邏輯會在前一個修改器後面定義的」_"後繼續執行。

33.fallback function \ call()\send()
contract ExecuteFallback{

  //回退事件,會把調用的數據打印出來
  event FallbackCalled(bytes data);
  //fallback函數,注意是沒有名字的,沒有參數,沒有返回值的
  function(){
    FallbackCalled(msg.data);
  }

  //調用已存在函數的事件,會把調用的原始數據,請求參數打印出來
  event ExistFuncCalled(bytes data, uint256 para);
  //一個存在的函數
  function existFunc(uint256 para){
    ExistFuncCalled(msg.data, para);
  }

  // 模擬從外部對一個存在的函數發起一個調用,將直接調用函數
  function callExistFunc(){
    bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)"));
    this.call(funcIdentifier, uint256(1));
  }

  //模擬從外部對一個不存在的函數發起一個調用,因爲匹配不到函數,將調用回退函數
  function callNonExistFunc(){
    bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()"));
    this.call(funcIdentifier);
  }
}

Call():call()是一個底層的接口,用來向一個合約發送消息,進行合約之間的交互,可是很不安全,通常是不用的。這個函數是這樣的,第一個參數是該訪問的函數的名字,後面的參數則是該函數所需的參數,調用它的是一個合約的地址

回退函數:則是當調用一個合約裏的某個函數時,若是該函數並不存在,那麼就會去調用該回調函數,回調函數無參,無名,無返回值,你能夠經過在裏面返回個什麼或者emit一個事件來顯示調用了該回退函數,該函數仍是頗有用的

msg.data其實就是你調用一個函數後,函數名,參數進行keccak256哈希後鏈接起來的data

send()函數發送ether
當咱們使用address.send(ether to send)向某個合約直接轉賬時,因爲這個行爲沒有發送任何數據,因此接收合約老是會調用fallback函數
function() payable{fallbackTrigged(msg.data);}

  function deposit() payable{//用來給合約存點錢
  }
在上述的代碼中,咱們先要使用deposit()合約存入一些ether,不然因爲餘額不足,調用send()函數將報錯

fallbackTrigged[
  "0x"
]
能夠看到,咱們成功使用send()發送了1wei到合約,觸發了fallback函數,附帶的數據是0x(bytes類型的默認空值),空數據。
這裏須要特別注意的是:
    1.    若是咱們要在合約中經過send()函數接收,就必須定義fallback函數,不然會拋異常。
    2.    fallback函數必須增長payable關鍵字,不然send()執行結果將會始終爲false。



fallback中的限制
send()函數老是會調用fallback,這個行爲很是危險,著名的DAO被黑也與這有關。若是咱們在分成時,對一系列賬戶進行send()操做,其中某個作惡意賬戶中的fallback函數實現了一個無限循環,將由於gas耗盡,致使全部send()失敗。爲解決這個問題,send()函數當前即使gas充足,也只會附帶限定的2300gas,故而fallback函數內除了能夠進行日誌操做外,你幾乎不能作任何操做。若是你還想作一些複雜的操做,解決方案看這裏:http://me.tryblockchain.org/blockchain-solidity-fallback-bestpractice.html。
下述行爲消耗的gas都將超過fallback函數限定的gas值:
    •    向區塊鏈中寫數據
    •    建立一個合約
    •    調用一個external的函數
    •    發送ether
因此通常,咱們只能在fallback函數中進行一些日誌操做:

33.底層的日誌接口(Low-level Interface to Logs):能夠代替event
經過函數log0,log1,log2,log3,log4,能夠直接訪問底層的日誌組件。logi表示總共有帶i + 1個參數(i表示的就是可帶參數的數目,只是是從0開始計數的)。其中第一個參數會被用來作爲日誌的數據部分,其它的會作爲主題(topics)。前面例子中的事件可改成以下:
log3(
    msg.value,
    0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
    msg.sender,
    _id
);
其中的長16進制串是事件的簽名,計算方式是keccak256("Deposit(address,hash256,uint256)")


34.繼承
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)

當基類的構造函數中若是須要傳參,那麼繼承它時的方式是:
contract Base {
    uint x;
    function Base(uint _x) { x = _x; }
}


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

繼承寫的順序是很重要的,從繼承少的到多的

35.抽象(Abstract Contracts)
抽象函數是沒有函數體的的函數。以下:
pragma solidity ^0.4.0;

contract Feline {
    function utterance() returns (bytes32);
}
這樣的合約不能經過編譯,即便合約內也包含一些正常的函數。但它們能夠作爲基合約被繼承。
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"; }
}
若是一個合約從一個抽象合約裏繼承,但卻沒實現全部函數,那麼它也是一個抽象合約。

36.接口(interface)
接口與抽象合約相似,與之不一樣的是,接口內沒有任何函數是已實現的,同時還有以下限制:
    1.    不能繼承其它合約,或接口。
    2.    不能定義構造器
    3.    不能定義變量
    4.    不能定義結構體
    5.    不能定義枚舉類
其中的一些限制可能在將來放開。
接口基本上限制爲合約ABI定義能夠表示的內容,ABI和接口定義之間的轉換應該是可能的,不會有任何信息丟失。
接口用本身的關鍵詞表示:
interface Token {
    function transfer(address recipient, uint amount);
}
合約能夠繼承於接口,由於他們能夠繼承於其它的合約。

37.library
使用庫合約的合約,能夠將庫合約視爲隱式的父合約(base contracts),固然它們不會顯式的出如今繼承關係中。意思就是不用寫is來繼承,直接能夠在合約中使用:
library Set {
  struct Data { mapping(uint => bool) flags; }
}

contract C {
    Set.Data knownValues;
}
但調用庫函數的方式很是相似,如庫L有函數f(),使用L.f()便可訪問。此外,internal的庫函數對全部合約可見,若是把庫想像成一個父合約就能說得通了。固然調用內部函數使用的是internal的調用慣例,這意味着全部internal類型能夠傳進去,memory類型則經過引用傳遞,而不是拷貝的方式。


library Set {
  // We define a new struct datatype that will be used to
  // hold its data in the calling contract.
  struct Data { mapping(uint => bool) flags; }

  // Note that the first parameter is of type "storage
  // reference" and thus only its storage address and not
  // its contents is passed as part of the call.  This is a
  // special feature of library functions.  It is idiomatic
  // to call the first parameter 'self', if the function can
  // be seen as a method of that object.
  function insert(Data storage self, uint value)
      returns (bool)
  {
      if (self.flags[value])
          return false; // already there
      self.flags[value] = true;
      return true;
  }

  function remove(Data storage self, uint value)
      returns (bool)
  {
      if (!self.flags[value])
          return false; // not there
      self.flags[value] = false;
      return true;
  }

  function contains(Data storage self, uint value)
      returns (bool)
  {
      return self.flags[value];
  }
}


contract C {
    Set.Data knownValues;

    function register(uint value) {
        // The library functions can be called without a
        // specific instance of the library, since the
        // "instance" will be the current contract.
        if (!Set.insert(knownValues, value))
            throw;
    }
    // In this contract, we can also directly access knownValues.flags, if we want.
}
上面的例子中:
    •    Library定義了一個數據結構體struct Data,用來在調用的合約中使用(庫自己並未實際存儲的數據)。若是函數須要操做數據,這個數據通常是經過庫函數的第一個參數傳入(Data storage self),按慣例會把參數名定爲self。
    •    另一個須要留意的是上例中self的類型是storage,那麼意味着傳入的會是一個引用,而不是拷貝的值,那麼修改它的值,會同步影響到其它地方,俗稱引用傳遞,非值傳遞。
    •    庫函數的使用不須要實例化,c.register中能夠看出是直接使用Set.insert。但實際上當前的這個合約自己就是它的一個實例。
    •    這個例子中,c能夠直接訪問knownValues。雖然這個值主要是被庫函數使用的

對比普通合約來講,庫的限制:
    •    無狀態變量(state variables)。
    •    不能繼承或被繼承
    •    不能接收ether。


附着庫(Using for)
指令using A for B;用來附着庫裏定義的函數(從庫A)到任意類型B。這些函數將會默認接收調用函數對象的實例做爲第一個參數。語法相似,python中的self變量同樣。
using A for *的效果是,庫A中的函數被附着在作任意的類型上。
在這兩種情形中,全部函數,即便那些第一個參數的類型與調用函數的對象類型不匹配的,也被附着上了。類型檢查是在函數被真正調用時,函數重載檢查也會執行。
using A for B;指令僅在當前的做用域有效,且暫時僅僅支持當前的合約這個做用域,後續也很是有可能解除這個限制,容許做用到全局範圍。若是能做用到全局範圍,經過引入一些模塊(module),數據類型將能經過庫函數擴展功能,而不須要每一個地方都得寫一遍相似的代碼了。
上面的例子就改爲了:

contract C {
    using Set for Set.Data; // this is the crucial change
    Set.Data knownValues;

    function register(uint value) {
        // Here, all variables of type Set.Data have
        // corresponding member functions.
        // The following function call is identical to
        // Set.insert(knownValues, value)
        if (!knownValues.insert(value))
            throw;
    }
}
//其實就是原本要訪問的話的語句是Set.insert(knownValues, value),如今是knownValues.insert(value),即將庫中的函數都附着在告終構體struct Data上,由於以前庫函數的第一個參數的聲明其實也是這個結構體(self),附着後調用時就能夠省略掉第一個參數了,就算沒有結構體,那麼附着的必定是函數的第一個參數聲明的那個self的類型
如:
library Search {
    function indexOf(uint[] storage self, uint value)

contract C {
    using Search for uint[];
    uint[] data;









html

相關文章
相關標籤/搜索