Zig 变量与常量
本章将深入探讨 Zig 中变量和常量的使用,包括它们的区别、最佳实践和高级用法。
变量与常量的基本概念
常量 (const)
常量是不可变的值,一旦赋值就不能修改:
zig
const std = @import("std");
pub fn main() void {
const name = "张三";
const age = 25;
const pi = 3.14159;
std.debug.print("姓名: {s}, 年龄: {}, π = {d:.5}\n", .{ name, age, pi });
// 以下代码会导致编译错误:
// name = "李四"; // 错误:不能修改常量
// age = 26; // 错误:不能修改常量
}变量 (var)
变量是可变的值,可以在运行时修改:
zig
const std = @import("std");
pub fn main() void {
var counter = 0;
var message = "开始";
std.debug.print("{s}: {}\n", .{ message, counter });
// 修改变量值
counter = 10;
message = "更新";
std.debug.print("{s}: {}\n", .{ message, counter });
}编译时常量 vs 运行时常量
编译时常量
编译时常量在编译期间就确定值:
zig
const std = @import("std");
// 编译时常量
const BUFFER_SIZE = 1024;
const VERSION = "1.0.0";
const MAX_USERS = 100;
// 编译时计算
const TOTAL_MEMORY = BUFFER_SIZE * MAX_USERS;
pub fn main() void {
std.debug.print("缓冲区大小: {}\n", .{BUFFER_SIZE});
std.debug.print("版本: {s}\n", .{VERSION});
std.debug.print("最大用户数: {}\n", .{MAX_USERS});
std.debug.print("总内存需求: {} 字节\n", .{TOTAL_MEMORY});
}运行时常量
运行时常量的值在运行时确定,但确定后不可修改:
zig
const std = @import("std");
fn getCurrentTime() i64 {
return 1640995200; // 模拟获取当前时间
}
pub fn main() void {
// 运行时常量
const start_time = getCurrentTime();
const user_input = "Hello, World!";
std.debug.print("程序启动时间: {}\n", .{start_time});
std.debug.print("用户输入: {s}\n", .{user_input});
// start_time 的值在运行时确定,但之后不能修改
}变量的可变性
完全可变变量
zig
const std = @import("std");
pub fn main() void {
var number = 42;
var text = "初始文本";
var flag = true;
std.debug.print("初始值 - 数字: {}, 文本: {s}, 标志: {}\n",
.{ number, text, flag });
// 修改所有变量
number = 100;
text = "更新文本";
flag = false;
std.debug.print("更新值 - 数字: {}, 文本: {s}, 标志: {}\n",
.{ number, text, flag });
}部分可变性
zig
const std = @import("std");
const Person = struct {
name: []const u8,
age: u32,
};
pub fn main() void {
// 结构体变量,字段可以修改
var person = Person{
.name = "王五",
.age = 30,
};
std.debug.print("初始: {s}, {}\n", .{ person.name, person.age });
// 可以修改字段
person.age = 31;
// person.name = "赵六"; // 注意:name 是 []const u8,指向的内容不可修改
std.debug.print("更新: {s}, {}\n", .{ person.name, person.age });
}数组和切片的可变性
可变数组
zig
const std = @import("std");
pub fn main() void {
// 可变数组
var numbers = [_]i32{ 1, 2, 3, 4, 5 };
std.debug.print("原数组: ");
for (numbers) |num| {
std.debug.print("{} ", .{num});
}
std.debug.print("\n");
// 修改数组元素
numbers[0] = 10;
numbers[4] = 50;
std.debug.print("修改后: ");
for (numbers) |num| {
std.debug.print("{} ", .{num});
}
std.debug.print("\n");
}常量数组
zig
const std = @import("std");
pub fn main() void {
// 常量数组
const fruits = [_][]const u8{ "苹果", "香蕉", "橙子" };
std.debug.print("水果列表: ");
for (fruits) |fruit| {
std.debug.print("{s} ", .{fruit});
}
std.debug.print("\n");
// 以下代码会导致编译错误:
// fruits[0] = "葡萄"; // 错误:不能修改常量数组
}切片的可变性
zig
const std = @import("std");
pub fn main() void {
var array = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 可变切片
var mutable_slice: []i32 = array[2..7];
// 只读切片
const readonly_slice: []const i32 = array[0..5];
std.debug.print("原数组: ");
for (array) |val| {
std.debug.print("{} ", .{val});
}
std.debug.print("\n");
// 通过可变切片修改
mutable_slice[0] = 100;
mutable_slice[2] = 200;
std.debug.print("修改后: ");
for (array) |val| {
std.debug.print("{} ", .{val});
}
std.debug.print("\n");
std.debug.print("只读切片: ");
for (readonly_slice) |val| {
std.debug.print("{} ", .{val});
}
std.debug.print("\n");
}指针的可变性
指向可变数据的指针
zig
const std = @import("std");
pub fn main() void {
var number: i32 = 42;
// 指向可变数据的可变指针
var ptr: *i32 = &number;
std.debug.print("原值: {}\n", .{number});
std.debug.print("通过指针: {}\n", .{ptr.*});
// 通过指针修改值
ptr.* = 100;
std.debug.print("修改后: {}\n", .{number});
// 指针本身也可以改变指向
var another_number: i32 = 200;
ptr = &another_number;
std.debug.print("新指向: {}\n", .{ptr.*});
}指向常量数据的指针
zig
const std = @import("std");
pub fn main() void {
const number: i32 = 42;
// 指向常量数据的指针
const const_ptr: *const i32 = &number;
std.debug.print("常量值: {}\n", .{const_ptr.*});
// 以下代码会导致编译错误:
// const_ptr.* = 100; // 错误:不能通过常量指针修改数据
}全局变量和常量
全局常量
zig
const std = @import("std");
// 全局常量
const PROGRAM_NAME = "我的程序";
const VERSION_MAJOR = 1;
const VERSION_MINOR = 0;
const VERSION_PATCH = 0;
// 计算得出的全局常量
const VERSION_STRING = std.fmt.comptimePrint("{}.{}.{}", .{ VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH });
pub fn main() void {
std.debug.print("程序名称: {s}\n", .{PROGRAM_NAME});
std.debug.print("版本: {s}\n", .{VERSION_STRING});
}全局变量
zig
const std = @import("std");
// 全局变量(需要谨慎使用)
var global_counter: i32 = 0;
var global_message: []const u8 = "初始消息";
fn incrementCounter() void {
global_counter += 1;
}
fn updateMessage(new_message: []const u8) void {
global_message = new_message;
}
pub fn main() void {
std.debug.print("初始状态 - 计数器: {}, 消息: {s}\n",
.{ global_counter, global_message });
incrementCounter();
updateMessage("更新的消息");
std.debug.print("更新状态 - 计数器: {}, 消息: {s}\n",
.{ global_counter, global_message });
}线程局部变量
zig
const std = @import("std");
// 线程局部变量
threadlocal var thread_counter: i32 = 0;
fn incrementThreadCounter() void {
thread_counter += 1;
}
pub fn main() void {
std.debug.print("线程计数器初始值: {}\n", .{thread_counter});
incrementThreadCounter();
incrementThreadCounter();
std.debug.print("线程计数器最终值: {}\n", .{thread_counter});
}变量初始化模式
延迟初始化
zig
const std = @import("std");
fn expensiveCalculation() i32 {
std.debug.print("执行昂贵的计算...\n", .{});
return 42;
}
pub fn main() void {
// 声明但不初始化
var result: i32 = undefined;
var should_calculate = true;
if (should_calculate) {
result = expensiveCalculation();
} else {
result = 0;
}
std.debug.print("结果: {}\n", .{result});
}条件初始化
zig
const std = @import("std");
pub fn main() void {
const debug_mode = true;
// 根据条件初始化
const log_level = if (debug_mode) "DEBUG" else "INFO";
const buffer_size = if (debug_mode) 1024 else 512;
std.debug.print("日志级别: {s}\n", .{log_level});
std.debug.print("缓冲区大小: {}\n", .{buffer_size});
}变量的生命周期
栈变量
zig
const std = @import("std");
fn createStackVariable() void {
var local_var = 42; // 栈变量
std.debug.print("栈变量: {}\n", .{local_var});
// local_var 在函数结束时自动销毁
}
pub fn main() void {
createStackVariable();
// local_var 在这里已经不存在
}静态变量
zig
const std = @import("std");
fn getStaticCounter() *i32 {
// 静态变量,程序运行期间一直存在
const static = struct {
var counter: i32 = 0;
};
static.counter += 1;
return &static.counter;
}
pub fn main() void {
const counter1 = getStaticCounter();
const counter2 = getStaticCounter();
const counter3 = getStaticCounter();
std.debug.print("第一次调用: {}\n", .{counter1.*});
std.debug.print("第二次调用: {}\n", .{counter2.*});
std.debug.print("第三次调用: {}\n", .{counter3.*});
}常量表达式
编译时计算
zig
const std = @import("std");
// 编译时常量表达式
const SECONDS_PER_MINUTE = 60;
const MINUTES_PER_HOUR = 60;
const HOURS_PER_DAY = 24;
const SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
const SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
// 编译时字符串操作
const GREETING = "Hello";
const TARGET = "World";
const MESSAGE = GREETING ++ ", " ++ TARGET ++ "!";
pub fn main() void {
std.debug.print("一天有 {} 秒\n", .{SECONDS_PER_DAY});
std.debug.print("消息: {s}\n", .{MESSAGE});
}编译时函数
zig
const std = @import("std");
// 编译时函数
fn fibonacci(n: u32) u32 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 编译时计算斐波那契数
const FIB_10 = comptime fibonacci(10);
const FIB_15 = comptime fibonacci(15);
pub fn main() void {
std.debug.print("斐波那契数列第10项: {}\n", .{FIB_10});
std.debug.print("斐波那契数列第15项: {}\n", .{FIB_15});
}变量和常量的最佳实践
1. 优先使用常量
zig
const std = @import("std");
pub fn main() void {
// ✅ 好的做法:默认使用 const
const user_name = "用户";
const max_attempts = 3;
const timeout_ms = 5000;
// 只有需要修改时才使用 var
var current_attempts = 0;
var is_connected = false;
std.debug.print("用户: {s}, 最大尝试次数: {}\n", .{ user_name, max_attempts });
}2. 明确的命名约定
zig
const std = @import("std");
// 全局常量:SCREAMING_SNAKE_CASE
const MAX_BUFFER_SIZE = 4096;
const DEFAULT_TIMEOUT = 30;
pub fn main() void {
// 局部变量和常量:snake_case
const user_id = 12345;
var connection_count = 0;
// 类型:PascalCase
const Point = struct {
x: f32,
y: f32,
};
const origin = Point{ .x = 0.0, .y = 0.0 };
std.debug.print("用户ID: {}, 连接数: {}\n", .{ user_id, connection_count });
}3. 合理的作用域
zig
const std = @import("std");
pub fn main() void {
const global_config = "全局配置";
// 限制变量作用域
{
const local_temp = "临时数据";
var processing_flag = true;
// 在这个块中使用临时变量
if (processing_flag) {
std.debug.print("处理中: {s}\n", .{local_temp});
processing_flag = false;
}
}
// local_temp 和 processing_flag 在这里不可访问
std.debug.print("全局配置: {s}\n", .{global_config});
}4. 避免全局可变状态
zig
const std = @import("std");
// ❌ 避免:全局可变变量
// var global_state = 0;
// ✅ 更好:使用结构体封装状态
const AppState = struct {
counter: i32,
message: []const u8,
pub fn init() AppState {
return AppState{
.counter = 0,
.message = "初始状态",
};
}
pub fn increment(self: *AppState) void {
self.counter += 1;
}
};
pub fn main() void {
var app_state = AppState.init();
std.debug.print("初始: {}, {s}\n", .{ app_state.counter, app_state.message });
app_state.increment();
app_state.message = "更新状态";
std.debug.print("更新: {}, {s}\n", .{ app_state.counter, app_state.message });
}实践练习
练习 1:配置管理
zig
const std = @import("std");
// TODO: 定义应用程序配置常量
// - 应用名称
// - 版本号
// - 默认端口
// - 最大连接数
// TODO: 定义一个配置结构体,包含可变的运行时配置
pub fn main() void {
// TODO: 创建配置实例
// TODO: 修改运行时配置
// TODO: 打印所有配置信息
}练习 2:计数器系统
zig
const std = @import("std");
// TODO: 实现一个计数器系统
// - 支持增加、减少、重置操作
// - 有最大值和最小值限制
// - 记录操作历史
const Counter = struct {
// TODO: 定义字段
pub fn init(min_val: i32, max_val: i32) Counter {
// TODO: 实现初始化
}
pub fn increment(self: *Counter) bool {
// TODO: 实现增加操作
}
pub fn decrement(self: *Counter) bool {
// TODO: 实现减少操作
}
pub fn reset(self: *Counter) void {
// TODO: 实现重置操作
}
};
pub fn main() void {
// TODO: 测试计数器系统
}总结
本章深入探讨了 Zig 中变量和常量的使用:
- ✅ 变量 (
var) 和常量 (const) 的区别 - ✅ 编译时常量 vs 运行时常量
- ✅ 不同类型数据的可变性
- ✅ 全局变量、线程局部变量的使用
- ✅ 变量的生命周期和作用域
- ✅ 常量表达式和编译时计算
- ✅ 最佳实践和代码风格
理解变量和常量的正确使用是编写高质量 Zig 代码的基础。在下一章中,我们将学习 Zig 的循环结构。