【鏈塊技術39期】智能合約基礎語言(三)——Solidity變量類型:值類型

原文鏈接:智能合約基礎語言(三):Solidity變量類型:值類型(下)

 

一、目錄

☛值類型和引用類型的區別

☛布爾類型(bool)

☛整型(int、uint)

☛定點型小數(fixed、ufixed)

☛地址類型(address)

☛定長字節數組(bytes1,bytes2,bytes3,...,bytes32)

☛有理數和整數字面量

☛地址字面量

☛字符串字面量

☛十六進制字面量(hex)

☛枚舉類型(enum)

二、值類型和引用類型的區別

Solidity變量類型分爲兩大類——值類型、引用類型

值類型:變量的存儲空間存的是變量的數據
引用類型:變量的存儲空間存的是變量數據所在的存儲空間的地址

注意:值傳遞和引用傳遞。值類型的變量,賦值是簡單的值傳遞,即兩個變量佔有獨立的存儲區域。引用類型賦值傳遞的是變量的引用,即兩個變量指向同一存儲區域

三、值類型——布爾(bool)

bool: 只有兩種值true和false(默認false)。

3.1 支持的運算符:

▪ ! 邏輯非

▪ && 邏輯與

▪ || 邏輯或

▪ == 等於

▪ != 不等於

3.2 實例

bool a = true;
bool b = !a;

// a == b -> false
// a != b -> true
// a || b -> true
// a && b -> false

邏輯與(&&)和邏輯或(||)都遵循短路原則,即如果根據前一個表達式可以得到運算結果,則不會執行後面的表達式。

四、值類型——整型(int/uint)

▪ int(m):有符號整數

▪ uint(m):無符號整數

▪ m關鍵字取值爲8~256步幅是8 ,表示在內存中2進制的位數,控制了整數的取值範圍,不寫默認爲256。

▪ uint和int分別是uint256和int256的別名。

▪ m一定要是8的整數倍

4.1 操作


 

比較:<=,<,==,!=,>=,>(結果爲bool)
位操作符:&,|,^(按位異或),~(按位取反)
算術運算符:+, - ,一元 - ,一元 +,*,/,%(取餘),**(冪),<<(左移),>>(右移)

注意:

▪ 除法總是截斷,但如果兩個運算符都是常量(或常量表達式),則它不會截斷。

▪ 除零和取餘有零引發異常。

▪ 左移幾位和右移幾位相當於乘以或者除以2的幾次方,如果參數爲負數的話會引發異常。

▪ 在Solidity中不支持八進制。

五、值類型——定點數

5.1 定點小數

到現在爲止還沒有被solidity完全支持,可以被聲明,不能被賦值也不能用定點數賦值fixed/ufixed 各種大小的有符號和無符號定點小數,ufixedMxN and fixedMxN關鍵字M代表定點數佔用的二進制位數,N代表定點數能表示多少位小數,M必須是8-256之間的,以8爲步幅的整數,N必須是0-80之間的整數,ufixed 和fixed 默認爲ufixed128x18和fixed128x18。

▪ 比較運算: <=, <, ==, !=, >=, > (結果爲bool)

▪ 算數運算: +, -, 一元-, 一元 +, *, /, % 取餘

六、值類型——地址類型(address)

在瞭解地址之前需要知道一個名詞---ABI協議

Application Binary Interface 應用二進制接口,是從區塊鏈外部與合約進行交互以及合約與合約間進行交互的一種標準方式。ABI是以太坊的一種合約間調用時的一個消息格式。類似Webservice裏的SOAP(Simple Object Access Protocol簡單對象訪問協議)協議一樣。

常見格式:

[
   {		
      "constant": false,	
      "inputs": [
	   {				
                "name": "index",	
                "type": "uint8"
	   },
	   {		
                 "name": "s",
                 "type": "string"
	   }
	],		
       "name": "removeItem",		
       "outputs": [
	    {				
               "name": "",				
               "type": "uint256"
	    }
       ],		
       "payable": false,		
       "stateMutability": "nonpayable",		
       "type": "function"
    }
]

地址:保存一個20字節值,使用40位16進制數表示。地址類型也有成員,並作爲所有合約的基礎。

6.1 地址成員

▪ 以wei位單位返回該地址的餘額

<address>.balance(uint256)

▪ 從當前合約地址中給調用函數的地址賬戶轉入amounts數量(以wei爲單位)的金額,即從當前合約轉賬到某賬戶地址。如果執行失敗,將拋出異常,需要支付2300gas的費用,不可以調整。

<address>.transfer(uint256 amount)

其中address就是要把合約中的幣轉到哪個賬號地址。

範例:

pragma solidity ^0.4.24;

contract AddressExample {    
    //定義一個接受以太幣的函數往合約裏充值
    function AddressExample()payable{}    
   //函數的參數分別是劃轉到某賬號地址以及轉賬數量
    function giveEthersTo(address _toAccount,uint amount){ 
        if (this.balance >=amount){
            _toAccount.transfer(amount);
        }
    }    
    function getBalance()view returns(uint){        
        return this.balance;
    }    
    //定義一個匿名的回退函數接收異常情況下退回的以太幣
    function() payable{}
}

▪ 如果合約地址調用transfer,那麼需要合約地址有payable類型的回退函數,並且會隨着轉賬一塊執行回退函數中的代碼,如果因爲回退函數中的代碼執行把gas消耗光了,EVM會拋出異常,轉賬也會被回退。

▪ send是低級對等的轉賬。執行失敗,不會拋出異常,會返回false,需要支付2300gas的費用,不可以調整。推薦使用transfer而不使用send。

<address>.send(uint256 amount) returns (bool) 

▪ call(), callcode() 和 delegatecall() 函數。

<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.calldelegate(...) returns (bool)

爲了和非ABI協議;也就是定義操作函數簽名,參數編碼,返回結果編碼等的合約進行交互,可以使用call() 函數, 它用來向另一個合約發送原始數據,支持任何類型任意數量的參數,每個參數會按規則(ABI協議)打包成32字節並一一拼接到一起。一個例外是:如果第一個參數恰好4個字節,在這種情況下,會被認爲根據ABI協議定義的函數器指定的函數簽名而直接使用。如果僅想發送消息體,需要避免第一個參數是4個字節。如下面的例子:

address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);

 call()

是一個底層的接口,用來向一個合約發送消息,也就是說如果你想實現自己的消息傳遞,可以使用這個函數。函數支持傳入任意類型的任意參數,並將參數打包成32字節,相互拼接後向合約發送這段數據。 call函數返回一個bool值,以表明執行成功與否。正常結束返回true,異常終止返回false。但無法獲取到結果數據。還可以提供.gas()修飾器進行調用:

namReg.call.gas(1000000)("register", "MyName");

類似還可以提供附帶以太幣:

nameReg.call.value(1 ether)("register", "MyName");

修飾器可以混合使用,修飾器調用順序無所謂。

nameReg.call.gas(1000000).value(1 ether)("register", "MyName");

 delegatecall()

call與delegatecall的功能類似,區別僅在於後者僅使用給定地址的代碼,僅僅是代碼會執行,其它信息則使用當前合約(如存儲,餘額等等)。

函數的設計目的是爲了使用存儲在另一個合約的庫代碼。

所以開發者在提供這樣的庫時,就要如何安排存儲來達到這樣的目的。

delegatecall()方法的目的是用來執行另一個合約中的庫代碼。所以開發者需要保證兩個合約中的存儲變量能兼容,來保證delegatecall()能順利執行。

 callcode()

未提供對msg.sender,msg.value的訪問權限。

call和callcode以及delegateCall的區別:

三者的區別主要體現在三個方面,作用域(上下文)、msg.sender、以及this。

以下是比較示例:

contract D {
  uint public n;
  address public sender;

  function callSetN(address _e, uint _n) {    
    _e.call(bytes4(sha3("setN(uint256)")), _n); // 執行結果:改變了E中的變量值, 而當前的同名變量並沒改變。 
  }

  function callcodeSetN(address _e, uint _n) {    
    _e.callcode(bytes4(sha3("setN(uint256)")), _n); // 執行結果:當前狀態變量被改變,而E的並沒改變。 
  }

  function delegatecallSetN(address _e, uint _n) {    
    _e.delegatecall(bytes4(sha3("setN(uint256)")), _n); //執行結果同callcode
  }
}

contract E {
  uint public n;
  address public sender;

  function setN(uint _n) {
    n = _n;
    sender = msg.sender;    
    // 如果D用call的方式調用,則msg.sender是D的合約地址
    //如果D用callcode的方式調用,則msg.sender不會被獲取
    //如果D用delegateCall的方式調用,msg.sender也不能被獲取
    // 如果是C中的函數foo()間接調用了. msg.sender則是C

    // 如果D用callcode或者delegateCall調用,則this指向D
  }
}

contract C {
    function foo(D _d, E _e, uint _n) {        
        _d.delegatecallSetN(_e, _n);
    }
}  

上面的這三個方法call(),delegatecall(),callcode()都是底層的消息傳遞調用,最好僅在萬不得已才進行使用,因爲他們破壞了Solidity的類型安全。 .gas() 在call(), callcode() 和 delegatecall() 函數下都可以使用, delegatecall()不支持.value()。

pragma solidity ^0.4.24;

contract Person{    
uint age = 10;     
    
    function increaseAge(string name, uint num) returns (uint){        
        return ++age;
    }    
    function getAge()returns (uint){        
        return age;
    }

}

contract CallTest{    

    function callByFun(address addr)returns (bool){
        bytes4 methodId = bytes4(keccak256("increaseAge(string,uint256)"));        
        return addr.call(methodId,"jack", 1);
    }
}

注意:

合約中使用的this表示當前合約地址;

所有的合約對象都可以被轉成地址類型,查詢當前合約的餘額。

address(this).balance
this.balance

七、值類型——定長字節數組

bytes1, ... ,bytes32,允許值以步長1遞增。byte默認表示bytes1。

7.1 操作

▪ 比較:<=,<,==,!=,>=,>(評估爲bool)

▪ 位運算符:&,|,^(按位異或),~(按位取反),<<(左移),>>(右移)

▪ 索引訪問:如果x的類型爲bytesI,則0 <= k <I的x [k]返回第k個字節(只讀)。

7.2 成員

.length產生字節數組的固定長度(只讀)。

八、值類型——有理數和整數字面量

整數常量和有理數常量均支持科學計數法。基數可以是小數,指數必須是整數。 例如2e10,-2e10,2e-10,2.5e10。

8.1 有理字面量(特指小數字面量)

▪ 有理數字面量帶一個.,在.的兩邊至少要有一個數字,有效的表示如下1.,.1,1.2

▪ 不允許位運算,小數不能用作指數

▪ 有理數本身支持任意精度,任何運算不會發生溢出或除法截斷,當被轉換成對應的其他類型,或者與其他類型運算時,不再保證精度。

8.2 整數字面量

▪ 由一系列0-9的數字組成的10進制數,存在十六進制表示形式“0x”開頭,不存在以“0”八進制的表示形式。

//十六進制表示
0x692a70d2e424a56d2c6c27aa97d1a86395877b3a

▪ 整數常量表達式的除法運算,不做截斷處理,結果是有理數。

注意:

數字字面量表達式一旦其中含有非字面量表達式,它就會被轉爲一個非字面量表達式。

pragma solidity ^0.4.24;
contract IntegerLiteralConvert{  
  function literalTest(){
    uint128 a = 1;    
    //uint128 b = 2.5 + a + 0.5;
    //2.5+a不能轉換成一個非字面量表達式
  }
}

九、值類型——地址字面量

地址字面量表現形式其實就是十六進制整數字面量,如果能夠通過地址校驗,就會被認爲是地址類型,如果不通過則它表示的是一個整數。例如:

0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

就是一個地址類型的字面量,如果長度爲39~40,沒有通過地址校驗的十六進制整數字面量,會被視爲有理數常量,並且會產生一個warning(警告提示)。

十、值類型——字符串常量

字符串字面量是用雙引號或單引號(“foo”或‘bar’)編寫,長度可變。 它們不像C語言那樣默認以0結尾; “foo”代表三個不是四個字節。 它們可以隱式轉換爲bytes1,…,bytes32。

pragma solidity ^0.4.24;

contract StringLiteralsTest{    
    bytes15 public name;    
    function setName(){        
        name = 'liankuaixueyuan';
    }
}

十一、值類型——十六進制字面量

十六進制字面量,以關鍵字hex打頭,後面緊跟用單或雙引號包裹的字符串。如hex"001122ff"。在內部會被表示爲二進制流,與字符串的存儲形式相同,可以與字符串進行隱式轉換,通過下面的例子來理解下是什麼意思:

pragma solidity ^0.4.24;

contract HexLiteral{    
    string name;
    bytes nameBytes;    
    function setName()public{
        name = hex"6c69616e6b7561697875657975616e";
        nameBytes = hex"6c69616e6b7561697875657975616e";      
      //nameBytes = hex"a";
      //由於一個字節是8位,所以一個hex是由兩個[0-9a-z]字符組成的。所以var b = hex“A”;不是成雙的,轉字符串是會報錯的

    }    
    function getName() public view returns (bytes,string){      
    return (nameBytes,name);
  }
}

十二、值類型——枚舉

枚舉類型是在Solidity中的一種用戶自定義類型。

pragma solidity ^0.4.24;

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

    function setGoStraight() public {
        choice = ActionChoices.GoStraight;  // 這裏通過訪問下標的形式得到相同的結果    
        // choice = ActionChoices(2);
        
    }    

    function getChoice() public view returns (ActionChoices) {        
        return choice;
    }    

    function getDefaultChoice() public view returns (uint) {        
        return uint(defaultChoice);
    }
}

注意:

▪ 枚舉可以顯式的轉換與整數進行轉換,但不能進行隱式轉換。顯式的轉換會在運行時檢查數值範圍,如果不匹配,將會引起異常。

▪ 枚舉類型應至少有一名成員。

 

本文完,獲取更多資訊,敬請關注區塊鏈工程師。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章