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

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

文章目录

  • 值类型
    • 有理数和整数字面量(Rational and Integer Literals)
    • 字符串字面量和类型(String Literals and Types)
    • Unicode 字面量(Unicode Literals)
    • 十六进制字面量(Hexadecimal Literals)
    • 枚举(Enums)
    • 用户定义值类型

在这里插入图片描述

值类型

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

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

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

科学记数法格式(如 2e10)也被支持,其中尾数(mantissa)可以是小数,但指数必须是整数。字面量 MeE 等价于 M * 10**E。例如,2e10-2e102e-102.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 内计算,可能会发生溢出。

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

对于字面量数字作为左操作数(或基数)和整数类型作为右操作数(指数)的移位和指数运算,总是使用 uint256(对于非负字面量)或 int256(对于负字面量)类型,而不管右操作数(指数)的类型。

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

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

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

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

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

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

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

字符串字面量可以使用双引号或单引号表示(例如 "foo"'bar'),并且它们可以分割成多个连续的部分(例如 "foo" "bar" 等同于 "foobar"),这在处理长字符串时非常有用。与 C 语言不同,字符串字面量并不表示以零结尾;例如 "foo" 只表示三个字节,而不是四个字节。与整数字面量一样,字符串字面量的类型可以变化,但如果符合要求,它们可以隐式转换为 bytes1bytes32 类型,bytesstring 类型。

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

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

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

  • \<newline>(转义实际的换行符)
  • \\(反斜杠)
  • \'(单引号)
  • \"(双引号)
  • \n(换行符)
  • \r(回车符)
  • \t(制表符)
  • \xNN(十六进制转义,参见下面)
  • \uNNNN(Unicode 转义,参见下面)

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

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

下面的示例中的字符串长度为十个字节。它以换行符字节开始,接着是双引号、单引号、反斜杠字符,最后是(没有分隔符的)字符序列 abcdef

"\n\"\'\\abc\
def"

任何 Unicode 行终止符(不是换行符的,如 LF、VF、FF、CR、NEL、LS、PS)都被视为终止字符串字面量。只有在换行符前没有 \ 时,换行符才会终止字符串字面量。

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"

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

枚举(Enums)

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

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

通过使用 type(NameOfEnum).mintype(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;  // 返回最小枚举值
    }
}

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

用户定义值类型

用户定义值类型允许对基本值类型进行零成本抽象。这类似于别名,但具有更严格的类型要求。

用户定义值类型使用 type C is V 来定义,其中 C 是新类型的名称,V 必须是内置的值类型(“基础类型”)。C.wrap 函数用于将基础类型转换为自定义类型。类似地,C.unwrap 函数用于将自定义类型转换回基础类型。

C 类型没有任何操作符或附加的成员函数,特别是 == 操作符未定义。显式和隐式的类型转换到其他类型或从其他类型转换是不允许的。

这些类型的值的数据表示继承自基础类型,并且基础类型也用于 ABI。

以下示例演示了一个自定义类型 UFixed256x18,表示一个带有 18 位小数的固定点类型,以及一个用于对该类型执行算术运算的最小库。

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

// 使用用户定义的值类型表示一个 18 小数点、256 位宽的固定点类型。
type UFixed256x18 is uint256;

/// 一个用于对 UFixed256x18 执行固定点运算的最小库。
library FixedMath {
    uint constant multiplier = 10**18;

    /// 添加两个 UFixed256x18 数字。如果溢出,则回退,依赖于 uint256 的检查算术。
    function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
        return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
    }

    /// 将 UFixed256x18 与 uint256 相乘。如果溢出,则回退,依赖于 uint256 的检查算术。
    function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
        return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
    }

    /// 取 UFixed256x18 数字的下限(即不超过 `a` 的最大整数)。
    function floor(UFixed256x18 a) internal pure returns (uint256) {
        return UFixed256x18.unwrap(a) / multiplier;
    }

    /// 将 uint256 转换为一个值相同的 UFixed256x18。
    /// 如果整数太大,则回退。
    function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
        return UFixed256x18.wrap(a * multiplier);
    }
}

注意: UFixed256x18.wrapFixedMath.toUFixed256x18 有相同的函数签名,但执行了两种非常不同的操作:UFixed256x18.wrap 函数返回一个具有相同数据表示的 UFixed256x18,而 toUFixed256x18 返回一个数值相同的 UFixed256x18

相关文章:

  • 在 RK3588 多线程推理 YOLO 时,同时开启硬件解码和 RGA 加速的性能分析
  • 【JavaEE】UDP数据报套接字编程
  • 傅里叶变换C++实现方式
  • [C++]C++入门篇 2025最新版
  • Leetcode 寻找两个正序数组的中位数
  • CAM获取操作(程序组)的加工时间
  • 短视频团队架构工作流程---2025.3.30 李劭卓
  • python: DDD+ORM+pyQt6 using MySql
  • 用 Python 实现机器学习小项目:从入门到实战
  • Pyside6 信号与槽
  • 阿里云下一代可观测时序引擎-MetricStore 2.0
  • CPM:大规模生成式中文预训练语言模型
  • 从头开始运行一个yolo11的分类模型
  • MySQL响应慢是否由堵塞或死锁引起?
  • 端到端自动驾驶VLA模型:技术解析与模型设计
  • PostgreSQL数据库迁移到Docker拉取的pg镜像中的
  • 3.30 学习总结 Java 常用API+图形化界面
  • Linux系统中修改主机名及设置固定IP地址保姆级教程
  • UE5学习笔记 FPS游戏制作29 更换武器时更换武器的图标
  • 大模型LLMs基于Langchain+FAISS+Ollama/Deepseek/Qwen/OpenAI的RAG检索方法以及优化
  • 快速提升网站排名/建站平台哪家好
  • 安庆做网站赌博代理/百度网页入口官网
  • 淄博做网站电话/中国最新军事新闻直播
  • 做分销微商城网站/seo搜索排名优化公司
  • 网站怎么做404 301/危机舆情公关公司
  • 海宁公司做网站/网站建设及网络推广