Day 5: 变量与基本数据类型
欢迎来到第五天!今天,我们将深入探讨Zig编程的基石:变量和基本数据类型。Zig是一门静态强类型语言,这意味着编译器在编译时就必须知道所有变量的类型。这个特性可以捕获大量潜在的错误。同时,Zig也提供了强大的类型推断系统,让你在编写代码时不必为每个变量都显式声明类型,兼顾了安全与便捷。
2. 变量声明
Section titled “2. 变量声明”在Zig中,变量分为可变(mutable)和不可变(immutable)两种。
-
不可变变量 (常量)
const: 使用const声明的变量一旦赋值后就不能再改变。这是Zig推荐的默认选择,因为它能让代码更易于推理和维护。const pi: f64 = 3.14159;// pi = 3.14; // 这行代码会导致编译错误 -
可变变量
var: 使用var声明的变量可以在后续代码中被重新赋值。var counter: i32 = 0;counter = 1; // 合法 -
类型推断: 在声明变量时,如果初始值是已知的,编译器可以自动推断其类型,你可以省略类型声明。
const gravity = 9.8; // 编译器推断为 f64 类型var score = 0; // 编译器推断为 integer 类型 (具体大小依赖于值)
3. 基本数据类型详解
Section titled “3. 基本数据类型详解”Zig提供了丰富的内建数据类型,让你能够精确控制内存布局。
-
整数 (Integers):
- Zig提供了多种位宽的整数类型,从
i8(有符号8位) 到i128,以及u8(无符号8位) 到u128。 isize和usize是指针大小的整数,它们的位宽与目标平台的CPU架构一致(32位系统上是32位,64位系统上是64位)。- 示例:
const year: u16 = 2025;
- Zig提供了多种位宽的整数类型,从
-
浮点数 (Floating-Point):
f16,f32,f64,f128: 不同精度的浮点数。f64相当于C语言的double。c_float,c_double: 用于与C代码互操作时,保证类型一致。- 示例:
const temperature: f32 = 26.5;
-
布尔值 (Booleans):
bool: 只有两个值true和false。- 示例:
const is_learning_zig: bool = true;
-
void:void类型表示“没有值”。一个不返回任何值的函数,其返回类型就是void。
4. 类型大小与对齐
Section titled “4. 类型大小与对齐”Zig提供了内建函数来在编译期获取类型的大小和对齐信息,这对于底层编程至关重要。
@sizeOf(T: type) -> comptime_int: 返回类型T所占用的字节数。@alignOf(T: type) -> comptime_int: 返回类型T的对齐要求(字节数)。
const std = @import("std");
pub fn main() !void { std.debug.print("Size of i32: {} bytes\n", .{@sizeOf(i32)}); // 输出: 4 bytes std.debug.print("Alignment of i32: {} bytes\n", .{@alignOf(i32)}); // 输出: 4 bytes
std.debug.print("Size of u8: {} bytes\n", .{@sizeOf(u8)}); // 输出: 1 byte std.debug.print("Alignment of u8: {} bytes\n", .{@alignOf(u8)}); // 输出: 1 byte}5. 作用域与遮蔽 (Shadowing)
Section titled “5. 作用域与遮蔽 (Shadowing)”-
作用域 (Scope): 变量的生命周期和可见性被限定在它所声明的代码块
{...}内。var a: i32 = 1;{var b: i32 = 2;std.debug.print("a={}, b={}\n", .{a, b}); // a和b都可见}// std.debug.print("b={}\n", .{b}); // 编译错误: b在此作用域不可见 -
遮蔽 (Shadowing): 在内部作用域可以声明一个与外部作用域同名的变量。此时,内部的变量会“遮蔽”外部的同名变量。Zig不鼓励这种做法,因为它可能导致混淆。
var c: i32 = 10;{var c: i32 = 20; // 遮蔽了外部的 cstd.debug.print("Inner c = {d}\n", .{c}); // 输出: Inner c = 20}std.debug.print("Outer c = {d}\n", .{c}); // 输出: Outer c = 10
6. 示例:类型转换与溢出
Section titled “6. 示例:类型转换与溢出”Zig的一大安全特性是没有隐式类型转换。所有类型转换都必须是显式的,使用 @as 内建函数。
const integer: i32 = 100;// const float: f32 = integer; // 编译错误: 类型不匹配const float: f32 = @as(f32, @floatFromInt(integer)); // 正确的转换方式
const big_int: i64 = 9999999999;// const small_int: i32 = @as(i32, big_int); // 在Debug/ReleaseSafe模式下会panic,因为值溢出了在Debug和ReleaseSafe模式下,整数溢出会导致程序 panic。而在ReleaseFast模式下,溢出会发生截断(wraparound),以追求极致性能。这种行为是明确且可控的。
7. 实践练习:温度转换器
Section titled “7. 实践练习:温度转换器”编写一个简单的程序 temp_converter.zig,将摄氏度转换为华氏度。
- 创建一个
const变量celsius并赋值25.0(类型为f32)。 - 根据公式
F = C * 9/5 + 32计算华氏度。 - 将结果存储在一个
const变量fahrenheit中。 - 使用
std.debug.print打印出结果,例如 “25.0 C is 77.0 F”。
这个练习将帮助你熟悉变量声明、基本类型 f32 和算术运算。
8. 常见问题
Section titled “8. 常见问题”问:整数溢出时,panic 和截断(wrap around)有什么区别?我应该如何选择?
答:
- Panic (默认行为): 在Debug和ReleaseSafe模式下,整数运算(加减乘)如果超出类型范围,程序会立即崩溃并报告错误。这是安全的默认行为,因为它能立即暴露bug。
- 截断 (Wrapping): 在ReleaseFast模式下,或使用特殊的“wrapping”运算符(如
+%)时,溢出会像C语言一样被截断。例如const x: u8 = 255; const y = x +% 1;结果y会是0。这只应在性能极其关键且你确切知道自己在做什么的情况下使用。
问:为什么Zig不像其他语言那样进行隐式类型转换?
答:这是为了安全和代码清晰度。隐式转换是许多难以发现的bug的根源(例如,从一个大的整数类型隐式转换为小的整数类型导致数据丢失)。要求显式转换 @as 使得代码的意图更加明确,所有潜在的数据丢失或精度变化都必须由程序员亲手写出。
今天,我们掌握了Zig的变量声明 (const vs var)、丰富的基本数据类型、作用域规则以及最重要的——显式类型转换。这些基础知识是构建任何复杂程序的必要条件。Zig在提供底层控制能力的同时,通过强类型和安全的默认设置,帮助我们编写更可靠的代码。
明天,我们将学习 const 的更深层次用法,以及Zig强大的编译期计算(comptime)的初步概念。