Rust学习笔记004:Rust的所有权机制
内存相关的基础知识
不同语言的内存管理系统
栈和堆
堆和栈的使用
引出所有权方案
String类型
Rust 的所有权机制
- Rust 的所有权机制是一种内存管理系统,它允许在编译时通过所有权、借用和生命周期来确保内存安全,同时避免了垃圾回收的运行时开销。这些概念共同构成了 Rust 强大的所有权系统,通过编译时的检查来防止一类内存错误,如空悬指针和双重释放等。这种系统使得 Rust 具有内存安全性、线程安全性和零成本抽象等特性。
1. 所有权规则:
- 每个值都有一个所有者: 在任意时刻,一个值只能有一个所有者。
- 所有者离开作用域时,值会被销毁: 当拥有某个值的变量离开其作用域时,Rust 会自动调用该值的
Drop
trait 中的drop
方法来释放其占用的资源。
2. 移动(Move):
- 当将一个值赋值给另一个变量时,所有权会从一个变量移动到另一个变量。这会导致原来的变量无法再使用该值。
let s1 = String::from("Hello");// 可以使用 from 函数从字符串字面值创建出 String 类型
let s2 = s1; // 所有权移动到变量 y
// println!("{}", x); // 这里编译器会报错,因为 x 不再拥有值的所有权
- 隐含的一个设计原则: Rust 不会自动创建数据的深拷贝,就运行时性能而言,任何自动赋值的操作都是廉价的
相关:C++中,移动构造函数
-
在C++中,移动构造函数(Move Constructor)是一种特殊的构造函数,用于在对象的所有权被转移(移动)时创建新对象。移动构造函数通常用于提高性能,特别是在涉及资源管理的情况下,比如使用动态内存分配的对象。
-
移动构造函数是通过右值引用(Rvalue Reference)来实现的。在C++11及以后的标准中,可以通过定义移动构造函数来支持右值引用。移动构造函数的目标是在没有不必要的资源复制的情况下,将资源从一个对象“移动”到另一个对象,以提高效率。
-
以下是一个简单的示例,演示了移动构造函数的用法:
#include <iostream>
#include <string>
class MyString {
public:
// 移动构造函数
MyString(MyString&& other) noexcept {
std::cout << "Move constructor called" << std::endl;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
// 构造函数
MyString(const char* str) {
size = strlen(str);
data = new char[size + 1];
strcpy(data, str);
}
// 析构函数
~MyString() {
delete[] data;
}
private:
char* data;
size_t size;
};
int main() {
MyString str1("Hello");
MyString str2 = std::move(str1); // 使用移动构造函数
return 0;
}
在上述例子中,MyString
类具有移动构造函数。在 main
函数中,通过 std::move
将 str1
的所有权移动给了 str2
。移动构造函数被调用后,str1
的资源被移动到 str2
,同时 str1
的数据成员被置为 nullptr
。
注意:在移动构造函数中,我们通常将被移动对象的数据成员设置为安全状态,以避免资源重复释放。此外,通常需要在移动构造函数中使用 noexcept
来指定该函数不会抛出异常,以便在一些情况下提供更好的性能。
移动构造函数对于提高性能和避免不必要的资源复制是非常有用的,特别是在处理大型数据结构、动态分配内存等情况下。
3. 克隆(Clone,Copy):感觉类似c++的深拷贝构造
如果需要拷贝堆上数据而不是移动所有权,可以使用 clone
方法。但这会增加内存开销。
let x = String::from("Hello");
let y = x.clone(); // 创建了一个新的 String 对象,所有权移动到变量 y
println!("x: {}, y: {}", x, y); // 这样是合法的,因为 x 仍然有效
4. 借用(Borrowing):
通过借用,可以暂时获取对值的引用而不移动所有权。借用分为可变借用和不可变借用。
fn main() {
let s1 = String::from("Hello");
let len = calculate_length(&s1); // 通过引用借用 s1
println!("Length of '{}' is: {}", s1, len); // 这里依然可以使用 s1,因为只是借用了引用
// 可变借用
let mut s2 = String::from("Rust");
modify_string(&mut s2); // 通过可变引用借用 s2
println!("Modified string: {}", s2);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn modify_string(s: &mut String) {
s.push_str(" is awesome!");
}
5. 生命周期(Lifetime):
生命周期是用于描述引用有效范围的标记。Rust 的生命周期系统确保引用在其所引用的值有效的情况下才能被使用。
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
fn main() {
let s1 = String::from("Rust");
let s2 = String::from("C");
let result;
{
let s3 = String::from("Java");
result = longest(s1.as_str(), s3.as_str());
} // s3 的生命周期结束,result 引用的值仍然是有效的
println!("The longest string is: {}", result);
}
在上述例子中,longest
函数使用了生命周期参数 'a
,它表示返回的引用的生命周期与输入参数的生命周期一致。
所有权与函数
在 Rust 中,所有权系统与函数的参数传递和返回密切相关,它确保在函数调用过程中对所有权的正确管理。
1. 所有权传递:
当你将一个值传递给函数时,它的所有权会被转移给函数。这意味着在函数中你可以使用该值,但在函数返回后,调用者将无法再使用它。
fn take_ownership(s: String) {
println!("Received: {}", s);
// s 的所有权在这里结束,s 的内存将被释放
}
fn main() {
let my_string = String::from("Hello");
take_ownership(my_string); // my_string 的所有权被传递给 take_ownership 函数
// println!("Value: {}", my_string); // 这里编译器会报错,因为 my_string 已经失去了所有权
}
2. 返回所有权:
函数也可以返回所有权,将所有权还给调用者。这可以通过在函数签名中指定返回类型为 ->
加上类型名。
fn return_ownership() -> String {
let s = String::from("Returned");
s // 所有权被返回给调用者
}
fn main() {
let result = return_ownership(); // 调用 return_ownership 后,result 拥有了返回的所有权
println!("Result: {}", result);
}
3. 引用传递:
-
在所有权用完后,再返回回来?
-
如果你想在函数中使用值但不获取其所有权,可以使用引用传递。引用允许你在不移动所有权的情况下访问值。
fn borrow_value(s: &String) {
println!("Borrowed: {}", s);
// s 是引用,所有权不会在这里转移
}
fn main() {
let my_string = String::from("Hello");
borrow_value(&my_string); // 传递了 my_string 的引用
println!("Value: {}", my_string); // 这是合法的,因为并未传递所有权
}
可变引用
字符串切片
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!