Solidity学习(3)

1. Solidity源文件的布局

源文件包括任意数量的合约定义、include指令和pragma指令。

1.1 版本pragma

源文件可以(应该)用一个所谓的版本注释来注释,以防止被编译以后可能会引入不兼容的编译器版本。 我们试图将这种变化保持在绝对最低限度,尤其是引入更改语义的方式也需要语法的改变,但这当然不总是可能的。 因此至少对于包含版本的突发更改,可以通过阅读更新日志,这些版本有0.x.0或x.0.0格式。

版本语法格式:

pragma solidity ^0.4.0;

1.2 导入其他源文件

1.2.1 语法和语义

类似JS的Import语句,但是Solidity不支持“default export”。

全局:

import "filename";

从“filename”中导入所有的全局symbols到当前全局范围

import * as symbolName from "filename";

创建新的全局symbol symbolName,其成员都是来自“filename”的全局symbols。

import {symbol1 as alias, symbol2} from "filename";

创建新的全局symbols“alias”和“symbol2”,它将分别从”filename” 引入symbol1 和 symbol2。

更方便的语法

import "filename" as symbolName;

等价于

import * as symbolName from "filename";

1.2.2 路径

. 当前目录;
.. 父目录;
不用.开头的都视为绝对路径。

从相同目录下import一个文件x作为当前文件:import "./x" as x;

import “x” as x; 是不同的文件引用(在全局中使用”include directory”)。

1.2.3 使用真正的编译器

solc:命令行编译器,重映射context:prefix=target,context:和=target部分都是可以选择的。例如:
如果你从github.com/ethereum/dapp-bin/克隆到本地/usr/local/dapp-bin,可以在源文件中使用以下内容:

import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

运行编译器:

solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol

作为一个更复杂的示例,假设您依赖使用一个非常旧版本的dapp-bin的模块。旧版本的dapp-bin在/usr/local/dapp-bin_old可以使用:

solc module1:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ \
     module2:github.com/ethereum/dapp-bin/=/usr/local/dapp-bin_old/ \
     source.sol

module2中为旧版本;
module1中为新版本。

Remix:提供从github上的自动重映射,并且自动检索网络上的文件,可以import 迭代映射:

import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;

1.3 注释

// This is a single-line comment.

/*
This is a
multi-line comment.
*/

2. 合约的结构

Solidity合约类似面向对象语言中的类。每合约可以包含声明状态变量、函数、函数修饰符、事件、结构类型和枚举类型。此外,合约可以从其他合约中继承。

2.1 状态变量

状态变量是永久存储在合约存储中的值。

pragma solidity ^0.4.0;

contract SimpleStorage {
    uint storedData; // State variable
    // ...
}

2.2 函数

函数是合约中的代码可执行单位。

pragma solidity ^0.4.0;

contract SimpleAuction {
    function bid() payable { // Function
        // ...
    }
}

函数调用可以在内部或外部发生,对其他合约有不同级别的可见性。

2.3 函数修饰符

函数修饰符可用于以声明方式修改函数的语义(参见合约部分中的函数修饰符)。

pragma solidity ^0.4.11;

contract Purchase {
    address public seller;

    modifier onlySeller() { // **Modifier**
        require(msg.sender == seller);
        _;
    }

    function abort() onlySeller { // **Modifier usage**
        // ...
    }
}

2.4事件

事件是与EVM日志设施的方便接口。

pragma solidity ^0.4.0;

contract SimpleAuction {
    event HighestBidIncreased(address bidder, uint amount); // Event

    function bid() payable {
        // ...
        HighestBidIncreased(msg.sender, msg.value); // Triggering event
    }
}

请参见合约部分中的事件,了解如何声明事件和如何在dapp中使用事件。

2.5 结构类型

Structs是自定义定义的类型,可以组合多个变量(参见类型部分中的struct)。

pragma solidity ^0.4.0;

contract Ballot {
    struct Voter { // Struct
        uint weight;
        bool voted;
        address delegate;
        uint vote;
    }
}

2.6 枚举类型

枚举可以用于创建具有有限值集的自定义类型(参见类型部分中的枚举)。

pragma solidity ^0.4.0;

contract Purchase {
    enum State { Created, Locked, Inactive } // Enum
}

3. 类型

Solidity是一种静态类型的语言,这意味着在编译时需要指定每个变量(state和local)的类型。Solidity提供了几种基本类型,可以组合成复杂类型。

3.1 值类型

值类型:值类型的变量总是按值传递的,作为函数参数或者在赋值中,总需要拷贝。

(1)布尔
bool: 为真or假

!:逻辑否
&&: 逻辑与;类似and
||:逻辑或,类似or
==:等
!=:不等

(2)整型
int/uint: 整型/无符号整型

(3)定点数(Fixed Point Numbers)

Solidity还没有完全的支持定点数。可以被声明,但不能被assigned或者from

fixed/ufixed;
ufixedMxN / fixedMxN, M表示按类型所取的位数,N表示有多少位小数。

(4)地址

address:保存一个20字节的值(一个以太地址的大小)。地址类型也有成员,并作为所有合约的基础。

从版本0.5.0开始,合约不是从地址类型派生出来的,但仍然可以显式地转换为地址。

(5)地址成员

  • balance and transfer

可以利用balance查询地址的余额,并利用transfer给地址发送以太币。

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

类似transfer的功能。一旦执行失败,当前的合约不会停止,但是send会返回false。

注意:使用send有一些危险:如果调用堆栈深度在1024(可能被调用者强制),则传输失败;如果接收方耗尽了gas,也会失败。因此为了保证安全的Ether传输,总是检查send的返回值,使用transfer,或者使用接收方提取资金的模式更好。

  • call,callcode and delegatecall

另外,合约的接口不是附在ABI 上,函数调用可以引用任意数量的参数,这些参数要填补成32字节,并被拼接。一个例外的情况是第一个参数被确切的编码成4字节,这种情况下,不用填补,直接使用函数符号。

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

call返回了一个布尔值,表示函数是否是正常调用结束(true)或引起了EVM异常(false)。不可能访问返回实际数据(这个我们需要提前知道编码和大小)。

可以用. gas() modifier调整所供应的gas:

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

以太币也可以:

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

这些modifier可以组织在一起,顺序随意:

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

注意:
不能使用gas和value修改器在重载函数中。

类似的,delegatecall函数:不同之处是它不仅使用给定地址的代码,其他的方面(storage,balance,。。。)从当前的合约中获取。delegatecall的目的是使用存储在另一个合约中的库代码。用户需要保证在两个合约中的存储布局适合于delegatecall的使用。

callcode的使用不需要提供原始的msg.sender和msg.value的值。

.gas()三个函数都可以使用,.value()不支持delegatecall。

(6)固定大小的数组

bytes1, bytes2, bytes3, …, bytes32.
byte 是 bytes1的别名.

  • .length 生成固定长度的数组(只读)

注意:也可以使用字节数组byte[ ],但是这个会浪费很多空间,最好使用bytes。

(7)动态数组

bytes:动态大小字节数据,不是一个值类型。
string:动态大小utf-8编码的字符串,不是一个值类型。

(8)地址常量?(Address Literals)

通过地址校验和测试的十六进制 literals ,例如:0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF 为地址类型。

(9)有理数和整数Literals

Solidity没有8进制数;
支持科学计数法;

Number literal expressions:数字文字表达式保留任意的精度,直到它们被转换成non-literal 类型(也就是说,用non-literal 的表达式一起使用它们)。这意味着计算不会溢出,而分隔不会在数字文字表达式中截断。(分割?截断?)

这里的意思好像是结果都是整数?
还有就是1+2和2+1的意思是一样的。

(10)String Literals

字符串常量写作:“foo”或者‘bar’,字符串不像C,不包含结束符。

(11)定长字节数组

字符串的长度类型可以是变长的。可以隐式的转换为:bytes1, bytes2, bytes3, …, bytes32.

pragma solidity ^0.4.0;

contract StringConvert{
    function test() returns (bytes3){
      bytes3 a = "123";

      //bytes3 b = "1234";
      //Error: Type literal_string "1234" is not implicitly convertible to expected type bytes3.

      return a;
  }
}

(12)转义字符

\xNN表示16进制。

(13)16进制字面量(Literals)

以关键字hex打头,后面为单引号或者双引号包裹的字符串。如hex”001122ff”。

pragma solidity ^0.4.0;

contract HexLiteral{
    function test() returns (string){
      var a = hex"001122FF";

      //var b = hex"A";
      //Expected primary expression

      return a;
  }
}

hex后面必须是双数,因为hex是由两个[0-9a-z]组成的。

16进制的字面量与字符串可以进行类似操作:

pragma solidity ^0.4.0;

contract HexLiteralBytes{
    function test() returns (bytes4, bytes1, bytes1, bytes1, bytes1){
      bytes4 a = hex"001122FF";

      return (a, a[0], a[1], a[2], a[3]);
  }
}

可以发现,它可以隐式的转为bytes,上述代码的执行结果如下:

Result: "0x001122ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000000"
Transaction cost: 21857 gas. 
Execution cost: 585 gas.
Decoded: 
bytes4: 0x001122ff
bytes1: 0x00
bytes1: 0x11
bytes1: 0x22
bytes1: 0xff

(14)枚举

一种用户自定义类型,可以显示的进行与整数进行转换,但不能进行隐式的转换。显示转换会在运行时检查数值范围,如果不匹配,会引起异常。枚举类型应至少有一个成员。

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;
    }

    // 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() returns (ActionChoices) {
        return choice;
    }

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

(15)函数类型

可以将函数赋值给一个函数类型的变量,可以将函数作为参数进行传递,可以在函数调用中返回一个函数。

有两种类型:internal,external。

  • 内部函数(internal)
    不能再当前合约的上下文环境以外的地方执行,内部韩束只能在当前合约内部被使用。如在当前的代码块内,包括内部库函数,和继承的函数中。

  • 外部函数(external)
    外部函数由一个地址和一个函数签名组成,它们可以通过并从外部函数调用返回。

  • 函数的定义

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

不写类型的话,默认为internal。

pragma solidity ^0.4.0;

contract Test{
    //默认是internal类型的
    function noParameter() returns (uint){}

    //无返回结果
    function noReturn1(uint x) {}

    //如果无返回结果,必须省略`returns`关键字
    //function noReturn2(uint x) returns {} 
}

如果一个函数变量没有初始化,直接调用它将会产生异常。如果delete了一个函数后调用,也会发生同样的异常。
如果外部函数类型在Solidity的上下文环境以外的地方使用,他们会被视为function类型。编码为20字节的函数所在地址,紧跟4字节的函数方法签名的共占24字节的bytes24类型。

  • 函数的internal与external:
    调用一个函数f()时,我们可以直接调用f(),或者使用this.f()。但两者有一个区别。前者是通过internal的方式在调用,而后者是通过external的方式在调用。请注意,这里关于this的使用与大多数语言相背。下面通过一个例子来了解他们的不同:
pragma solidity ^0.4.5;

contract FuntionTest{
    function internalFunc() internal{}

    function externalFunc() external{}

    function callFunc(){
        //直接使用内部的方式调用
        internalFunc();

        //不能在内部调用一个外部函数,会报编译错误。
        //Error: Undeclared identifier.
        //externalFunc();

        //不能通过`external`的方式调用一个`internal`
        //Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //this.internalFunc();

        //使用`this`以`external`的方式调用一个外部函数
        this.externalFunc();
    }
}
contract FunctionTest1{
    function externalCall(FuntionTest ft){
        //调用另一个合约的外部函数
        ft.externalFunc();

        //不能调用另一个合约的内部函数
        //Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //ft.internalFunc();
    }
}
  • 官方的列子(前者为内部函数类型,后者为外部函数)
pragma solidity ^0.4.5;

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) 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)
  {
    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) {
    // Here goes the check that the reply comes from a trusted source
    requests[requestID].callback(response);
  }
}

contract OracleUser {
  Oracle constant oracle = Oracle(0x1234567); // known contract
  function buySomething() {
    oracle.query("USD", this.oracleResponse);
  }
  function oracleResponse(bytes response) {
    require(msg.sender == address(oracle));
    // Use the data
  }
}

其实并看不懂。。。

3.2 引用类型

复杂类型,占用的空间更大,超过256字节,因为拷贝他们占用更多的空间。因此,我们需要考虑将他们存储在什么位置:内存(memory,数据不是永久存在的)或存储(storage,值类型中的状态变量)。

(1)数据位置

这些复杂类型:数组,结构体,在Solidity有一个额外的属性——数据的存储位置。可选为memory和storage。

memory存储位置和普通程序的内存一致。即分配即使用,越过作用域将不可被访问,等待被回收。区块链底层实现了图灵完备,所以有非常多的状态需要永久记录下来。比如,参与众筹的所有参与者。所以,需要使用storage类型,一旦使用storage这个类型,数据将永远存在。

默认函数参数,返回参数为memory;默认局部变量为storage;默认状态变量(合约声明的public变量)为storage。

calldata:存储的为函数参数,为只读类型,不会永久的存储一个数据位置。外部函数的参数(不包括返回的参数)强制指定为calldata。效果为memory类似。

不同数据位置变量赋值产生的结果不同。

将一个storage的状态变量,赋值给一个storage的局部变量,是通过引用传递。所以对于局部变量的修改,同时修改关联的状态变量。但另一方面,将一个memory的引用类型赋值给另一个memory的引用,不会创建另一个拷贝。

pragma solidity ^0.4.0;

contract DataLocation{
  uint valueType;
  mapping(uint => uint) public refrenceType;

  function changeMemory(){
    var tmp = valueType;
    tmp = 100;
  }

  function changeStorage(){
    var tmp = refrenceType;
    tmp[1] = 100;
  }

  function getAll() returns (uint, uint){
    return (valueType, refrenceType[1]);
  }
}
pragma solidity ^0.4.0;

contract C {
    uint[] x; // the data location of x is storage

    // the data location of memoryArray is memory
    function f(uint[] memoryArray) {
        x = memoryArray; // works, copies the whole array to storage
        var y = x; // works, assigns a pointer, data location of y is storage
        y[7]; // fine, returns the 8th element
        y.length = 2; // fine, modifies x through y
        delete x; // fine, clears the array, also modifies y
        // The following does not work; it would need to create a new temporary /
        // unnamed array in storage, but storage is "statically" allocated:
        // y = memoryArray;
        // This does not work either, since it would "reset" the pointer, but there
        // is no sensible location it could point to.
        // delete y;
        g(x); // calls g, handing over a reference to x
        h(x); // calls h and creates an independent, temporary copy in memory
    }

    function g(uint[] storage storageArray) internal {}
    function h(uint[] memoryArray) {}
}
  • 总结:
  • 强制的数据位置:外部函数(external function)的参数(不包括返回参数)强制为:calldata;状态变量(state variables)强制为:storage。
  • 默认数据位置(default data location):函数参数包括返回参数为memory;所有其他的局部变量为storage。

(2)数组

数组可以声明指定长度,或者变长。对storage的数组来说,元素类型可以是任意的,类型可以是数组,映射类型,数据结构等。但对于memory的数组来说,如果函数是对外可见的,那么函数参数不能是映射类型的数组,只能是支持ABI的类型。

一个类型为T,长度为k的数组,可以声明为T[k],而一个变长的数组则声明为T[]。
你还可以声明一个多维数据,如5个类型为uint的变长数组,可以声明为uint[][5] x。需要留心的是,相比非区块链语言,多维数组的长度声明是反的。

要访问第三个动态数据的,第二个元素,使用x[2][1]。数组的序号是从0开始的,序号顺序与定义相反。

bytes和string是一种特殊的数组。bytes类似byte[],但在外部函数作为参数调用中,会进行压缩打包,更省空间,所以应该尽量使用bytes4。string类似bytes,但不提供长度和按序号的访问方式。

由于bytes与string,可以自由转换,你可以将字符串s通过bytes(s)转为一个bytes。但需要注意的是通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。

类型为数组的状态变量,可以标记为public类型,从而让Solidity创建一个访问器,如果要访问数组的某个元素,指定数字下标就好了。

  • 创建一个数组

可使用new关键字创建一个memory的数组。与stroage数组不同的是,你不能通过.length的长度来修改数组大小属性。

pragma solidity ^0.4.0;

contract C {
    function f() {
        //创建一个memory的数组
        uint[] memory a = new uint[](7);

        //不能修改长度
        //Error: Expression has to be an lvalue.
        //a.length = 100;
    }

    //storage
    uint[] b;

    function g(){
        b = new uint[](7);
        //可以修改storage的数组
        b.length = 10;
        b[9] = 100;
    }
}
  • 字面量及内联数组

数组字面量,作为表达式编写的数组,不立即分配给其一个变量。

pragma solidity ^0.4.0;

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

定长数组不能与变长数组相互赋值。

  • 数组的属性和方法

数组有一个.length属性,表示当前的数组长度。storage的变长数组,可以通过给.length赋值调整数组长度。memory的变长数组不支持。

不能通过访问超出当前数组的长度的方式,来自动实现上面说的这种情况。memory数组虽然可以通过参数,灵活指定大小,但一旦创建,大小不可调整,对于变长数组,可以通过参数在编译期指定数组大小。

  • push方法

storage的变长数组和bytes都有一个push(),用于附加新元素到数据末端,返回值为新的长度。

pragma solidity ^0.4.0;

contract C {
    uint[] u;
    bytes b;

    function testArryPush() returns (uint){
        uint[3] memory a = [uint(1), 2, 3];

        u = a;

        return u.push(4);
    }

    function testBytesPush() returns (uint){
        b = new bytes(3);
        return b.push(4);
    }
}
  • 限制的情况

当前在外部函数中,不能使用多维数组。

另外,基于EVM的限制,不能通过外部函数返回动态的内容。

pragma solidity ^0.4.0;

contract C { 
    function f() returns (uint[]) { 
    }
}

在上面的例子中,通过web.js调用能返回数据,但在Solidity中不能返回数据。一种临时的解决办法,是使用一个非常大的静态数组。

pragma solidity ^0.4.0;

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;
    // Note that the following is not a pair of dynamic arrays but a
    // dynamic array of pairs (i.e. of fixed size arrays of length two).
    bool[2][] m_pairsOfFlags;
    // newPairs is stored in memory - the default for function arguments

    function setAllFlagPairs(bool[2][] newPairs) {
        // assignment to a storage array replaces the complete array
        m_pairsOfFlags = newPairs;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) {
        // access to a non-existing index will throw an exception
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) {
        // if the new size is smaller, removed array elements will be cleared
        m_pairsOfFlags.length = newSize;
    }

    function clear() {
        // these clear the arrays completely
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // identical effect here
        m_pairsOfFlags.length = 0;
    }

    bytes m_byteData;

    function byteArrays(bytes data) {
        // 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) {
        // Dynamic memory arrays are created using `new`:
        uint[2][] memory arrayOfPairs = new uint[2][](size);
        // Create a dynamic byte array:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(i);
        return b;
    }
}

(3)结构体

pragma solidity ^0.4.0;

contract CrowdFunding{
    struct Funder{
        address addr;
        uint amount;
    }

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

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

    function candidate(address beneficiary, uint goal) returns (uint compaingnID){
        // initialize
        campaigns[compaingnID++] = Campaign(beneficiary, goal, 0, 0);
    }

    function vote(uint compaingnID) payable {
        Campaign c = campaigns[compaingnID];

        //another way to initialize
        c.funders[c.funderNum++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function check(uint comapingnId) returns (bool){
        Campaign c = campaigns[comapingnId];

        if(c.amount < c.goal){
            return false;
        }

        uint amount = c.amount;
        // incase send much more
        c.amount = 0;
        if(!c.beneficiary.send(amount)){
            throw;
        }
        return true;
    }
}

上面的代码向我们展示的一个简化版的众筹项目,其实包含了一些struct的使用。struct可以用于映射和数组中作为元素。其本身也可以包含映射和数组等类型。

我们不能声明一个struct同时将这个struct作为这个struct的一个成员。这个限制是基于结构体的大小必须是有限的。

虽然数据结构能作为一个mapping的值,但数据类型不能包含它自身类型的成员,因为数据结构的大小必须是有限的。

需要注意的是在函数中,将一个struct赋值给一个局部变量(默认是storage类型),实际是拷贝的引用,所以修改局部变量值时,会影响到原变量。

当然,你也可以直接通过访问成员修改值,而不用一定赋值给一个局部变量,如campaigns[comapingnId].amount = 0

3.3 Mapping

映射或字典类型,一种键值对的映射关系存储结构。键的类型允许除映射外的所有类型,如数组,合约,枚举,结构体。值的类型无限制。

定义方式:mapping(_KeyType => _KeyValue) ,这里_KeyType 可以使任意类型

3.4 左值的相关运算符

  • 特殊的运算符 delete

delete运算符,用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。

delete对于映射类型几乎无影响,因为键可能是任意的,且往往不可知。所以如果你删除一个结构体,它会递归删除所有非mapping的成员。当然,你是可以单独删除映射里的某个键,以及这个键映射的某个值。

3.5 基本类型之间的转换

  • 显式转换
pragma solidity ^0.4.0;

contract DeleteExample{
    uint a;

    function f() returns (uint){
      int8 y = -3;
      uint x = uint(y);
      return x;
    }
}
  • 隐式转换

如果运算符支持两边不同的类型,编译器会尝试隐式转换类型,同理,赋值时也是类似。通常,隐式转换需要能保证不会丢失数据,且语义可通。如uint8可以转化为uint16,uint256。但int8不能转为uint256,因为uint256不能表示-1。

此外,任何无符号整数,可以转换为相同或更大大小的字节值。比如,任何可以转换为uint160的,也可以转换为address。

3.6 类型推断(Type Deduction)

为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断。

uint24 x = 0x123;
var y = x;

函数的参数,包括返回参数,不可以使用var这种不指定类型的方式。

需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。所以代码for (var i = 0; i < 2000; i++) {}将是一个无限循环,因为一个uint8的i的将小于2000。

pragma solidity ^0.4.4;

contract Test{
    function a() returns (uint){
      uint count = 0;
        for (var i = 0; i < 2000; i++) {
            count++;
            if(count >= 2100){
                break;
            }
        }
        return count;
    }
}

参考资料:
http://www.tryblockchain.org/Solidity-TypeDeduction-%E7%B1%BB%E5%9E%8B%E6%8E%A8%E6%96%AD.html
http://solidity.readthedocs.io/en/develop/solidity-in-depth.html

没有更多推荐了,返回首页