复杂类型。不同于之前值类型,复杂类型占的空间更大,超过256字节,因为拷贝它们占用更多的空间。由此我们需要考虑将它们存储在什么位置内存(memory,数据不是永久存在的)或存储(storage,值类型中的状态变量)。
Solidity引用类型官方文档
引用类型(Reference Types)
引用类型
包含
不定长字节数组(bytes)
字符串(string)
数组(Array)
结构体(Struts)
复杂类型,占用空间较大的。在拷贝时占用空间较大。所以考虑通过引用传递。
String和Bytes
string类型没有length属性,而bytes类型有length属性。
bytes1, bytes2, bytes3, ..., bytes32,如果在bytes后面带了数字进行声明时,最多可以保存32个字符。一旦声明以后,那么length属性就是你声明的长度。
string str;
bytes byt;
str = "Hello";
byt = "World";
/*
* @dev 获取长度
* @param uint 返回长度
*/
function lenght() public view returns(uint){
return byt.length;
}
数据位置(Data location)
复杂类型,如数组(arrays)
和数据结构(struct)
在Solidity中有一个额外的属性,数据的存储位置。可选为memory
和storage
。
memory
存储位置同我们普通程序的内存一致。即分配,即使用,越过作用域即不可被访问,等待被回收。而在区块链上,由于底层实现了图灵完备,故而会有非常多的状态需要永久记录下来。比如,参与众筹的所有参与者。那么我们就要使用storage
这种类型了,一旦使用这个类型,数据将永远存在。
基于程序的上下文,大多数时候这样的选择是默认的,我们可以通过指定关键字storage
和memory
修改它。
默认的函数参数,包括返回的参数,他们是memory
。默认的局部变量是storage
的。而默认的状态变量(合约声明的公有变量)是storage
。
另外还有第三个存储位置calldata
。它存储的是函数参数,是只读的,不会永久存储的一个数据位置。外部函数
的参数(不包括返回参数)被强制指定为calldata
。效果与memory
差不多。
数据位置指定非常重要,因为不同数据位置变量赋值产生的结果也不同。在memory
和storage
之间,以及它们和状态变量
(即便从另一个状态变量)中相互赋值,总是会创建一个完全不相关的拷贝。
将一个storage
的状态变量,赋值给一个storage
的局部变量,是通过引用传递。所以对于局部变量的修改,同时修改关联的状态变量。但另一方面,将一个memory
的引用类型赋值给另一个memory
的引用,不会创建另一个拷贝。
pragma solidity ^0.4.0;
contract C {
uint[] x; // the data location of x is storage
// the data location of memoryArray is memory
function f(uint[] memoryArray) {
x = memoryArray; // works, copies the whole array to storage
var y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// This does not work either, since it would "reset" the pointer, but there
// is no sensible location it could point to.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) {}
}
总结
强制的数据位置(Forced data location)
外部函数(External function)的参数(不包括返回参数)强制为:calldata
状态变量(State variables)强制为: storage
默认数据位置(Default data location)
函数参数(括返回参数:memory
所有其它的局部变量:storage
数组
数组是可以在编译时固定大小的,也可以是动态的。对于存储器数组来说,成员类型可以是任意的(也可以是其他数组,映射或结构)。对于内存数组来说 ,成员类型不能是一个映射;如果是公开可见的函数参数,成员类型是必须是ABI类型的。
固定大小k的数组和基本类型 T,可以写成 T[k], 动态数组写成 T[ ] 。例如, 有5个基本类型为 uint 的动态数组的数组 可以写成 uint[ ][5] ( 注意,和一些其他语言相比,这里的符号表示次序是反过来的)。为了访问第三动态数组中的第二个 uint, 必须使用 x[2][1](下标是从零开始的,访问模式和声明模式正好相反, 即x[2]是从右边剔除了一阶)。
bytes 和 string 是特殊类型的数组。 bytes 类似于 byte[ ],但它是紧凑排列在calldata里的。string 等于 bytes , 但不允许用长度或所以索引访问(现在情况是这样的)。
所以 bytes 应该优先于 byte[ ],因为它效率更高。
由于bytes与string,可以自由转换,你可以将字符串s通过bytes(s)转为一个bytes。但需要注意的是通过这种方式访问到的是UTF-8编码的码流,并不是独立的一个个字符。比如中文编码是多字节,变长的,所以你访问到的很有可能只是其中的一个代码点。
类型为数组的状态变量,可以标记为public类型,从而让Solidity创建一个访问器,如果要访问数组的某个元素,指定数字下标就好了。
对int数组的增删改查操作
在Browser-solidity的左侧复制以下代码:
pragma solidity 0.4.20;
/**
* 数组的增、删、改、查
*/
contract testArray {
//声明一个全局数组变量
uint[] public intArray;
/*
* 构造函数
* 默认向数组中添加一个2018
*/
function testArray(){
intArray.push(2018);
}
/*
* @dev 添加一个值到数组
* @param val uint, 要传入的数值
*/
function add(uint val){
intArray.push(val);
}
/*
* @dev 获取数组的长度
* @return len uint,返回数组的长度
*/
function length() returns (uint) {
return intArray.length;
}
/*
* @dev 更新数组的值
* @param _index uint, 指定的索引
* @param _value uint, 要修改的值
*/
function update(uint _index, uint _value){
intArray[_index] = _value;
}
/*
* @dev 获取指定数组索引的值
* @param _index uint, 索引值
* @return _value uint, 返回结果
*/
function valueByIndex(uint _index) returns (uint _value){
uint result = intArray[_index];
return result;
}
/*
* @dev 删除指定数组索引的值
* @param _index uint, 索引值
*/
function delByIndex(uint _index){
uint len = intArray.length;
if (_index > len) return;
for (uint i = _index; i
Solidity里面没有对数组类型提供天生的delete操作,因此delete操作写起来略为复杂。
首先要将删除的元素之后的元素逐个前移,
然后再删除最后一个元素,
最后还要将数组长度减少1
在Browser-solidity中调试:
对String数组的增删改查操作
pragma solidity 0.4.20;
contract testArray {
//声明一个全局数组变量
string[] public strArr;
/*
* 构造函数
* 默认向数组中添加一个字符
*/
function testArray() public{
strArr.push("Hi");
}
/*
* @dev 添加一个值到数组
* @param val string, 要传入的数值
*/
function Add(string str) {
strArr.push(str);
}
/*
* @dev 更新数组的值
* @param _index uint, 指定的索引
* @param _value uint, 要修改的值
*/
function update(uint _index, string _value){
if (_index > strArr.length-1) throw;
strArr[_index] = _value;
}
/*
* @dev 获取指定数组索引的值
* @param _index uint, 索引值
* @return _value string, 返回结果
*/
function valueByIndex(uint _index) returns (string _value){
if (_index > strArr.length-1) throw;
return strArr[_index];
}
/*
* @dev 删除指定数组索引的值
* @param _index uint, 索引值
*/
function delByIndex(uint _index){
uint len = strArr.length;
if (_index > len) return;
for (uint i = _index; i
在Browser-solidity中调试:
创建一个数组
可使用new关键字创建一个memory的数组。与stroage数组不同的是,你不能通过.length的长度来修改数组大小属性。我们来看看下面的例子:
pragma solidity ^0.4.0;
contract C {
function f() {
//创建一个memory的数组
uint[] memory a = new uint[](7);
//不能修改长度
//Error: Expression has to be an lvalue.
//a.length = 100;
}
//storage
uint[] b;
function g(){
b = new uint[](7);
//可以修改storage的数组
b.length = 10;
b[9] = 100;
}
}
字面量及内联数组
数组字面量,是指以表达式方式隐式声明一个数组,并作为一个数组变量使用的方式。下面是一个简单的例子:
pragma solidity ^0.4.0;
contract C {
function f() {
g([uint(1), 2, 3]);
}
function g(uint[3] _data) {
// ...
}
}
通过数组字面量,创建的数组是memory的,同时还是定长的。元素类型则是使用刚好能存储的元素的能用类型,比如代码里的[1, 2, 3],只需要uint8即可存储。由于g()方法的参数需要的是uint(默认的uint表示的其实是uint256),所以要使用uint(1)来进行类型转换。
还需注意的一点是,定长数组,不能与变长数组相互赋值,我们来看下面的代码:
pragma solidity ^0.4.0;
contract C {
function f() {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] x = [uint(1), 3, 4];
}
数组的属性和方法
length属性
数组有一个.length属性,表示当前的数组长度。storage的变长数组,可以通过给.length赋值调整数组长度。memory的变长数组不支持。
不能通过访问超出当前数组的长度的方式,来自动实现上面说的这种情况。memory数组虽然可以通过参数,灵活指定大小,但一旦创建,大小不可调整,对于变长数组,可以通过参数在编译期指定数组大小。
push方法
storage的变长数组和bytes都有一个push(),用于附加新元素到数据末端,返回值为新的长度。
pragma solidity ^0.4.0;
contract C {
uint[] u;
bytes b;
function testArryPush() returns (uint){
uint[3] memory a = [uint(1), 2, 3];
u = a;
return u.push(4);
}
function testBytesPush() returns (uint){
b = new bytes(3);
return b.push(4);
}
}
Structs
Structs结构体,和其他语言一样,可以定义任意类型的结构。Structs里面可以定义Mappings,同样在Mappings中也可以定义Structs。虽然结构本身可以作为mapping的成员值,但是结构不可以包含它本身类型,避免死循环。
示例:
pragma solidity 0.4.20;
/**
* 对 structs 进行测试
*/
contract TestStruct {
// Defines a new type with two fields.
struct User {
string name;
uint age;
}
//用户列表
User[] public users;
/*
* @dev 添加方法
*/
function add(string name, uint age) public{
users.push(
User({
name : name,
age : age
})
);
}
}
在Browser-solidity中调试: