solidity基础(2)

  1. solidity的数据位置特性深入了解
    • 简介:
      代码在执行前,一般会编译成指令。指令就是一个个逻辑,逻辑操作的是数据。代码或者说业务,操作的其实都是数据。非区块链中,代码操作的数据,一般会存到数据库中。
      在区块链中,区块链本身就是一个数据库。如果使用区块链标记物产的所有权,归属信息将会被记录到区块上,所有人都无法篡改,以标明不可争议的拥有权。所以在区块链的编程中,有一个数据位置的属性用来标识变量是否需要持久化到区块链上。
    • 数据位置的类型
      • 数据位置:变量的存储位置属性,有三种类型,memory,storage,calldata
        • memory:储存位置和我们普通程序的内存类似,即分配,即使用,越过作用域即不可被访问,等待被回收
        • storage:数据将永久存在于区块链上
        • calldata:一般只有外部函数的参数(不包括返回参数)被强制指定为calldata,这种数据位置是只读的,不会持久到区块链上
    • 默认数据位置
      1. 参数:
        默认的函数参数,包括返回的参数,是memory

         pragma solidity ^0.4.18;
         contract FunctionTest {
           function test(uint a) view returns(uint r) {
             //这里的a和r都是memory
             r = a;
           }
         }
        
      2. 局部变量和状态变量
        状态变量和局部变量,是storage;定义在函数外部的是状态变量,定义在函数内部的是局部变量

         pragma solidity ^0.4.18;
         contract FunctionTest {
           struct S{string a;uint b;}
           //状态变量,默认是storage
           S s;
           function test(S s) private {
             //局部变量,默认是storage
             S tmp;
           }
         }
        
    • 数据位置间相互转换
      1. storage转换为storage
        当我们把一个storage类型的变量赋值给另一个storage时,我们只是修改了它的指针

           pragma solidity ^0.4.18;
           contract Test {
             struct S{string s;uint n;}
             S s;
             function fTest(S storage t) internal {
               s = t;//此时,s的指针与t相同
               t.s = "123";//修改t就当于修改s
             }
             function b() public view returns(string) {
               s = S("456",0);
               fTest(s);
               return s.s;
               //返回值是"123"
             }
           }
        
      2. memory转换为storage

        • memory赋值给状态变量
          将一个memory类型的变量赋值给一个状态变量时,实际是将内存变量拷贝到存储中

           pragma solidty 0.4.18;
           contract Test {
             struct S{string a;uint b;}
             S s;
             function fTest(S memory a) internal {
               s = a;//只是把a的值赋给s
               a.a = "123";//修改a并不能对s造成修改
             }
             function b() public view returns(string) {
               functionTest(S("456",0));
               return s.s;
               //返回值任然是"456"
             }
           }
          

          由此,我们发现,在“memory与状态变量”的赋值中,他们只是值拷贝,后续他们不再有任何的关系

        • memory赋值给局部变量
          由于在区块链中,storage必须是静态分配存储空间的,局部变量虽然是一个storage的,但它仅仅是一个storage类型的指针,所以进行这样的赋值,实际会产生错误,当然我们可以通过修改参数或局部变量的类型来解决这个问题

           //将参数的数据位置变为storage
           function fTest(S storage t) internal {
             S tmp = t;
           }
           function b(S t) internal {
             //将局部变量的数据位置变为memory
             S memory tmp = t;
           }
          
      3. storage转为memory
        storage转为memory,实际是将数据从storage拷贝到memory中,和把memory转为状态变量的例子一样,我们可以归纳为,他们之间的转换,对memory的修改并不会在storage上生效,他们之间只是单纯的数据拷贝

      4. memory转为memory
        memory之间是引用传递,并不会拷贝数据,即在函数内部对memory的修改,在函数外依然生效。

    • 参考资料:
      官方文档
      什么是值传递,引用传递,指针传递

  2. solidity的数据位置特性深入了解
    在solidity中,支持编译定长数组和变长数组;如,一个类型为T,长度为k的数组,可以声明为T[k],类型为T的变长数组则声明为T[]
    • 创建一个数组

      1. 通过字面量创建定长数组

        • 字面量
        • 元素类型是刚好能存储的元素类型,例如[1,2,3],只需uint8便可存储,所以元素类型是uint8类型,而假设我们声明的类型不是uint8,那么元素类型必须通过显示的类型转换,如:uint[3] memory a = [uint(1),2,3];
        • 另外,字面量方式声明的数组是定长的,且实际长度要与声明的长度相匹配,否则编译器会报错
      2. new关键字
        对于变长数组,在初始化分配空间前不可使用,可以通过new关键字来初始化一个数组,之后才可以赋值使用;
        不能用new来初始话storage的局部变量,会报错

         pragma solidity ^0.4.18;
         contract NewArray {
           uint[] stateVar;
           function f() {
             //定义一个变长数组
             uint[] memory memVar;
             //不能在使用new初始化以前使用,编译后会报错
             //VM Exception: invalid opcode
             //memVar [0] = 100;
             //通过new来初始化变长数组
             memVar = new uint[](2);
             stateVar = new uint[](2);
             stateVar[0] = 1;
             memVar[0] = 1;
           }
         }
        
    • 数据的属性和方法

      1. length属性
        数组有一个length属性,对于storage的变长数组,可以通过给length赋值来调整数组长度

        • storage
          数据位置为storage的变长数组,可以通过length来不断的修改长度;
          还可以通过push()方法,来隐式的调整数组长度;
          不能通过对超出当前数组的长度序号元素赋值的方式,来实现数组长度的自动扩展
        • memory
          对于memory的变长数组,不支持修改length属性,来调整数组大小;memory的变长数组虽然可以通过参数灵活指定大小,但一旦创建,大小不可调整。
      2. push方法
        变长的storage数组和bytes(不包括string)有一个push()方法,可以将一个新元素附加到数组末端,返回值为当前长度
        push()方法不能用于定长数组和memory数组

         pragma solidity  ^0.4.18;
         contract Test {
           uint[] a;
           function fTest() public returns(uint[],uint) {
             a.push(1);
             a.push(2);
             //初始化,之前push的值被被初始化为0
             //同时长度变为初始化给定的值
             a = new uint[](1);
             //初始化之后仍然可以使用push()扩容
             a.push(3);
             return (a.a.length);
           }
        
           function b() public returns(uint[]) {
             uint[] memory c;
             //c.push(1)  error
           }
         }
        
      3. 下标
        与大多数语言一样,数组可以通过数字下标访问,从0开始
        如果状态变量的类型为数组,也可以标记为public,从而让solidity创建一个访问器

         pragma solidity ^0.4.18;
         contract Test {
           uint[] public a = [uint(1)];
         }
        

        如上面的合约在Remix运行后,需要我们填入下标数字来访问具体某个元素

    • 多维数组
      多维数组的定义与非区块链语言类似。如,我们要创建一个长度为5uint数组,每个元素又是一个变长数组。将被声明为uint[][5]注意:定义方式对比大多语言来说是反的,使用下标访问元素时与其他语言一致)

       pragma solidity ^0.4.18;
       contract Test {
         //声明一变长数组,里面的每一个元素是3长度的定长数组
         uint[3][] flags;
         function fTest() public returns(uint) {
           return flags.push([uint(1),2,3]);
         }
      
         function getValue() public returns(uint) {
           return flags[0][2];
           //结果为3
         }
       }
      
    • bytes与string
      bytesstring是一种特殊的数组,bytes类似byte[],但在外部函数作为参数调用中,会进行压缩打包,更省空间,所以应该尽量使用bytesstring类似bytes,但不提供长度和按序号的访问方式
      由于bytesstring,可以自由转换,例如,可以将字符串s通过bytes(s)转换为一个bytes,但是需要注意的是,通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以访问到的很有可能只是其中的一个代码点。
      同时,bytes和变长的storage数组一样,也支持push()

    • 限制
      在外部函数中,不能使用多维数组,另外,因为EVM的限制,不能通过外部函数返回变长的数据

       pragma solidity ^0.4.18;
       contract ArrayWarning {
         function f() returns(uint[]) {
           return new uint[](1);
         }
         /*function g() returns(uint[]) {
           return this.f();
         }*/
       }
      

      f()返回的是uint[]的变长数组,使用web3.js的方式可以访问,但是用solidity的外部函数的访问方式,将不能编译通过
      对于这样的问题的一种临时解决方法,是使用一个非常大的定长数组

    • 参考资料-padding问题

你可能感兴趣的:(solidity基础(2))