Solidity语法(二)引用类型(Reference Types)

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

Solidity引用类型官方文档

引用类型(Reference Types)

引用类型包含

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

复杂类型,占用空间较大的。在拷贝时占用空间较大。所以考虑通过引用传递。

String和Bytes

string类型没有length属性,而bytes类型有length属性。

bytes1, bytes2, bytes3, ..., bytes32,如果在bytes后面带了数字进行声明时,最多可以保存32个字符。一旦声明以后,那么length属性就是你声明的长度。

string str;
bytes byt;

str = "Hello";
byt = "World";

/*
 * @dev 获取长度
 * @param uint 返回长度
 */
function lenght() public view returns(uint){
  return byt.length;
}

数据位置(Data location)

复杂类型,如数组(arrays)数据结构(struct)在Solidity中有一个额外的属性,数据的存储位置。可选为memorystorage

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

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

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

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

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

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

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) {}
}

总结

强制的数据位置(Forced data location)

外部函数(External function)的参数(不包括返回参数)强制为:calldata
状态变量(State variables)强制为: storage

默认数据位置(Default data location)

函数参数(括返回参数:memory
所有其它的局部变量:storage

数组

数组是可以在编译时固定大小的,也可以是动态的。对于存储器数组来说,成员类型可以是任意的(也可以是其他数组,映射或结构)。对于内存数组来说 ,成员类型不能是一个映射;如果是公开可见的函数参数,成员类型是必须是ABI类型的。

固定大小k的数组和基本类型 T,可以写成 T[k], 动态数组写成 T[ ] 。例如, 有5个基本类型为 uint 的动态数组的数组 可以写成 uint[ ][5] ( 注意,和一些其他语言相比,这里的符号表示次序是反过来的)。为了访问第三动态数组中的第二个 uint, 必须使用 x[2][1](下标是从零开始的,访问模式和声明模式正好相反, 即x[2]是从右边剔除了一阶)。

bytes 和 string 是特殊类型的数组。 bytes 类似于 byte[ ],但它是紧凑排列在calldata里的。string 等于 bytes , 但不允许用长度或所以索引访问(现在情况是这样的)。

所以 bytes 应该优先于 byte[ ],因为它效率更高。

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

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

对int数组的增删改查操作

在Browser-solidity的左侧复制以下代码:

pragma solidity 0.4.20;
/**
 * 数组的增、删、改、查
 */
contract testArray {

    //声明一个全局数组变量
    uint[] public intArray;

    /*
     * 构造函数
     * 默认向数组中添加一个2018
     */
    function testArray(){
      intArray.push(2018);
    }

    /*
     * @dev 添加一个值到数组
     * @param val uint, 要传入的数值
     */
    function add(uint val){
        intArray.push(val);
    }

    /*
     * @dev 获取数组的长度
     * @return len uint,返回数组的长度
     */
    function length() returns (uint) {
      return intArray.length;
    }

    /*
     * @dev 更新数组的值
     * @param _index uint, 指定的索引
     * @param _value uint, 要修改的值
     */
    function update(uint _index, uint _value){
      intArray[_index] = _value;
    }

    /*
     * @dev 获取指定数组索引的值
     * @param _index uint, 索引值
     * @return _value uint, 返回结果
     */
    function valueByIndex(uint _index) returns (uint _value){
      uint result = intArray[_index];
      return result;
    }

    /*
     * @dev 删除指定数组索引的值
     * @param _index uint, 索引值
     */
    function delByIndex(uint _index){
      uint len = intArray.length;
      if (_index > len) return;
      for (uint i = _index; i<len-1; i++){
        intArray[i] = intArray[i+1];
      }
      delete intArray[len-1];
      intArray.length--;
    }
}

Solidity里面没有对数组类型提供天生的delete操作,因此delete操作写起来略为复杂。
首先要将删除的元素之后的元素逐个前移,
然后再删除最后一个元素,
最后还要将数组长度减少1

在Browser-solidity中调试:

int array

对String数组的增删改查操作

pragma solidity 0.4.20;
contract testArray {

  //声明一个全局数组变量
  string[] public strArr;
  
  /*
   * 构造函数
   * 默认向数组中添加一个字符
   */
    function testArray() public{
      strArr.push("Hi");
    }
  
  /*
   * @dev 添加一个值到数组
   * @param val string, 要传入的数值
   */
   function Add(string str) {
      strArr.push(str);
   }
  
  /*
   * @dev 更新数组的值
   * @param _index uint, 指定的索引
   * @param _value uint, 要修改的值
   */
   function update(uint _index, string _value){
      if (_index > strArr.length-1) throw;
      strArr[_index] = _value;
   }
   
   /*
    * @dev 获取指定数组索引的值
    * @param _index uint, 索引值
    * @return _value string, 返回结果
    */
   function valueByIndex(uint _index) returns (string _value){
      if (_index > strArr.length-1) throw;
      return strArr[_index];
  }
  
   /*
    * @dev 删除指定数组索引的值
    * @param _index uint, 索引值
    */
    function delByIndex(uint _index){
      uint len = strArr.length;
      if (_index > len) return;
      for (uint i = _index; i<len-1; i++){
        strArr[i] = strArr[i+1];
      }
      delete strArr[len-1];
      strArr.length--;
    }
}

在Browser-solidity中调试:

string array

创建一个数组

可使用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) {
        // ...
    }
}

通过数组字面量,创建的数组是memory的,同时还是定长的。元素类型则是使用刚好能存储的元素的能用类型,比如代码里的[1, 2, 3],只需要uint8即可存储。由于g()方法的参数需要的是uint(默认的uint表示的其实是uint256),所以要使用uint(1)来进行类型转换。

还需注意的一点是,定长数组,不能与变长数组相互赋值,我们来看下面的代码:

pragma solidity ^0.4.0;

contract C {
    function f() {
        // The next line creates a type error because uint[3] memory
        // cannot be converted to uint[] memory.
        uint[] x = [uint(1), 3, 4];
}

数组的属性和方法

length属性

数组有一个.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);
    }
}

Structs

Structs结构体,和其他语言一样,可以定义任意类型的结构。Structs里面可以定义Mappings,同样在Mappings中也可以定义Structs。虽然结构本身可以作为mapping的成员值,但是结构不可以包含它本身类型,避免死循环。

示例:

pragma solidity 0.4.20;
/**
 * 对 structs 进行测试
 */
contract TestStruct {

    // Defines a new type with two fields.
    struct User {
        string name;
        uint age;
    }

    //用户列表
    User[] public users;

    /*
     * @dev 添加方法
     */
    function add(string name, uint age) public{
        users.push(
            User({
                name : name,
                age : age
            })
        );
    }
}

在Browser-solidity中调试:

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

推荐阅读更多精彩内容