Skip to content

Latest commit

 

History

History
274 lines (204 loc) · 10.8 KB

File metadata and controls

274 lines (204 loc) · 10.8 KB

变量

Solidity 是一种静态类型语言,这意味着每个变量(状态变量和局部变量)都需要在编译时指定变量的类型。

Solidity 提供了几种基本类型,并且基本类型可以用来组合出复杂类型。

“undefined”或“null”值的概念在Solidity中不存在,但是新声明的变量总是有一个 默认值 ,具体的默认值跟类型相关。要处理任何意外的值,应该使用错误处理来恢复整个事务,或者返回一个带有第二个“bool”值的元组表示成功。

值类型

以下类型也称为值类型,因为这些类型的变量将始终按值来传递。 也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝。

值类型包含

  • 布尔(Booleans)
  • 整型(Integer)
  • 地址(Address)
  • 定长字节数组(fixed byte arrays)
  • 有理数和整型(Rational and Integer Literals,String literals)
  • 枚举类型(Enums)
  • 函数(Function Types)

变量声明后均有一个初值,是对应类型的“零态”,意即对应的类型的字节表示的全0。使用中需要特别小心的是这与其它语言的默认值如null或undefined有所不同,因为有时0也是一种业务值。下面我们来看一些常见类型的声明,以及它们的默认值:

pragma solidity ^0.5.0;

contract DeclareOfElement{
    bool b;//fasle

    uint i;//0

    address addr;//0x0

    bytes32 by;//0x0000000000000000000000000000000000000000000000000000000000000000

    bytes varBy;//0x

    string str;//

    uint8[] arr;//

}

在上述例子中,bool的默认值为false,bytes32的默认值为32字节长的0。对于引用类型,bytes类型默认值为空字节数组,string为默认值为空串,动态数组uint8[] arr为空。

Solidity还针对区块链场景引入了一些特别的值类型地址(Address)。地址类型表示以太坊地址的长度,大小为20个字节,160位,用一个uint160编码,地址虽然是值类型但是也包含一些方法和属性,是为便利智能合约开发专门设计的。

所有的地址都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用。当然如果地址代表一个普通账户时,就没有那么多丰富的功能了。

十六进制的字符串,凡是能通过地址合法性检查的,都会被认为是地址,如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

地址的balance方法,可以通过它得到一个地址的余额;

pragma solidity ^0.5.0;

contract addressTest{
    function getBalance(address addr) public view returns (uint){
        return addr.balance;
    }
}

如果只是想得到当前合约的余额,其实还可以用this改写:

pragma solidity ^0.5.0;

contract addressTest{
    function getBalance() public view returns (uint){
        return address(this).balance;
    }
}

地址的send方法,用来向某个地址发送货币(货币单位是wei)。

pragma solidity ^0.5.0;

contract Payable{

    address payable owner;

    constructor () public {
        owner = msg.sender;
    }
    
    function GetBlance(address addr) public view returns (uint256) {
        return addr.balance;
    }

    function Transfer() public payable returns(address, uint256){
        owner.transfer(msg.value);
        return (msg.sender, msg.value);
    }
}

这个合约实现的是充值。this.send(msg.value)意指向合约自身发送msg.value量的以太币。msg.value是合约调用方附带的以太币。

send方法在发送以太币失败以后只会返回false,并不会发生一个error,社区的一些开发者觉得这样不是太安全,最后经过社区讨论在#610这个讨论中确定加入,毕竟以太坊那么值钱了,所以在solidity 0.4.13中引入了一个新的转账方法transfer

transfer执行失败则会throw,这也就意味着使用send时一定要判断是否执行成功。默认情况下最好使用transfer(因为内置了执行失败的处理),二者在发送的同时传输2300gas,gas数量不可调整。

引用类型

引用类型可以通过多个不同的名称修改它的值,而值类型的变量,每次都有独立的副本。因此,必须比值类型更谨慎地处理引用类型。 目前,引用类型包括结构,数组和映射。如果使用引用类型,则必须明确指明数据存储哪种类型的位置(空间)。

对于值类型,声明变量后,即赋值为默认值,可正常使用。而对于引用类型是否仍需同其它语言一样进行显式初始化,进行内存分配,才能进一步使用呢,引用类型相对复杂,占用空间较大的。在拷贝时占用空间也比较大。所以solidity也和其他语言一样通过引用传递。常见的引用类型有:

  • 不定长字节数组(bytes)
  • 字符串(string)
  • 数组(Array)
  • 结构体(Struts)

数据位置

复杂类型,如数组(arrays)结构体(struct)在Solidity中有一个额外的属性,数据的存储位置,可选的为memorystorage。 memory存储位置同我们普通程序的内存一致。即分配,即使用,越过作用域即不可被访问,等待被回收。而在区块链上,由于底层实现了图灵完备,故而会有非常多的状态需要永久记录下来。比如,参与众筹的所有参与者。那么我们就要使用storage这种类型了,一旦使用这个类型,数据将永远存在。

基于程序的上下文,大多数时候这样的选择是默认的,我们可以通过指定关键字storage和memory修改它。

默认的函数参数,包括返回的参数,他们是memory。默认的局部变量是storage的1。而默认的状态变量(合约声明的公有变量)是storage。

另外还有第三个存储位置calldata。它存储的是函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。

数据位置指定非常重要,因为不同数据位置变量赋值产生的结果也不同。在memory和storage之间,以及它们和状态变量(即便从另一个状态变量)中相互赋值,总是会创建一个完全不相关的拷贝。

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

pragma solidity ^0.5.0;

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

  function changeMemory() public view {
    uint tmp = valueType;
    tmp = 100;
  }

  function changeStorage() public {
    mapping(uint => uint) storage tmp = refrenceType;
    tmp[1] = 100;
  }

  function getAll() public view returns (uint, uint){
    return (valueType, refrenceType[1]);
  }
}

下面来看下官方的例子说明:

pragma solidity ^0.5.0;

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

    // the data location of memoryArray is memory
    function f(uint[] memory memoryArray ) public {
        x = memoryArray; // works, copies the whole array to storage
        uint[] storage 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[] memory memoryArray) public {}
}

总结 强制的数据位置(Forced data location) 外部函数(External function)的参数(不包括返回参数)强制为:calldata 状态变量(State variables)强制为: storage 默认数据位置(Default data location) 函数参数(括返回参数:memory 所有其它的局部变量:storage

更改数据位置或类型转换将始终产生自动进行一份拷贝,而在同一数据位置内(对于 存储storage 来说)的复制仅在某些情况下进行拷贝。

动态数组

对于数组,声明后,仍需分配内存后方可访问,下面的代码会报错Exception during execution. (invalid opcode)。

pragma solidity ^0.5.0;

contract initial{
  function f() public pure returns (bytes1, uint8){
    bytes memory bs;
    uint8[] memory arr;
    return (bs[0], arr[0]);
  }
}

由于上例中,我们越界访问元素,故抛出了异常。如果要主动分配内存,进行初始化,如何做呢,一起来看看下面的实现。

pragma solidity ^0.5.0;

contract initial{
  function f() public pure returns (bytes1, uint8){
    bytes memory bs = new bytes(1);
    uint8[] memory arr = new uint8[](1);
    return (bs[0], arr[0]);
  }
}

上述代码通过new关键进行了内存分配,现在即可正常访问第一个数组元素了。

映射

映射的声明后,不用显式初始化即可使用,只是里面不会有任何值,下面是一个非常简单的映射的例子。

pragma solidity ^0.5.0;

contract DeclareOfMapping{
  mapping(uint => string) bar;

  function f() public returns (string memory){
    bar[0] = "foo";
    return bar[0];
  }
}

上面的例子中,我们定义了一个映射,然后返回了映射中其中的一个元素值。由于没有存值,故这里将返回的是空串。

枚举

枚举类型不用显式初始化,默认值将为0。即顺位第一个值。下面来看一个枚举的示例。

pragma solidity ^0.5.0;

contract DeclareOfEnum{
  enum Light{RED, GREEN, YELLOW}
  Light light;
  
  function f() public pure returns (Light){
    return Light.GREEN;
  }
}

上面的代码的输出结果为1,RED,GREEN,YELLO的值分别为0,1,2。

结构体

结构体声明后,不用显式初始化即可使用。当没有显式初始化时,其成员值均为默认值。一起来看一个结构体声明的例子:

pragma solidity ^0.5.0;

contract DeclareOfStruct{
  struct people{
    string name;
    uint age;
  }

  people qyuan = people({
      name: "hehao",
      age: 25
  });

  function f() public view returns(string memory, uint) {
    return (qyuan.name, qyuan.age);
  }
}

上面的代码中定义了一个结构体,并创建了一个变量people。所有结构体内的成员值均被赋值,上述代码运行后,将返回"hehao", 25。