天天看點

深入了解Solidity——值類型值類型(Value Type)

Solidity是一種靜态類型語言,這意味着每個變量(狀态變量和局部變量)需要在編譯時指定類型,或至少可以推倒出類型,請參閱下面的Type Deduction。Solidity提供了幾種可以組合形成複雜類型的基本類型。

另外,類型可以在包含運算符的表達式中互相互動。有關各種運算符的參考資料,請參閱運算符優先級。

值類型(Value Type)

以下類型也稱為值類型,這類變量在指派或傳參時,總是進行值拷貝。

布爾類型(Booleans)

布爾(

bool

):可能的取值為常量值

true

false

布爾類型支援的運算符有:

  • !邏輯非
  • && 邏輯與
  • || 邏輯或
  • == 等于
  • != 不等于

注意:運算符

&&

||

是短路運算符,如

f(x)||g(y)

,當

f(x)

為真時,則不會繼續執行

g(y)

整型(Integers)

int/uint:

表示有符号和無符号不同位數整數。支援關鍵字

uint8

uint256

,步長為8,

uint

int

預設對應的是

uint256

int256

整型支援的運算符有:

  • 比較運算符:

    <=

    ,

    <

    ,

    ==

    ,

    !=

    ,

    >=

    ,

    >

    (傳回布爾值(

    bool

    ):

    true

    false

    )
  • 位操作符:

    &

    |

    ^

    (異或),

    ~

    (取反)
  • 算術操作符:

    +

    -

    一進制運算-

    一進制運算+

    *

    /

    ,

    %(

    取餘),

    *

    (幂),

    <<

    (左移位),

    >>

    (右移位)
注解
除法總是截斷(隻是編譯為EVM的DIV操作碼),但如果兩個操作符都是字面量,就不會截斷。 除以0将引發運作時異常。移位運算的結果的正負取決于操作符左邊的數。x << y 和 x * 2***y 是相等, x >> y 和 x / 2**y 是相等的。這意味着不能進行負移位,即操作符右邊的數不可以為負數,否則會抛出運作時異常。
警告
Solidity中,右移位是和除等價的,是以右移位一個負數,向下取整時會為0,而不像其他語言裡為無限負小數。

定點數(Fixed Point Numbers)

警告
定點數 Solidity還不完全支援,目前可以用來聲明變量,但不可以用來指派。

fixed

/

ufixed

: 表示有符号和無符号的定點數。對于關鍵字

ufixedMxN

ufixedMxN

M

表示這個類型占用的位數,範圍為8到256,步長為8。

N

表示數點的個數,

N

的範圍為0到80之間。

ufixed

fixed

分别預設代表

ufixed128x19

fixed128x19

定點數支援的運算符有:

  • 比較運算符:

    <=

    ,

    <

    ,

    ==

    ,

    !=,

    >=

    ,

    >

    (傳回布爾值(

    bool

    ):

    true

    false

    )
  • 算術操作符:

    +

    -

    一進制運算-

    一進制運算+

    *

    /

    ,

    %

    (取餘)
注解
浮點數(即大多數語言中

float

double

)與定點數之間的主要差別在于,浮點數用于整數和小數部分(小數點後的部分)的位數是靈活的,而定點數嚴格定義。一般來說,在浮點數中,幾乎整個空間都用來表示數字,而隻有少量的位義了小數點的位置。

位址(Address)

address

:儲存20個位元組的值(一個以太坊位址的大小)。 位址類型也有成員,并作為所有合約的base。

位址支援的運算符有:

  • <=

    ,

    <

    ,

    ==

    ,

    !=

    ,

    >=

    >

注解
從版本0.5.0開始,合約不會從位址類型派生,但仍可以明确地轉換為位址。

位址成員(Members of Addresses)

  • 餘額

    balance

    和轉賬

    transfer

可以在Address Related中進行快速參考。

可以使用

balance

屬性查詢位址的餘額,并使用

transfer

函數将以太币(以wei為機關)發送到一個位址:

address x = ;
address myAddress = this;
if (x.balance <  && myAddress.balance >= ) x.transfer();
           
注解
如果

x

是合約位址,它的代碼(如果有回退fallback函數)将和

transfer

調用一起執行(這是EVM的特性,無法阻止)如果gas耗盡或者因為其他什麼原因失敗,以太币轉移将被回退,目前合約将抛出異常并停止。
  • 發送

    send

send

transfer

的很類似,差別在于,

send

如果執行失敗,目前合約不會停止并抛出異常,但

send

将傳回

false

警告
使用

send

存在一些危險:如果調用堆棧深度為1024,則轉賬将失敗(調用程式始終強制執行此操作),并且如果接受者的gas耗盡,也會失敗。 是以,為了讓以太币轉賬更安全,請務必檢查

send

的傳回值,或者直接使用

transfer

那就更好了。
  • 調用

    call

    , 調用碼

    callcode

    和代理調用

    delegatecall

另外,為了與不是附在ABI上的合約配合,

call

函數可以引用任意數量的參數,這些參數要填補成32位元組,并被拼接。一個例外的情況是第一個參數被确切的編碼成4位元組,這種情況下,不用填補,直接使用函數簽名。

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

call

函數傳回了一個布爾值,表示函數是否是正常調用結束(

true

)或引起了EVM異常(

false

)。不可能通路傳回實際資料(需要提前知道編碼和大小)。

可以用

.gas()

修飾符調整提供的gas:

同樣,所提供的以太币也可以被控制:

最後,這些修飾符可以結合使用,無需考慮使用順序:

注解
目前還不能在重載函數上使用

gas

value

修飾符。解決方法是引入一個gas和value的特例,并重新檢查它們是否存在于重載解析點。

delegatecall

函數可類似地使用:不同之處在于隻使用給定位址的代碼,所有其他方面(存儲,餘額…)來自目前合約。

delegatecall

的目的是使用存儲在另一個合約中的庫。

使用者必須確定兩個合約中的存儲結構适合使用

delegatecall

call

delegatecall

callcode

都是非常低級的函數,隻能作為最後的手段,因為它們會破壞Solidity的類型安全。

.gas()

可用于所有三種方法,而

.value()

不支援

delegatecall

注解
所有合約都繼承了位址成員,是以可以使用

this.balance

查詢目前合同的餘額。

callcode

的使用是不建議的,将來會被删除。
警告
所有這些函數都是低級的函數,應小心使用。任何不明合約都可能是惡意的,如果您調用它,則會将控制權移交給該合約。

定長位元組數組(Fixed-size byte arrays)

關鍵字有:

bytes1

,

bytes2

,

bytes3

, …,

bytes32

byte

預設表示

bytes1

定長位元組數組支援的運算符有:

  • 比較符:

    <=

    ,

    <

    ,

    ==

    ,

    !=

    ,

    >=

    ,

    >

    (傳回

    bool

  • 位操作符:

    &

    ,

    |

    ,

    ^

    (異或),

    ~

    (取反),

    <<

    (左移位),

    >>

    (右移位)
  • 索引通路: 如果

    x

    bytesI

    當0 <= k < I

    ,則

    x[k]

    傳回第

    k

    個位元組(隻讀)。

移位運算和整型(Integer)類似,移位運算的結果的正負取決于操作符左邊的數,且不能進行負移位。

成員變量:

  • .

    length

    :表示這個位元組數組的長度(隻讀)。
注解
可以通過

byte[]

使用位元組數組,但是當傳入調用時,它會浪費大量空間,每個元素占用31個位元組。 最好使用

bytes

動态位元組數組(Dynamically-sized byte array)

  • bytes

    :動态配置設定大小位元組數組, 參見Arrays,不是值類型!
  • string

    :動态配置設定大小UTF8編碼的字元類型,參看Arrays。不是值類型!

根據經驗:

bytes

用來存儲任意長度的位元組資料,

string

用來存儲任意長度的(UTF-8編碼)的字元串資料。

如果長度可以确定,盡量使用定長的如

byte1

byte32

中的一個,因為這樣更省空間。

位址字面量(Address Literals)

通過位址校驗和測試的十六進制文字,例如

0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

是位址類型。 長度在39到41位之間且未通過校驗和測試的十六進制文字會産生警告,并将其視為正常有理數字面量。

注解
EIP-55中定義了混合大小寫的位址校驗和格式。

有理數和整型字面量(Rational and Integer Literals)

整型字面量是有一系列0-9的數字組成,比如:

69

代表六十九。前置0在Solidity中是無效的。

小數字面量(Decimal fraction literals)帶了一個

.

, 在

.

的兩邊至少有一個數字,有效的表示如:

1.

,

.1

1.3

.

支援科學計數法,基數可以是小數,指數必須是整數, 有效的表示如:

2e10

,

-2e10

,

2e-10

,

2.5e1

數字字面量表達式本身支援任意精度,也就是可以不會運算溢出,或除法截斷。但當它被轉換成對應的非字面量類型,或者将他們與非字面量進行運算,則不能保證精度了。

如:

(2*800 + 1) - 2*800

的結果為

1

uint8

整型) ,盡管中間結果已經超過計算機字長。另外:

.5 * 8

的結果是

4

,盡管有非整形參與了運算。

隻要操作數是整型,整型支援的運算符都适用于整型字面量表達式。

如果兩個操作數是小數,則不允許進行位運算,指數也不能是小數,因為這可能會傳回無理數。

注解
Solidity對每一個有理數都有一個數值字面量類型。整數字面量和有理數字面量屬于數字字面量。所有的數字字面量表達式的結果都屬于數字字面量。是以

1 + 2

2 + 1

都屬于同樣的有理數的數字字面量

3

警告
整數字面量除法,在早期的版本中是被截斷的,但現在可以被轉為有理數了,如

5/2

的值為

2.5

,而不是

2

注解
數字字面量表達式,一旦其中含有非字面量表達式,它就會被轉為一個非字面量類型。盡管我們知道在以下示例中指派給

b

的表達式的值計算為整數,但部分表達式

2.5 + a

沒有類型檢查,是以代碼不會編譯。
uint128 a = ;
uint128 b =  + a + ;
           

字元串字面量(String Literals)

字元串常量是指由單引号,或雙引号引起來的字元串 (

"foo"

'bar'

)。字元串并不像C語言,包含結束符,

"foo"

這個字元串大小僅為三個位元組。和整數字面量一樣,字元串的長度和類型可以是變長的。字元串可以隐式的轉換為

byte1

,…

byte32

如果适合,也會轉為

bytes

string

字元串常量支援轉義字元,比如

\n

\xNN

\uNNNN

。其中

\xNN

表示16進制值,最終轉換合适的位元組。而

\uNNNN

表示Unicode編碼值,最終會轉換為UTF8的序列。

十六進制字面量(Hexadecimal literals)

十六進制字面量,以關鍵字

hex

打頭,後面緊跟用單或雙引号包裹的字元串,内容是十六進制字元串,如

hex"001122ff"

它的值會用二進制來表示。

十六進制字面量和字元串字面量類似,也可以轉換為位元組數組。

枚舉(Enums)

在Solidity中,枚舉可以用來自定義類型。它可以顯式地與整數進行互相轉換,但不能進行隐式轉換。顯式的轉換會在運作時檢查數值範圍,如果不比對,将會引起異常。枚舉類型應至少有一名成員。

pragma solidity ^;

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

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

    // Since enum types are not part of the ABI, the signature of "getChoice"
    // will automatically be changed to "getChoice() returns (uint8)"
    // for all matters external to Solidity. The integer type used is just
    // large enough to hold all enum values, i.e. if you have more values,
    // `uint16` will be used and so on.
    function getChoice() public view returns (ActionChoices) {
        return choice;
    }

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

函數類型(Function Types)

函數類型的變量可以由函數指派,而函數類型的函數參數可用于将函數傳遞給函數調用并從函數調用中傳回函數。 功函數類型有兩種:

  • 内部函數(internal functions)
  • 外部函數(external functions)

内部函數隻能在目前合約内(更具體地說,在目前代碼單元内部,其中還包括内部庫函數和繼承函數)内部調用,因為它們不能在目前合約的上下文之外執行。 調用内部函數是通過跳轉到其入口标簽來實作的,就像在内部調用目前合約的函數一樣。

外部函數由一個位址和一個函數簽名組成,它們可以通過外部函數調用傳遞并傳回。

函數類型的标注如下:

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

與參數類型不同,傳回類型不能為空 - 如果函數類型不應傳回任何内容,則必須省略整個

returns (<return types>

)部分。

預設情況下,函數類型是内部的,是以

internal

關鍵字可以省略。相反,合約函數在預設情況下是

public

的,隻有當用作類型的名稱時,預設是内部的。

有兩種方法可以通路目前合約中的函數:直接使用其名稱

f

或使用

this.f

。前者将通路内部函數,後者則通路外部函數。

如果函數類型變量未初始化,調用它将導緻異常,或者在使用

delete

之後調用某個函數,則會發生同樣的情況。

如果外部函數類型在Solidity上下文之外使用,則它們被視為

function

類型,它将函數辨別符後跟的位址一起編碼為單個

bytes24

類型。

請注意,目前合約的

public

函數既可以用作内部函數,也可以用作外部函數。要将

f

用作内部函數,隻需使用

f

,如果要使用其外部形式,請使用

this.f

此外,

public

external

函數還有一個名為

selector

的特殊成員,它傳回ABI函數選擇器:

pragma solidity ^;

contract Selector {
  function f() public view returns (bytes4) {
    return this.f.selector;
  }
}
           

使用内部函數的示例:

pragma solidity ^;

library ArrayUtils {
  // internal functions can be used in internal library functions because
  // they will be part of the same code context
  function map(uint[] memory self, function (uint) pure returns (uint) f)
    internal
    pure
    returns (uint[] memory r)
  {
    r = new uint[](self.length);
    for (uint i = ; i < self.length; i++) {
      r[i] = f(self[i]);
    }
  }
  function reduce(
    uint[] memory self,
    function (uint, uint) pure returns (uint) f
  )
    internal
    pure
    returns (uint r)
  {
    r = self[];
    for (uint i = ; i < self.length; i++) {
      r = f(r, self[i]);
    }
  }
  function range(uint length) internal pure returns (uint[] memory r) {
    r = new uint[](length);
    for (uint i = ; i < r.length; i++) {
      r[i] = i;
    }
  }
}

contract Pyramid {
  using ArrayUtils for *;
  function pyramid(uint l) public pure returns (uint) {
    return ArrayUtils.range(l).map(square).reduce(sum);
  }
  function square(uint x) internal pure returns (uint) {
    return x * x;
  }
  function sum(uint x, uint y) internal pure returns (uint) {
    return x + y;
  }
}
           

使用外部函數的示例:

pragma solidity ^;

contract Oracle {
  struct Request {
    bytes data;
    function(bytes memory) external callback;
  }
  Request[] requests;
  event NewRequest(uint);
  function query(bytes data, function(bytes memory) external callback) public {
    requests.push(Request(data, callback));
    NewRequest(requests.length - );
  }
  function reply(uint requestID, bytes response) public {
    // Here goes the check that the reply comes from a trusted source
    requests[requestID].callback(response);
  }
}

contract OracleUser {
  Oracle constant oracle = Oracle(); // known contract
  function buySomething() {
    oracle.query("USD", this.oracleResponse);
  }
  function oracleResponse(bytes response) public {
    require(msg.sender == address(oracle));
    // Use the data
  }
}
           
注解
Lambda或内聯函數在計劃中,但目前尚未支援。

上一篇:深入了解Solidity——合約結構

下一篇:深入了解Solidity——引用類型