「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」
前言
上一篇大概总结了智能合约的值类型,这篇让我们继续看一下引用类型。不同于之前值类型,复杂类型占的空间更大,超过256字节,因为拷贝它们占用更多的空间。由此我们需要考虑将它们存储在什么位置。
类型分析
1.数据位置
- memory:存储在内存里,只在函数内部使用,函数内变量不做特殊说明为
memory类型,声明在函数内部,和函数参数体内需要声明storage。 - storage:相当于全局变量。函数外合约内的都是
storage类型。 - calldata:保存函数的参数的特殊储存位置,只读,大多数时候和
memory相似。能优先使用就先使用,临时存储传入函数的参数,因为它既不会复制,也不能修改,而且还可以作为函数的返回值。
2.数组
可以声明时指定长度,或者是变长的。
数组可以是任何类型,包括映射和结构体。但是数组中的映射只能是storage类型。
如果是局部添加数组,注意的一点是 新版本的 局部的都要加memory,包括返回数组 返回值那里
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract C {
uint[5] fixedArr = [1,2,3,4,5];
function fos() public pure {
uint[] memory x = new uint[](3); //局部数组
x[0] = 1;
x[1] = 3;
x[2] = 4;
}
// 通过for循环计算数组值的总和
function sum() public view returns (uint) {
// fixedArr.length = 6;//报错
uint total = 0;
for(uint i = 0; i < fixedArr.length; i++) {
total += fixedArr[i];
}
return total;
}}
数组的属性
length: 长度 修改length 可改变动态长度数组的长度,但是0.6.0以上的版本都不能使用length修改数组长度
push:固定数组不能用 ,动态数组storage可以用 ,memory不能用
pop: 把push进来的数据 再顶出去
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract C {
uint[5] fixedArr = [1,2,3,4,5];
uint[4] arr1; // 固定数组的声明
uint[] arr2; // 动态长度数组的声明
// 声明并初始化
uint[2] arr3 = [1,2]; //固定数组的声明 并初始化
uint[] arr4 = [1,4,12,4,2]; //动态长度数组的声明 并初始化
// 使用new
uint[] public arr5 = new uint[](3);
function push() public{
arr5.push(1);
}
function fos() public pure {
uint[] memory x = new uint[](3);
x[0] = 1;
x[1] = 3;
x[2] = 4;
}
// 通过for循环计算数组值的总和
function sum() public view returns (uint) {
// fixedArr.length = 6;//报错
uint total = 0;
for(uint i = 0; i < fixedArr.length; i++) {
total += fixedArr[i];
}
return total;
}}
3.结构体
就是相当于我们java的对象的说法,也就是go语言里的结构体
// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.7.0 <0.9.0;/** * @title Ballot * @dev Implements voting process along with vote delegation */contract Ballot { struct Voter { uint weight; // weight is accumulated by delegation bool voted; // if true, that person already voted address delegate; // person delegated to uint vote; // index of the voted proposal } struct Proposal { // If you can limit the length to a certain number of bytes, // always use one of bytes1 to bytes32 because they are much cheaper bytes32 name; // short name (up to 32 bytes) uint voteCount; // number of accumulated votes } address public chairperson; mapping(address => Voter) public voters; Proposal[] public proposals; /** * @dev Create a new ballot to choose one of 'proposalNames'. * @param proposalNames names of proposals */ constructor(bytes32[] memory proposalNames) { chairperson = msg.sender; voters[chairperson].weight = 1; for (uint i = 0; i < proposalNames.length; i++) { // 'Proposal({...})' creates a temporary // Proposal object and 'proposals.push(...)' // appends it to the end of 'proposals'. proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } } /** * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. * @param voter address of voter */ function giveRightToVote(address voter) public { require( msg.sender == chairperson, "Only chairperson can give right to vote." ); require( !voters[voter].voted, "The voter already voted." ); require(voters[voter].weight == 0); voters[voter].weight = 1; } /** * @dev Delegate your vote to the voter 'to'. * @param to address to which vote is delegated */ function delegate(address to) public { Voter storage sender = voters[msg.sender]; require(!sender.voted, "You already voted."); require(to != msg.sender, "Self-delegation is disallowed."); while (voters[to].delegate != address(0)) { to = voters[to].delegate; // We found a loop in the delegation, not allowed. require(to != msg.sender, "Found loop in delegation."); } sender.voted = true; sender.delegate = to; Voter storage delegate_ = voters[to]; if (delegate_.voted) { // If the delegate already voted, // directly add to the number of votes proposals[delegate_.vote].voteCount += sender.weight; } else { // If the delegate did not vote yet, // add to her weight. delegate_.weight += sender.weight; } } /** * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. * @param proposal index of proposal in the proposals array */ function vote(uint proposal) public { Voter storage sender = voters[msg.sender]; require(sender.weight != 0, "Has no right to vote"); require(!sender.voted, "Already voted."); sender.voted = true; sender.vote = proposal; // If 'proposal' is out of the range of the array, // this will throw automatically and revert all // changes. proposals[proposal].voteCount += sender.weight; } /** * @dev Computes the winning proposal taking all previous votes into account. * @return winningProposal_ index of winning proposal in the proposals array */ function winningProposal() public view returns (uint winningProposal_) { uint winningVoteCount = 0; for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p; } } } /** * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then * @return winnerName_ the name of the winner */ function winnerName() public view returns (bytes32 winnerName_) { winnerName_ = proposals[winningProposal()].name; }}
这是官方的一个例子投票,Voter就是结构体,定义了一些属性。
struct可以用于映射和数组中作为元素。其本身也可以包含映射和数组等类型。
但是不能声明一个struct同时将这个struct作为这个struct的一个成员。
4.映射
一种键值对的映射关系存储结构。定义方式为mapping(_KeyType => _KeyValue)。键的类型允许除映射外的所有类型,如数组,合约,枚举,结构体。值的类型无限制,定义如下
contract MappingExample{
mapping(address => uint) public balances;
mapping(address => mapping(address => uint)) public balances;
function update(uint amount) returns (address addr){
balances[msg.sender] = amount;
return msg.sender;
}
}
映射并没有长度,键集合(或列表),值集合(或列表)这样的概念。
总结
比较重要的就是映射和结构体的使用,数组也常用,要多理解,基本掌握了这些类型,大部分合约的逻辑我们都能看明白了。