当前位置: 首页 > article >正文

Solidity04 Solidity值类型

文章目录

  • 一、布尔型
    • 1.1 运算符
    • 1.2 短路规则
    • 1.3 小结
  • 二、 整型
    • 2.1 运算符
      • 2.1.1 算术运算符
      • 2.1.2 比较运算符
    • 2.2 整型溢出
    • 2.3 小结
  • 三、地址类型
    • 3.1 定义地址类型变量
    • 3.2 为什么要区分 address 和 address payable
    • 3.3 类型转换
    • 3.4 成员变量
    • 3.5 成员函数
    • 3.6 小结
  • 四、定长字节数组
    • 4.1 定长字节数组是值类型
    • 4.2 比较运算符
    • 4.3 下标访问
    • 4.4 小结
  • 五、 枚举 enum
    • 5.1 定义枚举类型
    • 5.2 为什么使用枚举类型
    • 5.3 访问枚举值
    • 5.4 枚举类型的最大最小值
    • 5.5 枚举类型与整型的互相转换
    • 5.6 枚举类型作为函数参数或返回值
  • 六、完整代码示例

Solidity中的变量类型

  1. 值类型(Value Type):包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
  2. 引用类型(Reference Type):包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
  3. 映射类型(Mapping Type): Solidity中存储键值对的数据结构,可以理解为哈希表

我们将仅介绍常用类型,不常用的类型不会涉及,本篇将介绍值类型。

一、布尔型

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

// 布尔值
bool public _bool = true;

1.1 运算符

布尔值的运算符包括:

  • ! (逻辑非)
  • && (逻辑与,“and”)
  • || (逻辑或,“or”)
  • == (等于)
  • != (不等于)
// 布尔运算
bool public _bool1 = !_bool; // 取非
bool public _bool2 = _bool && _bool1; // 与
bool public _bool3 = _bool || _bool1; // 或
bool public _bool4 = _bool == _bool1; // 相等
bool public _bool5 = _bool != _bool1; // 不相等

在上述代码中:

变量 _bool 的取值是 true

_bool1_bool 的非,为 false

_bool && _bool1false

_bool || _bool1true

_bool == _bool1false

_bool != _bool1true

注意:运算操作后得到的结果依然是布尔类型。

1.2 短路规则

需要注意的是 ||&& 运算符 都服从短路规则。其规则如下:

  • f(x)||g(y),如果f(x)true,则g(y)不会再执行
  • f(x)&&g(y),如果f(x)fasle,则g(y)不会再执行

&&和||运算子的短路规则

下面的示例中有两个函数 isEven 判断一个数是否是偶数, isZero 判断一个数是否是0。其中 isZero 函数会有副作用,如果传入的参数为0,那么会使得 zeroCount 加一。

// SPDX-License-Identifier: MIT

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;
    }
}




// SPDX-License-Identifier: GPL-3.0
//这行指令指定该智能合约使用 Solidity 编译器版本 0.8.17 或更高版本,但不会使用 0.9.0 及之后版本。^ 符号表示兼容版本范围,从 0.8.17 开始的所有兼容版本(例如 0.8.18, 0.8.19 等)都可以使用。
pragma solidity ^0.8.17;


//声明了一个名为 BoolShortCircuit 的智能合约。合约是 Solidity 中代码和状态的封装,拥有状态变量、函数等。
contract BoolShortCircuit {

//声明了一个公开的 uint256 类型的状态变量 zeroCount,并将其初始化为 0。它用于记录 isZero 函数被调用时,传入参数为零的次数。
//public: 表示该变量是公开的,可以通过自动生成的 getter 函数来访问其值。
//uint256: 是 Solidity 中的一个整数类型,表示一个 256 位无符号整数。
    uint256 public zeroCount = 0;


//该函数通过检查 num % 2 == 0 来判断数字是否为偶数。如果是偶数,返回 true,否则返回 false。
//声明了一个私有的、返回布尔值 (bool) 的函数 isEven。它的作用是检查传入的数字 num 是否为偶数。
//private: 表示该函数只能在当前合约内部调用,不能从外部合约或外部用户直接调用。
//pure: 表示该函数没有读取或修改合约的状态(即没有访问区块链数据)。pure 函数是完全自给自足的,它的输出仅依赖于输入参数,而不依赖于状态变量。
//returns(bool): 函数返回一个布尔值。
    function isEven(uint256 num) private pure returns(bool) { 
        return num%2 == 0;
    }

//声明了一个私有的、返回布尔值的函数 isZero。它的作用是检查传入的数字 num 是否为零。
//private: 表示该函数只能在当前合约内部调用。
//returns(bool): 函数返回一个布尔值。

    // isZero函数有副作用,会改变状态变量zeroCount的值
    function isZero(uint256 num) private returns(bool) {
    
//在 isZero 函数中,当 num 等于零时,zeroCount 变量会增加 1。这是函数的副作用,因为它改变了合约的状态。具体地说,每当有零传递给 isZero 函数时,zeroCount 会增加 1,记录零的出现次数。
        if(num == 0) {
            zeroCount += 1; // 函数副作用,会改变zeroCount的值
        }
//返回 num == 0 的布尔值。如果 num 为零,返回 true,否则返回 false。
        return num == 0;
    }
}
  • 下面的 && 表达式中, isEven(1) 为false,所以,有副作用的函数 isZero(0) 不会执行。因此,不会影响到zeroCount的值。
isEven(1) && isZero(0) //逻辑与
  • 下面的 && 表达式中, isEven(2) 为true,所以会继续执行副作用函数 isZero(0) ,于是 zeroCount 会增加1。
isEven(2) && isZero(0) //逻辑与
  • 下面的 || 表达式中, isEven(2) 为true,所以,有副作用的函数 isZero(0) 不会执行。因此,不会影响到 zeroCount 的值。
isEven(2) || isZero(0) //逻辑或
  • 下面的 || 表达式中, isEven(1) 为false,所以会继续执行副作用函数 isZero(0) ,于是 zeroCount 会增加1。
isEven(1) || isZero(0) //逻辑或

1.3 小结

  1. 布尔类型是用 bool 关键字定义的,只有 truefalse 两种取值

  2. 可以使用的运算符有:逻辑非(!), 逻辑与(&&), 逻辑或(||), 等于(==), 不等于(!=)

  3. 逻辑或(||)和逻辑与(&&)都遵循短路规则,即在满足左边条件时不会再执行右边的操作

    所谓“短路规则”,一般出现在逻辑与(&&)和逻辑或(||)中。 当逻辑与(&&)的第一个条件为false时,就不会再去判断第二个条件; 当逻辑或(||)的第一个条件为true时,就不会再去判断第二个条件,这就是短路规则。

二、 整型

整型(integer)是不包含小数部分的数值型数据,正整数,负整数和0等都属于整型的范畴。比如账户余额,Gas,持有的 Token 数等都是用整型表示。Solidity 的整型有两种:

  • intM 有符号整型
  • uintM 无符号整型

其中 M 为8到256,步长为8。例如有 int8, int16, int32 … int256 。也有 uint8, uint16, uint32 … uint256 等等。其中, int8/uint8 占8bits, int16/uint16 占16bits,以此类推。

2.1 运算符

2.1.1 算术运算符

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

  • +(加)
  • -(减)
  • *(乘)
  • /(除)
  • %(取模)
  • **(幂)
  • <<(左移)
  • >>(右移)
假设a=5 , b=2  类型均为uint8

a+b  : 7
a-b  : 3
a*b  : 10
a/b  : 2
a%b  : 1
a**b : 25
a<<b : 20
a>>b : 1

2.1.2 比较运算符

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

  • <=(小于等于)
  • < (小于)
  • ==(等于)
  • !=(不等于)
  • >=(大于等于)
  • > (大于)
假设a=5 , b=2  类型均为uint8

a<=b : false
a<b  : false
a==b : false
a!=b : true
a>=b : true
a>b  : true

2.2 整型溢出

Solidity 整型运算结果可能出现溢出的情况,导致合约运行出现不符合预期的行为。在旧版本的 Solidity 中你可能会使用Openzeppelin 的 SafeMath 来防止整型溢出。不过在版本 >= 0.8.0 的 Solidity,一旦出现整型溢出,整个交易都会被终止。所以 Solidity 在整型溢出方面是安全的。(当然,前提是你使用了版本 >= 0.8.0 的 Solidity )

如下面的例子所示, uint8 能够储存的最大正整数为255,如果再加1,那么就会产生整型溢出。如果你执行下面的函数,Remix Terminal会输出: call to ExampleContract.overflow errored: Error occurred: revert. 。这意味着整个 transaction 已经被 revert。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17; 
import "hardhat/console.sol";

contract ExampleContract {
    function overflow() public pure {
        uint8 a = 255;
        a = a + 1; // 无溢出问题,因为在 Solidity 0.8.x 中,溢出会自动回滚
        console.log("a=%s", a);
    }
}

我们这里说的整型溢出是安全的,说的是一旦发生整型溢出,这个交易都会被终止,就好像什么事没发生一样。但是这并不意味着你的合约没有问题,需要注意其他的潜在风险。比如说你在遍历数组的时候, uint 类型的下标不小心变成了-1。这可能会导致你的合约拒绝服务(denial of service)。

2.3 小结

  1. Solidity有两种整型:有符号整型(intM)和无符号整型(uintM),M的取值范围为8到256,步长为8
  2. Solidity 的整型运算可能会出现溢出的情况
  3. 在版本 <0.8.0 的 Solidity 中,可以使用 Openzeppelin 的 SafeMath 库来防止整型溢出
  4. 在版本 >=0.8.0 的 Solidity 中,如果整型溢出,交易会被终止
  5. 虽然 Solidity 在整型溢出方面是安全的,但是这并不意味着你的合约没有问题,需要注意其他的潜在风险

三、地址类型

地址类型(address)是 Solidity 独有的一种类型。它被用来存放账户地址。在给其他账户转账,或者与其他合约交互的时候都需要使用到地址类型。好比如你要向别人的银行账户转账时需要知道对方的账户号码一样,Solidity的地址类型也扮演着类似的角色。

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

  • address : 普通地址类型(不可接收转账)
  • address payable : 可收款地址类型(可接收转账),比普通地址多了 transfersend 两个成员方法,用于接收转账。

这两种地址类型的主要的区别在于 address payable 能接受转账,而 address 不能。它有助于强制智能合约开发人员认真考虑一个地址是否应当接收以太币,如果该地址根本不需要接收以太币,那么就应当使用address类型。当地址被声明为address类型后,如果开发者试图向该地址转入以太币,就会导致编译时类型错误。

3.1 定义地址类型变量

address addr = 0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990;
address payable addr_pay = payable(0x8306300ffd616049FD7e4b0354a64Da835c1A81C);

上面我们定义了两个地址类型变量:addraddr_pay。并且直接将它们初始化为一个地址常量(也称地址字面值)。在 Solidity 中使用「地址字面值」初始化「地址类型变量」的时候不需要加 "" 或者 ’’

注意到在定义 addr_pay 的时候我们使用了一个 payable() 的函数,这是用来将地址字面值显式转换成 address payable 类型的。下面会有个小节讨论地址类型的转换。

3.2 为什么要区分 address 和 address payable

到现在为止,我们已经认识了地址类型的基本作用就是用来存放账户地址的。但是始终有个问题萦绕在我们心中:addressaddress payable 看起来差不不大,为何要对它们进行区分?直接统一使用 address 类型,想要转账就转账,不想转账就不转不就可以了吗?

Solidity 之所以要进行这样的区分是为了提高合约安全性。避免转账进某些合约后转不出来了,永远锁住在里面。

在此之前,我们要先了解 Solidity 的账户是有两种的:外部账户(external owned address),简写为 EOA;以及合约账户(contract address),简写为 CA。其中 EOA 就是我们平常在 MetaMask 上创建的那些账户。而合约账户则是在部署合约完成后返回的合约账户地址。

具体信息可查看:https://blog.csdn.net/cljdsc/article/details/144671253?spm=1001.2014.3001.5501

当我们把 Ether 转进 EOA 账户后,只要我们控制了 EOA 的私钥,我们就可以把 Ether 再转出来。而在 CA 账户情况则大为不同。CA 账户是由合约控制的,合约只能执行它定义过的操作。所以我们必须要在合约实现一个函数,定义好如何将账户下的 Ether 转出才行。否则这些 Ether 会被永远锁住在 CA 账户里面。

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

3.3 类型转换

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

  1. address payable 可以隐式地被转换成 address
  2. address 需要显式地使用 payable(addr) 函数转换成 address payable

在这里插入图片描述

隐式类型转换(address payable to address)

address payable addr_pay = payable(0x8306300ffd616049FD7e4b0354a64Da835c1A81C);
address addr = addr_pay; // **隐式类型转换**

显式类型转换(address to address payable)

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

3.4 成员变量

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

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

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

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值
}

3.5 成员函数

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

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

    函数签名

    transfer(uint256 amount)
    

    transfer 函数可以向目标地址转账。可以指定转账数量为 amount,单位为 Wei 。如果发送失败,直接 revert。执行 transaction 的 Gas 固定为2300。

    注意 transfer 函数仅 address payable 可以使用。

    在这里插入图片描述

  2. send

    函数签名

    send(uint256 amount) returns (bool)
    

    send 函数可以向目标地址转账。可以指定转账数量为 amount,单位为 Wei 。如果发送失败,返回 false 。注意到 sendtransfer 的区别是 send 返回 false ,而 transfer 直接 revert 。执行 transaction 的 Gas 同样固定为2300。

    注意 send 函数仅 address payable 可以使用。

    在这里插入图片描述

  3. transfer和send应该使用哪一个

    transfersend 都可以用来向另一个地址转账,那么我们应该选择用哪一个呢?答案是没有非常必要的理由,一律选 transfer 。因为 transfersend 的改进版,目的是在转账不成功的时候直接终止交易。而使用 send 时,你需要检查返回值看是否成功再决定做后续处理。有人可能忘记检查是否成功就进行下一步处理,导致合约有被攻击的风险。

  4. call

    函数签名

    call(bytes memory) returns (bool, bytes memory)
    

    使用 call 函数你可以与合约地址进行交互,调用其函数,或者直接向其转账。它有两个返回值,第一个是 bool ,显示函数调用是否成功。第二个是 bytes memory ,这个返回值是调用对方合约所返回的结果。与 sendtransfer 所不同的是, call 函数可以指定Gas。使用 call 函数,我们也可以给其他地址转账。

在这里插入图片描述

  1. delegatecall

函数签名

delegatecall(bytes memory) returns (bool, bytes memory)

delegatecall 是实现代理模式的手段。通过使用 delegatecall 你可以让当前合约只使用给定地址的代码,而使用当前合约的存储(如状态变量,账户余额等)。Delegate这个词就有代表,委派的意思。例如A合约委派B合约做一些操作,B合约只是代劳这些操作,操作的最后状态变更都需要记录到A合约上面。基于 delegatecall 的这种特性,Openzeppelin提出了代理模式,让你可以升级你的合约。详情我们会在 delegatecall 的单独章节展开。

在这里插入图片描述

  1. staticcall

staticcallcall 非常类似。它们的唯一区别就是 staticcall 不会改变合约的状态(包括当前合约和外部合约),一旦在调用的过程中改变了合约的状态(包括状态变量变更,账户余额改变等),那么会直接终止交易。引入 staticcall 提高了合约的安全性,因为只要你使用了 staticcall,你就可以肯定调用任何外部合约的函数不会对状态产生任何影响。而在引入 stacticall 之前,这是要通过阅读外部合约的代码来确保的。

在这里插入图片描述

3.6 小结

  1. Solidity 中的地址类型是用于转账和与其他合约交互的
  2. 地址类型用 address 表示,占据20bytes (160bits)。默认值为0x0
  3. 地址类型有两种:普通地址类型和可收款地址类型
  4. 可收款地址类型可以接受转账,而普通地址类型不能
  5. 可以使用 payable() 函数将地址字面值显式转换为可收款地址类型
  6. balance:可以获取地址余额
  7. transfer():可以向指定地址转账
  8. send():与 transfer() 函数类似,但是如果转账失败会抛出异常
  9. call():可以调用其他合约中的函数
  10. delegatecall():与 call() 函数类似,但是使用当前合约的上下文来调用其他合约中的函数
  11. staticcall(): 与 call() 函数类似,但是不会允许有改变状态变量的操作
  12. transfer()send() 函数只能在 address payable 类型中使用

四、定长字节数组

字节数组分为定长和不定长两种:

  • 定长字节数组: 属于值类型,数组长度在声明之后不能改变。根据字节数组的长度分为 bytes1, bytes8, bytes32 等类型。定长字节数组最多存储 32 bytes 数据,即bytes32
  • 不定长字节数组: 属于引用类型(之后的章节介绍),数组长度在声明之后可以改变,包括 bytes 等。
// 固定长度的字节数组
bytes32 public _byte32 = "MiniSolidity"; 
bytes1 public _byte = _byte32[0]; 

在上述代码中,字符串 MiniSolidity 以字节的方式存储进变量 _byte32。如果把它转换成 16 进制,就是:0x4d696e69536f6c69646974790000000000000000000000000000000000000000

_byte 变量的值为 _byte32 的第一个字节,即 0x4d

定长字节数组(fixed-size byte arrays)是 Solidity 独有的一种数据类型。与字面上的意思一样,它是固定长度的字节数组。基于静态字节数组,你可以构建出更紧凑,更节省存储空间的数据类型。

Solidity一共有32种静态字节数组: bytes1 , bytes2 , bytes3 , …, bytes32。一个更好的理解方式是把这32个静态字节数组理解为32种不同的值类型。而不是把它们理解为通常意义上的数组。这32种静态字节数组就好像是32种大小不一的乐高积木一样。通过将它们嵌入到「结构体」里面进行排列组合我们可以构建出新的数据类型。

4.1 定长字节数组是值类型

与你预想不同的是,定长字节数组属于值类型(value type),而不是引用类型。也就是说其变量所储存的是值,而不是数据的地址。基于这个设计,当我们要获取定长字节数组的数据的时候,不需要先获取地址然后再去获取数据。对于 size 比较小的数据,将其存储在定长字节数组会更高效,更节省存储空间。

4.2 比较运算符

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

  • <=(小于等于)
  • < (小于)
  • ==(等于)
  • !=(不等于)
  • >=(大于等于)
  • > (大于)
假设a=0x61 ('a'字符的ascii) , b=0x62 ('b'字符的ascii)  

a<=b : true
a<b  : true
a==b : false
a!=b : true
a>=b : false
a>b  : false

注:ascii值也就是 ASCII码

4.3 下标访问

我们可以像 Javascript 一样使用 [] 运算符来通过下标访问静态字节数组的某个元素。不过要注意避免越界访问。

bytes3 s = 'abc';
bytes1 ch = s[1]; // ch的值为0x62,也就是'b'的ascii值

4.4 小结

  1. 静态字节数组是固定长度的字节数组,是值类型,变量所储存的是值而不是数据的地址。
  2. Solidity一共有32种静态字节数组,如 bytes1, bytes2, bytes3, …, bytes32
  3. 比较运算符可以比较两个变量的数量大小关系,以及变量是否相等,比较运算符得到的结果是布尔值。
  4. 可以使用 [] 运算符来通过下标访问静态字节数组的某个元素,但要注意不要越界访问。

五、 枚举 enum

枚举(enum)是 Solidity 中用户定义的数据类型。它主要用于为 uint 分配名称,使程序易于阅读和维护。它与 C 语言 中的 enum 类似,使用名称来代替从 0 开始的 uint

// 用enum将uint 0, 1, 2表示为Buy, Hold, Sell
enum ActionSet { Buy, Hold, Sell }
// 创建enum变量 action
ActionSet action = ActionSet.Buy;

枚举可以显式地和 uint 相互转换,并会检查转换的无符号整数是否在枚举的长度内,否则会报错:

// enum可以和uint显式的转换
function enumToUint() external view returns(uint){
    return uint(action);
}

enum 是一个比较冷门的数据类型,几乎没什么人用。

枚举是组织收集有关联变量的一种方式。我们将变量可能的取值一一列举出来,收集在一起,然后取个统一的名字,这便定义了一个枚举类型。例如当你在开发一个链游的时候,需要根据键盘输入上下左右来控制游戏角色的动作。这时候你就可以定义一个 ActionChoices 类型,它有上下左右四个有限取值。

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

5.1 定义枚举类型

枚举类型的定义方式如下所示:

enum ActionChoices { 
    GoLeft,     // 底层表示为 0 
    GoRight,    // 底层表示为 1
    GoUp,       // 底层表示为 2
    GoDown      // 底层表示为 3
}

5.2 为什么使用枚举类型

使用枚举类型可以提高代码的类型安全性和可读性。因为它的变量可能取值只有有限的四个,一旦你赋予了其他值,合约在编译期就会报错。

例如,如果你用 uint8 类型来代表上下左右的动作,那么有可能你误传了 999 进去,合约可能会出现无法预料的后果。编译期是无法发现这样的 Bug 的。而使用枚举类型就不会遇到这样的问题。

提高代码可读性是显然易见的, ActionChoices 肯定比 uint8 更容易理解其变量所保存内容的含义。

5.3 访问枚举值

我们可以通过 . 操作符来访问枚举类型的某个枚举值。例如:

ActionChoices choice = ActionChoices.GoLeft;

5.4 枚举类型的最大最小值

枚举类型是一种特殊的整型,所以你可以获取枚举类型的最大最小值:

  • type(NameOfEnum).max 枚举类型的最大值
  • type(NameOfEnum).min 枚举类型的最小值
type(ActionChoices).max; // ActionChoices.GoDown ,也就是3
type(ActionChoices).min; // ActionChoices.GoLeft , 也就是0

5.5 枚举类型与整型的互相转换

枚举类型可以和任何整型进行互相转换:

  • uint(MyEnum) 将枚举类型转换成uint

  • MyEnum(myUint)uint转换成枚举类型

    在这里插入图片描述
    枚举类型与整型的互相转换

function enumToUint(ActionChoices c) public pure returns(uint) {
    return uint(c);
}

function uintToEnum(uint i) public pure returns(ActionChoices) {
    return ActionChoices(i);
}

5.6 枚举类型作为函数参数或返回值

如果枚举类型仅在当前合约定义,那么外部合约在调用当前合约的时候它获取得到的枚举类型返回值应该是怎么样的?答案是枚举类型会被编译器自动转换成 uint8 类型。所以外部合约看到的枚举类型是 uint8 类型。这是因为 ABI 中没有枚举类型,只有整型,所以编译器会自动执行这样的转换。

如下面的示例所示,getChoice 函数的返回值是ActionChoices类型。编译器会自动把返回值更改为 uint8 类型。所以外部合约看到的返回值类型是 uint8 类型。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Enum {
    enum ActionChoices { 
        GoLeft,     // 0
        GoRight,    // 1
        GoUp,       // 2
        GoDown      // 3
    }

    ActionChoices choice;

    // 因为ABI中没有枚举类型,所以这里的"getChoice() returns(ActionChoices)"函数签名
    // 会被自动转换成"getChoice() returns(uint8)"
    function getChoice() public view returns (ActionChoices) {
        return choice;
    }
}

注:枚举类型作为函数参数或返回值时自动被转换成uint8

enum 是一个比较冷门的数据类型,几乎没什么人用。

六、完整代码示例

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
contract ValueTypes{
    // 布尔值
    bool public _bool = true;
    // 布尔运算
    bool public _bool1 = !_bool; //取非
    bool public _bool2 = _bool && _bool1; //与
    bool public _bool3 = _bool || _bool1; //或
    bool public _bool4 = _bool == _bool1; //相等
    bool public _bool5 = _bool != _bool1; //不相等


    // 整数
    int public _int = -1;
    uint public _uint = 1;
    uint256 public _number = 20220330;
    // 整数运算
    uint256 public _number1 = _number + 1; // +,-,*,/
    uint256 public _number2 = 2**2; // 指数
    uint256 public _number3 = 7 % 2; // 取余数
    bool public _numberbool = _number2 > _number3; // 比大小


    // 地址
    address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
    address payable public _address1 = payable(_address); // payable address,可以转账、查余额
    // 地址类型的成员
    uint256 public balance = _address1.balance; // balance of address
    
    
    // 固定长度的字节数组
    bytes32 public _byte32 = "MiniSolidity"; // bytes32: 0x4d696e69536f6c69646974790000000000000000000000000000000000000000
    bytes1 public _byte = _byte32[0]; // bytes1: 0x4d
    
    
    // Enum
    // 将uint 0, 1, 2表示为Buy, Hold, Sell
    enum ActionSet { Buy, Hold, Sell }
    // 创建enum变量 action
    ActionSet action = ActionSet.Buy;

    // enum可以和uint显式的转换
    function enumToUint() external view returns(uint){
        return uint(action);
    }
}

http://www.kler.cn/a/510734.html

相关文章:

  • OpenHarmony-7.IDL工具
  • Visual Studio Code + Stm32 (IAR)
  • 2025年1月17日(点亮三色LED)
  • Vue.js 动态设置表格最大高度的实现
  • 基于springboot的口腔管理平台
  • springboot医院信管系统
  • LLMs之Dataset:中文互联网基础语料2.0的简介、下载和使用方法、案例应用之详细攻略
  • 【2024年华为OD机试】 (B卷,100分)- 字符串分割(Java JS PythonC/C++)
  • 【服务器】Ubuntu22.04配置静态ip
  • 【论文阅读】End-to-End Adversarial-Attention Network for Multi-Modal Clustering
  • 第13章:Python TDD完善货币加法运算(二)
  • 【MyDB】3-DataManager数据管理 之 4-数据页缓存
  • 综述:大语言模型在机器人导航中的最新进展!
  • 【机器学习】机器学习引领数学难题攻克:迈向未知数学领域的新突破
  • YOLOv9改进,YOLOv9检测头融合,适合目标检测、分割任务
  • 第6章:Python TDD实例变量私有化探索
  • 推荐一个开源的轻量级任务调度器!TaskScheduler!
  • 基于单片机的智能家居控制系统设计及应用
  • 利用R计算一般配合力(GCA)和特殊配合力(SCA)
  • Go Map 源码分析(一)
  • Windows蓝牙驱动开发-蓝牙 IOCTL
  • “AI 辅助决策系统:决策路上的智慧领航员
  • epoll函数为何是高效率的
  • 专业数据分析不止于Tableau,四款小众报表工具解析
  • re:Invent 2024 - CEO 主题演讲与 Matt Garman (前半)
  • PyTorch使用教程(9)-使用profiler进行模型性能分析