类型

翻译原文
date:20170617

Solidity是静态类型语言,这意味着每个变量的类型必须在编译的时候指定(或者知晓,查看以下类型推断章节)变量类型。Solidity提供了多种简单的类型,可以通过简单类型组合成复杂的类型。
另外,类型可以与包含操作符的表达式互动。浏览不同的操作符,参看操作符优先级章节。

值类型

以下的类型通常称为值类型,因为这些类型的变量通常传入的是值。例如在给函数传入参数的时候或者在赋值的时候,传入的是值的拷贝。

布尔类型(Booleans)

bool:可能的值只有true或者false.

操作符:

  • !逻辑非
  • &&逻辑与
  • ||逻辑或
  • ==
  • !=不等

操作符||&&实行的是短路规则。这意味着:在表达式f(x)||g(y)中,如果f(x)的值是trueg(y)将不会执行。

整形(Intergers)

int/uint:多种大小的有符号整数或者无符号整数。关键词uint8unint256每8位为一步(无符号从8位到256位)和int8int256。特别的,uintintuint256int256的别名。

操作符:

  • 比较符:<=,<,==,!=,>=,>(返回值是bool类型)
  • 位操作符: &,|,^(按位或),~(按位非)
  • 算术符:+,-,一元-,一元+*,/,%(求余),**(?exponentiation),<<(左移),>>(右移动)

除法通常是会被截断的(它只是编译成EVM的DIV操作码),但是如果两个操作数都是字面量或者字面表达式.(?Division always truncates (it just is compiled to the DIV opcode of the EVM), but it does not truncate if both operators are literals (or literal expressions).)
被0除或者对0取模会抛出运行时异常。
移位操作符的返回值的类型通常是左边操作数的类型。表达式x<<y等价于x * 2**y,x>>y等价于x / 2**y。这意味着负数的移位符号位会不变(ps:-2 << 2 = -8)。目前移动负数位将会抛出运行时异常。(ps:2<<-2就会异常)

警告:Solidity负数的向右移位和其他的语言是不一样的。在Solidity中,右移对应的是除法,所以右移负数的结果趋向于0(被截断了)。其他语言中,右移负数的值是除法不截断(趋向于负无穷大)(?In other programming languages the shift right of negative values works like division with rounding down (towards negative infinity).)

地址类(Address)

address是一个20字节的值(也是以太坊地址的长短)。Address也有成员变量,是所有合约的基础。

操作符:

  • <=,<,==,!=,>=>
地址类的成员变量
  • balancetransfer

快速浏览,参看地址类相关信息.
可以通过地址的balance属性查看账号余额,可以通过transfer将以太币发送给其他账号。

address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

注意:如果x是合约地址,合约代码(更具体些:是回调函数)将会于transefer函数一同执行(这是EVM的限制,而且不能被阻止)。如果执行过程中,gas消耗完毕了,或者执行失败,转账就会被退回,并抛出异常。

  • send
    发送是于transfer相对应的。如果执行失败,合约并不马上停止,抛出异常,而是会返回false.

警告:使用send会些风险:如果堆栈深度超过1024(该问题通常是调用端产生的),gas不足,会导致转账失败。所以为了使以太币能够安全转账,使用send就得检查返回结果。或者直接使用transfer会更好:使用接受者取回钱币的模式。(?use a pattern where the recipient withdraws the money)

  • call,callcodedelegatecall

另外,为了给那些没有依附到ABI(Application Binary Interface)的合约提供接口,call函数可以接受任意多个不同类型的参数。这些参数会扩展到32位,并且串联起来。但是第一个参数是编码为4个字节的。?In this case, it is not padded to allow the use of function signatures here.

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

call函数返回一个布尔量,用来说明调用的函数是正确执行(返回true)还是发生异常(返回false)。所以我们不可能得到真实的数据返回。(为了得到真实数据,我们需要了解编码和大小。?for this we would need to know the encoding and size in advance)

一个类似的方法,函数delegatecallcall不同的是,只是用了指定地址的地址码,其他的参数(storage,余额等)用的都是当前合约的。使用delegatecall的目的是使用保存在其他合约中的库。用户必须确认确认两个合约的storage必须合适才行。在homestead版本之前,只有一个callcode参数可用,但是不能获取到msg.sendermsg.value.

所有这三个参数call,delegatecallcallcode都是非常低级的函数,并且只在迫不得已的情况下使用。因为他们会危害Solidity的类型安全。

以上三个函数都可以使用.gas()函数,但是delegatecall并不支持.value()函数。
注意:所有合约都继承了address的成员变量,所以可以通过this.balance来访问当前合约的余额
警告:这些函数都是底层函数,并小心使用。任意未知的合约可能是恶意的。如果你调用它,你就把控制权交给了它,它可以反过来再调用你。所以执行完毕之后,你的变量可能已经发生改变了。

固定大小的字节数组

bytes1,bytes2,bytes3,...bytes32bytebytes1的别名。

操作:

  • 比较:<=,<,==,!=,>=,>(返回bool
  • 位操作:&,|,^(按位异或),~(按位取反),<<(左移),>>(右移)
  • 索引:如果x的类型是bytesI,那么x[k]中,有0 <= k <= I,返回第k位(只读)。

移位操作符的右操作数可以是任意整数类型(但是返回做操作数的类型)。右操作数表示的是移位的个数。如果右操作数是负数,那么会引起运行时异常。

成员变量:

  • .length 返回字节数组的长度(只读)
动态大小的字节数组

bytes:动态大小的字节数组,参看Arrays,不是一种值类型。
string:动态大小的UTF-8编码的字符串,参看Arrays,不是一种值类型。

根据经验来说,如果需要用到任意长度的字节数据,那么使用bytes;如果需要用到任意长度的字符串(UTF-8)数据,那么使用string.如果你可以确定长度,那么使用bytes1bytes32,因为它们会便宜些。

固定长度的数字

//todo 待完善

地址字面量

通过传递地址检验和测试的十六进制字面量是address类型,例如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF。39位到41位长度的十六进制字面量,没有通过检验和测试的会产生警告,并且作为平常的有理数字面量。

有理数整形字面量

整形变量是由0-9组成的一串数字。它们是十进制的。例如69就是69。Solidity中并没有八进制,以0开头的是不合法的。

十进制小数,以.隔开,左右必须至少有一个字符,例如1.,.11.3
科学表示法也是支持的,基数可以是小数,但是指数不可以。例如2e10,-2e10,2e-102.5e1

数字字面量表达式保持精确的值。直到类型变为非字面量类型(例如和非字面量的值一起使用)。这意味着数字字面量的计算不会溢出或者除法的时候不会截断。

例如,(2**800 + 1) - 2**800的结果是常量1(类型是uint8类型)。尽管中间值超过了机器字的大小。(? although intermediate results would not even fit the machine word size)另外,.5*8的结果是4(因为表达式中没有用到非整数)。

如果结果不是整数,会选择合适的ufixed或者fixed类型来表示它们。大小会计算出足够它们使用的大小。(大概有理数中最差的情况。)

在表达式var x = 1/4;中,x会使用ufixed0x8类型,但是在var x = 1/3中,将会使用ufixed0x256类型。因为1/3在二进制中无法精确表示。所以会是一个精确值。

可以用在整数中的任何操作符,也可以用在数字字面量表达式中,只要操作数是整数。如果有操作数是小数,位操作符是不可以使用的。指数如果是小数,指数运算也是不允许的。(因为会产生物理数)。

注意:Solidity对于每种有理数,都有一个数字字面量类型。整数字面量和有理数字面量都属于数字字面量类型。另外,所有的数字字面量表达式(例如只包含数字字面量和操作符的表达式)都是数字字面量类型。所以数字字面量表达式1+22+1都是属于相同的数字字面量类型——有理数——3

注意:很多有尽十进制小数,如5.3743,在二进制表示法中是无尽的。5.3743的正确类型是ufixed8x248因为这样可以更加精确的表示该数。如果你想用其他类型,如ufixed(像ufixed128x128),你必须要指明期望的精度:x + ufixed(5.3743)

警告:在早期版本中,整数字面量的除法会有截断。但是现在会转换为有理数。例如5 / 2并不等于2,而是2.5

注意:当数字字面量表达式与非字面量类型一起使用的时候,会转变为非字面量类型。在如下所示的例子中,尽管我们知道赋给b的值是整形,但是在表达式中间,仍然使用固定大小的浮点类型(非有理数字面量),所以代码不会编译。

uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
字符串字面量

字符串字面量由单引号或者双引号包裹(“foo”或者'bar')。?They do not imply trailing zeroes as in C; "foo"代表3个字节,不是四个。?As with integer literals, their type can vary, but they are implicitly convertible to bytes1, ..., bytes32, if they fit, to bytes and to string.
字符串字面量支持换码符(escape characters),例如\n\xNN\uNNNN\xNN使用十六进制的值,并插入合适的字节数据(?\xNN takes a hex value and inserts the appropriate byte,)。\uNNNN使用的是unicode码,插入的是UTF-8的字符串。(?while \uNNNN takes a Unicode codepoint and inserts an UTF-8 sequence)

十六进制字面量

十六进制数据以hex关键字打头,并且通过双引号或者单引号包裹(hex"001122FF")。该字面量内容必须是十六进制数据的字符串,值必须是代表这些字符的二进制数据。(?Their content must be a hexadecimal string and their value will be the binary representation of those values.)
十六进制字面量有点像字符串字面量,并且也有一些转换限制。

枚举类型

枚举类型为用户提过了一种在Solidity中自定义类型的方法。他们可以方便的转换为整形,或者从整形转换而来。但是隐式变换是不允许的。显式变换在代码执行的时候检查值的范围,执行失败会产生一个异常。枚举类型至少需要一个数字。

pragma solidity ^0.4.0;

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

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

    // 由于枚举类型不是ABI的一部分。getChoice函数会自动的变换为“getChoice() returns (uint8)”。整数类型的大小为能够保存所有enum值的大小。例如:如果你有更大的数值,就会使用uint16等。
    function getChoice() returns (ActionChoices) {
        return choice;
    }

    function getDefaultChoice() returns (uint) {
        return uint(defaultChoice);
    }
}
函数类型

函数类型是函数的类型。函数类型的变量可以通过函数赋值,函数类型的参数可以传递给函数。函数也可以通过函数返回。函数类型分为两种-内部函数和外部函数。
内部函数只能在当前合约的内部使用(更加准确的说,是当前代码单元里,代码单元中包含内部库函数和继承函数),因为他们不能在合约外部使用。调用内部函数就像跳转到函数的标签处,就像在合约内部调用函数。(?Calling an internal function is realized by jumping to its entry label, just like when calling a function of the current contract internally.)
外部函数由地址和函数签名组成。他们可以从外部函数调用中被传递或者返回。(?External functions consist of an address and a function signature and they can be passed via and returned from external function calls.)
函数类型的格式如下:

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

与参数类型做对比,返回类型不能为空,如果函数没有返回,那么returns (<return types>)都应该删除。
默认情况下,函数类型是内部的,所以internal关键字可以删除。
在当前合约中可以由两种方法调用函数:要么直接通过函数名称,f或者使用this.f。第一种是内部函数调用,后一种是外部函数。
如果函数类型变量没有初始化,那么调用就会引发异常。如果你对函数delete之后,再调用,同样会引发这个错误。
如果外部函数类型在Solidity外部调用,他们会被当作function类型,该类型将地址和函数识别码一同以bytes24类型编码。

需要注意的是,当前合约的公有函数既可以用作内部函数,也可以用作外部函数。如果想用作内部函数,那么直接通过f(函数名称)来调用,或者如果想用作外部函数,通过this.f来调用。

以下的例子显示了如何使用内部函数类型:

pragma solidity ^0.4.5;

library ArrayUtils {
  // 内部函数可以在内部库内部使用,因为他们的执行上下文一致
  function map(uint[] memory self, function (uint) returns (uint) f)
    internal
    returns (uint[] memory r)
  {
    r = new uint[](self.length);
    for (uint i = 0; i < self.length; i++) {
      r[i] = f(self[i]);
    }
  }
  function reduce(
    uint[] memory self,
    function (uint, uint) returns (uint) f
  )
    internal
    returns (uint)
  {
    r = self[0];
    for (uint i = 1; i < self.length; i++) {
      r = f(r, self[i]);
    }
  }
  function range(uint length) internal returns (uint[] memory r) {
    r = new uint[](length);
    for (uint i = 0; i < r.length; i++) {
      r[i] = i;
    }
  }
}

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

如下例子,说明如何使用外部函数:

pragma solidity ^0.4.11;

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) {
    requests.push(Request(data, callback));
    NewRequest(requests.length - 1);
  }
  function reply(uint requestID, bytes response) {
    // 这里需要确认返回的数据来自受信任的源
    requests[requestID].callback(response);
  }
}

contract OracleUser {
  Oracle constant oracle = Oracle(0x1234567); // 已知合约
  function buySomething() {
    oracle.query("USD", this.oracleResponse);
  }
  function oracleResponse(bytes response) {
    require(msg.sender == address(oracle));
    // 使用数据
  }
}

lambda表达式或者行内函数还在计划开发进程中,尚未支持。

引用类型

复杂的类型,例如,某种类型,总是不能很好的适应256位大小,相比较我们之前的类型,需要更加小心处理。因为拷贝它们可能会产生很大的花费。我们必须考虑将它们保存在内存(非连续的)中还是在storage(状态变量保存的地方)中。

数据位置(Data location)

每种复杂的类型,例如数组和结构体,有一种另外的注释,数据位置(data location),关于是否保存在内存中,还是在Storage里。根据上下文,会有个默认的类型。但是我们也可以通过添加storage或者memory关键字来改变这个类型。函数的参数(包括返回值)都是保存在memory中的,本地变量,状态变量默认是storage
还有第三种数据位置,“calldata”,这是一个保存函数参数的非连续区,不可修改区。外部函数的参数(不包含返回参数)强制的保存在"calldata"中,和memory差不多。
理解数据位置是非常重要的,因为它改变了赋值的行为:storage和内存间的赋值,以及赋值给转台变量(或者从其他变量赋值)总是会生成一个独立的拷贝。赋值给本地变量只是赋值了一个引用。并且这个引用总是指向状态变量,即使这个状态变量在程序运行期间发生了变化。换句话说,在内存数据之间的赋值不会生成一个数据拷贝。

pragma solidity ^0.4.0;

contract C {
    uint[] x; // 该数据保存在storage中

    // memoryArray的数据保存在memory中
    function f(uint[] memoryArray) {
        x = memoryArray; // 没毛病,将整个数组保存到storage中
        var y = x; // 没毛病,给指针赋值,y的数据位置为storage
        y[7]; // 可以的,返回第8个元素
        y.length = 2; // 可以的,通过y来改变x
        delete x; // 可以的,清空array,并且也改变了y
        // 下面的语句是错误的,本该在storage中生成一个暂时的,没有命名的Array /
        // 但是storage已经静态的被分配了:
        // y = memoryArray;
        // 以下语句也是错误的,因为它会重置指针,但是它并没有实质的指向。
        // delete y;
        g(x); // 调用g函数,参数为x的引用
        h(x); // 调用h函数,并且在memory中生成一个单独的,暂时的,x的拷贝
    }

    function g(uint[] storage storageArray) internal {}
    function h(uint[] memoryArray) {}
}
总结

强制数据位置:

  • 外部函数的参数(不包括返回值):calldata
  • 状态变量:storage

默认数据位置:

  • 函数的参数(包括返回值):内存
  • 所有其他本地变量:storage
数组

数组可以在编译的时候确定其长度,或者它们可以动态变化。对于storage数组,元素类型可以是任意的(例如,其他数组,映射或者结构体);对于内存数组,它不能成为映射,而且如果它是公有函数的参数,那么只能是ABI类型。
一个具有k长度的,元素类型为T的数组表示为T[k],动态长度的数组为T[]。例如,一个包含有五个动态数组的数组,可以表示为uint[][5](注意这里和其他的语言的记法相反)。获取到第三个数组中的第二个元素,可以用x[2][1](索引从0开始,这里获取数据和定义是两种相反的方式,如x[2] shaves off one level in the type from the right)
bytesstring是特殊的数组。bytes就相当于是byte[],但是它位于calldata,而且比较紧凑(?but it is packed tightly in calldata)。string相当于是bytes,但是目前为止不能修改长度和字符索引。
所以bytesbyte[]相比,会优先选择byte[],因为更加便宜。
注意:如果你想要获取string的byte表达,可以使用bytes(s).length/bytes(s)[7] = x;。你要注意的是,你访问的是UTF-8的低级字节表达,不是单独的一个字符。
数组也可以是public的,solidity会生成它的getter函数。调用getter函数必须要有一个索引参数。

分配内存数组

在内存中创建一个可变长度的数组可以通过new关键字来实现。为了与storage数组相抵制,内存数组不能通过改变.length来改变长度。

pragma solidity ^0.4.0;

contract C {
    function f(uint len) {
        uint[] memory a = new uint[](7);
        bytes memory b = new bytes(len);
        // 这里,a.length == 7,b.length == len
        a[6] = 8;
    }
}
数组字面量/线性数组

数组字面量,是写作表达式一样的数组,它不是马上被赋值到一个变量中。

pragma solidity ^0.4.0;

contract C {
    function f() {
        g([uint(1), 2, 3]);
    }
    function g(uint[3] _data) {
        // ...
    }
}

数组字面量的类型是固定长度的内存数组,元素类型为给出元素的公共类型。[1,2,3]的类型是uint[8] memory,因为所有元素的类型是uint8。正因为如此,在上面例子中,第一个元素就很有必要转换为uint。需要注意的是,当前固定长度的内存数组不能赋值给可变长度的内存数组,所以如下的代码是不可行的:

program solidity ^0.4.0

contract C {
    function f() {
        // 
        uint[] x = [uint(1),3,4];
    }
}

未来会把这个限制移除,但是现在有些难题尚未解决,比如如何在ABI中传递数组。

成员变量

length:
数组有一个length成员变量来指示数组的长度。动态长度数组保存在storage中(不是在内存中),能够动态变化长度。程序不会自动的访问长度以外的元素。内存数组一旦创建,它的长度是固定的(但是是动态的,例如可以在运行的时候决定长度)

push:
动态storage数组和bytes(不是string)有一个成员函数,称为push,可以用来在数组末尾添加元素。该函数返回新的长度。

警告:在外部函数中还不能使用二维数组

**警告:由于EVM的限制,不能在外部函数调用中返回动态内容。在contract C { function f() returns (uint[]) { ... } }中的函数f,如果在web3中调用,将会返回数据,但是在solidity中调用,不会返回数据。
现在一个变通的方法是返回一个静态的足够大的数组。
**

pragma solidity ^0.4.0;

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;
    // 注意这里定义的不是两个动态数组,而是一个动态数组,其元素为长度为2的数组。
    bool[2][] m_pairsOfFlags;
    
    // newPairs会保存在memory中 - 函数参数的默认类型。
    function setAllFlagPairs(bool[2][] newPairs) {
        // 赋值给storage数组,替换整个数组。
        m_pairsOfFlags = newPairs;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) {
        // 越界访问将会产生异常
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) {
        // 如果数组的长度变小了,那么多余的元素将会被删除
        m_pairsOfFlags.length = newSize;
    }

    function clear() {
        // 清空数组
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // 清空数组
        m_pairsOfFlags.length = 0;
    }

    bytes m_byteData;

    function byteArrays(bytes data) {
        // byte 数组 ("bytes") 是不同的,因为它们保存时没有保留空余的空间
        // 但是以"uint8[]"的类型来对待
        // ?byte arrays ("bytes") are different as they are stored without padding,
        // ?but can be treated identical to "uint8[]"
        m_byteData = data;
        m_byteData.length += 7;
        m_byteData[3] = 8;
        delete m_byteData[2];
    }

    function addFlag(bool[2] flag) returns (uint) {
        return m_pairsOfFlags.push(flag);
    }

    function createMemoryArray(uint size) returns (bytes) {
        // 动态的内存数组通过new关键字生成。
        uint[2][] memory arrayOfPairs = new uint[2][](size);
        // 生成一个动态byte数组
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(i);
        return b;
    }
}
结构体

Solidity提供了定义类型的方法:通过结构体。例子如下所示:

pragma solidity ^0.4.11;

contract CrowdFunding {
    // 定义新的类型,包含两个数据字段
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID 是返回值变量
        // 生成一个新的结构体数据,并保存在storage中。 We leave out the mapping type.
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) payable {
        Campaign c = campaigns[campaignID];
        // 生成一个新的内存数据。通过给定的值进行初始化,并拷贝到storage中。
        // 你也可以通过 Funder(msg.sender, msg.value) 进行初始化.
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) returns (bool reached) {
        Campaign c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

合约并不提供众筹合约的所有函数,但是它包含了理解结构体的基本概念。结构体类型可以用在数组和映射中,并且它本身可以有映射和数组。

结构体类型包含本身的类型的字段是不可以的,尽管这个结构体是映射的值类型(?although the struct itself can be the value type of a mapping member.)。这个限制是很有必要的,因为结构体的大小应该有限。
要注意在所有函数中,结构体类型是怎样赋值给本地变量的(默认是storage数据位置)。它并没有复制结构体,而是保存了他的引用,所以通过本地变量,直接给所引用的结构体成员赋值也会改变数据。

当然,你也可以直接访问结构体的字段,不需要将它赋值给一个本地变量,例如campaigns[campaignID].amount = 0.

映射

映射类型通过这样的形式定义:mapping(_KeyType => _ValueType)。这里_KeyType可以是除了映射,动态长度的数组,合约,枚举和结构体之外的任何类型。_ValueType可以是所以的类型,包括映射。
映射可以看成是一个hash表,这个表的所有可能的键初始化为byte表示全为0的值:这个是默认值.相似的:键数据不是存储在映射里,而是键的keccak256hash,用来查找对应的值。
所以映射没有长度或者键值的概念,也不是集合。(?Because of this, mappings do not have a length or a concept of a key or value being “set”.)
映射只允许状态变量(或者在内部函数中的storage引用类型)。
可以设置映射为public,solidity会自动生成getter。_KeyType是getter函数必须的参数,返回值为_ValueType
_ValueType值同样可以是映射,会递归产生getter函数。(?The _ValueType can be a mapping too. The getter will have one parameter for each _KeyType, recursively.)

pragma solidity ^0.4.0;

contract MappingExample {
    mapping(address => uint) public balances;

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

contract MappingUser {
    function f() returns (uint) {
        return MappingExample(<address>).balances(this);
    }
}

注意:映射不可以遍历,但是可以在此基础上生成新的结构。例如,可遍历映射

使用LValues的操作符

如果a是一个LValue(例如,一个变量或者是可以被赋值something),那么可以使用以下的操作符。
a += e是和a = a + e一样的。操作符-=*=/=%=|=&=^=也是对应的。a++a--分别对应于a += 1a -= 1。但是表达式本身还是保持a的原始值。相反的,--a++a同样是对a加上1,但是返回了改变后的值。

删除

delete a将a类型的初始值赋给a。例如,对于整形,这个相当于是a = 0。它也可以用于数组,将一个长度为0的动态长度数组赋值给变量,或者是一个与之具有相通长度的静态数组,但是所有元素都被初始化的数组。对于结构体,将初始化所有的字段。
delete对整个映射是无效的(因为key可以是任何值,而且通常我们都不知道)。所以,如果是delete结构体,它将会重置所有非映射类型的字段,并且将递归字段,直到遇上映射。
delete a其实是对a重新赋值,例如重新赋值为一个对象。

pragma solidity ^0.4.0;

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() {
        uint x = data;
        delete x; // 将x设置为0,并不影响其他数据
        delete data; //将data设置,不会影响到x,因为x保存的是值的拷贝
        uint[] y = dataArray;
        delete dataArray; // 将dataArray.length设置为0, 但是因为uint[]是一个复杂的对象,所以y的值也被影响,因为y是storage对象的引用。
        // 另一方面: "delete y"是无效的,因为引用一个storage对象必须要有一个storage对象。
    }
}

初级类型的转换

隐式的转换

如果操作符被用于不同的类型时,编译器会尝试对一个操作数进行隐式的转换成另一个操作数的类型(赋值的时候也是一样的)。一般情况下,如果语意上有意义,并且没有信息损失,值类型之间的隐式转换是可行的。uint8可以转换为uint16int128可以转换为int256,但是int8不能转换为uint256(因为uint256不能存储负数)。另外,无符号整形可以转换为同样大小的或者更大的bytes,但是反过来不行。任何可以转换为uint160的类型可以转换为address.

显式的转换

如果编译器不允许隐式转换,但是你知道你要怎么做扽时候,可以用显式转换。需要注意的是,显式转换可能会导致有不期望的结果,所以要充分测试,并且保证结果是你期望的。下面的例子说明了从负数的int8转换为uint类型:

int8 y = -3;
uint x = uint(y);

这个代码端最后,x的值变为0xfffff..fd(64个十六进制字符),值为-3。

如果将类型显式转换为更小的类型,那么高位会被裁切掉:

uint32 a = 0x12345678;
uint16 b = uint16(a); // 现在b的值为0x5678

类型推断

简便起见,并不是总是需要为变量显式的指定类型,编译器会自动的对第一个表达式的类型进行引用,并赋值:

uint24 x = 0x123;
var y = x;

这里,y的类型将会是uint24。函数参数或者返回值不能用var.
警告:类型只能从第一个表达式推断,所以下面的例子是个死循环。因为i的类型为uint8,该类型的所有值都是小于2000的。for (var i = 0; i < 2000; i++) { ... }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 本章内容 使用对象 创建并操作数组 理解基本的 JavaScript 类型 使用基本类型和基本包装类型 引用类型的...
    闷油瓶小张阅读 677评论 0 0
  • 引用类型的值时引用类型的一个实例。在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起。有...
    cooore阅读 280评论 0 1
  • 爱我的母亲, 你们亦是如此。 今天我想给大家分享一些我和我母亲的故事。 妈,以后我赚钱养家,你负责貌美如花。 她,...
    随心小芝阅读 318评论 2 2
  • 诗歌/夕木夏睿 你悄悄地来到了 就像一阵风吹过我的世界 你悄悄地有走了 就像暴风骤雨一样及时 就让狂风吹乱我头发,...
    晨立夏尘阅读 242评论 0 0
  • 上帝为她关上一扇门,一定会为她打开一扇窗。 1. “妈妈,为什么我明明看得见,他们还要笑我是瞎子?” 刚上幼儿园的...
    栀子茗香阅读 690评论 22 12