对solidity数据类型的分析梳理
在任何程序开发语言的知识结构中,数据类型都属于"基础部分",solidity也不例外,helloworld之后就是数据类型。但说实话,solidity的数据类型不太一样。所涉及的技术内幕多,更麻烦的是它碎片化。一种东西一旦碎片化,缺少那种内在的一致性,一种看上去统一的和谐的概念,简洁明了,符合直觉,那它的理解和记忆就都会比较困难的,因为我们的头脑其实会通过一种知识的结构性的延申去吸纳和消化的。所以,对于solidity数据类型,应该竭力从中整理出一套理解的秩序。现在回头看当初讲数据类型,讲的不好,就是因为没有能够把这个秩序提炼出来。不过文档和别的视频教程也都这样--但也因为这样,显得更有必要。
做这件事情的关键,应该首先建立几条线索,我们就按照这个方法来。
关于变量的location
location是让数据类型(或变量。在表达上总会遇到一种二元概念,就是关于类型与其实例,变量是类型的实例,后面我们不再区分。说location,是指类型所声明的变量的location)这个问题变得复杂化的最主要原因,没有之一。
首先我们要清楚一点,其他语言没有所谓location,它们用value type和reference type就能表达这个问题:value type的变量在stack上,而reference type的变量在堆上。evm模型中可以直接访问一种叫storage的东西,它是“持久化的存储”,这在其他语言中不存在。其他语言就是内存和堆栈(或寄存器,我们忽略这种差别,都当作一种堆栈机器),总之都是临时性存储,而持久化问题跟核心机器模型无关,是当作外部设备的。这一点完全不同。这使得reference type变得复杂。其他语言中,reference的变量就是指向一个堆上的数据块,但solidity中,它指向的位置可以是memory,可以是storage,也可以是直接指向来自上下文的msg的calldata数据块中的一部分。这样问题就复杂了。
value type和reference type和location的纠缠
局部变量
首先我们抛开成员变量,只讨论局部变量。参数也跟局部变量类似,没有单独讨论的必要。 message中的calldata是只读的,没有什么复杂性,也不涉及。
要从根本上理解值与引用,还真是必须涉及汇编。
先看其他面向对象高级语言。对于java这种语言,无论是成员变量、局部变量还是函数参数或返回值,变量“本身”都是stack中的值,value type(的变量)就存储被它本身持有,也就是在stack中。变量本身和变量的值是一体的,只存在于堆栈中。而refrence type的值在堆栈中,而且通过new操作分配。
solidity中的局部变量也是如此,它本身都在stack中,如果是valuetype,那这个value也在stack中;但是如果是reference,却指向不同性质的存储空间:内存,或storage;这时就有一个关于赋值操作的规则:
location相同的两个变量之间赋值,是引用赋值
location不同的两个变量之间赋值,是数据拷贝
这种语义使得问题复杂化,使用起来要相当小心。对于java来所,引用就是引用,引用类型变量的赋值操作就是指向另一个堆中的数据块。solidity设计这种语义的目的还是基于效率,gas费用。但语言变得不够安全。
成员变量
成员变量都存在storage中,那引用类型的成员变量之间的赋值,是否与引用类型的局部变量之间一样,因其都是在storage中,所以也是引用赋值呢?比如如下代码:

跟下面代码:

表现是否一样?是不一样的。后者是引用赋值,前者是数据拷贝。理解起来非常麻烦!其实在成员变量中,引用数据类型中的“引用”这个词失去意义了。
关于动态数组
应该这样讲,由于受到location的影响,memory中的动态数组跟storage中的动态数组,完全是两回事,简直就是两种不同的数据类型
概念不同,一种是运行时动态扩展,一种是运行时初始化
操作不同,memory动态数组没有push pop
初始化方式不同。memory中用 new 操作符。
关于new操作符
使用new操作符的地方有三处:内存动态数组,bytes、string。后两者内存变量和成员变量都可以用new。
new 操作符是在“运行时可以确定数据长度”时用;上述三种数据类型都是这样
struct初始化就不用new,因为它编译时就能确定数据块大小
这些问题理解到位,也不太容易
定长字节数组
定长字节数组式value type中的特例,它是数组,却归为value type值类型,原因是它定长。当然照此逻辑也可以定义一个value type的“定长16位整型数组”,不这么做,是因为用处不大。这是特例!
关于string和bytes
string面向可读的字符串,bytes面向二进制,这两个类型可以看作是元素为byte1的一般动态数组的进一步封装,但他们在操作性质上跟动态数组差别很大
它们在成员变量和memory中的初始化方式和操作没有多少区别,都可以赋值字面量,用new初始化。
它们之间的区别是,bytes按下标可写,string不行
既然一个string变量可以用new 初始化出一个某个长度的数据块,但这个数据块有不能按照下标写操作,那这个初始化有什么用呢?这就涉及bytes和string之间的强制转换:把它转换成bytes,就可以照下标写操作。非常绕!
就说这么多,其实还有很多情况,不一一列举了。有时间可以做个视频讲。总之solidity数据类型是比较麻烦的。根本原因还是为了EVM这个资源紧张的运行时环境而委屈了自己。