跟我学 Solidity :引用变量

2020-12-15 10:32:43 浏览数 (1)

  • 译文出自:登链翻译计划[1]
  • 译者:翻译小组[2]
  • 校对:Tiny 熊[3]

欢迎阅读跟我学习 Solidity系列中的另一篇文章。在上一篇文章[4],中,我们了解了数据位置的工作方式以及何时可以使用以下三个位置:memorystoragecalldata

在本文中,我们将继续学习 Solidity 中的变量。这次,我们将重点放在引用类型上,该引用类型应显式指定数据位置,正如我们在前几篇文章中提到的那样。我们还将看到如何定义映射,枚举和常量。

数组(Arrays)

在Solidity[5]中,我们有两种类型的数组:存储数组和内存数组。

存储数组(Storage arrays)

这些数组被声明为状态变量,并且可以具有固定长度或动态长度。

动态存储数组可以调整数组的大小,它们通过访问push()pop()方法来调节长度。

代码语言:javascript复制
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

contract A {
      uint256[] public numbers;// 动态长度数组
      address[10] private users; // 固定长度数组
      uint8 users_count;

      function addUser(address _user) external {
          require(users_count < 10, "number of users is limited to 10");
          users[users_count] = _user;
          users_count  ;
      }

      function addNumber(uint256 _number) external {
          numbers.push(_number);
      }

内存数组(Memory arrays)

这些数组以memory作为其数据位置声明。它们也可以具有固定长度或动态长度,但是不能调整动态大小的内存数组的大小(即,不能调用push()pop()方法),数组的大小必须预先计算。

使用new关键字声明动态大小的内存数组,如下所示:

代码语言:javascript复制
Type[] memory a = new Type[](size "] memory a = new Type[")
代码语言:javascript复制
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

contract B {

     function createMemArrays() external view {
         uint256[20] memory numbers;
         numbers[0] = 1;
         numbers[1] = 2;

         uint256 users_num = numbers.length;
         address[users_num] memory users1; // 错误 :  应该是整数常量或常量表达式

         address[] memory users2 = new address[](users_num "] memory users2 = new address[");
         users2[0] = msg.sender; // OK
         users2.push(msg.sender); // 错误 : member push is not available

     }

}

这里要提到的另一点是关于何时使用内存数组并编写如下内容:

代码语言:javascript复制
uint256[] memory array;
array[0] = 1;

你不会收到任何警告,但最终将得到无效的操作码,因为根据内存中布局[6]的描述,array将指向零插槽,因此切勿写入。请记住,在使用数组之前,请务必先对其进行初始化,以便获取有效的地址。

数组切片(Array slices)

数组切片只能与calldata数组一起使用,形式为x[start:end]。切片的第一个元素是x [start],最后一个元素是x[end-1]

开始和结束都是可选的:开始默认为 0,结束默认为数组的长度。

特殊的动态大小数组

  1. byte[]bytes

这些数组可以保存任意长度的原始字节数据。两者之间的区别在于,byte []遵循数组类型的规则,并且如文档 Solidity 中的内存数组的描述[7],数组的元素总是占据 32 个字节的倍数。这意味着如果一个元素的长度小于 32 字节的倍数,则将对其进行填充,直到其适合所需的大小为止。

对于byte数组,每个元素将浪费 31 个字节,而bytesstring不是这种情况。我要提醒你,从内存中读取或写入一个字(32 个字节)会消耗 3 gas,这就是为什么建议使用bytes而不是byte[]的原因。

  1. string

字符串是 UTF-8 数据的动态数组。与其他语言相反,Solidity 中的 string 不提供获取字符串长度或执行两个字符串的连接或比较的功能(需要使用库)。可以使用bytes(<string>)将字符串转换为字节数组。这将返回字符串的 UTF-8 表示形式的低级字节。

注意:可以将一个字符编码为一个以上的字节,因此字节数组的长度不一定是字符串的长度。

字符串常量

请参见文档的此部分[8]

stringbytes

文档的大多数示例都使用bytes32而不是string,并且如果可以限制字符串的字节数,则应该使用值类型bytes1 ... bytes32,因为便宜得多。

结构体(Struct)

与在 C 和 C 中一样,结构体允许你定义自己的类型,如下所示:

代码语言:javascript复制
struct Donation {
      uint256 value;
      uint256 date;
}

定义结构体后,就可以开始将其用作状态变量或在函数中使用。

为了初始化一个结构体,我们有两种方法:

  • 使用位置参数:
代码语言:javascript复制
Donation donation = Donation(
msg.value,
block.timestamp
);
  • 使用关键字:
代码语言:javascript复制
Donation donation = Donation({
value : msg.value,
date: block.timestamp
});

第二种方法将避免我们必须记住结构体成员的顺序,因此它可能比第一种有用。

使用点访问结构体的成员:

代码语言:javascript复制
uint256 donationDate = myDonation.date;

“虽然结构体本身可以是映射成员的值类型,也可以在动态大小的数组里使用,但是结构体不能包含其自身类型的成员。这个限制是必要的,因为结构体的大小必须是有限的。” — Solidity 文档[9]

映射(Mappings)

你可以将映射视为大量的键/值存储,其中每个可能的键都存在,并且可以使用该键来设置或检索任何值。

映射声明如下:

代码语言:javascript复制
mapping( KeyType => ValueType) VariableName

KeyType可以是任何内置值类型(我们在第一篇[10]介绍过)、字节或字符串中看到的值、也可以是任何合约或枚举类型。ValueType可以是任何类型,包括映射,数组和结构体。

这里要提到的一件事是,映射变量唯一允许的数据位置是storage,它只能声明为状态变量、存储指针或库函数的参数。

枚举(Enum)

枚举允许你将自定义类型下的相关值分组,如以下示例所示:

代码语言:javascript复制
enum Color { green , blue, red }

使用以下语法可以访问enum值:

代码语言:javascript复制
Color defaultColor = Color.green;

注意:枚举也可以在文件级别上声明,而不是在合约或库定义中。

常量和不可变状态(Immutable)变量

状态变量可以声明为constantimmutable。在这两种情况下,构造合约后都无法修改变量。对于constant,该值必须在编译时确定,而对于immutable,则是在构造时赋值。

编译器不会为这些变量保留一个存储槽,而是在每次出现时会由相应的值替换。

常量使用关键字constant声明:

代码语言:javascript复制
uint256 constant maxParticipants = 10;

对于不可变状态变量,使用关键字immutable声明它们:

代码语言:javascript复制
contract C {
      address immutable owner = msg.sender;
      uint256 immutable maxBalance;

      constructor(uint256 _maxBalance){
           maxBalance = _maxbalance;
      }
}

你可以在文档的本部分[11]中找到有关常量和不可变状态变量的更多详细信息。

注意:也可以在文件级别定义constant变量。

delete 关键字

我想补充的最后一件事是在 Solidity 中使用delete。它用于将变量设置为其初始值,这意味着该语句delete a的行为如下:

  • 对于整数,它等于a = 0
  • 对于数组,它分配长度为零的动态数组或长度相同的静态数组,并将所有元素设置为其初始值。
  • delete a[x]删除数组索引x处的项目,并保持所有其他元素和数组长度不变。这尤其意味着它在数组中留有间隙。
  • 对于结构体,它将重置结构体的所有成员。
  • delete对映射没有影响(因为映射的键可能是任意的,并且通常是未知的)。

练习时间:Crud(增删改查)

在本练习中,我们将创建一个用于管理用户的合约。

说明如下:

  • 创建一个新文件并添加一个名为 Crud 的合约。
  • 创建一个名为 User 的结构体,其中包含用户的 ID 和名称。
  • 添加两个 public 状态变量:1) 动态的用户数组;2) 每次我们创建新用户时 ID 都会增加。

下一步是创建 Crud 函数,但是由于我没有向你介绍 Solidity 函数,因此将为你提供声明函数的语法。在下一篇文章中,我们将对它们进行详细的讨论:

代码语言:javascript复制
function function_name(<param_type> <param_name>) <visibility> <state mutability> [returns(<return_type>)]{ ... }

可见性(visibiliity)可以是:public,private,internal,external。状态可变性(state mutability)可以是:view,pure,payable。

这是你将创建的函数的描述:

1. add

可见性:public 状态可变性:空

此函数将用户名作为参数,使用新 ID 创建 User 实例(每次添加新用户时 ID 都会自动递增),并将新创建的用户添加到数组中。

2. read

可见性:public 状态可变性:view

此函数获取要查找的用户的 ID,如果找到则返回用户名,否则回退(稍后会详细讨论异常)。

3. update

可见性:public 状态可变性:空

此函数将获取用户的 ID 和新名称,然后在找到相应用户时对其进行更新,如果该用户不存在,则回退该交易。

4. destroy

可见性:public 状态可变性:空

此函数将用户的 ID 删除,如果找到,则将其从数组中删除;如果用户不存在,则回退交易。

提示:由于最后三个函数都需要查找用户,因此你将需要创建一个私有函数,该函数将获取用户的 ID 并在数组中返回其索引(如果找到),以避免重复相同的代码。

以下是完整代码:

代码语言:javascript复制
// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

contract Crud {

    struct User {
        uint256 id;
        string name;
    }

    User[] public users;
    uint256 public nextId = 1;

    function add(string memory name) public {
        User memory user = User({id : nextId, name : name});
        users.push(user);
        nextId  ;
    }

    function read(uint256 id) public view returns(string memory){
        uint256 i = find(id);
        return users[i].name;
    }

    function update(uint256 id, string memory newName) public {
        uint256 i = find(id);
        users[i].name = newName;
    }

    function destroy(uint256 id) public {
        uint256 i = find(id);
        delete users[i];
    }

    function find(uint256 id) private view returns(uint256){
        for(uint256 i = 0; i< users.length; i  ) {
            if(users[i].id == id)
                return i;
        }
        revert("User not found");
    }

}

至此,我们对变量的讨论结束了。下次,我们将研究功能以及如何在 Solidity 中使用它们,因此,如果你想了解更多信息,请继续关注即将发布的文章。


本翻译由 Cell Network[12] 赞助支持。

原文:https://medium.com/better-programming/learn-solidity-variables-part-3-3b02ca71cf06


参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

上一篇文章: https://learnblockchain.cn/article/1759

[5]

Solidity: https://learnblockchain.cn/docs/solidity/

[6]

内存中布局: https://learnblockchain.cn/docs/solidity/internals/layout_in_memory.html

[7]

]`遵循数组类型的规则,并且如文档 [Solidity中的内存数组的描述: https://learnblockchain.cn/docs/solidity/internals/layout_in_memory.html

[8]

文档的此部分: https://learnblockchain.cn/docs/solidity/types.html#string-literals

[9]

Solidity文档: https://learnblockchain.cn/docs/solidity/types.html#structs

[10]

第一篇: https://learnblockchain.cn/article/1758

[11]

文档的本部分: https://learnblockchain.cn/docs/solidity/contracts.html#constant-immutable

[12]

Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain

0 人点赞