Day 7: 运算符与表达式
欢迎来到第七天!今天我们将全面探索Zig中的运算符。运算符是构成表达式的基本工具,用于执行数据计算、比较和逻辑判断。Zig的运算符设计清晰,行为可预测,并且与C语言保持了很大程度的一致性。一个关键区别是,Zig不允许运算符重载,所有函数调用都必须是显式的,这符合其“无隐藏控制流”的哲学。
2. 算术运算符
Section titled “2. 算术运算符”Zig支持标准的算术运算符:
+(加),-(减),*(乘)/(除): 对于整数,除法会向零截断。例如5 / 2结果是2,-5 / 2结果是-2。%(取模): 结果的符号与被除数相同。例如5 % 2是1,-5 % 2是-1。
const a = 10;const b = 3;const sum = a + b; // 13const div = a / b; // 3const rem = a % b; // 1溢出行为: 在Debug和ReleaseSafe模式下,这些标准算术运算符 +, -, * 会在发生溢出时触发panic。在ReleaseFast模式下则会截断。
对于需要明确溢出行为的场景,Zig提供了特殊的运算符:
+%(wrapping add),-%(wrapping sub),*%(wrapping mul): 溢出时截断。+?(catching add),-?(catching sub),*?(catching mul): 溢出时返回一个可选类型?T,值为null。
3. 逻辑与比较运算符
Section titled “3. 逻辑与比较运算符”-
比较运算符:
==(等于),!=(不等于),<(小于),<=(小于等于),>(大于),>=(大于等于)。它们返回一个bool值。 -
逻辑运算符:
and(&&): 逻辑与。如果左侧为false,则不会评估右侧(短路行为)。or(||): 逻辑或。如果左侧为true,则不会评估右侧(短路行为)。!(not): 逻辑非。
const is_sunny = true;const is_warm = false;
if (is_sunny and is_warm) { // false // ...}if (is_sunny or is_warm) { // true // ...}注意: Zig 0.12.0版本后,&& 和 || 被 and 和 or 关键字替代,以提高可读性。
4. 位运算符
Section titled “4. 位运算符”对于底层编程,位运算必不可少。
&(按位与),|(按位或),^(按位异或)~(按位取反)<<(左移),>>(右移)
const flags: u8 = 0b0000_1010;const mask: u8 = 0b0000_1100;
const combined = flags | mask; // 0b0000_1110const common = flags & mask; // 0b0000_1000const toggled = flags ^ mask; // 0b0000_0110
const shifted = flags << 2; // 0b0010_10005. 运算符优先级
Section titled “5. 运算符优先级”Zig的运算符优先级与C语言非常相似。当不确定时,最佳实践是使用括号 () 来明确指定运算顺序。
一个简化的优先级顺序(从高到低):
()(函数调用),.(成员访问),[](数组访问)!(逻辑非),~(按位取反),-(负号)*,/,%+,-<<,>>&,^,|==,!=,<,<=,>,>=andor
6. 安全的溢出处理
Section titled “6. 安全的溢出处理”除了wrapping运算符,Zig还提供了内建函数来更安全地处理可能溢出的运算:
@addWithOverflow(T: type, a: T, b: T) -> struct { T, bool }: 返回一个包含结果和是否溢出的布尔值的结构体。@mulAdd(T: type, a: T, b: T, c: T) -> T: 计算(a * b) + c,并在中间乘法步骤检查溢出。
const std = @import("std");
pub fn main() !void { var a: u8 = 200; var b: u8 = 100; const result = @addWithOverflow(u8, a, b);
std.debug.print("Result: {d}, Overflowed: {any}\n", .{ result[0], result[1] }); // 输出: Result: 44, Overflowed: true // 200 + 100 = 300, 超出u8范围(0-255), 300 % 256 = 44}7. 示例:位运算实现整数幂
Section titled “7. 示例:位运算实现整数幂”我们可以使用位运算和循环来高效地计算一个数的整数次幂。
const std = @import("std");
fn power(base: i32, exp: u32) i32 { var res: i32 = 1; var b = base; var e = exp; while (e > 0) { if (e & 1 == 1) { res *= b; } b *= b; e >>= 1; } return res;}
pub fn main() !void { const p = power(2, 10); std.debug.print("2^10 = {d}\n", .{p}); // 输出: 1024}8. 实践练习:表达式解析原型
Section titled “8. 实践练习:表达式解析原型”这是一个挑战性练习,旨在综合运用今天的知识。
尝试编写一个函数,它接受一个非常简单的字符串表达式,如 "5+3" 或 "9-2",然后计算并返回结果。
- 函数签名可以是
fn eval(expr: []const u8) !i32。 - 你不需要处理复杂的优先级或括号,只处理单个运算符和两个个位数字。
- 你需要遍历字符串,找到运算符,然后解析两边的数字。
- 使用
switch语句来处理不同的运算符 (+,-,*,/)。
这个练习将让你思考如何将字符转换为数字,并处理简单的逻辑分支。
9. 常见问题
Section titled “9. 常见问题”问:浮点数运算的精度问题如何处理?
答:与所有语言一样,Zig的浮点数运算也遵循IEEE 754标准,存在精度限制。对于需要高精度的计算(如金融领域),应该避免使用原生浮点数。可以考虑使用标准库提供的任意精度算术库,或者使用定点数算术(将整数用于表示小数)。
问:既然 and 和 or 会短路,那么按位与 & 和按位或 | 会吗?
答:不会。& 和 | 总是会评估两边的表达式,因为它们是按位操作,需要两边的所有位信息来计算结果。
10. 总结
Section titled “10. 总结”今天,我们学习了Zig中丰富的运算符,从基础的算术运算到强大的位运算。我们理解了Zig对运算符溢出的安全处理机制,以及如何通过 and/or 实现逻辑短路。最重要的是,我们再次看到了Zig的设计哲学:明确、可预测且安全。
明天,我们将学习控制程序流程的两个基本结构:if 和 switch。