solidity0.5.x笔记(3)-其他

变量类型—特殊的运算符delete

Solidity中有个特殊的操作符delete用于释放空间,因为区块链做为一种公用资源,为避免大家滥用。且鼓励主动对空间的回收,释放空间将会返还一些gas。

delete关键字的作用是对某个类型值a赋予初始值。比如如果删除整数delete a等同于a = 0。

删除基本类型

对于基本类型,使用delete会设置为对应的初始值:

bool b = true;//false

uint i = 100;//0

address addr = msg.sender;//0x0

bytes  memory varBy = "123";//0x0

string memory str = "hello world!";//""

删除bool类型是false,变长字节数组是0x0。string则是空串。

删除枚举

删除枚举类型时,会将其值重置为序号为0的值。

pragma solidity ^0.5.0;

contract DeleteEnum{

  enum Light{RED, GREEN, YELLOW}

  Light light;

  function f() public returns (Light){
    light = Light.GREEN;

    delete light;

    return light;
  }

}

上面的例子中,删除light后,light将被置为序号为0的值。

删除函数

尝试了一下删除函数,会报错Error: Expression has to be an lvalue.,看来不能删除函数。

删除结构体

删除一个结构体,会将其中的所有成员变量一一置为初值,我们来看一个例子。

pragma solidity ^0.5.0;

contract DeleteStruct{
  struct S{
    uint a;
    string b;
    bytes c;
  }

  S s;

  function delStruct() public returns (uint, string memory , bytes memory){
    s = S(10, "Hello world!", "abc");

    //删除结构体将重置所有元素
    delete s;

    return (s.a, s.b, s.c);
  }
}

在上面的例子中,我们声明了结构体s,调用delete s,结构体的值将变为其对应类型uint,string,bytes的初始值0,空串和0x0。

删除映射

映射是一个特殊的存在,由于映射的键并不总是能有效遍历(数据结构没有提供接口,也并不总是需要关心所有键是什么),所存的键的数量往往是非常大的,所以我们并不能直接删除一个映射。

如果直接删除一个映射会报错Unary operator delete cannot be applied
但我们可以指定键来删除映射中的某一项:

map[msg.sender] = 100;
//可以按key删除map
delete map[msg.sender];

删除结构体中的映射

如果删除一个结构体时,其中含有映射类型,会跳过映射类型。我们来看一个删除含映射的结构体示例:

pragma solidity ^0.5.0;

contract MappingStructDelete{

  struct MapStruct{
    mapping(address => uint) m;
    uint i;
  }

  MapStruct ms;

  function delMapping() public returns (uint, uint){
    ms = MapStruct(200);
    ms.m[msg.sender] = 2000;

    //删除一个结构体
    delete ms;

    return (ms.m[msg.sender], ms.i);
  }
}

上面的示例中,删除结构体ms,并没有影响其中映射ms.m的值。

删除数组

对于定长数组,删除时,是将数组内所有元素置为初值。

而对于变长数组时,则是将长度置为0。

pragma solidity ^0.5.0;

contract DeleteDynamicArray{
  function delDynamicArr() pure public returns (uint){
     uint[] memory a = new uint[](7);
     a[0] = 100;
     a[1] = 200;

     delete a;

     return (a.length);
  }
}

删除数组的一个元素

我们也可以删除数组的一个元素,有一点违反直觉的是,删除一个元素后,数组会留个空隙在那里。比如三个元素的数组,删除了第二个元素,只是将第二个元素置为了初始值,其它没变。

pragma solidity ^0.5.0;

contract DeleteArrayEle{
  function delArrEle() public pure returns (uint, uint, uint){
    uint[] memory arr = new uint[](3);

    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;

    delete arr[1];

    return (arr[0], arr[1], arr[2]);
  }
}

上述的代码运行后,将返回1,0,3。删除只是赋值,并没有移动元素。

gas使用的考虑

上文中,我们了解到,删除时会忽略映射,以及数组的某个元素被删除后,并不会自动整理数组。这些看起来很不符合常理,其实是基于对gas限制的考虑。因为如果映射或数组非常大的情况下,删除或维护它们将变得非常消耗gas。

不过,清理空间,可以获得gas的返还。但无特别意义的数组的整理和删除,只会消耗更多gas,需要在业务实现上进行权衡。

清理的最佳实践

由于本身并未提供对映射这样的大对象的清理,所以存储并遍历它们来进行清理,显得特别消耗gas。一种实践就是能复用就复用,一般不主动清理。下面是一个数组的插入实现,比如增加一个计数器,直接忽略已使用过的位置。

uint numElements = 0;
uint[] array;

function insert(uint value) {
    if(numElements == array.length) {
        array.length += 1;
    }
    array[numElements++] = value;
}

function clear() {
    numElements = 0;
}

上面的例子中,我们在数组新增时,直接忽略掉已使用过的槽位。而在代码内,我们使用numElements来代替array.length,以获取当前数组所在的位置。

如果这种大对象是在某个事件发生时,一次性使用,然后需要回收的。一个更有效的方式是,在发生某个事件时,创建一个新合约,在新合约完成逻辑,完成后,让合约suicide。清理合约占用空间返还的gas就退还给了调用者,来节省主动遍历删除消耗的额外gas。

删除的注意事项

删除本质是对一个变量赋初值。所以我们删除storage的引用时会报错,因为storage的引用并没有自己已分配的存储空间,所以不能对storage的引用直接赋初值。

pragma solidity ^0.5.0;

contract DeleteStorageRef{

  struct S{
      string name;
      uint age;
  }
  
  S s;
  
  function DelStorageRef() public {
    S storage storageRef = s;
    
    //Error: Unary operator delete cannot be applied to type struct DeleteStorageRef.S storage pointer
    delete storageRef;
    
    delete s;
  }
}

上面的例子中,删除storageRef会报错。

变量类型—基本类型间的转换

隐式转换

如果一个运算符能支持不同类型。编译器会隐式的尝试将一个操作数的类型,转为另一个操作数的类型,赋值同理。

一般来说,值类型间的互相转换只要不丢失信息,语义可通则可转换。下面,我们来看一个整数转换的例子:

pragma solidity ^0.5.0;

contract Int{
  function conversion() public pure returns (uint16){
    uint8 a = 1;
    //隐式转换
    uint16 b = a;

    return (b);
  }
}

上面的例子中,我们将一个uint8的变量a隐式的转换为了uint16。同理它还支持转为uint32,uint128和uint256。

另外,无符号整数可以被转为同样,或更大的字节的类型。但需要注意的是,不能反过来转换。由于address是20字节大小,所以它与int160大小是一样。

pragma solidity ^0.4.24;

contract IntToAddress{
  function f() returns (uint){
    uint160 i = 10;
    address addr = i;
    return addr.balance;
  }
}

上面的例子中,将uint160的i转为了一个address。

显式转换

编译器不会将语法上不可转换的类型进行隐式转换,此时我们要通过显式转换的方式,比如将一个有符号整数,转为一个无符号整数。

pragma solidity ^0.5.0;

contract ExplicitConversion{
  function f() public pure returns (int8){
    uint8 a = 1;

    //强制转换
    int8 b = int8(a);
    return b;
  }
}

你可能感兴趣的:(solidity)