Skip to content

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, eE 后面必须跟一个整数指数, 表示乘以 10 的指数次方
  • 十六进制表示法: 以 0x0X 开头, 如 0x1.91eb86p+1, pP 后面必须跟一个整数指数, 表示乘以 2 的指数次方

浮点数字面量可以通过在字面量后添加类型后缀来指定类型, 如 3.14f32, 2.71f64

浮点数字面量中也可以使用下划线 _ 来分隔数字, 以提高可读性, 如 3.14_15_92

不存在负浮点数字面量, 负号 - 被视为一元运算符

字面量的类型会根据上下文进行推断, 没有足够上下文时, 默认为 f64

字符字面量

字符字面量以单引号 ' 包围, 如 'a', '中', 中间必须是单个 Unicode 字符, 允许使用转义序列

字符字面量的类型均为 char

转义序列

  1. 基本转义序列:
  2. \n: 换行符
  3. \r: 回车符
  4. \t: 制表符
  5. \\: 反斜杠
  6. \': 单引号, 在字符串字面量中也可以直接使用
  7. \": 双引号, 在字符字面量中也可以直接使用
  8. \0: 空字符
  9. \x 开头的十六进制字节转义序列, 如 \x41 表示字符 A, 注意: 十六进制数字只有两位, 必须对应一个合法的 Unicode 字符
  10. \u{} 包围的 Unicode 码点转义序列, 如 \u{4E2D} 表示字符

字符串字面量

字符串字面量以双引号 " 包围, 如 "Hello, world!", 内部可以包含任意数量的 Unicode 字符, 允许使用转义序列(见转义序列)

字符串字面量的类型均为 str

布尔字面量

布尔字面量有两个取值: truefalse

布尔字面量的类型均为 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 方法