天天看点

深入理解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——引用类型