【区块链安全 | 第十五篇】类型之值类型(二)

在这里插入图片描述

值类型

有理数和整数字面量(Rational and Integer Literals)

整数字面量由一串数字(范围为 0-9)组成,并按十进制解析。例如,69 表示六十九。Solidity 中没有八进制字面量,且前导零无效。

十进制小数字面量由一个小数点(.)和至少一个小数点后的数字组成。例如,.1 和 1.3 是有效的(但 1. 是无效的)。

Solidity 也支持科学记数法格式(如 2e10),其中尾数(mantissa)可以是小数,但指数必须是整数。字面量 MeE 等价于 M * 10**E。例如,2e10、-2e10、2e-10 和 2.5e1 都是有效的。

我们可以使用下划线分隔数字字面量中的数字,以提高可读性。例如,十进制的 123_000、十六进制的 0x2eff_abde、科学记数法表示的 1_2e345_678 都是有效的。下划线只能放在两个数字之间,且只能使用一个连续的下划线。数字字面量中的下划线不会改变字面量的语义,它们仅用于增强可读性。

数字字面量表达式具有任意精度,直到它们被转换为非字面量类型(即与其他非字面量表达式一起使用,或通过显式转换)。这意味着数字字面量表达式中的计算不会溢出,除法操作也不会截断。

例如,(2^800 + 1) - 2^800 的结果是常量 1(类型为 uint8),尽管中间结果无法完全适应机器字长。此外,.5 * 8 结果是整数 4(尽管其中使用了非整数)。

注意:
虽然大多数运算符在应用于字面量时会产生字面量表达式,但有些运算符不遵循这一模式:

  • 三元运算符(… ? … : …)

  • 数组下标(<array>[<index>]

例如,你可能期待像 255 + (true ? 1 : 0) 或 255 + [1, 2, 3][0] 这样的表达式等价于直接使用字面量 256,但实际上它们会在类型 uint8 范围内计算,可能会发生溢出。

如图:

在这里插入图片描述

任何可以应用于整数的运算符也可以应用于数字字面量表达式,只要操作数是整数。如果其中任何一个是小数,则不允许使用位运算,并且如果指数是小数,则不能使用指数运算(因为这可能导致非有理数结果)。

对于字面量数字作为左操作数(或基数)与整数类型作为右操作数(指数)的移位和指数运算,Solidity 总是使用 uint256(对于非负字面量)或 int256(对于负字面量)类型,无论右操作数(指数)的类型是什么。

在 Solidity 0.4.0 版本之前,整数字面量的除法会进行截断,但现在会转换为有理数类型,即 5 / 2 不等于 2,而是 2.5。

Solidity 为每个有理数提供了一个数字字面量类型。整数字面量和有理数字面量属于数字字面量类型。此外,所有数字字面量表达式(即仅包含数字字面量和运算符的表达式)都属于数字字面量类型。所以表达式 1 + 2 和 2 + 1 都属于同一个有理数类型。

数字字面量表达式一旦与非字面量表达式一起使用,就会被转换为非字面量类型。

在以下代码中,分配给 b 的表达式会评估为一个整数:

uint128 a = 1;
uint128 b = 2.5 + a + 0.5;

a 的类型是 uint128,由于 2.5 和 uint128 之间没有共同的类型,Solidity 编译器会拒绝这段代码。

如图所示:

在这里插入图片描述

字符串字面量和类型(String Literals and Types)

字符串字面量可以使用双引号或单引号表示(例如 “foo” 或 ‘bar’),并且它们可以分割成多个连续的部分(例如 “foo” “bar” 等同于 “foobar”),这在处理长字符串时非常有用。与 C 语言不同,字符串字面量不表示以零结尾;例如,“foo” 只表示三个字节,而不是四个字节。与整数字面量类似,字符串字面量的类型也可以变化,但它们可以隐式转换为 bytes1 到 bytes32 类型,bytes 和 string 类型。

例如,bytes32 samevar = “stringliteral” 中,字符串字面量会在赋值给 bytes32 类型时以原始字节的形式进行解释。

字符串字面量只能包含可打印的 ASCII 字符,这意味着它们只能包含从 0x20 到 0x7E 范围的字符。

此外,字符串字面量还支持以下转义字符:

  • \<newline>(转义实际的换行符)

  • \\(反斜杠)

  • \'(单引号)

  • \"(双引号)

  • \n(换行符)

  • \r(回车符)

  • \t(制表符)

  • \xNN(十六进制转义,参见下面)

  • \uNNNN(Unicode 转义,参见下面)

xNN 使用十六进制值并插入相应的字节,而 \uNNNN 使用 Unicode 代码点并插入 UTF-8 编码序列。

注意:
在 Solidity 0.8.0 之前,还有三个额外的转义序列:\b、\f 和 \v。这些转义符在其他编程语言中常见,但在实际应用中不常用。如果需要使用这些转义符,可以通过十六进制转义插入,即 \x08、\x0c 和 \x0b,就像插入其他 ASCII 字符一样。

Unicode 字面量(Unicode Literals)

普通的字符串字面量只能包含 ASCII 字符,而 Unicode 字面量(以 unicode 关键字为前缀)可以包含任何有效的 UTF-8 序列。它们同样支持与普通字符串字面量相同的转义序列。

例如:

string memory a = unicode"Hello 😃";

十六进制字面量(Hexadecimal Literals)

十六进制字面量以 hex 关键字为前缀,并被双引号或单引号包围(例如 hex"001122FF" 或 hex’0011_22_FF’)。它们的内容必须是十六进制数字,并且可以选择性地使用单个下划线作为字节边界的分隔符。字面量的值将是十六进制序列的二进制表示。

多个由空格分隔的十六进制字面量会被连接成一个字面量。例如,hex"00112233" hex"44556677" 等同于 hex"0011223344556677"。

十六进制字面量与字符串字面量类似,但它们不能隐式转换为字符串类型。

示例代码如下:

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

contract HexLiteralExample {
    // 使用十六进制字面量定义字节数据
    bytes constant hexData1 = hex"001122FF";
    bytes constant hexData2 = hex"4455_66_77"; // 可使用下划线分隔字节

    // 拼接两个十六进制字面量(会在编译时合并)
    bytes constant concatenated = hex"00112233" hex"44556677";

    function getHexData1() external pure returns (bytes memory) {
        return hexData1;
    }

    function getHexData2() external pure returns (bytes memory) {
        return hexData2;
    }

    function getConcatenated() external pure returns (bytes memory) {
        return concatenated;
    }
}

在这里插入图片描述

在这里插入图片描述

枚举(Enums)

枚举是 Solidity 中创建用户定义类型的一种方式。它们可以显式地转换为任何整数类型,但不允许隐式转换。整数到枚举的显式转换会在运行时检查值是否在枚举的范围内,如果不在该范围内则会触发 Panic 错误。枚举至少需要一个成员,且声明时的默认值是第一个成员。枚举的成员数量不能超过 256 个。

枚举的数据表示与 C 语言中的枚举类似:选项由从 0 开始的连续无符号整数值表示。

通过使用 type(NameOfEnum).min 和 type(NameOfEnum).max,可以获得给定枚举的最小值和最大值。

例如:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

contract test {
    // 定义枚举类型 ActionChoices,表示四个动作选项
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    
    // 创建一个 ActionChoices 类型的变量 choice 来存储当前选择
    ActionChoices choice;
    
    // 声明一个常量 defaultChoice,默认为 GoStraight
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;

    // public 函数 setGoStraight 用于将 choice 设置为 GoStraight
    function setGoStraight() public {
        choice = ActionChoices.GoStraight;
    }

    // public 函数 getChoice 返回当前的 choice,注意:枚举类型在外部函数中会转换为 uint8 类型
    // 由于枚举类型不是 ABI 的一部分,所以 "getChoice" 的签名会变成 "getChoice() returns (uint8)"
    function getChoice() public view returns (ActionChoices) {
        return choice;
    }

    // public 函数 getDefaultChoice 返回默认值 GoStraight 对应的整数值
    // 返回的是 uint 类型的数字,表示 GoStraight 在枚举中的整数值
    function getDefaultChoice() public pure returns (uint) {
        return uint(defaultChoice);
    }

    // public 函数 getLargestValue 返回枚举类型中最大的值
    function getLargestValue() public pure returns (ActionChoices) {
        return type(ActionChoices).max;  // 返回最大枚举值
    }

    // public 函数 getSmallestValue 返回枚举类型中最小的值
    function getSmallestValue() public pure returns (ActionChoices) {
        return type(ActionChoices).min;  // 返回最小枚举值
    }
}

枚举也可以在文件级别声明,而不需要在合约或库定义中。

用户定义值类型

用户定义值类型(User-Defined Value Types)允许开发者在不引入额外运行时开销的前提下,对基础类型(如 uint256、address 等)进行类型封装,实现更强的类型安全。这种机制提供了类似类型别名的功能,但在类型系统中更加严格。

用户定义值类型的语法为:

type C is V;

其中 C 是新定义的类型名,V 是必须来自内建的值类型(即基础类型)。

可以使用以下函数进行类型转换:

  • C.wrap(value):将基础类型值转换为用户定义类型。

  • C.unwrap(value):将用户定义类型值转换为基础类型。

需要注意的是:

  • 用户定义的值类型不继承基础类型的任何操作符或成员函数。

  • 运算符(例如 ==)是未定义的,必须显式调用 unwrap 后再进行比较或其他操作。

  • 不允许任何隐式或显式的类型转换,除非通过 wrap / unwrap。

这些类型的内部表示与其基础类型一致,并且在 ABI 编码/解码时使用基础类型的方式。

示例合约代码如下:

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

// 定义一个用户自定义类型 U256,基于 uint256
type U256 is uint256;

contract UDVTExample {
    // 定义一个变量,类型为 U256
    U256 public myValue;

    // 设置值时需使用 wrap
    function setValue(uint256 _value) public {
        myValue = U256.wrap(_value);
    }

    // 获取值时需使用 unwrap
    function getValue() public view returns (uint256) {
        return U256.unwrap(myValue);
    }

    // 示例函数,使用 unwrap 进行比较
    function isEqual(uint256 _value) public view returns (bool) {
        return U256.unwrap(myValue) == _value;
    }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋说

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值