-
solidity的数据位置特性深入了解
- 简介:
代码在执行前,一般会编译成指令。指令就是一个个逻辑,逻辑操作的是数据。代码或者说业务,操作的其实都是数据。非区块链中,代码操作的数据,一般会存到数据库中。
在区块链中,区块链本身就是一个数据库。如果使用区块链标记物产的所有权,归属信息将会被记录到区块上,所有人都无法篡改,以标明不可争议的拥有权。所以在区块链的编程中,有一个数据位置的属性用来标识变量是否需要持久化到区块链上。 - 数据位置的类型
-
数据位置
:变量的存储位置属性,有三种类型,memory,storage,calldata
- memory:储存位置和我们普通程序的内存类似,即分配,即使用,越过作用域即不可被访问,等待被回收
- storage:数据将永久存在于区块链上
- calldata:一般只有外部函数的参数(不包括返回参数)被强制指定为
calldata
,这种数据位置是只读的,不会持久到区块链上
-
- 默认数据位置
-
参数:
默认的函数参数,包括返回的参数,是memory
pragma solidity ^0.4.18; contract FunctionTest { function test(uint a) view returns(uint r) { //这里的a和r都是memory r = a; } }
-
局部变量和状态变量
状态变量和局部变量,是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; } }
-
- 数据位置间相互转换
-
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" } }
-
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; }
-
storage
转为memory
将storage
转为memory
,实际是将数据从storage
拷贝到memory
中,和把memory
转为状态变量的例子一样,我们可以归纳为,他们之间的转换,对memory
的修改并不会在storage
上生效,他们之间只是单纯的数据拷贝memory
转为memory
memory
之间是引用传递,并不会拷贝数据,即在函数内部对memory
的修改,在函数外依然生效。
-
- 参考资料:
官方文档
什么是值传递,引用传递,指针传递
- 简介:
-
solidity的数据位置特性深入了解
在solidity中,支持编译定长数组和变长数组;如,一个类型为T,长度为k的数组,可以声明为T[k]
,类型为T的变长数组则声明为T[]
-
创建一个数组
-
通过字面量创建定长数组
- 字面量
- 元素类型是刚好能存储的元素类型,例如
[1,2,3]
,只需uint8
便可存储,所以元素类型是uint8
类型,而假设我们声明的类型不是uint8
,那么元素类型必须通过显示的类型转换,如:uint[3] memory a = [uint(1),2,3];
- 另外,字面量方式声明的数组是定长的,且实际长度要与声明的长度相匹配,否则编译器会报错
-
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; } }
-
-
数据的属性和方法
-
length属性
数组有一个length
属性,对于storage
的变长数组,可以通过给length
赋值来调整数组长度- storage
数据位置为storage
的变长数组,可以通过length
来不断的修改长度;
还可以通过push()
方法,来隐式的调整数组长度;
不能通过对超出当前数组的长度序号元素赋值的方式,来实现数组长度的自动扩展 - memory
对于memory
的变长数组,不支持修改length
属性,来调整数组大小;memory
的变长数组虽然可以通过参数灵活指定大小,但一旦创建,大小不可调整。
- storage
-
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 } }
-
下标
与大多数语言一样,数组可以通过数字下标访问,从0开始
如果状态变量的类型为数组,也可以标记为public
,从而让solidity创建一个访问器pragma solidity ^0.4.18; contract Test { uint[] public a = [uint(1)]; }
如上面的合约在Remix运行后,需要我们填入下标数字来访问具体某个元素
-
-
多维数组
多维数组的定义与非区块链语言类似。如,我们要创建一个长度为5
的uint
数组,每个元素又是一个变长数组。将被声明为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
bytes
与string
是一种特殊的数组,bytes
类似byte[]
,但在外部函数作为参数调用中,会进行压缩打包,更省空间,所以应该尽量使用bytes
,string
类似bytes
,但不提供长度和按序号的访问方式
由于bytes
与string
,可以自由转换,例如,可以将字符串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的外部函数的访问方式,将不能编译通过
对于这样的问题的一种临时解决方法,是使用一个非常大的定长数组
-
solidity基础(2)
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 1.编辑器说明 (1)推荐编辑器目前尝试 Solidity 编程的最好的方式是使用 Remix (需要时间加载,请...
- 序言本文是 Solidity 文档(以太坊官方 Solidity 开发手册)中文版连载的第五部分。这个连载的前四部...
- 阿耐是我非常喜欢的商业小说作家,她最为出名的《大江东去》我不止一次地在平台上跟朋友推荐.不过有趣的是,让大多数我朋...