智能合约入门系列(五) -引用类型

253 阅读2分钟

「这是我参与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;
    }
}

映射并没有长度,键集合(或列表),值集合(或列表)这样的概念。

总结

比较重要的就是映射和结构体的使用,数组也常用,要多理解,基本掌握了这些类型,大部分合约的逻辑我们都能看明白了。