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](<font color=red>注意:定义方式对比大多语言来说是反的,使用下标访问元素时与其他语言一致</font>)

       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问题

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

推荐阅读更多精彩内容