07 Expression¶
本部分着重介绍 YIAN 中构成表达式的基本要素(如字面量, 变量), 以及表达式的组合方式(如算术表达式, 逻辑表达式等).
Literals¶
整数字面量¶
- 十进制: 如
123 - 十六进制: 以
0x或者0X开头, 如0x7B, 字母可以大写或小写 - 八进制: 以
0o或者0O开头, 如0o173 - 二进制: 以
0b或者0B开头, 如0b01111011 - 字节字面量: 以
b开头, 用于指代一个u8值的字符, 如b'A'表示65, 字符必须是单个 ASCII 字符, 也允许使用\x开头的十六进制转义序列, 如b'\x41'也表示65
除了字节字面量外, 整数字面量可以通过在字面量后添加类型后缀来指定类型, 如 123i32, 0x7Bu64
除了字节字面量外, 可以在整数字面量中使用下划线 _ 来分隔数字, 以提高可读性, 如 1_000_000
不存在负数字面量, 负号 - 被视为一元运算符
字面量的类型会根据上下文进行推断, 没有足够上下文时, 默认为 i32
浮点数字面量¶
- 小数点表示法: 如
3.14,0.001, 小数点前后都必须有数字 - 科学计数法: 如
1.23e4,5.67E-8,e或E后面必须跟一个整数指数, 表示乘以 10 的指数次方 - 十六进制表示法: 以
0x或0X开头, 如0x1.91eb86p+1,p或P后面必须跟一个整数指数, 表示乘以 2 的指数次方
浮点数字面量可以通过在字面量后添加类型后缀来指定类型, 如 3.14f32, 2.71f64
浮点数字面量中也可以使用下划线 _ 来分隔数字, 以提高可读性, 如 3.14_15_92
不存在负浮点数字面量, 负号 - 被视为一元运算符
字面量的类型会根据上下文进行推断, 没有足够上下文时, 默认为 f64
字符字面量¶
字符字面量以单引号 ' 包围, 如 'a', '中', 中间必须是单个 Unicode 字符, 允许使用转义序列
字符字面量的类型均为 char
转义序列¶
- 基本转义序列:
\n: 换行符\r: 回车符\t: 制表符\\: 反斜杠\': 单引号, 在字符串字面量中也可以直接使用\": 双引号, 在字符字面量中也可以直接使用\0: 空字符- 以
\x开头的十六进制字节转义序列, 如\x41表示字符A, 注意: 十六进制数字只有两位, 必须对应一个合法的 Unicode 字符 - 以
\u{}包围的 Unicode 码点转义序列, 如\u{4E2D}表示字符中
字符串字面量¶
字符串字面量以双引号 " 包围, 如 "Hello, world!", 内部可以包含任意数量的 Unicode 字符, 允许使用转义序列(见转义序列)
字符串字面量的类型均为 str
布尔字面量¶
布尔字面量有两个取值: true 和 false
布尔字面量的类型均为 bool
数组字面量¶
数组字面量以方括号 [] 包围, 元素之间用逗号 , 分隔, 如 [1, 2, 3], 元素类型必须一致. 数组字面量的类型为 T[N], 其中 T 为元素类型, N 为元素数量, 如上例的类型为 i32[3]
元组字面量¶
元组字面量以圆括号 () 包围, 元素之间用逗号 , 分隔, 如 (1, true, 'a'), 元素类型可以不一致. 元组字面量的类型为 (T1, T2, ...), 其中 T1, T2,... 分别为各个元素的类型, 如上例的类型为 (i32, bool, char)
允许使用单元素元组, 如 (42,), 注意: 单元素元组必须在元素后面加上逗号 ,, 否则会被视为普通的括号表达式
Variables¶
变量是一个可变的内存位置, 用于存储数据. 在 YIAN 中, 变量必须先声明后使用, 声明语法为 <type> <name>, 如 int x.
左值性¶
表达式可以划分为左值表达式和右值表达式. 左值表达式指的是可以出现在赋值语句左边的表达式, 例如变量 x 就是一个左值表达式, 因为我们可以写 x = 42. 其他表达式都是右值表达式, 例如字面量 42 就是一个右值表达式, 因为我们不能写 42 = x. 左值表达式可以当成右值表达式来使用, 但反之不行.
在表达式的基本要素中, 变量是左值表达式, 字面量是右值表达式. 此外, 某些运算也能产生也能产生左值表达式:
- 索引运算:
arr[i] - 字段访问:
person.name - 解引用:
*ptr
运算¶
表达式可以通过运算进行组合, 产生新的表达式. YIAN 支持以下运算:
- 算术运算:
+,-,*,/,%, 一元+,- - 算数赋值运算:
+=,-=,*=,/=,%= - 位运算:
&,|,^,~,<<,>> - 位赋值运算:
&=,|=,^=,<<=,>>= - 比较运算:
==,!=,<,>,<=,>= - 逻辑运算:
and,or,not - 索引运算:
[] - 解引用运算:
* - 取址运算:
& - 成员访问运算:
. - 范围运算:
.. - 这个运算事实上是创建标准库类型
Range<T>的一个语法糖, 详见标准库章节
运算符重载¶
以下运算符支持重载:
- 算术运算
- 算数赋值运算
- 位运算
- 位赋值运算
- 比较运算
- 索引运算
- 解引用运算
可以通过实现 trait 的方式重载这些运算, 具体重载方式详见标准库章节
自动解引用¶
自动解引用适用于这样的场景: 你持有一个指向类型 T 值的指针, 你想要直接使用 T 的方法或属性, 此时通过自动解引用, 你不需要显式的通过 * 来解引用指针, 就可以直接调用 T 的方法或访问 T 的属性.
两种场景会触发自动解引用:
- 字段访问
- 方法调用
字段访问的自动解引用¶
只有指针类型的变量才会在字段访问时触发自动解引用, 实例:
struct Point {
i32 x
i32 y
}
Point* p = dyn Point(x=1, y=2)
i32 x = p.x // 自动解引用为 (*p).x
p.y = 3 // 自动解引用为 (*p).y = 3
方法调用的自动解引用¶
除指针类型外, 任何重载了 Deref trait 的类型在方法调用时都会触发自动解引用. 编译器在解析方法调用时, 如果发现调用对象的类型没有名称对应的方法, 就会尝试对调用对象进行自动解引用, 直到找到一个类型上有对应方法为止. 实例:
// 来自标准库
struct Vec<T> {
// ...
}
impl<T> Vec<T> {
pub static Self new() {
// ...
}
pub push(T item) {
// ...
}
}
impl<T> Deref<T[]> for Vec<T> {
// ...
}
Vec<i32> v = Vec<i32>.new()
v.push(42) // 调用 Vec<T> 自身的 push 方法
Option<i32> first = v.first() // Vec<T> 没有 first 方法, 但是 T[] 有 first 方法, 通过自动解引用调用 T[] 的 first 方法