一.引用类型介绍
1.不定长字节数组
- 特点
- 动态字节数组
- 引用类型(说明可以使用storage来修饰,进行引用传递,指针的效果)
- 支持 下标索引
- 支持 length、push方法 (push会帮助分配空间的)
- 可以修改
- 以十六进制格式赋值
注意的坑:
旧版本的remix可以直接在remix中使用"helloworld"形式给bytes赋值,新版本不允许,必须使用0x格式
pragma solidity ^0.4.25;
contract BytesTest {
//创建 usrName , USRNAME, _usrName
bytes public _name = new bytes(1);
bytes public _name2;
function setLength(uint length) {
_name.length = length;
}
function getLength() view returns (uint) {
return _name.length;
}
function setName(bytes name){
_name = name;
_name2 = name;
}
function changeName(bytes1 name){
_name[0] = name;
}
function changeName2(bytes1 name) {
//未分配空间时,调用访问下标会出错
_name2[0] = name;
}
function pushTest() public {
_name2.push("a");
}
function setInside(){
_name = "helloWorld";
_name2 = "HELLOWORLD2";
}
}
2.字符串
- 动态尺寸的UTF-8编码字符串,是特殊的可变字节数组
- 引用类型
- 不支持下标索引
- 不支持length、push方法
- 可以修改(需通过bytes转换)
pragma solidity ^0.4.25;
contract StringTest {
string public _name = "Linda";
function nameBytes() constant returns (bytes) {
return bytes(_name);
}
function nameLength() constant returns (uint) {
return bytes(_name).length;
}
function changeName() public {
bytes(_name)[0] = "L";
//_name[0] = "H"; //ERROR,字符串不直接支持下标访问
}
function changeLength() {
bytes(_name).length = 15;
bytes(_name)[14] = "x";
}
}
字符串处理很不便(字符串拼接),有一个字符串插件:https://github.com/Arachnid/solidity-stringutils
import "github.com/Arachnid/solidity-stringutils/strings.sol";
contract StringTest {
using strings for *;
string public myStr;
function foo(string strPart) {
myStr = myStr.toSlice().concat(strPart.toSlice());
}
}
3.转换
pragma solidity ^0.4.25;
contract ConvertTest {
bytes10 b10 = 0x68656c6c6f776f726c64; //helloworld
//bytes bs10 = b10; //无法直接转换
bytes public bs10 = new bytes(b10.length);
//1. 固定字节数组转动态字节数组
function fixedBytesToBytes() public{
for (uint i = 0; i< b10.length; i++){
bs10[i] = b10[i];
}
}
//2.string转动态字节数组(不定长字节数组)
string greeting = "helloworld";
bytes public b1;
function StringToBytes() public {
b1 = bytes(greeting);
}
//3. 动态字节数组转string
string public str3;
function BytesToString() public {
fixedBytesToBytes();
str3 = string(bs10);
}
//4. fixed bytes to String,error
function FiexBytesToString(){
//string tmp = string(b10);
}
}
4.数组
4.1 内置数组
已介绍
- string (不定长)
- bytes(不定长字节数组)
- bytes1…bytes32(定长字节数组)
4.2 自定义数组
4.2.1 定长数组
- 类型T,长度K的数组定义为 T[K],例如:uint [5] numbers, byte [10] names;
- 内容可变
- 长度不可变,不支持push
- 支持length方法
pragma solidity ^0.4.25;
contract ArrayTest {
//test1:
uint [10] value = [1,2,3,4,5];
uint public sum;
function getSum(){
sum = 0;
for (uint i = 0; i < value.length; i++){
sum += value[i];
}
}
function changeValue(){
value[0] = 2; //内容可修改
//value.length = 100; //报错,长度不可修改
}
function getString () public view returns (string) {
string strMsg = 'Hello';
bytes5 strArr = strMsg;
bytes helloworld;
for (var i = 0; i < strArr.length; i++) {
helloworld.push(strArr[i]);
}
return string(helloworld);
}
}
4.2.2 不定长数组
- 定义格式为T [ ],例如:string[ ] names, byte[ ] citys
- 内容可以修改
- 可以改变长度(仅限storage类型)
支持length
、push
方法
pragma solidity ^0.4.25;
contract ArrayTest {
//创建方式一:字面量形式
uint [] public c = [uint(1), 2, 3];
function h() public {
c.push(4);
}
//创建方式二:new 关键字
//a. storage类型数组,状态变量,最初为空,下标访问时越界
uint[] public b;
//复杂类型在局部是引用
function g(){
b = new uint[](7);
//可以修改数组的长度
b.length = 10;
b[9] = 100;
b.push(101);
}
//b. memory类型数组
function f() public returns (uint[]){
uint[] memory a = new uint[](7);
//不能修改长度
//Error: Expression has to be an lvalue.
//a.length = 100;
//a.push(10);
for (uint i = 0; i< a.length; i++){
a[i] = i;
}
return a;
}
}
5.结构体
pragma solidity ^0.4.5;
contract Test {
//定义结构之后无分号,与枚举一致
struct Student {
string name;
uint age;
uint score;
string sex;
}
//两种赋值方式
Student public stu1 = Student("lily", 18, 90, "girl");//注意顺序
Student public stu2 = Student({name:"Jim", age:20, score:80, sex:"boy"});//不需要考虑顺序
Student[] public Students;
function assign() public {
Students.push(stu1);
Students.push(stu2);
stu1.name = "Lily";
}
}
6.映射表
- 语法:
mapping(keyType => valueType)
- 键的类型(keyType) 除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。
- 值的类型(valueType) 可以是包括映射类型在内的任何类型。
- 无法判断一个mapping中是否包含某个key,因为它认为每一个都存在,不存在的返回0或false。
- 映射可以被视作为一个哈希表,在映射表中,不存储键的数据,仅仅存储它的
keccak256
哈希值,用来查找值时使用。 - 映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用。
- 不支持length
pragma solidity ^0.4.25;
contract MappingTest {
// id -> name
mapping(uint => string) public id_names;
constructor() public{
id_names[1] = "lily";
id_names[2] = "Jim";
id_names[3] = "Lily";
}
function getNameById(uint id) public returns (string){
return id_names[id];
}
function setNameById(uint id) public returns (string){
id_names[id] = "Hello";
}
// mapping 没有length属性
// function getMapLength() public returns (uint){
// return id_names.length;
// }
}
- HeiMaBank
pragma solidity ^0.4.25;
contract HMBank{
mapping(address => uint) public usrCount;
function saveMoney(uint money) public {
usrCount[msg.sender] += money;// 根据当前调用者的 address 增加 mapping中对应的值
}
function getMoney(uint money) public{
usrCount[msg.sender] -= money;// 根据当前调用者的 address 减少 mapping中对应的值
}
function showMoney() public view returns(uint){
return usrCount[msg.sender]; // 根据当前调用者的 address 获取mapping 里的对应值
}
}
- 官方案例:
pragma solidity ^0.4.25;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(this);
}
}
7.关键字 delete
pragma solidity ^0.4.25;
contract DeleteExample {
uint public age;
function changeAge(uint num){
age += num;
}
function deleteAge(){
delete age;
}
}
-
delete age 的结果是将 age 的类型在初始化时的值赋值给 age 。即对于整型变量来说,相当于
age = 0
, 但 delete 也适用于数组,对于动态数组来说,是将数组的长度设为 0,而对于静态数组来说,是将数组中的所有元素重置。 如果对象是结构体,则将结构体中的所有属性重置。 -
delete 对整个映射是无效的(因为映射的键可以是任意的,通常也是未知的)。 因此在你删除一个结构体时,结果将重置所有的非映射属性,这个过程是递归进行的,除非它们是映射。 然而,单个的键及其映射的值是可以被删除的。
理解
delete a
的效果就像是给a
赋值很重要,换句话说,这相当于在a
中存储了一个新的对象。
pragma solidity ^0.4.25;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // 将 x 设为 0,并不影响数据
delete data; // 将 data 设为 0,并不影响 x,因为它仍然有个副本
uint[] storage y = dataArray;
delete dataArray;
// 将 dataArray.length 设为 0,但由于 uint[] 是一个复杂的对象,y 也将受到影响,
// 因为它是一个存储位置是 storage 的对象的别名。
// 另一方面:"delete y" 是非法的,引用了 storage 对象的局部变量只能由已有的 storage 对象赋值。
}
}
8.数据位置
- 存储位置
- 值传递、引用传递
复杂类型,不同于之前值类型
,占的空间更大,超过256字节,因为拷贝它们占用更多的空间,如数组(arrays)
和数据结构(struct)
,他们在Solidity中有一个额外的属性,即数据的存储位置:memory
和storage
。
8.1 内存(memory)
- 数据不是永久存在的,存放在内存中,越过作用域后无法访问,等待被回收。
- 被memory修饰的变量是直接拷贝,即与上述的值类型传递方式相同。
8.2 存储(storage)
- 数据永久保存在。
- 被storage修饰的变量是引用传递,相当于只传地址,新旧两个变量指向同一片内存空间,效率较高,两个变量有关联,修改一个,另外一个同样被修改。
- 只有引用类型的变量才可以显示的声明为
storage
。 - 状态变量是stroage类型,无法更改;
- 局部变量(仅限数据结构或数组,string)默认是storage类型,可以声明为memory类型。
pragma solidity ^0.4.25;
contract Test {
string public name= 'lily';
uint256 public num = 10;
function call1() public {
setName1(name);
}
function setName1(string src) private {
//function setName1(string memory src) private {
num = 100;
bytes(src)[0] = "L";
}
function call2() public {
setName2(name);
}
function setName2(string storage src) private {
num = 1000;
bytes(src)[0] = "L";
}
function localTest () public view returns(uint){
string memory tmp = name;
//string storage tmp = name;
uint x = num;
x= 10000;
return x;
bytes(tmp)[0] = "L";
}
}