Solidity-11-types Reference Types
Reference Types
引用类型的值可以通过多个不同的名称进行修改。
将此与值类型进行对比,只要使用值类型的变量,您就会获得一个独立的副本。
因此,必须比值类型更仔细地处理引用类型。
目前,引用类型包括结构、数组和映射。
如果使用引用类型,则始终必须显式提供存储类型的数据区域:内存(其生命周期仅限于外部函数调用),存储(存储状态变量的位置,生命周期所在的位置) 仅限于合约的生命周期)或 calldata(包含函数参数的特殊数据位置)。
更改数据位置的赋值或类型转换将始终引发自动复制操作,而同一数据位置内的赋值仅在某些情况下复制存储类型。
数据位置 Data location
每个引用类型都有一个附加注释,即“数据位置”,关于它的存储位置。
共有三个数据位置:内存、存储和调用数据。
Calldata 是存储函数参数的不可修改、非持久性区域,其行为主要类似于内存。
- NOTE
如果可以,请尝试使用 calldata 作为数据位置,因为它可以避免复制,并且可以确保数据不能被修改。
具有 calldata 数据位置的数组和结构也可以从函数返回,但不能分配此类类型。
- NOTE
在 0.6.9 版本之前,引用类型参数的数据位置仅限于外部函数中的调用数据、公共函数中的内存以及内部和私有函数中的内存或存储。
现在所有函数都允许内存和调用数据,无论它们的可见性如何。
- NOTE
在 0.5.0 版本之前,数据位置可以省略,并且会根据变量的类型、函数类型等默认到不同的位置,但现在所有复杂类型都必须给出明确的数据位置。
数据位置和分配行为
数据位置不仅与数据的持久性有关,还与分配的语义有关:
storage 和 memory 之间的分配(或来自 calldata)总是创建一个独立的副本。
从内存到内存的分配只会创建引用。 这意味着对一个内存变量的更改在引用相同数据的所有其他内存变量中也可见。
从存储到本地存储变量的分配也只分配一个引用。
存储的所有其他分配始终复制。 这种情况的示例是对状态变量或存储结构类型的局部变量成员的赋值,即使局部变量本身只是一个引用。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 =0.4.16 =0.4.16 =0.4.16 =0.4.0 =0.4.16 =0.6.0 newSize)
pairsOfFlags.pop();
} else if (newSize > pairsOfFlags.length) {
while (pairsOfFlags.length =0.8.5 =0.6.0 Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// We cannot use "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"
// because the right hand side creates a memory-struct "Campaign" that contains a mapping.
Campaign storage c = campaigns[campaignID];
c.beneficiary = beneficiary;
c.fundingGoal = goal;
}
function contribute(uint campaignID) public payable {
Campaign storage c = campaigns[campaignID];
// Creates a new temporary memory struct, initialised with the given values
// and copies it over to storage.
// Note that you can also use Funder(msg.sender, msg.value) to initialise.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
该合约不提供众筹合约的全部功能,但它包含理解结构所需的基本概念。
结构类型可以在映射和数组中使用,它们本身可以包含映射和数组。
结构不可能包含自己类型的成员,尽管结构本身可以是映射成员的值类型,也可以包含其类型的动态大小的数组。
这个限制是必要的,因为结构的大小必须是有限的。
请注意,在所有函数中,结构类型如何分配给具有数据位置存储的局部变量。
这不会复制结构,而只会存储一个引用,以便对局部变量成员的赋值实际上写入状态。
当然,您也可以直接访问该结构的成员,而无需将其分配给局部变量,如 campaigns[campaignID].amount = 0
。
- NOTE
在 Solidity 0.7.0 之前,允许包含仅存储类型(例如映射)成员的内存结构,并且像上面示例中的活动 [campaignID] = Campaign(beneficiary, goal, 0, 0)
之类的分配将起作用并且只是默默地跳过 那些成员。
参考资料
https://docs.soliditylang.org/en/latest/types.html#reference-types