运算、运算符¶
为什么不使用 operator 这样的关键字?
我认为 fn \add(MyInt rhs) -> MyInt 这样的写法与 auto operator+(MyInt rhs) -> MyInt 这样的写法相比更加简洁,也容易理解。
优先顺序:
- 后缀
- 前缀
- 二元
- 乘、除、取模
- 加、减
- 移位
- 按位与
- 按位异或
- 按位或
- 逻辑与 (
&&) - 逻辑异或 (
^^) - 逻辑或 (
||) - 比较
- 赋值
前缀运算¶
| 运算 | 名称 | 解释 | 注释 |
|---|---|---|---|
| +a | pos | 正号 | |
| -a | neg | 负号 | |
| ~a | flip | 按位取反 | |
| !a | not | 逻辑取反 | |
| ++a | linc | 前缀自增 | |
| --a | ldec | 前缀自减 | |
| a[] | deref | 解引用 | 你没办法重载指针类型 |
| &a | addr | 取地址 | 不可重载 |
后缀运算¶
| 运算 | 名称 | 解释 |
|---|---|---|
| a++ | rinc | 后缀自增 |
| a-- | rdec | 后缀自减 |
| a[] | deref | 解引用 |
| a[b] | index | 数组索引 |
对于整数索引,应当是 isize 或 usize 类型。
对于键值索引,可以是任意类型。
注意数组索引必须写作 arr[1] 而不能是 1[arr]。属于是远古遗物了
如果重载索引:
fn \index(usize i) -> i32 {
return (array + i)[];
}
二元运算¶
算术运算符¶
| 运算 | 名称 | 解释 | 注释 |
|---|---|---|---|
| a + b | add | 加法 | |
| a - b | sub | 减法 | |
| a * b | mul | 乘法 | |
| a / b | div | 除法 | |
| a % b | mod | 取模 |
位运算符¶
| 运算 | 名称 | 解释 |
|---|---|---|
| a & b | band | 按位与 |
| a | b | bor | 按位或 |
| a ^ b | bxor | 按位异或 |
| a << b | shl | 左移 |
| a >> b | shr | 右移 |
| a <<< b | ushl | 无符号左移 |
| a >>> b | ushr | 无符号右移 |
| a <<> b | rol | 循环左移 |
| a >>< b | ror | 循环右移 |
注意浮点数不能进行位运算,如果你实在需要位运算,请将浮点数转换为 bits 类型,而不是像 C 中那样使用整数指针。
// 可以使用
f32 value = (123.456 as b32 << 1) as f32;
// 而不是
var f32 value = 123.456;
[u32] ptr = &value;
ptr[] <<= 1;
复合赋值运算符¶
| 运算 | 名称 | 解释 |
|---|---|---|
| a += b | iadd | 加并赋值 |
| a -= b | isub | 减并赋值 |
| a *= b | imul | 乘并赋值 |
| a /= b | idiv | 除并赋值 |
| a %= b | imod | 取模并赋值 |
| a &= b | iband | 按位与并赋值 |
| a |= b | ibor | 按位或并赋值 |
| a ^= b | ibxor | 按位异或并赋值 |
| a <<= b | ishl | 左移并赋值 |
| a >>= b | ishr | 右移并赋值 |
比较运算符¶
| 运算 | 名称 | 解释 | 注释 |
|---|---|---|---|
| a == b | eq | 等于 | |
| a != b | ne | 不等于 | |
| a < b | lt | 小于 | |
| a <= b | le | 小于等于 | |
| a > b | gt | 大于 | |
| a >= b | ge | 大于等于 | |
| a <=> b | cmp | 比较 | |
| a === b | seq | 严格相等 | 可重载 |
| a !== b | sne | 严格不等 | 可重载 |
| a $== b | beq | 二进制相等 | 不可重载 |
| a ~== b | bne | 二进制不等 | 不可重载 |
| a &== b | same | 相同 | 不可重载 |
| a &!= b | diff | 不同 | 不可重载 |
-
<=>
比较运算符,返回 -1、0、1 表示小于、等于、大于。对于重载,实现
<=>即可,==、!=、<、<=、>、>=会自动调用<=>。当然你也可以手动实现其它函数,Lumos 会自动将未实现的比较运算符转换为已实现的。
-
===!==
严格相等,仅当两个对象的类型和值都相同时返回 true。你没听错,严格相等是可重载的,但你不能重载不同类型的严格相等运算。
对于严格相等运算,你可以使用与==不同的逻辑。struct MyInt { i32 value; fn \eq(MyInt rhs) -> bool { return value == rhs.value; } fn \seq(MyInt rhs) -> bool { return value === rhs.value; } fn \seq(f64 rhs) -> bool; // 这是不行的,不同类型间的严格相等不可重载 }
$==~==
二进制相等,仅当两个对象的二进制表示完全相同时返回 true。
注意二进制表示的比较包含位数,例如i32 1 $== i64 1为 false。
-
&==&!=
判断左右是否是相同的对象,不比较值。
类似于&a == &b,任意一边为字面量时始终得到 false。
&==与&!=是唯一可以比较引用的运算符。i32 a = 1; ref i32 b = a; i32 c = a; assert(a &== b); // true assert(a &== c); // false
逻辑运算符¶
| 运算 | 名称 | 解释 | 注释 |
|---|---|---|---|
| a && b | and | 逻辑与 | |
| a || b | or | 逻辑或 | |
| a ^^ b | xor | 逻辑异或 |
对于逻辑异或,请不要使用 expr1 != expr2 而是使用 expr1 ^^ expr2。
其他运算符¶
| 运算 | 名称 | 解释 | 注释 |
|---|---|---|---|
a as type |
cast | 类型转换 | 不可重载 |
sizeof(a) |
sizeof | 计算大小 | 不可重载 |
typeof(a) |
typeof | 获取类型 | 不可重载 |
typenameof(a) |
typenameof | 获取类型名称 | 不可重载 |
a->b |
arrow | 自定义成员访问 | 都自定义了当然可重载啦 |
注意这里和 C/C++ 不同,, 和 . 不是运算符,而作为分隔符。
实际上你也不能够重载它们所以效果没有什么区别。
-
typeof是唯一运算结果为类型的运算符,它可以直接当作类型使用。i32 a = 1; typeof(a) b = 2; // 相当于 i32 b = 2;
typenameof的运算结果为字符串。
¶
- 交换律:
a + b与b + a等效 - 结合律:
(a + b) + c与a + (b + c)等效
算术运算符简写¶
我们约定以下运算符等效,其互换不应影响程序的行为:
a = a 运算符 b;
a 运算符= b;
对于自定义运算符也应当遵循此约定。
自定义运算符无法定义 xx= 形式的复合赋值运算符,而是由其定义 xx 二元运算符后自动生成对应的复合赋值运算符。
a = a + b 与 a += b
a = a - b 与 a -= b
a = a * b 与 a *= b
a = a / b 与 a /= b
依此类推
自增、自减¶
a++ 与 a += 1
a-- 与 a -= 1
a if expr else b 是唯一的条件表达式。
a and b 与 a && b
a or b 与 a || b
not a 与 !a
逻辑运算符规则¶
&& || ^^ 符合交换律,即:
a && b 与 b && a 等效
a || b 与 b || a 等效
依此类推
重载运算符¶
与 C++ 中 opeator+ 一类的写法不同,Lumos 使用反斜杠加运算名来代表运算符,例如 \add。
| 运算 | 名称 | 运算 | 名称 | 运算 | 名称 |
|---|---|---|---|---|---|
a + b |
add | a & b |
band | a && b |
and |
a - b |
sub | a \| b |
bor | a \|\| b |
or |
a * b |
mul | a ^ b |
bxor | a ^^ b |
xor |
a / b |
div | a << b |
shl | a == b |
eq |
a % b |
mod | a >> b |
shr | a != b |
ne |
-a |
neg | ~a |
flip | !a |
not |
a |
get | a < b |
lt | a <= b |
le |
a = b |
set | a > b |
gt | a >= b |
ge |
a++ |
rinc | a-- |
rdec | a <=> b |
cmp |
++a |
linc | --a |
ldec | a[] |
deref |
所有类似 a += b 的运算名称都是 i 加上对应的运算符名称,例如 add 的增量运算符是 iadd。
\ 开头的运算符¶
有些运算符并没有对应的符号版本,但也非常重要,这些运算符以 \ 开头。
为什么不使用特定名称的函数?
我认为不应该出现语法或优化和一些非标准库的特定名称函数有关的情况,所以我将它们定义为运算符。
不过实际上还是有一些特定名称的函数会被编译器识别,例如 add sub 等。
\hash计算哈希值\next获取下一个值\prev获取上一个值\clone克隆对象\sizeof获取对象大小\alignof获取对象对齐值
优化假设¶
运算律¶
对于运算律,我们将它们实现为特性:
Commutative交换律Associative结合律Distributive分配律
实现了这些特性的运算符可以被编译器用来进行优化。
默认情况下我们要求 add mul 实现交换律与结合律,mul 实现分配律。
比较传递性¶
对于比较运算符,我们将传递性实现为特性:
TransitiveEq等于传递性TransitiveCmp比较传递性SelfConsistent自反性
对于浮点数,我们还实现了可能无法比较的特性:
PartialEq部分等于性PartialOrd部分有序性
对应运算符 peq pord 等,为浮点数实现这些并将 == <=> 等运算符绑定到它们上面。
浮点默认遵循 IEEE 754 标准的比较规则,但也同时实现全序浮点类型 `t
实现了这些特性的比较运算符可以被编译器用来进行优化。
一般情况下我们要求 eq 实现等于传递性与自反性,cmp 实现比较传递性与自反性。
在比较上实现自反性需要对于对象
ab,如果a > b为 true 则b < a必须为 true,反之亦然。
附加变更(目前未合并到文档)¶
我希望将余数与模运算区分开来:
rem取余运算,符号与被除数相同mod取模运算,符号与除数相同
返回的而类型与被除数相同。
| 表达式 | rem (取余) |
mod (取模) |
|---|---|---|
5, 3 |
2 |
2 |
-5, 3 |
-2 |
1 |
5, -3 |
2 |
-1 |
-5, -3 |
-2 |
-2 |
对于浮点数,我们定义如下运算:
frem浮点数取余运算fmod浮点数取模运算
对于需要对齐的情况,我们定义如下运算:
alignup向上对齐运算aligndown向下对齐运算alignnear向最近对齐运算isaligned判断对齐运算ceil向上取整运算,仅对浮点数有效,在编译时已知整数类型上会导致编译错误floor向下取整运算,仅对浮点数有效,在编译时已知整数类型上会导致编译错误
ceil 与 floor 也在整数类型上定义,返回其原值,但会导致编译错误以防止误用。
数学运算扩展:
- addition
+add普通加法(溢出时抛出异常)+^addo饱和加法(溢出时返回类型最大值或最小值)+%addw带宽加法(溢出时截断为类型位宽)+!addc带进位加法(返回结果与进位标志)
- subtraction
-sub普通减法(溢出时抛出异常)-^subo饱和减法(溢出时返回类型最大值或最小值)-%subw带宽减法(溢出时截断为类型位宽)-!subc带借位减法(返回结果与借位标志)
- multiplication
*mul普通乘法(溢出时抛出异常)*^mulo饱和乘法(溢出时返回类型最大值或最小值)*%mulw带宽乘法(溢出时截断为类型位宽)*!mulc带溢出乘法(返回结果与溢出值)
- division
/div普通除法/%divrem带余数除法(返回商与余数)/+divceil向上取整除法/-divfloor向下取整除法
对于可能抛出异常的操作都在编译时进行检查,如果能证明不会抛出异常则不会生成运行时检查代码,但如果用户进行了错误的假设则可能导致大范围的未定义行为。
这点目前写在 暂存草案 中。
对于浮点数类型:
+fadd浮点数加法-fsub浮点数减法*fmul浮点数乘法/fdiv浮点数除法%frem浮点数取余
符号表示只是暂定的,但名称已经确定。