一、 主线
次课程主线:练习结构体和数组的使用
训练内容:把用户输入的 newStr 存储到 Struct 中,然后在把结构体存储到数组的永久性存储中;接着需要修改合约,当用户调用 sayHello 时需要传入 id,然后从数组中查找该 id 对应的结构体,如果找到就返回该 id 对应的打招呼短语,否则返回 strVar 作为兜底;
二、solidity 中的数据结构
-
struct: 结构体,把不同的数据类型的数据组成一个结构,可以是基础数据类型可以数据结构或者基础数据类型
-
array: 把相同数据类型的数据组成一个结构
-
mappping: 映射,键值对,对象,以低时间复杂度查找到某一个值
2.1 、结构体
结构体类比 go 里面的 struct,和 TS 中的 interface(或者 type )类似。其作用就是提前约定你这个结构体里面存储了哪些字段,这些字段对应的值类型是什么。
2.1.1 声明
尝试声明一下结构体,使用 struct 关键字。
这个类比 TS 的 interface,又有点像是 class。这是因为后面这个 Info 可以当成构造函数一样调用,调用时传入对应的值,就可以创建实例对象;
但是肯定有类型声明的作用,真正的变量声明还需要等用的时候给变量做类型标识。
声明结构体的格式: struct 结构体名称 { value 的类型 空格 key }
例如我们声明一个结构体,用于保存一次交易调用方发过来的信息:
- 用户输入的字符串
- id
- 交易调用方的地址
代码如下:
go
struct Info {
string phrase;
uint256 id;
address addr;
}
2.1.2 实例化
所谓实例化就是创建一个对象,创建的时候按传入对应类型的值即可。
solidity
Info memory info = Info(newString, _id, msg.sender); // memory 临时存放
从这段代码可以看出,Info 还发挥了类似构造函数的作用。,
2.2 数组
在 Solidity 中 array 数组是一种数据结构,声明数组要求数组中的各个成员的类型是相同的。这一点和其他强类型语言中的数组保持一致,和 JS 中不同,JS 中的数组对类型不做要求,JS 中的数组更像元组。
2.2.1 声明数组
例如我声明一个数组,用于保存所有交易发生时合约调用方传入的数据。 声明的格式:类型 [] 变量名;
类型的话,可以是自定义的类型例如结构体。
2.2.2 数组创建
数组的创建,和结构体不同,创建和实例化同步完成。例如声明一个存放 Info 类型的数组,代码如下:
go
Info [] infos;
2.2.3 练习
当用户调用 Helloworld 合约时,我们把数据存放到 Info 数组中。当用户调用 sayHello 时传入对应的 id,我们根据 id,从合约中找到他存的数据发送给用户。如果找不到就用 strVar 兜底。
java
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract HelloWorld {
struct Info {
string phrase;
uint256 id;
address addr;
}
Info[] infos; // 数据默认存储在 storage 中吗?为啥它不用声明存储类型?
string strVar = 'hello world';
// 现在需要返回用户输 id 的指定内容
function sayHello (uint256 _id ) public view returns(string memory) {
// 遍历数组找到 _id 对应的那个
for(uint256 i = 0; i < infos.length; i++) {
if (infos[i].id == _id) {
return addinfo(infos[i].phrase);
}
}
// Warning: Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable. 这个警告是说有些条件没有返回值
return addinfo(strVar);
}
// 稍等一下在下面的 Deployed Contracts 中就有 了
function setHelloWorld(string memory newString, uint256 _id) public {
// 在这里新的 newString 存储到结构体当中
// 操作一个数据结构的时候需要显式的声明其存储类型
// address 不需要再单独声明变量(形参),可以从 msg 中获取,msg 升级 solidity 中的环境变量 EnviromentVariable,编译器提供的,无需声明即可访问
// msg.sender 即谁调用合约的这笔交易是谁发起的:值是个地址
// msg.value
// 声明一个 Info 类型的变量需要掉用户 Info() 传入对应的实际值?这些由顺序吗?
Info memory info = Info(newString, _id, msg.sender); // memory 临时存放
infos.push(info);
}
function addinfo(string memory helloWorld) internal pure returns(string memory) {
return string.concat(helloWorld, " from Frank's concat.");
}
}
在这段代码中有几个小点:
- for 循环语句的声明:for (uint256 i = 0; i < infos.length; i++)
- address 即交易调用方的地址,可以从 msg 中获取。msg 是 solidity 中的环境变量 EnviromentVariable,编译器提供的,无需声明即可访问。
- msg.sender 即谁调用合约的这笔交易是谁发起的:值是个地址
- msg.value
2.3 mapping
mapping 是类似 JS 中的对象或者说叫做哈希表,是一种通过 key 获取对应的值的数据结构。
2.3.1 声明 mapping
mapping 是 key => value 结构,这个语法很像 php 的键值数组声明方式,但是注意,这个只是他的类型声明。
声明 mapping 格式: mapping(key 类型 => value 类型)+空格+变量名;
2.3.2 练习 mapping 使用
训练内容:
思考一个问题:结合上面的 setHello 是把结构体存储到了数组
中,在 sayHello 方法中每次采用 for loop 查找数据,当很多人使用这个合约的时候,查找的次数和时间就会越来越多,需要的计算单元也会越来越多,这就导致你的交易越来越贵。
那怎么解决这个问题呢?
为了提高效率,我们使用 mapping 结构存储数据,把 id 当做key,用来查找数据可以一下子找到数据。
mapping 的操作,和 JS 的对象操作一样的:使用 [key]
操作符的方式访问数据,修改则直接对同名 key 赋新值即可。
scss
mapping(uint256 => Info) infoBak;
// 赋值
// 完成对 值结构体的 赋值
Info memory info = Info(newString, _id, msg.sender)
// 赋值和修改呢
infoBak[_id] = info;
// 读取
infoBak[_id].phrase
代码如下:
diff
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract HelloWorld {
struct Info {
string phrase;
uint256 id;
address addr;
}
- Info[] infos; // 数组
+ mapping(uint256 => Info) infoMapping; // mapping
string strVar = 'hello world';
+ // 现在需要返回用户输 id 的制定内容分
function sayHello (uint256 _id ) public view returns(string memory) {
- // 遍历数组找到 _id 对应的那个
- for(uint256 i = 0; i < infos.length; i++) {
- if (infos[i].id == _id) {
- return addinfo(infos[i].phrase);
- }
- }
- return addinfo(strVar);
+ // infoMapping[_id].phrase 即可渠道 phrase,但是如何判断是否真的找到呢?
+ // 答案:从 infoMapping[_id] 这个结构体中找到最容易为空的那一项,
+ // 判断其是否为空值,这里面选择 address
+ if (infoMapping[_id].addr == address(0x0)) {
+ // 空地址 address(0x0) 16进制的 0
+ return addinfo(strVar);
+ } else {
+ return addinfo(infoMapping[_id].phrase);
+ }
+ }
// 稍等一下在下面的 Deployed Contracts 中就有 了
function setHelloWorld(string memory newString, uint256 _id) public {
// 声明一个 Info 类型的变量需要掉用户 Info() 传入对应的实际值?这些由顺序吗?
Info memory info = Info(newString, _id, msg.sender); // memory 临时存放
- info.push(info)
+ // 改为 mapping 存储
+ infoMapping[_id] = info;
}
function addinfo(string memory helloWorld) internal pure returns(string memory) {
return string.concat(helloWorld, " from Frank's concat.");
}
}
# 三、总结
本章我们学习了 solidity 的数据结构:
1. struct: 结构体
2. array: 同类型的放到一个数组中,使用索引访问数据
3. mapping: 哈希表,以 key => value 的形式存储数据,使用 key 访问数据