文章目录
- 值类型
- 有理数和整数字面量(Rational and Integer Literals)
- 字符串字面量和类型(String Literals and Types)
- Unicode 字面量(Unicode Literals)
- 十六进制字面量(Hexadecimal Literals)
- 枚举(Enums)
- 用户定义值类型
值类型
有理数和整数字面量(Rational and Integer Literals)
整数字面量由一串数字(范围为 0-9)组成,按十进制解析。例如,69 代表六十九。Solidity 中没有八进制字面量,且前导零是无效的。
十进制小数字面量由一个小数点(.
)和至少一个小数点后的数字组成。例如,.1
和 1.3
是有效的(但 1.
是无效的)。
科学记数法格式(如 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
内计算,可能会发生溢出。
任何可以应用于整数的运算符也可以应用于数字字面量表达式,只要操作数是整数。如果其中任何一个是小数,则不允许使用位运算,并且如果指数是小数,则不允许使用指数运算(因为这可能导致非有理数)。
对于字面量数字作为左操作数(或基数)和整数类型作为右操作数(指数)的移位和指数运算,总是使用 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 编码序列。
注意
在版本 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).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; // 返回最小枚举值
}
}
枚举也可以在文件级别声明,而不需要在合约或库定义中。
用户定义值类型
用户定义值类型允许对基本值类型进行零成本抽象。这类似于别名,但具有更严格的类型要求。
用户定义值类型使用 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.wrap
和 FixedMath.toUFixed256x18
有相同的函数签名,但执行了两种非常不同的操作:UFixed256x18.wrap
函数返回一个具有相同数据表示的 UFixed256x18
,而 toUFixed256x18
返回一个数值相同的 UFixed256x18
。