Rust:所有权

    • 拷贝 & 移动
      • 堆栈
      • 拷贝
      • 移动
      • 克隆
    • 所有权
      • 变量的初始权限
      • 指针的双重权限
      • 权限的动态变化
      • 引用赋值
        • 重新借用
      • 函数调用时的权限
        • 移动
        • 拷贝
        • 借用
          • 不可变借用
          • 可变借用
      • 复合类型的权限
        • 结构体
        • 元组
        • 数组


传统语言的内存管理要么依赖程序员手动管理(C/C++),要么依赖垃圾回收器(Java/Python)。Rust开创性地提出了第三条路:通过编译期的所有权系统自动管理内存,既保证内存安全,又避免运行时开销

手动管理存在四大问题:

  1. 内存泄漏:忘记释放
  2. 悬垂指针:使用已释放的内存
  3. 重复释放:对同一内存多次释放
  4. 野指针:使用未初始化指针

垃圾回收的代价:

  • 运行时性能开销
  • 不可预测的暂停时间
  • 额外的内存占用

Rust通过所有权系统在编译期解决所有内存安全问题,运行时零开销


拷贝 & 移动

堆栈

在类似于 C/C++,Rust 这样的语言中,用户可以自己决定把数据存放在堆区或者栈区。

当在函数内部声明一个局部变量,就是把数据存放在了栈区,这个栈区内存会跟随着函数的结束而自动销毁。

Rust想把内存开在堆上时,可以使用Box这个智能指针:

fn heap_allocation() {let x = 5; // 在栈区分配内存let r = Box::new(42);        // 在堆上分配整数let arr = Box::new([1,2,3]); // 在堆上分配数组// Box在栈上存储指针,指向堆上的实际数据println!("堆上的值: {}", r);
}   // 离开作用域时,堆内存自动释放

变量 r 在栈区,其内存储了一个指针,指针指向了堆区的内存,当变量 r 离开作用域销毁时,会把堆区的内存一起释放。

在这里插入图片描述

如图中,变量x存储的就是真实的数据5,而两个 Box 指针存储的是指向堆区的地址,可以通过地址找到对应的数据。

当函数heap_allocation调用结束,三个栈区的变量会随着函数一起被回收,但是堆区的数据怎么办?

在这里插入图片描述

在 C/C++ 中这种情况很容易发生,然是 Rust 杜绝了这种情况。

在 Rust 中,指针分为原生的指针 & 和· &mut 它们是非所有权指针,在编译器就会被借用检查器进行检查,防止内存泄露,具体机制后文会讲解。

另一种指针是智能指针,比如此处的 Box 就是一个智能指针。它使用了 RAII 机制,当 rarr 离开作用域,会把对应的堆区内存一起回收。可以理解为智能制造是一种所有权指针,它决定了堆区数据的生命周期有多久,并保证其会被回收。

对于这种内存模型,还有一种问题就是堆区内存的拷贝问题,这会导致重复释放,或者内存泄露这样的问题。

例如以下情况:

在这里插入图片描述

最开始变量 r1 指向了堆区的数据,随后变量 r2 指向了相同的数据。

如果 r1r2 都是智能指针,栈区被销毁的时候,堆区的 42 就会被分别释放两次,导致重复释放。

如果 r1r2 都是非所有权指针,比如 &i32,栈区被销毁的时候,堆区的 42 不会发生销毁,导致内存泄露。

此时就需要 Rust 的拷贝机制 + 所有权机制一起配合了,Rust 保证上图的情况一定不会发生。


拷贝

对于普通变量,= 赋值默认就是拷贝:

fn simple_copy() {let x = 5;      // 普通整数let y = x;      // 发生拷贝,而非移动println!("x: {}, y: {}", x, y);  // 两者都有效
}

普通变量(整数、浮点数、字符、布尔值)的数据存储在栈上,拷贝成本低,并且会随着栈内存自动释放

在这里插入图片描述

当函数 simple_copy 调用结束,由于两个变量都在栈区,会被正常销毁。

但是对于智能指针,就不能这么处理,否则会导致前文的重复释放问题,此时就要引入另一个概念:移动。


移动

当智能指针进行赋值或传参时,Rust会发生移动(move),即所有权的转移。

fn move_semantics() {let r1 = Box::new(42);let r2 = r1;  // 所有权从r1转移到r2// println!("{}", r1);  // 编译错误:r1已失效println!("{}", r2);     // 正确:r2是新的所有者
}

如下图:

在这里插入图片描述

起初 r1 指向堆区内存,后续把 r1 赋值给 r2,它触发的不是拷贝,而是移动。此时 r2 指向堆区数据,但是 r1 失效了,被标记为 已移动。后续用户不能通过 r1 访问堆区变量,当函数调用结束,r1 也不会触发 RAII 销毁堆区的数据。

移动语义通过确保同一时刻只有一个所有者,防止了重复释放问题


克隆

如果用户就是希望把堆区的内存拷贝一份,创建两个副本怎么办?

智能指针提供了 clone() 方法,它会进行深拷贝,把堆区的数据也拷贝一份。

fn box_clone() {let r1 = Box::new(42);let r2 = r1.clone();  // 调用clone方法,深拷贝// 现在两个Box指向不同的堆内存println!("r1: {}, r2: {}", r1, r2);  // 都有效
}

内存模型如下图:

在这里插入图片描述

克隆不是把栈区的指针进行拷贝,而是对堆区的数据进行了深拷贝,随后两个指针指向了不同的堆区数据。

clone()方法会分配新的堆内存并复制数据,成本较高但安全


所有权

Rust的权限系统是理解所有权和借用的基础。通过精确控制变量对数据的访问权限,Rust在编译期就能保证内存安全。

Rust所有权系统建立在三条规则之上:

  1. 每个值都有且仅有一个所有者
  2. 所有权可以移动,但转移后原变量失效
  3. 所有者离开作用域时,值被自动释放

而为了实现这三条规则,引入了 RWO 三种权限,在编译期就会对权限进行检查。

Rust中的每个变量对其数据可能拥有三种基本权限:

  • R (Read): 读取数据的权限,允许读取但不能修改数据
  • W (Write): 修改数据的权限,允许修改数据的内容
  • O (Own): 所有权权限,允许转移所有权或释放内存

变量的初始权限

变量在声明时会获得初始权限,这些权限直接决定了变量能进行什么操作。

不可变变量

当我们使用let声明一个不可变变量时:

  • 获得R权限:因为任何变量都应该能读取自己的值
  • 获得O权限:因为变量需要负责管理自己的内存
  • 没有W权限:因为是不可变的,所以不能修改
let x = Box::new(42);
println!("{}", x);         // ✓ 使用R权限读取
// *x = 10;                // ✗ 没有W权限,不能修改
let y = x;                 // ✓ 使用O权限转移所有权

可变变量

使用let mut声明的可变变量获得完整的RWO权限:

let mut x = 5;
println!("{}", x);         // ✓ 使用R权限读取
*x = 10;                   // ✓ 使用W权限修改
let y = x;                 // ✓ 使用O权限转移所有权

不可变引用

不可变引用是Rust中最常见的借用形式。它指向的值仅有R权限:只能读取,不能修改

let x = 5;
let ref_x = &x;
println!("{}", ref_x);     // ✓ 使用引用的R权限
// *ref_x = 10;            // ✗ 指向的值没有W权限,不能修改

可变引用

相比于不可变引用,可变引用多出来 W 权限,可以修改指向的值。

let mut x = 5;
let ref_mut_x = &mut x;
println!("{}", ref_mut_x);  // ✓ 使用引用的R权限
*ref_mut_x = 10;           // ✓ 使用指向值的W权限

指针的双重权限

在Rust中,指针(引用)存在两层权限:

  1. 指针变量本身的权限:控制指针本身的读取、修改和所有权
  2. 指针指向内容的权限:控制对指针所指向数据的访问权限
let mut value = 42;
let ptr = &mut value;// ptr本身的权限:
// R: 可以读取指针的地址
// O: 拥有指针本身的所有权// *ptr(指向内容)的权限:
// R: 可以读取指向的值
// W: 可以修改指向的值R权限)

对于Rust的原生指针,是一种非所有权指针,它并不持有数据,只能读取或修改数据。也就是说当变量 ptr 离开作用域的时候,不会把指向的数据进行销毁,这是它与智能指针最大的区别。

fn pointer_dual_permissions() {let mut value = 42;let ptr = &mut value;*ptr = 100;
}

当一个变量持有 O 权限,当离开作用域时,就会清理对应的内存。

以上代码中,对于数据 42,最开始 Ovalue 这个变量手中,ptr 引用到数据后没有转移走它的 O 权限。

当函数结束,对于栈区先进后出,ptr 先离开作用域,此时 ptr 自己销毁自己,但是不会销毁数据 42。而后 value 离开作用域,它持有数据 42 的所有权,就会把该数据一起销毁。


权限的动态变化

权限并非一成不变的,而是会随着创建引用,参数传递等行为发生变化。

不可变引用

let mut data = Box::new(42);    
// *data: R W O - 初始拥有全部权限let borrowed = &data;                    
// *data: R - 保留读权限,失去 W O 权限
// **borrowed: R - 指向内容只读// *data = 100;            // 错误:data失去W权限
// let moved = data;       // 错误:data失去O权限println!("{}", borrowed);  
// borrowed使用结束
// *data重新获得 W O 权限*data = 100;              // 正确:权限已恢复

当创建不可变引用时:

  • 原变量保留基本的R权限,暂时失去WO的权限
  • 引用获得R权限
  • 引用结束后,原变量恢复权限

这是因为 borrow 是一个不可变引用,那么在这个引用存在期间,Rust 向用户保证数据不会发生任何改变,更不会被销毁或者转移。

为了实现这个特性,Rust 把原本 *dataWO 权限给关闭了,防止用户通过 *data 来修改或者转移变量,保证内容不变且不会被转移。

可变引用

let mut value = Box::new(42);
// *value: R W Olet mut_ref = &mut value;
// *value: - (失去所有权限)
// **mut_ref: R W// println!("{}", value);  // 错误:没有 R 权限**mut_ref = 100;        // 正确:**mut_ref有W权限
// mut_ref使用结束
// *value重新获得 R W O 权限

当创建可变引用时:

  • 原变量保留基本的R权限,暂时失去RWO的权限
  • 引用获得RW权限
  • 引用结束后,原变量恢复权限

相比于不可变引用,可变引用多出 W 权限,对应的原变量也会暂时失去 W 权限。


引用赋值

前文提到了拷贝与移动两个概念,对于引用和可变引用,它们赋值时的行为是不同的。

不可变引用

let data = Box::new(42);
let ref1 = &data;
// **ref1: R let ref2 = ref1; // 拷贝引用
// **ref2: R // ref1和ref2都指向同一个数据
println!("{ref1} {ref2}");   // ✓ 正确:两个引用可以共存

以上代码中,ref2ref1 进行了拷贝,两个引用同时持有对数据的 R 权限。

Rust 允许多个不可变引用同时读取一块数据

可变引用

let mut value = Box::new(42);
// *value: R W Olet mut_ref1 = &mut value;
// **mut_ref1: R W
// *value: - (失去所有权限)let mut_ref2 = mut_ref1;    // 移动所有权
// **mut_ref2: R W
// **mut_ref1: - (失去所有权限)println!("{}", mut_ref1); // ❌ 错误:mut_ref1已失效
println!("{}", mut_ref2); // ✓ 正确:只有mut_ref2可用

对于可变引用,通过 = 进行赋值时,发生移动,此时 **mut_ref1 失去了所有权限,不允许再使用。

Rust 同时只允许一个可变引用指向数据

以上内容,其实揭示了Rust的一个核心规则:不允许一个数据同时存在“可变性”和“别名”

如果一个数据是可变的,那么只有一个变量可以操作它。(独占读写)

  • 创建一个可变引用,原变量会失去所有权限。
  • 可变引用发生赋值,是发生了移动而非拷贝。

如果一个数据是不可变的,那多个变量可以同时进行读取。(共享只读)

  • 创建一个不可变引用,原变量保留 R 权限,失去 WO
  • 不可变引用发生赋值,是发生了拷贝,此时多个引用指向同一数据。

重新借用

重新借用允许我们基于现有引用创建新的引用,而不需要访问原始数据:

let data = Box::new(42);
let data_ref = &data;// 通过已有的可变引用重新借用
let reborrowed = & *data_ref ;

分析一下语法,data_ref 是指向数据的引用,*data_ref 先解引用获取数据,再通过&进行引用,这样reborrowed就引用到了相同的数据。

但是不觉得很别扭吗?先解引用,再进行引用,明明 let reborrowed = data_ref 就可以完成相同操作,为什么还需要重新借用?

重新借用主要是为了创建一个更短生命周期的引用,或者缩小引用的权限。

例如以下代码:

let mut data = Box::new(42);
let outer_ref = &mut data;
// *outer_ref: R W{let inner_ref = outer_ref;// *outer_ref: - 失去所有权限// *inner_ref: R Wprintln!("inner: {inner_ref}");
}println!("outer: {outer_ref}"); // 错误: 没有任何权限

这段代码会报错,因为 outer_ref 是一个可变引用,先前讲过可变引用赋值时,进行的是移动。当inner_ref移动走了outer_ref的权限后,在内部作用域结束时,却不归还这个权限,所以外部的outer_ref就无法访问了。

如果想要实现这种,临时的更短生命周期的引用,就需要重新借用:

let mut data = Box::new(42);
let outer_ref = &mut data;
// *outer_ref: R W{let inner_ref = &mut *outer_ref; // 重新借用// *outer_ref: - 失去所有权限// *inner_ref: R Wprintln!("inner: {inner_ref}");
} // inner_ref 生命周期结束,*outer_ref 恢复 RW 权限println!("outer: {outer_ref}"); // 正确

重新借用,会在新的引用生命周期结束时,恢复原引用的权限

因为在 Rust 中,认为移动赋值这种行为,就是把我持有的数据交给你了,以后我不再使用了。所以使用 let inner_ref = outer_ref 创建引用,outer_ref 的权限不会恢复。

但是 &mut *outer_ref 在语义上明确了,我是在创建一个针对底部数据新的引用,我并没有表示我以后不再使用 outer_ref 了,所以当 inner_ref 生命周期结束,outer_ref 的所有权会被归还。

此外,重新借用还可以缩小所有权:

let mut data = Box::new(42);
let outer_ref = &mut data;
// *outer_ref: R W{let inner_ref = & *outer_ref; // 重新借用// *outer_ref: R// *inner_ref: Rprintln!("inner: {outer_ref}"); // 正确println!("inner: {inner_ref}"); // 正确
} // inner_ref 生命周期结束,*outer_ref 恢复 RW 权限println!("outer: {outer_ref}"); // 正确

原本的 outer_ref 是一个可变引用,在内部作用域,重新借用为了一个不可变引用。此时 *outer_ref 保留 R 失去 W,可以理解为在这一小段作用域内部,用户表明不会通过引用修改数据,缩小了权限。


函数调用时的权限

函数调用是 Rust 所有权系统的重要应用场景。根据传递的参数类型,Rust 会采用不同的权限处理策略。


移动

当传递具有所有权的类型(如 StringVecBox 等)时,会发生所有权转移:

fn take_ownership(inner: Box<i32>) {println!("函数内: {}", inner);// *inner 在这里拥有完整的所有权
} // inner 离开作用域,Box 被自动释放fn main() {let outer = Box::new(42);// *outer: R W Otake_ownership(outer); // *outer: - 所有权转移到函数内,失去所有权限// println!("{}", outer); // 错误:outer 已失效
}

权限变化分析:

  • 调用前:*outer 拥有 R W O 权限
  • 调用时:*outer 失去所有权限,转移到函数参数 *inner
  • 函数内:*inner 拥有 R W O 权限,可以自由使用和销毁
  • 调用后:inner 连带着堆区的 Box 一起释放,*outer 永久失效

如图:

在这里插入图片描述

当在 main 函数中调用 take_ownership,此时参数传递,原本outer指向堆区内存,移动给了inner,不能再通过outer访问堆区数据。

在这里插入图片描述

函数调用结束后,inner变量伴随着函数的栈区被释放,与此同时由于inner持有O权限,触发 innerRAII 机制,把堆区的数据一并回收。

很多人此时去访问outer,就会发生错误,编译器不允许,因为outer调用完后已经失去了所有权限。

假如用户确实是把数据传进去后不在使用了,这个方案是可行的。但是如果还要继续使用,那么就要考虑其他方案了。

例如将变量重新返回:

fn take_ownership(inner: Box<i32>) -> Box<i32> {println!("函数内: {}", inner);inner
}fn main() {let mut outer = Box::new(42);outer = take_ownership(outer);println!("{}", outer); // 正确
}

它的所有权转移如图:

在这里插入图片描述

所有权在调用时移动到内部,在返回时又移动给外部,从始至终只有一个变量可以访问数据,并且堆区数据没有被销毁。


拷贝

对于拷贝的类型(如基本数据类型),会发生值拷贝而不是移动:

fn make_copy(x: i32) {println!("函数内: {}", x);// x 是栈上的值,离开作用域时自动清理
}fn main() {let num = 42;make_copy(num); // num 的值被拷贝到函数内println!("原值仍然可用: {}", num); // ✓ 正确!
}

权限变化分析:

  • 调用前:num 拥有 R O 权限
  • 调用时:num 的值被复制,函数参数 x 获得独立的副本
  • 调用后:num 保持完整的 R O 权限
  • 函数内:x 拥有独立的 R O 权限

如图:

在这里插入图片描述

这种情况下,内外两个变量从头到尾持有的都是不同的数据,不会出现数据的冲突。


借用

使用引用 & 来借用数据,不获取所有权,这是 Rust 中最常用的参数传递方式。

不可变借用
fn borrow_box(inner: &Box<i32>) {println!("借用: {}", inner);// inner 指向的值只有 R 权限,不能修改
}fn main() {let outer = Box::new(42);borrow_box(&outer); // 传递不可变引用println!("原值仍然可用: {}", outer); // 正确
}

权限变化分析:
调用前:*outer 拥有 RO 权限
调用时:*outer 保留 R 权限,暂时失去 O 权限
函数内:*inner 获得 R 权限
调用后:*outer 重新获得 O 权限

在这里插入图片描述

借用时,内部通过不可变引用拿到数据的权限。

在这里插入图片描述

当函数调用结束,inner跟随栈区销毁,由于是借用,不具有O的所有权,因此不会把堆区的数据一起销毁,而是把权限归还外部。


可变借用
fn borrow_box(inner: &mut Box<i32>) {println!("借用: {}", inner);**inner = 100;
}fn main() {let mut outer = Box::new(42);borrow_box(&mut outer); // 传递可变引用println!("原值仍然可用: {}", outer); // 正确
}

权限变化分析:

  • 调用前:*outer 拥有 RWO 权限
  • 调用时:*outer 保留 R 权限,暂时失去 WO 权限
  • 函数内:*inner 获得 R 权限
  • 调用后:*outer 重新获得 WO 权限

看起来可变借用形式传参,与不可变借用是相同的,只是多出来了 W 权限。

看似很合理,但是此处却有蹊跷,现在将代码改成以下写法:

fn borrow_box(inner_ref: &mut Box<i32>) {println!("借用: {}", inner_ref);**inner_ref = 100;
}fn main() {let mut outer = Box::new(42);let outer_ref = &mut outer;borrow_box(outer_ref); // 传递可变引用println!("原值仍然可用: {}", outer); // 正确
}

先前说过,可变引用之间的赋值是移动,此处把 outer_ref 赋值到了 inner_ref,按理来说函数调用结束,outer_ref就会失效!

如果你不记得了,可以回看之前的这段代码:

let mut data = Box::new(42);
let outer_ref = &mut data;
// *outer_ref: R W{let inner_ref = outer_ref;// *outer_ref: - 失去所有权限// *inner_ref: R Wprintln!("inner: {inner_ref}");
}println!("outer: {outer_ref}"); // 错误: 没有任何权限

这两个场景是类似的,都是需要在一个更小的作用域内部创建一个新的引用,但是函数调用结束后外部的引用所有权会恢复,而以上代码不会。

这是因为:函数引用传参时,不是直接赋值,而是重新借用

以下两行代码是完全等效的:

borrow_box(outer_ref);
borrow_box(&mut *outer_ref);

因为 Rust 会隐式的把引用传参转变为重新借用,当内部函数调用完毕,外部的引用会恢复所有权


复合类型的权限

到目前为止,我们主要分析的对象都是单个值(比如 Box<i32>i32)。但在真实项目里,绝大多数数据结构都不是“单块变量”,而是复合类型:结构体、元组、数组、切片。

结构体

结构体的每一个字段都相当于一个变量,它们的权限是彼此独立的

struct Person {name: String,age: i32,
}fn struct_permissions() {let mut person = Person {name: String::from("Alice"),age: 25,};let name_ref = &person.name;   // 借用 person.name: 失去 W + O// person.age: 权限不受影响person.age += 1;               // 可以修改 age,因为它的权限没损失// person.name.push_str(" Smith"); // 错误,name 被不可变借用,失去 W 权限
}

Rust 编译器能把借用精确到字段级,当你借用 person.name 时,只这一块字段的权限发生衰减,person.age 完全不受影响,这意味着 结构体允许局部可变,只要字段之间没有交叉冲突


元组

元组其实就是匿名结构体,所以规则完全一致,各个位置的元素是互相独立的字段。

fn tuple_permissions() {let mut tuple = (String::from("hello"), 42);let str_ref = &tuple.0;       // tuple.0: 权限衰减至 R// tuple.1: 权限完整tuple.1 += 1; // 正确,独立字段// 如果传给函数时整体借用:fn get_first(t: &(String, i32)) -> &String {&t.0}let first = get_first(&tuple); // 此时 tuple 整体失去 W+O 权限
}

区别重点在最后一段:

  • &tuple.0:精准借用一个字段
  • &(tuple):整体借用

数组

数组和切片与结构体最大的不同在于:它们是连续存储。 一旦借走了一个元素,编译器就不能保证不会通过偏移方式访问到其它元素,于是采用了保守策略:借用一个元素时,整个数组失去所有权

fn array_permissions() {let mut arr = [1, 2, 3, 4, 5];let first_ref = &mut arr[0]; // 可变借用第一个元素// 整个数组失去 R W O 权限// arr[1] += 1;             // 错误,权限被整体锁定// let second = arr[2];     // 同理,不能读取*first_ref = 10;            // 只能通过已有借用操作
}

数组在内存布局上是一片连续空间,arr[0]arr[1] 物理上只差一个偏移。译器无法静态推断两个元素访问一定互不干扰,于是干脆剥夺整个数组的所有权,保证绝对安全。


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

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

相关文章

Elasticsearch数据迁移快照方案初探(二):快照创建与多节点存储问题解决

快照仓库创建成功 经过前面的配置修改&#xff0c;我们成功创建了快照仓库&#xff1a; curl -X PUT "https://[ES_HOST]:9200/_snapshot/backup_repo" \-H "Content-Type: application/json" \-u "[USERNAME]:[PASSWORD]" \-k \-d {"type&…

DeepSeek大模型风靡云平台,百度智能云、阿里云、腾讯云等多个平台宣布上线DeepSeek模型

近日&#xff0c;百度智能云、华为云、阿里云、腾讯云、360数字安全、云轴科技等多个平台纷纷宣布上线DeepSeek大模型&#xff0c;这一消息无疑为AI开发者和企业用户带来了全新的机遇和选择。本文将探讨DeepSeek大模型上线的背景、意义以及未来的发展趋势。 首先&#xff0c;我…

position属性

文章目录Position属性&#x1f9ed; 一、position 属性的取值&#x1f4dd; 二、各属性值详解与示例1. static&#xff08;静态定位&#xff09;2. relative&#xff08;相对定位&#xff09;3. absolute&#xff08;绝对定位&#xff09;4. fixed&#xff08;固定定位&#xf…

通信中间件 Fast DDS(二) :详细介绍

目录 1.引言 2.DDS的基本原理 3.FastDDS 的核心特性 4.FastDDS 的核心架构 5.典型应用场景 6.FastDDS 的安装与快速上手 7.学习资源与社区 1.引言 FastDDS&#xff08;原称 Fast RTPS&#xff09;是由西班牙公司 eProsima 开发的一款开源、高性能、实时性强的数据分发服…

【69页PPT】智慧方案智慧校园解决方案(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808811/91776074 资料解读&#xff1a;【69页PPT】智慧方案智慧校园解决方案 详细资料请看本解读文章的最后内容 智慧校园的概念与背景 智慧校园是…

FPGA的工作原理

FPGA&#xff08;现场可编程门阵列&#xff09;的核心工作原理是通过可配置的硬件架构&#xff0c;让用户在芯片出厂后自主定义电路逻辑&#xff0c;实现从“通用硬件”到“专用硬件”的灵活转换&#xff0c;本质是用可编程资源搭建出符合特定需求的数字电路。一、核心架构&…

构建生产级RAG系统:从数据处理到智能体的全流程实践

构建生产级RAG系统&#xff1a;从数据处理到智能体的全流程实践 检索增强生成&#xff08;RAG&#xff09;技术已成为打造高级知识问答系统的核心&#xff0c;但从原型到稳定高效的生产级系统&#xff0c;需突破数据处理、检索优化、智能决策等多重挑战。本文以某型号工业设备…

Java-代理

在 Java 开发中&#xff0c;代理模式是一种非常重要的设计模式&#xff0c;它通过引入代理对象来控制对目标对象的访问&#xff0c;从而实现额外功能的增强。一、代理模式的基本概念代理模式的核心思想是&#xff1a;通过一个代理对象来间接访问目标对象&#xff0c;在不修改目…

【基础知识】互斥锁、读写锁、自旋锁的区别

从定义、工作原理、适用场景和性能开销四个维度来剖析这三种锁的区别 核心结论 这三种锁的核心区别在于它们应对“锁已被占用”情况时的行为策略不同,而这直接决定了它们的性能和适用场景。 锁类型 核心策略 适用场景 互斥锁 (Mutex) 等不到,就睡 通用的独占访问,临界区执行…

智慧清洁革新者:有鹿机器人自述

晨曦微露&#xff0c;当城市还未完全苏醒&#xff0c;我已悄然完成数万平方米的清洁工作。作为有鹿智能巡扫机器人&#xff0c;我很荣幸能与您分享如何以科技之力重塑清洁行业的标准与体验。卓越技术&#xff1a;重新定义清洁新标准我搭载的聪明大脑是基于Master2000通用具身智…

python学习打卡day48

知识点回顾&#xff1a; 随机张量的生成&#xff1a;torch.randn函数卷积和池化的计算公式&#xff08;可以不掌握&#xff0c;会自动计算的&#xff09;pytorch的广播机制&#xff1a;加法和乘法的广播机制 ps&#xff1a;numpy运算也有类似的广播机制&#xff0c;基本一致 im…

记一次雪花算法 ID 精度丢失的Bug:前端接收到的 Long 被“四舍五入”了?

后端生成的 ID&#xff1a;1961005746230337538 前端收到的 ID&#xff1a;1961005746230337500 —— 少了 38&#xff1f;&#xff01;这不是 Bug&#xff0c;是 JavaScript 的“安全整数”陷阱&#xff01;本文记录一次真实项目中因 雪花算法 ID 精度丢失 导致的线上问题&…

零知开源——基于STM32F407VET6和ADXL345三轴加速度计的精准运动姿态检测系统

✔零知IDE 是一个真正属于国人自己的开源软件平台&#xff0c;在开发效率上超越了Arduino平台并且更加容易上手&#xff0c;大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码&#xff0c;让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品&am…

Android14 init.qcom.usb.rc详解

本文以高通平台为例&#xff0c;介绍其USB子系统启动以及USB各种配置动态切换的逻辑&#xff0c;它是以configfs架构实现动态配置USB。 相关文档 1. USB子系统的启动 1.1 on boot阶段 1.1.1 重启恢复用户选择的USB配置 当设备重启时恢复用户选择的USB配置&#xff0c;避免每…

Docker的常用命令及简单使用

1、docker的常用命令 1.1、帮助命令 docker version # 显示docker的版本信息 docker info # 显示docker的系统信息&#xff0c;包括镜像和容器的数量 docker 指令 --help # 查看某个指令的帮助命令可以通过docker --help查看docker常用命…

HGDB全文检索/中文分词的使用

文章目录文档用途详细信息文档用途 本文用于HGDB全文检索/中文分词的介绍&#xff0c;其介绍内容在附件&#xff0c;使用案例见正文 详细信息 一、创建扩展 highgo# create extension zhparser;CREATE EXTENSION highgo# \dFp List of text search parsers Schema…

baijian xiaomaodawang

我将为你创建一个基于Go 1.20.8和Gin框架的博客系统项目。以下是完整的实现方案&#xff1a; 项目创建流程 打开Goland&#xff0c;创建新项目选择Go项目&#xff0c;设置GOROOT为Go 1.20.8项目名称&#xff1a;blog-system启用Go Modules 项目结构 blog-system/ ├── cmd/ │…

Node.js的特性

Node.js的特性 Node.js具有几个显著特性&#xff1a; 事件驱动&#xff1a;Node.js采用事件驱动机制来处理请求和响应&#xff0c;这种机制可以帮助开发者处理大量并发请求&#xff0c;提高系统的性能和可靠性。 非阻塞I/O&#xff1a;Node.js使用异步I/O原语来实现非阻塞I/O操…

交叉编译linux-arm32位程序

目标平台rv1126 芯片 arm32位架构 在ubuntu22.04上交叉编译&#xff1a; 编译器下载地址&#xff1a; Linaro Releases 或者&#xff1a; wget http://releases.linaro.org/components/toolchain/binaries/6.4-2017.11/arm-linux-gnueabihf/gcc-linaro-6.4.1-2017.11-x86_6…

S 3.1深度学习--卷积神经网络

卷积层 图像原理 卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09; 图像在计算机中是一堆按顺序排列的数字&#xff0c;数值为 0 到 255。0 表示最暗&#xff0c;255 表示最亮。 图像识别 上图是只有黑白颜色的灰度图&#xff0c;而更普遍的图片表达…