内容简介:链块学院区块链系列网课已上线学习路径已为您规划好离区块链工程师就差这一张图的距离了~
1
目录
☛数据位置
☛数组
☛结构体
2
引用类型——数据位置
不同于之前值类型,引用类型占的空间更大,超过256字节,因为拷贝它们占用更多的空间。由此我们需要考虑将它们存储在什么位置?内存(memory,数据不是永久存在的)或存储(storage,数据永久的保存在数据块上)
2.1 数据位置分类
▪memory
▪storage
▪calldata
▪stack
2.1.1 memory
存储位置同我们普通程序的内存类似。即分配,即使用,越过作用域即不可被访问,等待被回收。
2.1.2 storage
数据将永远存在于区块链上。
2.1.3 calldata
一般只有外部函数的参数(不包括返回参数)被强制指定为calldata。这种数据位置是只读的,不会持久化到区块链。
2.1.4 语法格式
1. 栈实际是内存中的一个数据结构,每个栈元素占256位,栈的最大深度位1024;
2. 值类型的局部变量存储在栈中;
3. 在栈中保存一个很小的局部变量,gas开销最小,几乎免费使用,但是数量有限。
基于程序的上下文,大多数时候数据位置的选择是默认的,我们可以通过在变量名前声明memory还是storage来定义该变量的数据位置。
2.2 数据位置默认规则
▪ 函数参数、函数返回参数默认为memory
▪ 局部变量(作用域为局部)以及状态变量(作用域为全局)默认storage类型。
2.3 强制的数据位置
▪ 外部函数(External function)的参数(不包括返回参数)强制为:calldata。
▪ 状态变量(State variables)强制为: storage。
▪ 值类型的局部变量是存储在栈上。
2.4 不同数据位置变量间相互赋值
2.4.1 storage
当我们把一个storage类型的变量赋值给另一个storage时,只是修改了它的指针(引用传递)。
在上面的代码中,我们将传入的storage变量,赋值给另一个临时的storage类型的tmp时,并修改tmp.a = "Test",最后我们发现合约的状态变量s也被修改了。
2.4.2 memory给storage赋值
因为局部变量和状态变量的类型都可能是storage。所以我们要分开来说这两种情况。
☞ 2.4.2.1 memory赋值给状态变量
将一个memory类型的变量赋值给一个状态变量时,实际是将内存变量拷贝到存储中。
通过上例,我们发现,在memoryToState()中,我们把tmp赋值给s后,再修改tmp值,并不能产生任何变化。赋值时,完成了值拷贝,后续他们不再有任何的关系。
☞ 2.4.2.2 memory赋值给状态变量
由于在区块链中,storage必须是静态分配存储空间的。局部变量虽然是一个storage的,但它仅仅是一个storage类型的指针。如果进行这样的赋值,实际会产生一个错误。
通过上面的代码,我们可以看到这样的赋值的确不被允许。你可以通过将变量tmp改为memory来完成这样的赋值。
2.4.3 storage转为memory
将storage转为memory,实际是将数据从storage拷贝到memory中。
在上面的例子中,我们看到,拷贝后对tmp变量的修改,完全不会影响到原来的storage变量。
2.4.4 memory转为memory
memory之间是引用传递,并不会拷贝数据。我们来看看下面的代码。
在上面的代码中,memoryToMemory()传递进来了一个memory类型的变量,在函数内将之赋值给tmp,修改tmp的值,发现外部的memory也被改为了other memory。
注意:
1. 对于值类型,总是会进行拷贝
2. 不能将memory的函数参数赋值给storage局部变量
3. 不能通过引用销毁storage
2.5 实例
2.6 不同存储的消耗(gas消耗)
▪ storage 会永久保存合约状态变量,开销最大. 大概5000~20000。
▪ memory 仅保存临时变量,函数调用之后释放,开销很小。
▪ calldata 和memory差不多。
▪ stack 保存很小的局部变量,几乎免费使用,但有数量限制 具体gas消耗值请参考http://yellowpaper.io/。
3
引用类型——数组
数组在所有的语言当中都是一种常见类型。在Solidity中,可以支持编译期定长数组和变长数组。一个类型为T,长度为k的数组,可以声明为T[k],而一个变长的数组则声明为T[]。
3.1 使用字面量创建数组
创建数组时,我们可以使用字面量,隐式创建一个定长数组。
通过上面的代码,我们可以发现。
首先元素类型是刚好能存储的元素的类型,比如代码里的[1, 2, 3],只需要uint8即可存储。但由于我们声明的变量是uint(默认的uint表示的其实是uint256),所以要使用uint(1)来进行显式的类型转换。
其次,字面量方式声明的数组是定长的,且实际长度要与声明的相匹配,否则编译器会报错
Type string memory[1] memory is not implicitly convertible to expected type string memory[2] memory。
3.2 使用new关键字创建数组
对于变长数组,在初始化分配空间前不可使用,可以通过new关键字来初始化一个数组。
我们声明了一个storage的stateVar,和一个memory的memVar。它们不能在使用new关键字初始化前使用下标方式访问,会报错VM Exception: invalid opcode。可以根据情况使用如例子中的new uint;来进行初始化。
3.3 数组属性
3.3.1 length属性
数组有一个length属性,表示当前的数组长度。对于storage的变长数组,可以通过给length赋值调整数组长度。
在上面这个例子中,我们可以看到,通过stateVar.length++语句对数组长度进行自增,我们就得到了一个不断变长的数组。 还可以使用后面提到的push()方法,来隐式的调整数组长度。
3.4 数组函数
变长的storage数组和bytes(不包括string)有一个push()函数。可以将一个新元素附加到数组末端,返回值为当前长度。push函数支持数组的初始化。
3.5 memory数组
对于memory的变长数组,不支持修改length属性,来调整数组大小。memory的变长数组虽然可以通过参数灵活指定大小,但一旦创建,大小不可调整。
如果状态变量的类型为数组,也可以标记为public类型,从而让Solidity创建一个访问器。(public类型的状态变量都有默认的访问器)访问器对于外界使用者表现为一个函数, 因此可以通过调用函数的方式访问某些值。 另外在remix中表现为一个可以点击的按钮, 可以获取对应的public类型的值。
如上面的合约在Remix运行后,需要我们填入的是一个要访问序号的数字,来访问具体某个元素。
3.6 多维数组
我们要创建一个长度为5的数组,每个元素又是一个变长uint数组,将被声明为uint[][5]。 反之, 假如要创建一个变长数组, 每个元素又是一个长度是5的数组, 将被声明为uint[5][],比如下边的例子:
在上面的代码中,我们声明了一个二维数组,它是一个变长的数组,里面的每个元素是一个长度为2的数组。要访问这个数组flags,第一个下标为变长数组的序号,第二个下标为长度为2的数组序号。
3.7 bytes与string
bytes和string是一种特殊的数组。
由于bytes与string,可以自由转换,你可以将字符串s通过bytes(s)转为一个bytes。可以以这种方式获得字符串长度,以及获取字符中字符的UTF-8编码。
4
引用类型——结构体
结构体,Solidity中的自定义类型。我们可以使用Solidity的关键字struct来进行自定义。结构体内可以包含字符串,整型等基本数据类型,以及数组,映射,结构体等复杂类型。数组,映射,结构体也支持自定义的结构体。我们来看一个自定义结构体的定义:
在上面的代码中,我们定义了一个简单的结构体Student,它包含一些基本的数据类型。另外我们还定义了一个稍微复杂一点的结构体Class,它包含了其它结构体Student,以及数组,映射等类型。
数组类型的students和映射类型的index的声明中还使用了结构体。
4.1 结构体定义的限制
我们不能在结构中定义一个自己作为类型,这样限制原因是,自定义类型的大小不允许是无限的。我们来看看下述的代码:
在上面的代码中,我们尝试在A类型中定义一个A a;,将会报错Error: Recursive struct definition.。虽然如此,但我们仍然能在类型内用数组,映射来引用当前定义的类型,如变量mappingMemberOfOwn,arrayMemberOfOwn所示。
4.2 初始化
4.2.1 直接初始化
如果我们声明的自定义类型为A,我们可以使用A(变量1,变量2, ...)的方式来完成初始化。来看下面的代码:
上面的代码中,我们按定义依次填入值,即可完成了初始化。需要注意的是,参数要与定义的数量匹配。当你填的参数与预计初始化的参数不一致时,会提示Error: Wrong argument count for function call: 2 arguments given but expected 3. Members that have to be skipped in memory: map。另外,在初始化时,需要忽略映射类型,后面有具体说明。
4.2.2 命名初始化
还可以使用类似JavaScript的命名参数初始化的方式,通过传入参数名和对应值的对象。这样做的好处在于可以不按定义的顺序传入值。我们来看看下面的例子:
上面的例子中,通过在参数对象中,指定键为对应的参数名,值为你想要初化的值,我们即完成了初始化。同样需要注意的是,参数要与定义的个数一致,否则会报类似这样的错误Error: Wrong argument count for function call: 2 arguments given but expected 3. Members that have to be skipped in memory: map。另外,在初始化时,需要忽略映射类型,后面有具体说明。
4.2.3 结构体中映射的初始化
由于映射是一种特殊的数据结构,所以你可能只能在storage变量中使用它。
上面的例子中,我们定义的了一个storage的状态变量storageVar,完成了映射类型的存储空间分配。然后我们就能对映射类型赋值了。
如果你尝试对memory的映射类型赋值,会报错Error: Member "map" is not available in struct StructMappingInitial.A memory outside of storage.。
4.3 结构体的可见性
关于可见性,当前只支持internal的,后续不排除放开这个限制。
4.3.1 继承中使用
结构体由于是不对外可见的,所以你只可以在当前合约,或合约的子类中使用。包含自定义结构体的函数均需要声明为internal的。
在上面的代码中,我们声明了f(S s),由于它包含了struct的S,所以不对外可见,需要标记为internal。你可以在当前类中使用它,如f1()所示,你还可以在子类中使用函数和结构体,如B合约的g()方法所示。
4.3.2 跨合约的临时解决方案
结构体,由于是动态内容。当前不支持在多个合约间互用,目前一种临时的方案如下:
在上面的例子中,我们手动将要返回的结构体拆解为基本类型进行了返回。
5
内存变量的布局
Solidity预留了3个32字节大小的槽位(存储空间):
▪ 0-64:哈希方法的暂存空间(scratch space)
▪ 64-96:当前已分配内存大小(也称空闲内存指针(free memory pointer))
注: 暂存空间是2个32字节大小,作为一个整体单元。
暂存空间可在语句之间使用(如在内联编译时使用)
Solidity总是在空闲内存指针所在位置创建一个新对象,且对应的内存永远不会被释放(也许未来会改变这种做法)。
有一些在Solidity中的操作需要超过64字节的临时空间,这样就会超过预留的暂存空间。他们就将会分配到空闲内存指针所在的地方,但由于他们自身的特点,生命周期相对较短,且指针本身不能更新,内存也许会,也许不会被清零(zerod out)。因此,大家不应该认为空闲的内存一定已经是清零(zeroed out)的。
6
状态变量的存储模型
大小固定的变量(除了映射,变长数组以外的所有类型)在存储(storage)中是依次连续从位置0开始排列的。如果多个变量占用的大小少于32字节,会尽可能的打包到单个storage槽位里,具体规则如下:
▪ 在storage槽中第一项是按低位对齐存储(lower-order aligned) 。
▪ 基本类型存储时仅占用其实际需要的字节。
▪ 如果基本类型不能放入某个槽位余下的空间,它将被放入下一个槽位。
▪ 结构体和数组总是使用一个全新的槽位,并占用整个槽(但在结构体内或数组内的每个项仍遵从上述规则)。
-END-
链块学院区块链系列网课已上线
学习路径已为您规划好
离区块链工程师就差这一张图的距离了~
本文完,获取更多资讯,敬请关注区块链工程师。
来源:链块学院
本文由布洛克专栏作者发布,代表作者观点,版权归作者所有,不代表布洛克科技观点
——TheEnd——
关注“布洛克科技”
以上所述就是小编给大家介绍的《智能合约基础语言(五):Solidity变量类型:引用类型》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 智能合约(三):Solidity变量类型:值类型
- 智能合约攻击分析之庞氏代币合约漏洞
- 检测了3万多份智能合约,这份白皮书找到了9大智能合约安全漏洞(附下载链接)
- 智能合约工程
- 智能合约微服务
- 智能合约入门-new
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java技术手册(第6版)
Benjamin J Evans、David Flanagan / 安道 / 人民邮电出版社 / 2015-12-1 / 79.00
《Java技术手册 第6版》为《Java 技术手册》的升级版,涵盖全新的Java 7 和Java 8。第1部分介绍Java 编程语言和Java 平台,主要内容有Java 环境、Java 基本句法、Java 面向对象编程、Java 类型系统、Java的面向对象设计、Java 实现内存管理和并发编程的方式。第2部分通过大量示例来阐述如何在Java 环境中完成实际的编程任务,主要内容有编程和文档约定,使......一起来看看 《Java技术手册(第6版)》 这本书的介绍吧!
Markdown 在线编辑器
Markdown 在线编辑器
正则表达式在线测试
正则表达式在线测试