Skip to content

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 的循环结构。

本站内容仅供学习和研究使用。