数据位置
在合约中声明和使用的变量都有一个数据位置,指明变量值应该存储在哪里。
Solidity 提供4种类型的数据位置:
StorageMemoryCalldataStack
本关卡我们将重点讲述 Storage 与 Memory。
Storage 与 Memory
Storage 该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。
保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。
Memory 内存位置是临时数据,比 Storage 便宜。它只能在函数中访问。你可以把它想象成每个单独函数的内存(RAM)。
通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。
大多数时候你都用不到这些关键字,默认情况下 Solidity 会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。
然而也有一些情况下,你需要手动声明存储类型,主要用于处理函数内的结构体和 数组时:
contract SandwichFactory {
struct Sandwich {
string name;
string status;
}
Sandwich[] sandwiches;
function eatSandwich(uint _index) public {
// Sandwich mySandwich = sandwiches[_index];
// ^ 看上去很直接,不过 Solidity 将会给出警告
// 告诉你应该明确在这里定义 `storage` 或者 `memory`。
// 所以你应该明确定义 `storage`:
Sandwich storage mySandwich = sandwiches[_index];
// ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针
// 在存储里,另外...
mySandwich.status = "Eaten!";
// ...这将永久把 `sandwiches[_index]` 变为区块链上的存储
// 如果你只想要一个副本,可以使用`memory`:
Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了
// 另外
anotherSandwich.status = "Eaten!";
// ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响
// 不过你可以这样做:
sandwiches[_index + 1] = anotherSandwich;
// ...如果你想把副本的改动保存回区块链存储
}
}如果你没有完全理解究竟应该使用哪一个,不用担心。在本实训中,我们将告诉你何时使用 storage 或是 memory,并且当你不得不使用到这些关键字的时候,Solidity 编译器也会有警示。
现在你只要知道在某些场合下需要你显式地声明 storage 或 memory 就够了!
实战练习
我们给我的宠物增加一个配对繁殖的功能。两个宠物各自的DNA结合在一起会形成一个新的宠物DNA。
创建一个名为
mateAndMultiply的函数。 使用两个参数:_petId(uint类型 )和_targetDna(也是uint类型)。 设置可见性为public。我们不希望别人用我们的区块宠物去配对。 首先,我们确保对自己区块宠物的所有权。 通过在函数内添加一个
require语句来确保msg.sender只能是这个区块宠物的主人(类似于我们在 createRandomPet 函数中做过的那样,通过 petToOwner 映射传入 _petId 等于 msg.sender 来解决)。
注意:同样,因为我们的答案检查器比较呆萌,只认识把 msg.sender 放在前面的答案,如果你切换了参数的顺序,它就不认得了。 但你正常编码时,如何安排参数顺序都是正确的。
为了获取这个区块宠物的DNA,我们的函数需要声明一个数据类型为 Pet 名为 myPet 的storage型变量。 将其值设定为在 pets 数组中索引为 _petId 所指向的值。


