【Rust 精进之路之第3篇-变量观】`let`, `mut` 与 Shadowing:理解 Rust 的变量绑定哲学

系列: Rust 精进之路:构建可靠、高效软件的底层逻辑
作者: 码觉客
发布日期: 2025-04-20

引言:为数据命名,Rust 的第一道“安全阀”

在上一篇文章中,我们成功搭建了 Rust 开发环境,并用 Cargo 运行了第一个程序,迈出了坚实的一步。现在,是时候深入了解构成程序的基本单元了。变量,作为在内存中存储和引用数据的核心机制,在任何编程语言中都至关重要。你可能对 C、Java 或 Python 等语言中的变量声明和使用非常熟悉。

然而,当你开始接触 Rust 时,会发现它在处理变量的方式上,从一开始就展现了其独特且深思熟虑的设计理念。最引人注目的就是对“可变性”的严格控制。与许多主流语言默认变量可变不同,Rust 坚定地选择了默认不可变 (immutable)。这个看似增加了少许“麻烦”的设计,实际上是 Rust 强大的安全保证体系的基石,对于编写可维护、尤其是在并发环境下可靠的代码至关重要。

本文将详细探讨 Rust 中变量的声明方式 (let)、如何审慎地引入可变性 (mut)、定义真正恒定值的常量 (const) 与贯穿程序生命周期的静态变量 (static),以及一个既实用又可能引起讨论的特性——变量“遮蔽 (Shadowing)”。理解这些概念及其背后的原因,不仅是掌握 Rust 语法的基本要求,更是开始领悟 Rust 如何从语言层面就帮助我们构建更健壮、更易于推理的软件系统的关键所在。

一、let:默认的契约——不可变绑定与类型推断

在 Rust 中,我们使用 let 关键字来引入一个新的变量绑定。值得注意的是,Rust 社区倾向于使用“绑定 (binding)”而非“赋值 (assignment)”,以强调 let 语句是将一个名称与一块内存数据关联起来的行为。而这个绑定的核心特性就是:默认不可变

fn main() {// 使用 let 绑定变量 x,并初始化为 5。// Rust 的类型推断足够智能,可以推断出 x 的类型是 i32 (默认整数类型)let x = 5;println!("x 的值是: {}", x); // 输出: x 的值是: 5// 再次尝试给 x 赋予新值 - 这违反了不可变性契约// x = 6; // 编译错误: cannot assign twice to immutable variable `x`let message = "Hello"; // message 被推断为 &str 类型 (字符串切片)// message = "World"; // 同样编译错误println!("message 的值是: {}", message);// 你也可以显式标注类型let y: f64 = 3.14; // 明确指定 y 为 64 位浮点数println!("y 的值是: {}", y);
}

编译器是这条规则的严格执行者。任何对不可变绑定的再次赋值尝试都会在编译阶段被捕获,程序根本无法通过编译。

默认不可变性的深层价值:

这个设计决策是 Rust 安全哲学的核心体现,带来了显著的好处:

  1. 增强代码可读性与可预测性: 当你阅读一段 Rust 代码时,看到一个没有 mutlet 绑定,你可以立即确信这个变量的值在其作用域内不会发生改变。这极大地降低了理解和推理代码状态的认知负担,尤其是在处理复杂逻辑或遗留代码时。想象一下调试一个长函数,如果大部分变量都是不可变的,追踪数据流会容易得多。
  2. 编译时安全保证: 许多难以察觉的运行时 Bug 源于状态的意外变更。Rust 将这种检查提前到编译时,强制开发者明确意图。如果代码能编译通过(在 safe Rust 范畴内),就意味着你已经消除了大量因意外修改变量而导致的潜在错误。
  3. 为“无畏并发”奠定基础: 不可变数据是并发编程的福音。多个线程可以同时读取不可变数据而无需任何同步机制(如锁),因为不存在数据竞争的风险。Rust 的所有权和借用系统(我们将在后面深入学习)与默认不可变性协同工作,构成了其强大的并发安全模型的基础。

同时,Rust 强大的类型推断 (Type Inference) 机制使得在大多数情况下,你无需显式标注变量类型,编译器能根据初始值和上下文推断出来,保持了代码的简洁性。

二、mut:显式声明——审慎地引入可变性

当然,程序需要处理变化的状态。Rust 并没有禁止可变性,而是要求你显式地、有意识地选择它。通过在 let 后面添加 mut 关键字,你可以声明一个变量绑定是可变的 (mutable)

fn main() {// 使用 let mut 声明一个可变变量 counterlet mut counter: u32 = 0; // 显式标注类型为 u32println!("计数器初始值: {}", counter); // 输出: 0counter = counter + 1; // 合法操作,因为 counter 是可变的println!("计数器加 1 后: {}", counter); // 输出: 1let mut name = String::from("Alice"); // 创建一个可变的 Stringprintln!("初始名字: {}", name);name.push_str(" Smith"); // 调用 String 的方法修改其内容println!("修改后名字: {}", name);
}

重点理解: mut 是绑定的一部分,它修饰的是变量名(即这个“标签”允许被贴到不同的值上,或者允许修改其指向的值的内容,取决于类型),而不是类型本身。let mut x: i32 是正确的,而 let x: mut i32 是错误的语法。

可变性的权衡与惯用法:

引入 mut 意味着赋予了代码改变状态的能力,这带来了灵活性,但也引入了复杂性。你需要更仔细地追踪变量值的变化,尤其是在较长的函数或跨模块交互中。

Rust 的编程风格强烈建议优先选择不可变性。只在逻辑确实需要(例如循环计数、累积结果、修改集合内容如 VecString)时才使用 mut。这样做的好处是:

  • 意图清晰: mut 关键字像一个警示牌,明确标示出代码中可能发生状态变化的地方。
  • 局部化影响: 尽量将可变性限制在最小的必要范围内(例如,一个函数内部),避免不必要的可变状态扩散。
  • 拥抱函数式风格: 鼓励通过创建新值(例如使用 map, filter 等迭代器方法,或者使用 Shadowing)来处理数据转换,而不是原地修改。

三、const:恒定之值——编译时确定的不变量

Rust 提供了常量 (Constants),使用 const 关键字声明。它们代表了程序中真正意义上的、固定不变的值。与不可变 let 绑定相比,const 有着更严格的定义和不同的特性:

  1. 绝对不可变: const 不能使用 mut。它们的值在编译后就固定下来。
  2. 编译时求值: const 的值必须是一个常量表达式 (Constant Expression),即其值必须在编译期间就能完全计算出来。不能依赖任何运行时才能确定的信息(如函数调用结果、环境变量等)。
  3. 类型必须显式标注: 声明 const 时,类型注解是强制性的。
  4. 全局可用性: const 可以在任何作用域声明,包括模块的根作用域(全局作用域)。
  5. 无固定内存地址(通常): 编译器通常会将 const 的值直接“内联”到使用它的地方,类似于 C/C++ 中的 #define 但带有类型检查。这意味着常量本身在运行时可能不作为一个独立的内存对象存在。
  6. 命名约定: 遵循全大写字母和下划线分隔的约定(如 SECONDS_IN_HOUR)。
// 定义一些数学和物理常量
const PI: f64 = 3.141592653589793;
const SPEED_OF_LIGHT_METERS_PER_SECOND: u32 = 299_792_458;// 定义配置相关的常量
const MAX_CONNECTIONS: usize = 100;
const DEFAULT_TIMEOUT_MS: u64 = 5000;// 也可以用于简单的计算,只要能在编译时完成
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;fn main() {println!("圆周率约等于: {}", PI);println!("默认超时时间: {}ms", DEFAULT_TIMEOUT_MS);println!("三小时等于 {} 秒", THREE_HOURS_IN_SECONDS);// const 不能是运行时才能确定的值// use std::time::Instant;// const START_TIME: Instant = Instant::now(); // 编译错误!now() 是运行时函数
}// `const fn` 允许在编译时执行更复杂的计算来初始化常量
const fn compute_initial_value(x: u32) -> u32 {x * x + 1 // 这个计算可以在编译时完成
}
const INITIAL_VALUE: u32 = compute_initial_value(5); // 合法

const 的价值:

  • 语义清晰: 明确表达一个值是程序设计中的固定参数或不变真理。
  • 性能优化: 编译时求值和内联可以提高运行时性能。
  • 代码维护: 将魔法数字或配置值定义为常量,易于查找和修改。

四、static:贯穿全程——具有固定地址的静态变量

Rust 的静态变量 (Static Variables) 使用 static 关键字声明,它们代表在程序的整个生命周期内都存在的值。其关键特性:

  1. 'static 生命周期: 这是 Rust 中最长的生命周期,表示变量与程序本身“同寿”。
  2. 固定内存地址:const 不同,static 变量在内存中有确定的、固定的存储位置。程序中所有对该 static 变量的引用都指向这个唯一的地址。这使得获取静态变量的引用(指针)成为可能。
  3. 可变性 (static mut) 与 unsafe
    • static 变量可以是可变的,使用 static mut 声明。
    • 然而,任何对 static mut 变量的访问(读取或写入)都必须在 unsafe { ... } 代码块中进行。这是 Rust 的一个核心安全规则。
    • 原因: 全局可变状态是数据竞争的主要温床。编译器无法在编译时静态地保证对 static mut 的并发访问是安全的(因为它绕过了借用检查器的保护)。unsafe 块意味着开发者向编译器保证:“我知道这里的风险,并且我已经采取了必要的外部措施(如锁、原子操作或其他同步机制)来确保线程安全。” 如果没有这些措施,就可能导致未定义行为。
  4. 类型必须显式标注。
  5. 命名约定:const 相同,全大写。
use std::sync::atomic::{AtomicUsize, Ordering};// 不可变的静态变量,通常用于全局配置或只读数据
static APPLICATION_VERSION: &str = "1.0.2";// 使用原子类型实现线程安全的全局计数器 (推荐方式)
static SAFE_COUNTER: AtomicUsize = AtomicUsize::new(0);// 可变的静态变量 (极不推荐,仅作演示)
static mut UNSAFE_GLOBAL_DATA: Vec<i32> = Vec::new(); // 全局可变 Vec,非常危险!fn increment_safe_counter() {// 原子操作是线程安全的,不需要 unsafeSAFE_COUNTER.fetch_add(1, Ordering::SeqCst);
}fn add_to_unsafe_data(value: i32) {// 必须使用 unsafe,并且需要外部同步来保证安全,这里省略了同步,非常危险!unsafe {UNSAFE_GLOBAL_DATA.push(value);}
}fn main() {println!("应用版本: {}", APPLICATION_VERSION);increment_safe_counter();println!("安全计数器: {}", SAFE_COUNTER.load(Ordering::SeqCst)); // 输出: 1// add_to_unsafe_data(42); // 即使在单线程,也需要 unsafe// unsafe {//     println!("不安全数据: {:?}", UNSAFE_GLOBAL_DATA);// }// 何时可能需要 static?// 1. 需要一个全局的、有固定地址的实例 (例如 FFI 中传递给 C 库的回调上下文)// 2. 需要一个在编译时初始化,但在整个程序生命周期内保持不变的复杂对象 (可以使用 lazy_static 或 once_cell 库安全地初始化)
}

static vs const 深入对比:

特性conststatic
求值时间编译时编译时初始化 (值必须是常量表达式)
内存地址通常无固定地址 (可能内联)有固定内存地址
生命周期无 (值直接使用)'static (整个程序运行期间)
可变性永不可变mut (但访问需 unsafe)
存储位置可能在代码段或优化掉通常在静态数据区 (.data 或 .bss)
主要用途定义不变常量、配置定义全局状态、固定地址数据 (谨慎使用可变)

核心建议: 优先使用 const 定义不变值。需要全局固定地址时才考虑 static极力避免使用 static mut,应选择 Mutex, RwLock, Atomic 类型等线程安全的并发原语来管理共享可变状态,通常结合 lazy_staticonce_cell 来进行安全的初始化。

五、Shadowing (遮蔽):同名新绑定,灵活的值演化

Rust 提供了一个名为遮蔽 (Shadowing) 的特性,它允许你在同一作用域内,使用 let 关键字再次声明一个与先前变量同名的变量。这个新变量会“遮蔽”掉旧变量,使得在当前及内部作用域中,该名称指向的是新变量。

理解遮蔽的关键:

  • 创建全新变量: 遮蔽不是修改(mutate)旧变量的值或类型。它是创建了一个完全独立的新变量,只不过复用了之前的名称。旧变量依然存在,只是在当前作用域内暂时无法通过该名称访问(一旦离开新变量的作用域,旧变量可能重新变得可见)。
  • 允许类型变更: 正因为是创建新变量,所以遮蔽后的变量可以拥有与被遮蔽变量不同的类型。这是它与 mut 修改的核心区别(mut 不能改变变量类型)。
  • 作用域规则: 遮蔽遵循词法作用域。内部作用域的遮蔽不会影响外部作用域。
fn main() {let x = 5;println!("(1) x = {}", x); // 输出: 5// 遮蔽 x,创建一个新的 xlet x = x + 10; // 新 x 的值是旧 x (5) + 10 = 15println!("(2) x = {}", x); // 输出: 15{// 在新的作用域内再次遮蔽 xlet x = "hello"; // 这个 x 是 &str 类型,遮蔽了外层的数字 xprintln!("(3) 内部 x = {}", x); // 输出: hello} // 内部作用域结束,字符串 x 消失// 回到外部作用域,数字 15 的 x 重新可见println!("(4) 回到外部 x = {}", x); // 输出: 15// 示例:逐步处理用户输入let input_str = "  42  "; // 原始输入,类型 &strprintln!("原始输入: '{}'", input_str);let input_str = input_str.trim(); // 遮蔽,去除首尾空格,类型仍为 &strprintln!("去除空格后: '{}'", input_str);let number = input_str.parse::<i32>(); // 尝试解析,结果是 Result<i32, _>// 这里不使用遮蔽,因为需要处理 Resultmatch number {Ok(num) => {// 可以在这里遮蔽 number (如果需要继续使用这个名字)let number = num * 2; // 遮蔽,新 number 是 i32 类型println!("解析成功并乘以 2: {}", number); // 输出: 84}Err(_) => {println!("解析失败");}}// 也可以用 let number = number.unwrap(); 等方式遮蔽,但需确保 Ok
}

遮蔽的实用场景与考量:

  • 值的转换与精炼: 非常适合在一系列步骤中处理数据,每一步的结果用相同的名字表示演化后的状态,例如类型转换、单位换算、数据清洗(如 trim 示例)。这避免了创造一堆类似 value_step1, value_step2 的临时变量名。
  • 保持概念统一: 当一个变量的逻辑含义保持不变,但其具体表示或类型发生变化时,使用遮蔽可以维持代码的概念连贯性。
  • 有限作用域内的临时覆盖: 在一个代码块内部临时使用一个同名变量,而不影响外部同名变量。

注意事项: 虽然遮蔽很方便,但在冗长或复杂的函数中过度使用可能导致混淆——读者需要仔细追踪当前哪个“版本”的变量在起作用。因此,建议在逻辑清晰、作用域相对较小的范围内适度使用遮蔽,始终以代码的可读性可维护性为首要标准。

六、设计哲学:安全、显式与清晰——Rust 对状态管理的深思

通过对 let, mut, const, static 和 Shadowing 的探讨,我们可以更清晰地看到 Rust 在状态管理上的核心设计原则:

  • 安全是默认选项: 默认不可变性将意外修改状态的风险降至最低,构成了 Rust 内存安全和并发安全的基础。
  • 意图必须显式: 无论是引入可变性 (mut) 还是处理潜在不安全的操作 (unsafe for static mut),都需要开发者明确表达意图,不能模棱两可。
  • 区分不同性质的不变性: conststatic 为编译时常量和全局静态值提供了不同的语义和实现,让概念更清晰。
  • 提供受控的灵活性: Shadowing 在不破坏不可变性原则的前提下,提供了一种实用的值演化和名称重用机制。

这些设计并非为了限制开发者,而是为了赋能开发者。通过在编译时强制执行更严格的规则,Rust 帮助我们构建出更可靠、更易于推理、更适应并发环境的软件系统。它将许多传统上需要在运行时担心或通过测试覆盖的问题,提前暴露在开发阶段,大大降低了后期维护成本和风险。

七、常见问题回顾与深化 (FAQ)

  • Q1: 默认不可变会不会让代码更啰嗦?
    • A: 初期可能会感觉需要多打 mut,但长期来看,它带来的代码清晰度和安全性收益远超这点“麻烦”。Rust 的函数式编程特性(如迭代器、mapfilter)和 Shadowing 也提供了很多无需 mut 就能优雅处理数据转换的方法。
  • Q2: static mut 真的很糟糕吗?它存在的意义是什么?
    • A: 是的,它非常容易误用并导致严重问题(数据竞争、未定义行为)。其主要存在意义是为了与 C 语言库进行 FFI(外部函数接口)交互,因为 C 语言中全局可变变量很常见。在纯 Rust 代码中,几乎总有更安全的替代方案(Mutex, Atomic 等)。使用 static mut 意味着你放弃了 Rust 编译器的安全保障,必须自己承担全部责任。
  • Q3: Shadowing 和其他语言的变量重用(比如 Python)有何不同?
    • A: Python 等动态类型语言中,同一个变量名可以随时指向不同类型的值,这是语言动态性的体现。Rust 的 Shadowing 是在静态类型系统下实现的:每次 let 都是一次新的类型检查和绑定,旧变量(及其类型)在作用域内被隐藏。它更像是在同一个“标签”下创建了多个不同类型、生命周期可能重叠但定义独立的变量。
  • Q4: 在函数参数中,是默认不可变吗?如何接受可变参数?
    • A: 是的,函数参数默认也是不可变绑定。如果函数需要修改传入的参数(通常是通过可变引用),参数类型需要明确标记为可变引用,例如 fn modify(value: &mut i32)。我们将在后续关于引用和借用的章节详细探讨。

总结:变量绑定——构筑 Rust 可靠性的第一块砖

本文深入探讨了 Rust 中变量声明与使用的各种机制:let 的默认不可变性、mut 的显式可变性、const 的编译时常量、static 的全局静态变量(以及 static mut 的风险),还有灵活的 Shadowing 特性。

我们不仅学习了它们的语法和行为,更重要的是理解了这些设计背后贯穿着 Rust 对安全性、显式性和清晰性的执着追求。Rust 通过在语言层面就对状态变化进行严格管理,帮助开发者从源头避免错误,构建出更加健壮和可靠的软件。

掌握好 Rust 如何定义和管理变量,是理解其所有权、借用等核心概念的基础。现在我们熟悉了为数据命名的规则,下一站,我们将开始探索 Rust 所提供的丰富的数据类型本身。

下一篇预告:【数据基石·上】标量类型——深入了解 Rust 中的整数、浮点数、布尔和字符类型。这些基础类型在 Rust 中有哪些细节和特性值得我们关注?敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79793.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

stm32(IO口的最高速度)

如果我们写入速度 快到一种程度 肯定就不能完全按理想的来了 当我们写01快起来 中间的保持时间就会越来越少 就逐渐往下面变化 所以其实 我们如果改变上升时间 和 下降时间 还是能将最后的波形 变成为正常的波形的。 不用追求高速 &#xff0c;满足要求下 选低速的即可。 因…

String +memset字符串类题型【C++】

tips&#xff1a; 1、寻找最大公共子串时&#xff0c;如果字符串可以旋转但是不能反转&#xff0c;考虑在每个字符串后重复一次自身&#xff0c;如 "abcd" 变为 "abcdabcd"&#xff0c;这样在用dp就可以了。 如何变环拆环为链&#xff1a; cin>>n&…

基于论文的大模型应用:基于SmartETL的arXiv论文数据接入与预处理(三)

上一篇 介绍了数据接入处理的整体方案设计。本篇介绍基于SmartETL框架的流程实现。 5. 流程开发 5.1.简单采集流程 从指定时间&#xff08;yy年 mm月&#xff09;开始&#xff0c;持续采集arXiv论文。基于月份和顺序号&#xff0c;构造论文ID&#xff0c;进而下载论文PDF文件…

[Swift]Xcode模拟器无法请求http接口问题

1.以前偷懒一直是这样设置 <key>NSAppTransportSecurity</key> <dict><key>NSAllowsArbitraryLoads</key><true/><key>NSAllowsArbitraryLoadsInWebContent</key><true/> </dict> 现在我在Xcode16.3上&#xff…

Python基础总结(八)之循环语句

文章目录 一、for循环1.1 for循环格式1.2 for ...else1.3 for...break1.4 for...continue 二、while循环2.1 while循环格式2.2 while...break2.3 while...continue2.4 while ...else 循环语句就如其名&#xff0c;就是重复的执行一段代码&#xff0c;直到满足退出条件时&#x…

vuex实现同一页面radio-group点击不同按钮显示不同表单

本文实现的是点击单一规格和多规格两个按钮会在页面显示不同的表单 方法一 <!-- 单规格和多规格的切换 --> <el-form label-width"80px" class"text-align-left"><el-form-item label"商品规格"><!-- 监听skus_type的改…

AI编写的“黑科技风格、自动刷新”的看板页面

以下的 index.html 、 script.js 和 styles.css 文件&#xff0c;实现一个具有黑科技风格、自动刷新的能源管理系统实时监控看板。 html页面 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name&q…

Vim使用完全指南:从基础到高效编辑

Vim使用完全指南&#xff1a;从基础到高效编辑 一、Vim简介与基本概念 Vim&#xff08;Vi IMproved&#xff09;是从vi发展出来的一个功能强大的文本编辑器&#xff0c;以其高效性和灵活性著称&#xff0c;特别适合程序开发和系统管理任务。与常规文本编辑器不同&#xff0c;…

时序约束高级进阶使用详解三:Create_Clock

目录 一、前言 二、设计示例 2.1 设计代码 2.2 schematic 2.3 no overwriteing 2.4 约束到非时钟引脚 三、Create_clock应用 3.1 时钟输入端口 3.2 7系列高速收发器输出管脚 3.3 部分原语的输出管脚 3.4 主时钟路径上创建主时钟 3.5 虚拟时钟 3.6 差分时钟的约束 …

箱线图(盒须图)QCPStatiBox

一、QCPStatisticalBox 概述 QCPStatisticalBox 是 QCustomPlot 中用于绘制箱线图(盒须图)的类&#xff0c;可以显示数据的五个关键统计量&#xff1a;最小值、第一四分位数(Q1)、中位数、第三四分位数(Q3)和最大值&#xff0c;以及可能的异常值。 二、主要属性 属性类型描述…

人形机器人马拉松:北京何以孕育“领跑者”?

“机器人每跑一小步&#xff0c;都是人类科技的一大步”&#xff0c;这句对阿姆斯特朗登月名言的仿写&#xff0c;恰如其分地诠释了全球首场人形机器人半程马拉松赛事的里程碑意义。 2025年4月19日&#xff0c;北京亦庄半程马拉松暨人形机器人半程马拉松圆满结束。在总长21.09…

基于Python的推荐算法的电影推荐系统的设计

标题:基于Python的推荐算法的电影推荐系统的设计与实现 内容:1.摘要 本文围绕基于Python的推荐算法的电影推荐系统展开研究。背景在于随着电影数量的急剧增加&#xff0c;用户在海量电影中找到符合自身喜好的影片变得困难。目的是设计并实现一个高效准确的电影推荐系统&#x…

【深度学习】详解矩阵乘法、点积,内积,外积、哈达玛积极其应用|tensor系列02

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; Yaoyao2024往期回顾&#xff1a;【深度学习】你真的理解张量了吗&#xff1f;|标量、向量、矩阵、张量的秩|01每日一言&#x1f33c;: “脑袋想不明白的&#xff0c;就用脚想”…

面试常用基础算法

目录 快速排序归并排序堆排序 n n n皇后问题最大和子数组爬楼梯中心扩展法求最长回文子序列分割回文串动态规划求最长回文子序列最长回文子串单调栈双指针算法修改 分割回文串滑动窗口栈 快速排序 #include <iostream> #include <algorithm>using namespace std;…

相对路径和绝对路径解析

在 Linux/Unix 和文件系统中&#xff0c;绝对路径和相对路径是描述文件或目录位置的两种方式&#xff0c;它们的核心区别在于路径的起点和使用场景。以下是详细对比&#xff1a; 目录 1. 定义与起点 2. 符号与语法 3. 使用场景 4. 实际示例 示例 1&#xff1a;定位文件 示…

【算法数据结构】leetcode37 解数独

37. 解数独 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 题目要求每一行 &#xff0c;每一列&#xff0c;每个3*3 的子框只能出现一次。每个格子的数字范围1-9. 需要遍历每个空格填入可能的数字&#xff0c;并验证符合规则。如果符合就填入&#xff0c;不符…

Vector的学习

vector简介 vector的相关文档对于想深入了解的同学可以参考这个文档进行学习。 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不…

Vue常用指令入门

1. v-for 作用&#xff1a;用于遍历对象或数组 注意&#xff1a;需要提供key属性&#xff0c;可以提高性能和避免渲染错误&#xff0c;值通常为index或item.id <li v-for"(item, index) in items" :key"index">{{ item }} </li>2. v-if,v-el…

在机器视觉检测中为何选择线阵工业相机?

线阵工业相机&#xff0c;顾名思义是成像传感器呈“线”状的。虽然也是二维图像&#xff0c;但极宽&#xff0c;几千个像素的宽度&#xff0c;而高度却只有几个像素的而已。一般在两种情况下使用这种相机&#xff1a; 1. 被测视野为细长的带状&#xff0c;多用于滚筒上检测的问…

线性DP:最长上升子序列(子序列可不连续,子数组必须连续)

目录 Q1&#xff1a;简单遍历 Q2&#xff1a;变式&#xff08;加大数据量&#xff09; Q1&#xff1a;简单遍历 Dp问题 状态表示 f(i,j) 集合所有以第i个数结尾的上升子序列集合-f(i,j)的值存的是什么序列长度最大值max- 状态计算 &#xff08;其实质是集合的划分&#xff09;…