Solidity入门(2)- 数据类型

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


注释

当你编写Solidity合约时,添加注释对解释代码的主要功能或使用场景是非常重要的。注释仅供开发者阅读,不会对程序的执行产生任何影响。在Solidity中,你可以通过两种方式来添加注释:

  • 使用 // 来添加单行注释。
  • 使用 /.../ 来添加多行注释。
bash 复制代码
// This is a single-line comment.

/*
This is a
multi-line comment.
*/

变量

bash 复制代码
uint x = 3;
uint y = 4;

数据位置

我们之前提到,变量是存储在某种介质上的。在传统应用程序中,数据可能存储在内存或硬盘上。存储在内存中的数据是易失的,程序一旦退出运行,这些数据就会消失。而存储在硬盘上的数据是持久的,当程序再次运行时,这些数据会被重新读取。同样地,在智能合约中,也存在不同的数据存储位置,这些位置决定了数据的持久性。

Solidity 中有三种数据存储位置,分别指定变量的存储方式:

  • storage: 数据永久存储在区块链上,通常用于状态变量。
  • memory: 数据存储在内存中,是临时的,当函数调用结束后数据就会消失。
  • calldata: 数据存储在一个专门用于存放函数参数的区域,这也是临时的。

在这些存储位置中,标记为 storage 的数据位置通常关联到我们所说的"状态变量",它们的数据是永久记录在区块链上的。这种机制确保了区块链数据的不可变性和合约状态的持续性。

声明与定义

在很多资料中,你可能会看到"声明"(declaration)和"定义"(definition)这两个概念的区分。一般来说,声明是指为变量指定名字和数据类型的过程,而定义则不仅包括指定名称和数据类型,还包括为变量分配存储空间和赋予初始值。

然而,在我们接下来的讨论中,我们不会特别区分这两个概念。我们会交替使用"声明"和"定义",主要是因为在编写智能合约的上下文中,这种区分并不会对我们的工作产生重大影响。过于严格地区分这两者可能反而会使文章变得更难理解。

在 Solidity 中,一旦变量被声明,如果没有手动初始化,它会自动被赋予一个默认的初始值,这通常称为"零值初始化"。因此,不论变量是否被显式初始化,它最终都会拥有一个初始值。这就是为什么在 Solidity 的环境下,区分声明与定义不是特别必要的原因。

数据类型

智能合约在本质上是计算机程序,因此它能够处理多种不同的数据类型,每种类型都有其特定的表示方式和操作方法。在 Solidity 中,基于参数传递的方式,数据类型可以分为两大类:「值类型」和「引用类型」。

  • 值类型:这类类型的数据在传递时会被复制,每次传递都是一个独立的副本。
  • 引用类型:相对于值类型,引用类型的数据在传递时不复制其本身,而是传递对原始数据的引用。

值传递

bash 复制代码
uint8 a = 1;
uint8 b = a;

引用传递

传递数据地址的方法通常被称为「引用传递」(pass by reference)。在这种方法中,赋值或参数传递时传递的是数据的地址,而不是数据本身。

在下面的示例中,我们定义了两个字节数组 bts1 和 bts2。在代码的第二行,通过赋值 bts2 = bts1,bts2 和 bts1 都开始指向同一个数据地址。因此,当修改其中任何一个数组的内容时,另一个数组的内容也会相应地发生变化,因为它们共享同一个存储位置。这展示了引用传递如何在实际应用中工作,以及它如何影响关联变量的值。

bash 复制代码
bytes memory bts1 = "btc";
bytes memory bts2 = bts1;

console.log("bts1: %s", string(bts1)); // bts1: btc
console.log("bts2: %s", string(bts2)); // bts2: btc

bts2[0] = 'e'; //这里只改了bts2[0]的值,但是你会发现bts1[0]的值也会跟着变动

console.log("bts1: %s", string(bts1)); // bts1: etc
console.log("bts2: %s", string(bts2)); // bts2: etc

布尔类型与运算符

布尔类型是只有 true 或 false 两种可能取值的类型。在 Solidity 中,布尔类型变量可以使用 bool 关键字进行定义。

运算符

布尔类型可以使用的运算符和作用如下所示,运算操作后得到的结果依然是布尔类型。

  • ! (逻辑非)
  • && (逻辑与)
  • || (逻辑或 )
  • == (等于)
  • != (不等于)

短路规则

需要注意的是,逻辑运算符 || 和 && 在 Solidity 中都遵循短路规则(short-circuiting)。其规则如下:

  • 对于 f(x) || g(y),如果 f(x) 为 true,则 g(y) 不会被执行。
  • 对于 f(x) && g(y),如果 f(x) 为 false,则 g(y) 不会被执行。
    &&和 || 运算子的短路规则

以下是一个示例,包含两个函数 isEven 和 isZero。其中,isEven 判断一个数是否为偶数,isZero 判断一个数是否为 0。如果传入的参数为 0,isZero 会产生副作用,使得 zeroCount 加 1。

bash 复制代码
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.17;

contract BoolShortCircuit {
    uint256 public zeroCount = 0;

    function isEven(uint256 num) private pure returns(bool) {
        return num%2 == 0;
    }

    // isZero函数有副作用,会改变状态变量zeroCount的值
    function isZero(uint256 num) private returns(bool) {
        if(num == 0) {
            zeroCount += 1; // 函数副作用,会改变zeroCount的值
        }
        return num == 0;
    }
}

整型

整型(integer)是不包含小数部分的数值型数据,包括正整数、负整数和 0 等。账户余额、Gas、持有的 Token 数量等通常都用整型表示。在 Solidity 中,整型有两种类型:

  • intM:有符号整型
  • uintM:无符号整型
    其中,M 的取值范围为 8 到 256,步长为 8。例如,有 int8、int16、int32 等等,一直到 int256。相应地,也有 uint8、uint16、uint32,一直到 uint256。其中,int8 和 uint8 占用 8 位(8 bits),int16 和 uint16 占用 16 位,以此类推。

运算符

算术运算符

算术运算符可以用来进行四则运算,得到的结果是整型。

  • +(加)
  • -(减)
  • *(乘)
  • /(除)
  • %(取模)
  • **(幂)
  • <<(左移)
  • >>(右移)

比较运算符

通过比较运算符,我们可以比较两个变量的数量大小关系,以及变量是否相等。比较运算符得到的结果是布尔值。

bash 复制代码
- <=(小于等于)
- < (小于)
- ==(等于)
- !=(不等于)
- >=(大于等于)
- > (大于)

位运算符

bash 复制代码
位运算符用来对二进制位进行操作,其执行结果是整型。

&(按位与)
|(按位或)
^(按位异或)
~(按位取反)

地址类型

地址类型(address)是 Solidity 独有的一种类型,用于存放账户地址。在给其他账户转账或与其他合约交互时,需要使用地址类型。类似于向他人银行账户转账时需要知道对方的账户号码,Solidity 的地址类型也扮演着类似的角色。

Solidity 的地址类型用关键字 address 表示。它占据 20 字节(160 位),默认值为 0x0,表示空地址。地址类型可以细分为两种:

  • address:普通地址类型(不可接收转账)
  • address payable:可收款地址类型(可接收转账)

这两种地址类型的主要区别在于,address payable 能接受转账,而 address 不能。接下来,我们将先介绍如何定义地址类型的变量,然后再解释为什么要区分这两种地址类型。

bash 复制代码
address addr = 0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990;
address payable addr_pay = payable(0x8306300ffd616049FD7e4b0354a64Da835c1A81C);

到现在为止,我们已经了解了地址类型的基本作用是存放账户地址。但是,我们仍有一个疑问:address 和 address payable 看起来区别不大,为什么要区分它们?直接统一使用 address 类型,想要转账就转账,不想转账就不转不就可以了吗?

Solidity 之所以要进行这样的区分,是为了提高合约的安全性,避免 Ether 转入某些合约后无法转出,导致资金永远被锁住。

首先,我们需要了解 Solidity 中账户有两种类型:外部账户(externally owned address,简称 EOA)和合约账户(contract address,简称 CA)。EOA 是我们在 MetaMask 上创建的那些账户,而 CA 是在部署合约后生成的合约地址。

当我们将 Ether 转入 EOA 后,只要我们控制了 EOA 的私钥,就可以将 Ether 再转出来。然而,CA 账户情况则不同。CA 账户是由合约控制的,合约只能执行其定义过的操作。因此,我们必须在合约中实现一个函数,定义如何将账户中的 Ether 转出,否则这些 Ether 会被永远锁在 CA 账户中。

因此,每次向 CA 账户转账时,我们都必须问自己:这个合约是否已经定义了转出 Ether 的逻辑。使用 address payable 明确告诉编译器你已确认转账到该地址是安全的。这不仅提高了合约的安全性,也更方便开发者进行调试。

address 和 address payable 之间可以互相进行类型转换。主要遵循两条规则。

  • address payable 可以隐式地被转换成 address
  • address 需要显式地使用 payable(addr) 函数转换成 address payable
bash 复制代码
address payable addr_pay = payable(0x8306300ffd616049FD7e4b0354a64Da835c1A81C);
address addr = addr_pay; _// 隐式类型转换_

address addr = 0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990;
address payable addr_pay = payable(addr); _// 显式类型转换_

成员变量

地址类型有三个成员变量,分别为:

  • balance :该地址的账户余额,单位是 Wei
  • code :该地址的合约代码,EOA 账户为空,CA 账户为非空
  • codehash :该地址的合约代码的 hash 值
    获取成员变量值

下面展示了如何获取地址的成员变量。其中 this 代表的是当前合约。

bash 复制代码
function get_balance() public view returns(uint256) {
     return address(this).balance; _//获取地址账户余额_
}

function get_code() public view returns(bytes memory) {
    return address(this).code; _//获取合约代码_
}

function get_codehash() public view returns(bytes32) {
    return address(this).codehash; _//获取合约代码的hash值_
}

成员函数

地址类型有五个成员函数:

  • transfer(uint256 amount):向指定地址转账,失败时抛出异常(仅 address payable 可以使用)。
  • send(uint256 amount):与 transfer 函数类似,但失败时不会抛出异常,而是返回布尔值(仅 address payable 可以使用)。
  • call(...):调用其他合约中的函数。
  • delegatecall(...):与 call 类似,但使用当前合约的上下文来调用其他合约中的函数,修改的是当前合约的数据存储。

msg.sender 和 msg.value

在 Solidity 中,msg.sender 和 msg.value 是全局上下文变量(属于 msg 命名空间),用于获取当前交易 / 调用的核心信息,是智能合约开发中最常用的变量之一。以下是详细解析:

  • 一、核心概念:msg 命名空间
    msg 是 Solidity 内置的全局对象,包含当前调用(交易 / 内部调用)的上下文信息,所有 msg.* 变量的取值在单次函数执行中是固定的(即使内部调用其他函数,也不会改变)。
  • 二、msg.sender:调用者 / 发起者地址
  1. 定义
    msg.sender 返回当前函数调用的直接发起者的以太坊地址(类型为 address/address payable)。
  2. 关键特性
    类型:Solidity 0.8.x 中默认是 address,如需接收 ETH 需显式转换为 address payable(如 payable(msg.sender))。
    调用层级:
    外部用户直接调用合约:msg.sender = 用户钱包地址;
    合约 A 调用合约 B 的函数:msg.sender = 合约 A 的地址(而非最终用户地址);
    内部函数调用(同一合约内):msg.sender 仍为外部调用的发起者(不会变为合约自身地址)。
    不可篡改:由以太坊虚拟机(EVM)填充,合约无法主动修改,是身份验证的核心依据。
  3. 典型用途
    权限控制(如仅合约所有者可调用函数);
    记录资产归属(如 NFT 铸造、代币转账的发起者);
    验证操作发起者身份。

示例代码

bash 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SenderExample {
    address public owner;
    mapping(address => uint) public userBalances;

    constructor() {
        // 部署合约的地址成为所有者
        owner = msg.sender;
    }

    // 仅所有者可调用的函数
    function onlyOwner() public view {
        require(msg.sender == owner, "Not owner");
    }

    // 记录调用者的余额
    function deposit() public payable {
        // msg.sender 是调用 deposit 的地址
        userBalances[msg.sender] += msg.value;
    }
}
  • 三、msg.value:附带的 ETH 金额
  1. 定义
    msg.value 返回当前调用中附带的 ETH 数量(单位为 wei,1 ETH = 10^18 wei),类型为 uint256。
  2. 关键特性
    仅适用于 payable 函数:如果非 payable 函数被调用时附带 ETH,交易会直接回滚;
    调用层级:
    外部用户调用合约并转 ETH:msg.value = 用户发送的 ETH 数量;
    合约 A 调用合约 B 的 payable 函数并转 ETH:msg.value = 合约 A 转发的 ETH 数量;
    无 ETH 附带时:msg.value = 0;
    单位:默认是 wei,需通过单位换算(如 ether、gwei)提升可读性。
  3. 典型用途
    接收 ETH 并验证金额(如众筹、NFT 铸造定价);
    转账时附带 ETH(结合 payable 地址);
    计算用户充值的 ETH 数量。

示例代码

bash 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ValueExample {
    // NFT 铸造价格:0.1 ETH
    uint256 public mintPrice = 0.1 ether;

    function mintNFT() public payable {
        // 验证发送的 ETH 等于铸造价格
        require(msg.value == mintPrice, "Invalid ETH amount");
        
        // 可执行铸造逻辑...
        
        // 向调用者退款(如有多余)
        if (msg.value > mintPrice) {
            // 需将 msg.sender 转为 payable 才能转账
            payable(msg.sender).transfer(msg.value - mintPrice);
        }
    }

    // 查看当前合约余额
    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

静态字节数组

静态字节数组(fixed-size byte arrays)是 Solidity 中独有的一种数据类型。与其字面意义相符,它们具有固定长度的字节数组。基于静态字节数组,你可以构建更为紧凑、节省存储空间的数据类型。

Solidity 共有 32 种静态字节数组:bytes1、bytes2、bytes3,依此类推,直至 bytes32。更好地理解这一概念是将这 32 种静态字节数组视为 32 种不同的值类型,而非通常意义上的数组。这 32 种静态字节数组类似于大小各异的乐高积木,通过将它们嵌入到"结构体"中并进行排列组合,我们可以构建出新的数据类型。

下标访问

bash 复制代码
bytes3 s = 'abc';
bytes1 ch = s[1]; _// ch的值为0x62,也就是'b'的ascii值_

字面值

字面值(literal)是指在程序中无需变量保存,直接表示为具体数字或字符串值的方式。Solidity 支持多种字面值类型,包括:

  • 地址字面值
  • 有理数和整数字面值
  • 字符串字面值
  • Unicode 字面值

枚举类型

枚举是一种组织和收集有关联变量的方法。我们逐一列举变量可能的取值,将它们收集在一起,并为它们取一个统一的名称,这样就定义了一个枚举类型。举例来说,当你开发一个链游时,也许需要根据键盘输入的上下左右来控制游戏角色的动作。这时,你可以定义一个名为 ActionChoices 的枚举类型,其中包含上下左右四个有限的取值。

Solidity 的枚举类型与 C 语言中的相似,都是一种特殊的整型。它定义了一组名称和整数值之间的对应关系,内部表示为从 0 开始的正整数。

bash 复制代码
enum ActionChoices { 
    GoLeft,     _// 底层表示为 0 _
    GoRight,    _// 底层表示为 1_
    GoUp,       _// 底层表示为 2_
    GoDown      _// 底层表示为 3_
}

使用枚举值

bash 复制代码
ActionChoices choice = ActionChoices.GoLeft;

自定义值类型

"自定义值类型"(User Defined Value Types)是用户定义的一种"值类型"。与之类似的另一种自定义类型是结构体,不过结构体是引用类型,我们稍后会详细介绍。"自定义值类型"类似于别名,但并不等同于别名。别名与原类型完全相同,只是名称不同。然而,"自定义值类型"则不具备原始类型的操作符(如 +、-、*、/等),其主要价值在于提高类型安全性和代码可读性。

「自定义值类型」的定义 通过 type C is V 可以定义新的"自定义值类型",其中 C 是新定义的类型,V 必须是 Solidity 的原生类型。例如,下面的例子定义了两种新类型:

bash 复制代码
type Weight is uint128;
type Price  is uint128;

数组

数组的声明

数组是一种数据结构,用于存储同类型元素的有序集合。根据数组长度是否可以改变,可以将数组分为以下两种类型:

  • 静态数组(Static Array)
  • 动态数组(Dynamic Array)

静态数组声明

bash 复制代码
T[arrSize] DataLocation arrName;

其中,arrSize 表示数组的长度,DataLocation 表示数据位置,而 arrName 则是你为这个数组取的任意名字。

数组是一种引用类型。请注意:在声明和定义数组时,必须使用三个数据位置关键字之一:storage、memory、calldata。

声明静态数组

bash 复制代码
uint[3] memory nftMem;
uint[3] storage nftStorage;

在 Solidity 中,需要注意静态数组的大小必须在编译时确定。这意味着你不能使用变量来指定数组的大小。举例来说,下面的代码是不合法的:

bash 复制代码
uint size = 2;
uint[size][size] memory array; _// 非法,size 是变量,不能用来指定数组大小_

动态数组声明

假设 T 是一种类型,那么动态数组的声明格式如下:

bash 复制代码
T[] DataLocation arrName;
bash 复制代码
uint[] memory nftMem;
uint[] storage nftStorage;

数组的初始化

静态数组的初始化

  • 零值初始化
bash 复制代码
uint[3] memory nftArr; _//所有元素都是0_
  • 数组字面值初始化
bash 复制代码
_//必须使用uint(1000)显式地将「数组字面值」第一个元素的类型转换成uint_
uint[3] memory nftArr = [uint(1000), 1001, 1002];

需要注意的是,数组字面值的基础类型是由其第一个元素的类型确定的。例如,在上面的例子中,[uint(1000), 1001, 1002] 的基础类型是 uint,因为第一个元素是 uint(1000)。其他所有元素都会隐式地被转换成第一个元素的类型。

动态数组初始化

动态数组的初始化需要使用关键字 new。它的所有元素值会被「零值初始化」,即赋予默认值。以下是一个整型动态数组的初始化示例:

初始化了一个有三个元素的动态数组,元素值被初始化为零值

bash 复制代码
uint n = 3;
uint[] memory nftArr = new uint[](n);
bash 复制代码
uint[] storageArr = [uint(1), 2]; _// 动态数组只有在storage位置才能用数组字面值初始化_

静态数组和动态数组是不同的类型,因此它们之间不能相互赋值

下标访问

bash 复制代码
uint[3] memory nftArr1 = [uint(1000), 1001, 1002];
nftArr1[0] = 2000;
nftArr1[1] = 2001;
nftArr1[2] = 2002;

uint[] memory nftArr2 = new uint[](3);
nftArr2[0] = 1000;
nftArr2[1] = 1001;
nftArr2[2] = 1002;

数组切片

数组切片(array slice)是建立在数组基础上的一种视图(view)。其语法形式为 arr[start:end]。这个视图包含的是从索引 start 到索引 end-1 的元素。与数组不同的是,切片是没有具体类型的,也不会占据存储空间。它是一种方便我们处理数据的抽象方式。

数组切片只能作用于 calldata

bash 复制代码
uint[5] storageArr = [uint(0), 1, 2, 3, 4];
function foo() public {
    uint[3] storage s1 = storageArr[1:4]; _// 编译错误,不能对 storage 位置的数组进行切片_

    uint[5] memory memArr = [uint(0), 1, 2, 3, 4];
    uint[3] memory s2 = memArr[1:4]; _// 编译错误,不能对 memory 位置的数组进行切片_
}

数组成员

数组是一种组合数据类型,它包含若干成员变量和成员函数。在本节中,我们将详细介绍这些成员变量和成员函数。

成员变量 数组,无论是静态还是动态,都具有一个成员变量:length。这个成员变量记录了数组的长度。我们可以通过点操作符(.)来访问这个值:

获取数组的长度

bash 复制代码
uint[3] memory arr1 = [uint(1000), 1001, 1002];
uint[] memory arr2 = new uint[](3);
uint arr1Len = arr1.length; _// 3_
uint arr2Len = arr2.length; _// 3_

数组成员函数

只有当动态数组位于存储(storage)位置时,它才具备成员函数。与此相对,静态数组以及位于 calldata 或 memory 中的动态数组不具备任何成员函数。这些成员函数可以改变数组的长度,具体包括:

  • push():在数组末尾增加一个元素,并赋予零值,使得数组长度增加一。
  • push(x):将元素 x 添加到数组末尾,同样使数组长度增加一。
  • pop():从数组末尾移除一个元素,导致数组长度减少一。
    注意 只有当动态数组的数据位置为存储(storage)时,才可以使用成员函数 push(), push(x), 和 pop()。这三个函数都会影响数组的长度:

注意这三个成员函数会改变数组的长度

push 和 pop 函数示例

bash 复制代码
uint[] arr; _//定义在storage位置的数组_

function pushPop() public {
    _// 刚开始没有任何元素_
    arr.push(); _// 数组有一个元素:[0]_
    arr.push(1000); _//数组有两个元素:[0, 1000]_
    arr.pop(); _// 数组剩下一个元素: [0]_
}
bash 复制代码
uint[3] memory arr;
arr.push(1); _//编译错误,只有 storage 上的动态数组才能调用 push 函数_
arr.pop(); _// 编译错误,只有 storage 上的动态数组才能调用 pop 函数_

uint[] memory arr = new uint[](3);
arr.push(1); _//编译错误,只有 storage 上的动态数组才能调用 push 函数_
arr.pop(); _// 编译错误,只有 storage 上的动态数组才能调用 pop 函数_

多维数组

bash 复制代码
uint[3][3] memory arr;

结构体

在 Solidity 中,有三种基本的引用类型:数组、结构体和映射类型。数组是将相同类型的元素集合到一起,形成一种新的数据类型。

结构体则是将不同类型的元素绑定在一起,创建出一种复合类型。结构体在 Solidity 中的应用非常广泛,具体体现在以下几个方面:

数据管理:结构体能够将多种不同类型的数据组合在一起,便于进行统一管理。

函数参数处理:通过结构体,我们可以将多个数据作为一个整体传入函数,无需将其拆解为多个独立参数。

返回值管理:同样地,结构体也可以用来从函数返回多个值,简化数据处理。

增强表达力:结构体的使用增强了 Solidity 的编程表达能力,因为结构体可以与其他结构体、数组或映射类型进行嵌套,从而使得代码结构更加清晰,易于理解。

定义一个结构体 在 Solidity 中定义一个结构体需要使用 struct 关键字。这允许你创建一个自定义的复合数据类型,集合不同的数据元素。下面是一个简单的例子,演示如何定义一个名为 Book 的结构体,其中包括书名和价格两个元素:

定义结构体类型

bash 复制代码
struct Book {
    string title; _// 书名_
    uint price;   _// 价格_
}

结构体的声明

bash 复制代码
StructName DataLocation varName;

其中 StructName 是你定义的结构体名称, DataLocation 是数据位置。然后 varName 是给结构体变量取的任意名字。

bash 复制代码
Book memory book;

结构体的初始化

在 Solidity 中,初始化结构体有两种常见方法。其中一种是通过键值对的形式,明确指定每个成员的初始值。这种方式为结构体的每个字段赋予明确的值,确保初始化过程清晰且易于理解。下面是一个示例:

bash 复制代码
Book memory book1 = Book(
    {
        title: "my book title",
        price: 25
    }
);
bash 复制代码
Book memory book2 = Book("my book title", 25);

获取结构体成员

bash 复制代码
Book memory book3;
book3.title = "my book title"; _// 给结构体成员赋值_
book3.price = 25; _// 给结构体成员赋值_

console.log("title: %s", book3.title); _// 获取结构体成员值_

结构体可以和数组,映射类型互相嵌套

通过使用结构体,数据管理在 Solidity 中变得更加方便和高效。结构体不仅可以独立使用,还可以与数组和映射类型进行嵌套(映射类型将在下一节中详细介绍)。这种嵌套能力显著增强了 Solidity 的编程表达力和灵活性。如下所示:

结构体数组

bash 复制代码
Book[] public lib; _// Book数组,状态变量_
function addNewBook(Book memory book) public {
    lib.push(book);
}

结构体在 Solidity 中的应用非常灵活,其中还可以包含数组作为其成员。这为构建更复杂和动态的数据结构提供了可能。

以书籍为例,一本书可能有多个合著者。为了能够有效地存储这些信息,我们可以在书籍的结构体中添加一个数组来记录所有作者的名字。例如:

bash 复制代码
struct Book {
    string title; _// 书名_
    uint price;   _// 价格_
    string[] author; _// 作者_
}

映射类型(mapping)

接下来我们看看如何声明一个映射类型的变量。其声明格式如下:

bash 复制代码
mapping(KeyType => ValueType) varName;

在 Solidity 中,映射类型的声明涉及键(KeyType)和值(ValueType)。键的类型(KeyType)必须是内置的值类型,如 uint、string、bytes 等。重要的是要注意,键不能是数组、结构体或其他映射类型等引用类型。

如何使用 key 存取 value

如果你想通过 key 来存取对应 value , 可以使用 [] 操作符。

使用 [] 操作符

新增一个键值对

bash 复制代码
airDrop[0xaaaaa...] = 100;

通过 key 获取 value

bash 复制代码
unit amount = airDrop[0xbbbb...];

映射类型只能声明在 storage

bash 复制代码
mapping(address => uint) memory myMap; _// 编译错误_

映射类型可以与数组,结构体互相嵌套

bash 复制代码
struct Book {
    uint isbn;
    string title; _// 书名_
    uint price;   _// 价格_
}

mapping(uint => Book) lib; _// 从 ISBN 到 Book 的映射关系_
相关推荐
皮皮学姐分享-ppx2 小时前
中国绿色制造企业数据(绿色工厂|绿色供应链|绿色园区|绿色产品,2017-2023)
大数据·人工智能·经验分享·科技·区块链·制造
小明的小名叫小明2 小时前
Solidity入门(4)-合约及其组成结构
区块链·solidity
Yunpiere2 小时前
Web3:互联网的“去中心化”革命
web3·去中心化·区块链
友莘居士2 小时前
Solidity高阶函数:函数参数的实战应用
区块链·solidity·高阶函数·函数参数
友莘居士5 小时前
Solidity的delete运算符详解
区块链·solidity·以太坊·delete运算符
Web3VentureView7 小时前
特朗普回归到全球金融震荡:链上制度正成为新的稳压器
大数据·金融·web3·去中心化·区块链
区块链小八歌17 小时前
从电商收入到链上资产:Liquid Royalty在 Berachain 重塑 RWA 想象力
大数据·人工智能·区块链
YSGZJJ21 小时前
股指期货的基本概念是什么?
区块链