AI Library · 知识库

# 变量与可变性:计数器和固定配置

学习目标:区分声明、赋值与遮蔽,用固定上限和可变计数器观察类型推断及作用域。

前置知识与范围:整数比较、简单循环和函数;本篇只讨论局部标量,不展开共享状态或并发同步。

# 问题与边界

一次任务最多执行 3 步:固定配置 limit = 3,计数器从 0 开始,每次循环增加 1,达到上限停止。预览范围使用自己的上限 1,不能修改任务上限;最终计数必须为 3。每个实现都检查初始值、预览值、外层配置与最终值。

这里没有外部输入和业务失败分支:上限是代码内固定的正整数。若断言失败,代表程序违反自身不变量,直接让程序失败,不伪装成正常输出。本篇不接受用户配置、不设计可热更新的配置容器,也不将“绑定不能重赋值”等同于“对象深度不可变”。

# 关键差异

语言 固定配置 可变计数器及推断 同名局部遮蔽
Rust 默认不可变 let let mut 新 let 可遮蔽,甚至改变类型
Go const 常量 := 推断,= 赋值 内层块可以;同块 := 须有新变量
Java final var 推断局部类型 不可在重叠局部作用域重复声明
JS / TS const let;TS 静态推断 内层块可以
Kotlin val var 内层作用域可以
Python 大写命名约定 名称动态绑定 if/for 不建局部作用域;函数可以
C++ const auto 推断 内层块可以

# 各语言实现

下列程序各自完整,使用标准库,分别保存到独立目录。不要把不同语言或不同文章的同名文件混放。

# Rust

Rustfn main() {
    let limit = 3;
    let mut count = 0;
    assert_eq!(count, 0);
    while count < limit {
        count += 1;
    }
    {
        let limit = 1;
        assert_eq!(limit, 1);
    }
    assert_eq!(limit, 3);
    assert_eq!(count, 3);
    println!("{count}");
}

mut 允许对同一绑定重新赋值,但不会允许改变它的类型。遮蔽创建新绑定,与修改旧值不同;内层离开后旧绑定仍在。let 不可重赋值也不等同于编译期 const。参见 Rust 变量与可变性。

# Go

Gopackage main

import "fmt"

func main() {
    const limit = 3
    count := 0
    if count != 0 { panic("wrong initial count") }
    for count < limit {
        count++
    }
    {
        const limit = 1
        if limit != 1 { panic("wrong preview limit") }
    }
    if limit != 3 || count != 3 { panic("outer state changed") }
    fmt.Println(count)
}

Go 没有通用的不可变局部变量关键字;这里的数值可以用真正的常量。若配置来自运行时函数,不能把结果放进 const。内层 := 容易误建同名变量,本例用小块有意展示这个边界。参见 Go 常量与短变量声明。

# Java

Javapublic class Main {
    static final int limit = 3;
    static void preview(int limit) {
        if (limit != 1 || Main.limit != 3) throw new AssertionError();
    }
    public static void main(String[] args) {
        final var taskLimit = limit;
        var count = 0;
        if (count != 0) throw new AssertionError();
        while (count < taskLimit) {
            count++;
        }
        preview(1);
        if (taskLimit != 3 || limit != 3 || count != 3) throw new AssertionError();
        System.out.println(count);
    }
}

var 是编译期局部类型推断,不是动态类型。Java 不允许在仍可见的局部 taskLimit 内再声明同名局部;因此本例用方法参数 limit 遮蔽类字段,显式用 Main.limit 访问字段,而不是写出无法编译的“八语言对齐”内层局部。参见 Java 局部变量与遮蔽规则。

# JavaScript

JavaScriptfunction run() {
    const limit = 3;
    let count = 0;
    if (count !== 0) throw new Error("wrong initial count");
    while (count < limit) {
        count += 1;
    }
    {
        const limit = 1;
        if (limit !== 1) throw new Error("wrong preview limit");
    }
    if (limit !== 3 || count !== 3) throw new Error("outer state changed");
    return count;
}
console.log(run());

const 禁止重新绑定;如果值是对象,仍可能修改其属性。let 和 const 有块作用域,不应把旧式 var 的函数作用域规则直接套上来。参见 ECMAScript let 与 const。

# TypeScript

TypeScriptfunction run(): number {
    const limit = 3;
    let count = 0;
    if (count !== 0) throw new Error("wrong initial count");
    while (count < limit) {
        count += 1;
    }
    {
        const limit = 1;
        if (limit !== 1) throw new Error("wrong preview limit");
    }
    if (limit !== 3 || count !== 3) throw new Error("outer state changed");
    return count;
}
console.log(run());

TS 推断 count 为 number,后续不能赋字符串;这不意味着运行时的 JavaScript 变量获得了额外保护。const 的静态字面量类型与对象的深层只读也是不同问题。参见 TypeScript 变量声明。

# Kotlin

Kotlinfun main() {
    val limit = 3
    var count = 0
    check(count == 0)
    while (count < limit) {
        count += 1
    }
    run {
        val limit = 1
        check(limit == 1)
    }
    check(limit == 3 && count == 3)
    println(count)
}

val 是只读引用,var 可重新赋值。run { ... } 在此只用来建立预览的局部作用域并立即执行,不涉及异步任务;局部同名声明可能触发遮蔽警告,教学示例有意保留。参见 Kotlin 变量与作用域函数 run。

# Python

不要使用会关闭断言的 -O。

Pythondef run() -> int:
    LIMIT = 3
    count = 0
    assert count == 0
    while count < LIMIT:
        count += 1

    def preview() -> None:
        LIMIT = 1
        assert LIMIT == 1

    preview()
    assert LIMIT == 3 and count == 3
    return count

print(run())

LIMIT 大写只是约定,解释器不会阻止重新赋值。函数中的赋值默认创建该函数局部名称,除非声明 global 或 nonlocal;这里刻意通过嵌套函数获得独立作用域。参见 Python 名称绑定与 PEP 8 常量。

# C++

C++#include <cassert>
#include <iostream>

int main() {
    const auto limit = 3;
    auto count = 0;
    assert(count == 0);
    while (count < limit) {
        ++count;
    }
    {
        const auto limit = 1;
        assert(limit == 1);
    }
    assert(limit == 3 && count == 3);
    std::cout << count << '\n';
}

auto 在初始化时推断一次类型,不允许之后变成字符串。const auto 的这个整数不可修改;指针的顶层 const 与所指对象是否 const 则是另一层问题。参见 C++ 占位类型与块作用域。引用是滚动工作草案,不作为新增标准特性的基线证明。

# 迁移误区与适用限制

# 运行与验证

语言版本统一见示例基线,实际执行状态见验证记录。本篇给出的是预期行为,不宣称已经运行通过;各语言按上方命令独立执行。Java 使用显式检查而非默认关闭的 assert;C++ 不要定义 NDEBUG,Python 不使用优化模式,以保留示例断言。

内置检查覆盖初始零值、三次更新、内层配置 1、外层配置保持 3,以及最终计数 3。本篇没有把应当编译失败的代码混入可运行程序;不可重赋值和遮蔽规则由相邻官方引用说明。

预期标准输出:按顺序输出 3,每项独占一行,末尾有换行。 断言失败或未预期异常应使运行失败,而不是继续输出成功提示。

# 相关阅读