天天看點

Solidity開發-資料類型篇

作者:前端日月潭

Solidity是強類型語言,任何變量在使用時都要明确說明變量的類型。弱類型語言與強類型語言有明顯的差別,弱類型語言隻有在運作時才能确定其資料類型,是一種動态類型的語言;而強類型語言在編譯時就必須确認其長度和大小,是一種靜态語言。

Solidity沒有空類型的資料,任何類型的變量如果沒有初始化數值,則編譯器将賦予其預設值,當系統運作出現非期望出現的值時,則需要将交易復原,将區塊狀态恢複到交易發生前的狀态。

Solidity開發-資料類型篇

值類型

傳值的資料類型意味着在參數傳遞和變量指派的過程中資料總是被複制後再進行傳遞,任何對新變量的修改都不影響源資料。Solidity的傳值資料類型有以下幾種。

布爾

關鍵字:bool,取值範圍:true、false,操作符:!、&&、||、==、!=

說明:|| 和 && 操作符遵循最短電路原則,即對于表達式f(x)|| g(y),隻要f(x)是true, g(y)就不會被執行;而對于表達式f(x)&&g(y),隻要表達式f(x)是false, g(y)就不會被執行

整型

關鍵字:int8~int256、uint8~uint256,分别表示不同長度的有符号和無符号整型,每隔8比特位為一個類型,int是int256的别名,uint是uint256的别名。操作符:<=、<、==、!=、>=、>、&、|、^、~、<<、>>、+、-、*、/、%、**。

說明:對于整數的位操作,是對數值的二進制補碼表示的二進制值進行的位操作,比如~int256(0)== int256(-1),即對0的所有位取反之後,其值在有符号數的表達式裡面表示的是-1。

對于整型的減法運算,因為二進制補碼的運算方式,需要考慮到數值表示範圍溢出的情況,比如下列兩種情況的運算結果可能與其他語言的運算結果有所不同。

uint256(0)- uint256(1)
    int x = -2**255;
    assert(-x == x);
           

對于指數運算,唯一需要注意的就是0**0=1

位址類型

關鍵字:address、address payable,這兩個關鍵字基本相同,都是20位元組的以太坊位址,隻是address payable比address多了transfer和send這兩個成員。

設定address payable的目的就是明确說明該位址可以接收ETH。操作符: <=、<、==、!=、>=、>。address的成員函數有:

//查詢餘額
    <address>.balance(uint256)
    
    //轉移ETH
    <address payable>.transfer(uint256 amount) 
    
    //轉移ETH的底層接口
    <address payable>.send(uint256 amount)returns(bool) 
    
    //底層調用函數
    <address>.call(bytes memory)returns(bool, bytes memory)
    
    //底層調用函數
    <address>.delegatecall(bytes memory)returns(bool, bytes memory)
    
    //底層調用函數
    <address>.staticcall(bytes memory)returns(bool, bytes memory)
           

合約類型

使用者自定義的合約也是一種資料類型,如前面講到的MetaCoin合約就是一種資料類型,可以在其他合約中引用該資料類型。合約類型可以和位址類型進行顯式的類型轉換,在後面的智能合約實踐中,會有多種場景使用此類型轉換。合約類型不支援任何操作符号,不過可以調用合約中的external函數和public變量。

固定長度的數組

關鍵字:bytes1、bytes2、bytes3、…、bytes32,byte是bytes1的别名。操作符:<=、<、==、!=、>=、>、&、|、^、~、<<、>>、[]。成員函數:.length,表示其固定的位元組個數。對于bytes1類型的變量,其.length屬性為1;bytes32類型的變量的.length屬性為32。

字面常量

  • 位址常量:例如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF。
  • 有理數和整數常量:例如69、1.、.6、1.3、2e10等。

枚舉類型

枚舉類型是使用者自定義Solidity資料類型的方式之一,它可以與整型進行顯式的類型轉換,枚舉的值由0開始遞增。舉例如下:

01   pragma solidity >=0.4.16 <0.6.0;
    02
    03   contract test {
    04       //定義枚舉類型
    05       enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    06       ActionChoices choice;                           //定義該類型變量
    07       //枚舉指派
    08       ActionChoices constant defaultChoice = ActionChoices.GoStraight;
    09
    10       function setGoStraight()public {
    11           choice = ActionChoices.sGoStraight;      //修改枚舉變量
    12       }
    13       //擷取枚舉值
    14       function getChoice()public view returns(ActionChoices){
    15           return choice;
    16       }
    17
    18       //枚舉變量類型轉換
    19       function getDefaultChoice()public pure returns(uint){
    20           return uint(defaultChoice);
    21       }
    22   }
           

函數類型

函數類型類似于C語言中的函數位址,可以通過自定義函數原型的方式,定義滿足某種傳回值和參數格式的函數類型。其定義方式如下。

function  (<parameter   types>)  {internal|external}   [pure|view|payable]
[returns(<return types>)]
           

函數類型分為内部函數和外部函數,内部函數隻能在目前的智能合約中被使用,而外部函數可以通過位址和函數簽名被外部合約或系統調用。在預設情況下,函數類型是内部函數,internal關鍵字可以省略。

但是函數類型與函數的可見類型不同,函數的可見類型不能省略。一個外部函數的變量可以轉換為address類型的資料,該位址資料是本函數所在的智能合約的位址。

兩種不同的函數類型,A函數類型的變量隐式轉換成B函數類型時,需要滿足以下條件才能成功轉換:A函數類型與B函數類型的參數完全一樣,否則編譯器會提示錯誤;傳回類型一樣;調用類型,即内部和外部屬性一樣;對區塊鍊狀态的修改類型,A函數類型要比B函數類型要求嚴格。滿足以上條件,A函數類型的變量才能隐式地轉換為B函數類型,對于區塊鍊狀态修改的嚴格性排序依次為pure > view > payable > non-payable。

如果函數沒有在初始化或執行delete之後再被調用,則會進入失敗的斷言,交易會被復原。如果一個外部函數超出了其上下文環境,則會被當作一個address封裝在一個bytes24的資料裡面。public函數可以在内部被調用,也可以在外部被調用。

pragma solidity 0.5.2;
    //函數庫命名
    library utils {                              
      //定義函數
      function callA(uint result, function(uint)pure returns(uint)FA)
      internal   //各個函數描述符
      pure
      returns(uint){
        uint squre = FA(result);
        return squre;
      }
    }
    
    //定義合約使用庫函數
    contract funcTest {    
      //引用定義的庫函數
      using utils for *;                     
      function A(uint r)internal pure returns(uint sqr){
        return r * r;
    }
      function caller(uint x)public pure returns(uint){
      //使用函數庫的函數
      return utils.callA(x, A);         
    }
}
           

在上面的代碼示例中,A函數作為一種内部函數類型的變量,會被傳遞給目前代碼上下文中的utils域并被調用。

引用類型

引用類型的資料可能會通過多種途徑修改,是以使用引用類型的資料時需要特别謹慎。目前可用的引用類型的資料有數組(array)、結構體(struct)和映射(mapping)。每種引用類型的資料在使用時都需要明确指出其存儲的位置。存儲位置分為3類,具體如下。

  • memory:其生命周期僅僅局限于一次函數調用,類似于C語言的棧變量。
  • storage:狀态變量存儲的地方,參與交易資料和區塊資料的運算,可以永久存儲,類似于磁盤存儲。
  • calldata:一種特殊的存儲位置,此處用來存放外部函數調用的參數。

不同資料存儲位置的資料,不僅存儲持久性不同,其指派的文法和執行方式也各不相同。

數組(array)

Solidity的數組分為固定大小的數組和動态大小的數組,T[k]表示k大小的T類型的數組,T[]表示T類型元素的動态數組。需要注意的是,5個動态uint元素的數組的表示方式為uint[][5],而在其他程式設計語言中往往将5放到第一個中括号中;而X[3]永遠表示3個X元素,即使X本身是一個數組,此屬性也和其他程式設計語言不同。

數組下标從0開始,這與大部分程式設計語言相同,但是其通路順序和變量聲明順序是相反的。比如,變量uint[][5] x memory表示x是一個存放在memory中的、5個unit大小的數組,這5個數組元素是一個uint類型的動态數組,但是當通路第3個動态數組(下标是2)中的第2個uint元素(下标是1)時,其通路表達式為x[2][1]。

數組元素可以是任意類型的,包括映射類型和結構體類型;當數組通路越界時,會觸發斷言assertion,調用失敗;通過push成員可以将新元素添加到數組末尾,通過修改length屬性可以改變數組的大小。

bytes和string是特殊的數組類型,bytes與byte[]類似,但是byte[]存儲在calldata或memory中時,需要嚴格補足32位元組的長度,是以請盡量使用bytes類型變量。string與bytes類似,隻是string沒有length操作和下标通路。當使用固定長度的原始位元組資料時,使用bytes類型變量;當使用固定長度UTF-8的位元組資料時,使用string類型變量。

01   pragma solidity >=0.4.16 <0.6.0;
    02
    03   contract C {
    04       //定義合約函數,對memory類型變量進行操作
    05       function f(uint len)public pure {
    06           uint[] memory a = new uint[](7);//動态配置設定數組大小
    07           bytes memory b = new bytes(len);
    08           assert(a.length == 7);
    09           assert(b.length == len);
    10           a[6] = 8; //通過數組下标指派
    11       }
    12   }
           

結構體(struct)

結構體是Solidity程式設計語言聲明使用者自定義資料類型的重要方式。下面的示例代碼說明了結構體的相關屬性和特點。

01   pragma solidity >=0.4.11 <0.6.0;
    02
    03   contract CrowdFunding {
    04       struct Funder {               //定義一個結構體,記錄位址及相應的額度
    05           address addr;
    06           uint amount;
    07       }
    08
    09       struct Campaign {             //定義一個結構體,記錄活動相關資料
    10           address payable beneficiary;
    11           uint fundingGoal;
    12           uint numFunders;
    13           uint amount;
    14           mapping(uint => Funder)funders;
    15       }
    16       uint numCampaigns;
    17       mapping(uint => Campaign)campaigns;
    18       //本函數說明了結構體初始化的方式
    19       function newCampaign(address payable beneficiary, uint goal)public
returns(uint
    20            campaignID){
    21           campaignID = numCampaigns++;
    22           campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    23       }
    24        //本函數說明了結構體的構造過程及修改存儲和成員變量屬性的方式
    25       function contribute(uint campaignID)public payable {
    26           Campaign storage c = campaigns[campaignID];
    27           c.funders[c.numFunders++] = Funder({addr: msg.sender, amount:
msg.value});
    28           c.amount += msg.value;
    29       }
    30        //本函數示範了如何從存儲中讀取結構體,以及通路結構體的成員變量
    31       function  checkGoalReached(uint  campaignID) public  returns (bool
reached){
    32           Campaign storage c = campaigns[campaignID];
    33           if(c.amount < c.fundingGoal)
    34               return false;
    35           uint amount = c.amount;
    36           c.amount = 0;
    37           c.beneficiary.transfer(amount);
    38           return true;
    39       }
    40   }
           

映射類型(mapping)

mapping類似于其他語言的hast table,隻是mapping并不存儲key的值,而是存儲key的keccak256 Hash值。其文法結構是mapping(_KeyType =>_ValueType),其中KeyType可以是任意系統自帶的資料類型,而_ValueType可以是系統自帶的資料類型,也可以是使用者自定義的資料類型,還可以是mapping的資料類型。

繼續閱讀