rust变量绑定、拷贝、转移、引用、指针

该博客围绕Rust语言展开,详细介绍了Clone、Copy特征,变量绑定与赋值的不同场景,引用和Borrow的使用规则及生命周期。还深入剖析了多种智能指针,如NonNull、UnsafeCell等的含义、用法和源码,以及指针的搭配使用和分类,最后提及了ref的相关内容。

目录

一,Clone、Copy

1,基本类型

2,类型的Clone特征

3,显式声明结构体的Clone特征

4,类型的Copy特征

5,显式声明结构体的Copy特征

5,变量和字面量的特征

6,特征总结

二,变量绑定、赋值

1,变量绑定:Clone拷贝场景

2,变量绑定:Copy拷贝场景

3,变量绑定:所有权转移场景

4,转移的永久性

5,赋值语句

三,引用、Borrow

1,对常量的引用

2,对变量的不可变引用

3,对变量的可变引用

4,函数调用

5,Borrow、BorrowMut

四,引用总结

1,引用的生命周期

2,对字面量的引用

3,对普通变量的引用

4,对引用变量的引用

5,对同一变量的引用

6,链式引用

五,智能指针

0,相关通用知识

(1)deref

(2)智能指针嵌套

(3)隐式转换导致拥有多个同名函数

1,NonNull

(1)一句话含义

(2)源码解析

2,PhantomData

(1)一句话含义

(2)源码解析

3,UnsafeCell

(1)一句话含义

(2)具体含义

(3)用法示例

(4)安全性

(5)源码解析

4,Atomic*、内部可变性

5,Cell

(1)一句话含义

(2)用法示例

(3)get方法

(4)源码解析

6,RefCell

(1)一句话含义

(2)用法示例

(3)源码解析

7,Mutex

(1)一句话含义

(2)源码解析

8,RwLock

(1)一句话含义

(2)具体含义

(3)源码解析

9,Arc

(1)一句话含义

(2)源码解析

10,RcBox

(1)一句话含义

(2)源码解析

11,Rc

(1)一句话含义

(2)用法

(3)Rc的borrow

(4)源码解析

12,UniqueRc

(1)源码解析

13,Box

(1)源码解析

14,ThinBox

(1)源码解析

六,智能指针搭配使用

1,Arc+Mutex

2,Arc+RwLock

七,指针总结

1,引用

2,胖指针

3,智能指针

4,原始指针、unsafe块

八,ref


一,Clone、Copy

1,基本类型

rust基本类型包括:

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 true 和 false
  • 所有浮点数类型,比如 f64
  • 字符类型,char

2,类型的Clone特征

拥有Clone特征的类型:

  • 基本类型
  • String
  • 容器
  • 显式声明Clone特征的结构体

没有Clone特征的类型:

  • 没有显式声明Clone特征的结构体(结构体默认)

递归决定是否有Clone特征的类型:

  • 元组,当且仅当其包含的类型都有Clone特征的情况下,其自身有Clone特征。

3,显式声明结构体的Clone特征

显式声明结构体的Clone特征的前提条件:当且仅当结构体中的成员都具有Clone特征的情况下,可以显式声明Clone特征。

#[derive(Clone)]
struct S{}

#[derive(Clone)]
struct P{
    a:i32,
    b:S,
}

4,类型的Copy特征

拥有Copy特征的类型:

  • 基本类型
  • 显式声明Copy特征的结构体

没有Copy特征的类型:

  • String
  • 容器
  • 没有显式声明Copy特征的结构体(结构体默认)

递归决定是否有Copy特征的类型:

  • 元组,当且仅当其包含的类型都有Copy特征的情况下,其自身有Copy特征。

5,显式声明结构体的Copy特征

一定是具有Clone特征的类型,才可能具有Copy:

pub trait Copy: Clone {
    // Empty.
}

显式声明结构体的Copy特征的前提条件:当且仅当结构体中的成员都具有Copy特征的情况下,可以显式声明Copy特征。

#[derive(Clone,Copy)]
struct P{
    a:i32,
}

fn main() {
    let x:P=P{a:5};
    let y=x;
    assert_eq!(x.a,5);
}

5,变量和字面量的特征

字面量会自动推导出类型,所以变量和字面量都有唯一确定的类型。

变量和字面量是否具有clone和copy特征,完全取决于其类型是否具有。

6,特征总结

所有类型可以分为3类:

没有clone和copy特征,有clone没有copy特征,有clone和copy特征。

二,变量绑定、赋值

1,变量绑定:Clone拷贝场景

对于有clone特征的变量或字面量,可以调用clone函数进行拷贝而不转移所有权。

#[derive(Clone)]
struct P{
    a:i32,
}

fn main() {
    let x:P=P{a:5};
    let y=x.clone();
    assert_eq!(x.a,5);
}

2,变量绑定:Copy拷贝场景

如果let绑定语句的等号右边是一个有Copy特征的变量或字面量,那么这是一个拷贝行为。

    let x = 5;
    let xx = x;
    assert_eq!(5, x);
    assert_eq!(6, xx+1);

3,变量绑定:所有权转移场景

如果let绑定语句的等号右边是一个没有Copy特征的变量或字面量,那么这是一个所有权转移的行为。

错误代码:

    let x = vec![1,2,3];
    assert_eq!(x[0],1);
    let y=x;
    assert_eq!(x[0],1); // 错误

错误原因:y转移走了所有权,不能再使用x

4,转移的永久性

错误代码:

fn f(){
    let x = "he".to_string();
    if false{
        let y = x;
    }
    println!("{}",x);
}

错误原因:y转移了x的所有权之后,x就再也不能用了,即使y的生命周期结束了也一样。

即使这里用if false包起来了,只要这部分代码能编译,y就转移走x的所有权。

5,赋值语句

无论是let语句,还是赋值语句,有Copy的就会Copy,没有Copy的就转移所有权

PS:等号右边是形如x.clone()的,虽然不会转移x本身的所有权,但是会转移走clone函数返回值的所有权。

struct S{
    x:i32
}
fn main() {
    let c= S{x:1};
    let mut d =S{x:2};
    d=c;
    assert_eq!(d.x, 1);

    let mut x = Vec::from([1,2,3]);
    let mut y = Vec::from([1,2,4]);
    y=x;
    assert_eq!(y[2], 3);

    let x = 5;
    let mut y = 6;
    y=x;
    assert_eq!(x, 5);
    assert_eq!(y, 5);
}

三,引用、Borrow

1,对常量的引用

fn main() {
    let x:P=P{a:6};
    let y= & x;
    assert_eq!(x.a,6);
    assert_eq!(y.a,6);
    assert_eq!((*y).a,6);
    println!("end");
}

常量只有可读性,原变量x和引用变量y都持有读的能力。

这里y可以直接用,也可以先解引用再用。

2,对变量的不可变引用

正确代码:

struct P{
    a:i32,
}
fn main() {
    let mut x:P=P{a:6};
    let y= &x;
    assert_eq!(x.a,6);
    assert_eq!(y.a,6);
    assert_eq!((*y).a,6);
    x.a=5;
    assert_eq!(x.a,5);
    println!("end");
}

变量x持有读写能力,不可变的引用y只有读能力。

错误代码:

struct P{
    a:i32,
}
fn main() {
    let mut x:P=P{a:6};
    let y= &x;
    x.a=5;
    if false{
        assert_eq!(y.a,5);
    }
    println!("end");
}

错误原因:在y的读行为结束之前,x不能执行写行为,否则会造成冲突。即使后面的y的读写包在了if false里面也是一样,因为编译器不做这种代码运行推导。

同一个变量可以引用多次,也可以对引用变量再进行引用:

struct P{
    a:i32,
}
fn main() {
    let mut x:P=P{a:6};
    let y= &x;
    let z=&x;
    let z2=&z;
    let z3=&z2;
    let z4=&z3;
    assert_eq!(x.a,6);
    assert_eq!(y.a,6);
    assert_eq!(z.a,6);
    assert_eq!(z4.a,6);
    assert_eq!((*z4).a,6);
    assert_eq!((**z4).a,6);
    assert_eq!((***z4).a,6);
    assert_eq!((****z4).a,6);
    println!("end");
}

这里的z4可以直接读成员,也可以解引用若干次再使用。

3,对变量的可变引用

正确代码:

struct P{
    a:i32,
}
fn main() {
    let mut x:P=P{a:6};
    let y= &mut x;
    assert_eq!(y.a,6);
    y.a=5;
    assert_eq!(x.a,5);
    println!("end");
}

错误代码:

struct P{
    a:i32,
}
fn main() {
    let mut x:P=P{a:6};
    let y= &mut x;
    assert_eq!(x.a,6);
    assert_eq!(y.a,6);    
    println!("end");
}

错误原因:y的读写行为结束之前,x不能执行读行为,否则会造成冲突

可变引用和不可变引用不能同时存在,否则会造成冲突。

4,函数调用

错误代码:

fn fun(x:Vec<i32>)->i32{
    x[0]+1
}

fn main() {
    let x = vec![1,2,3];
    assert_eq!(fun(x),2);
    assert_eq!(x.len(), 3);
    println!("end");
}

错误原因:函数调用时转移走了所有权。

正确代码:

fn fun(x:&Vec<i32>)->i32{
    x[0]+1
}

fn main() {
    let x = vec![1,2,3];
    assert_eq!(fun(&x),2);
    assert_eq!(x.len(), 3);
    println!("end");
}

实现方式:函数入参改成引用类型,传参时也要改成引用。

5,Borrow、BorrowMut

泛型的Borrow特征比单纯的引用更加强大,比如可以引用数据内的部分数据,或者和指针相关的操作。

pub trait Borrow<Borrowed: ?Sized> {
    #[stable(feature = "rust1", since = "1.0.0")]
    fn borrow(&self) -> &Borrowed;
}

pub trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {
    #[sta
<think>好的,我现在需要回答用户关于C++中指针传递、值传递和引用传递的区别及应用场景的问题。首先,我需要回顾一下已有的参考资料,确保准确理解每个传递方式的特点。 首先,根据引用[1]、[2]、[4],值传递是将实参的副本传递给函数,函数内部修改的是副本,不影响原始值。这点在示例代码中也很清楚,比如modifyValue函数里的x被修改为100,但main函数中的a仍然保持原值。这说明值传递适用于不需要修改原始数据的场景,或者处理小型数据时效率较高的情况。 接下来是引用传递,引用[3]和[4]提到,引用传递是传递变量的别名,函数内对引用的操作直接影响原始变量引用必须初始化且不能为空,这使得它在需要直接修改原变量或者传递大型数据结构时更高效,避免了拷贝开销。例如,swap函数使用引用参数可以直接交换两个变量的值,而不需要指针的语法。 然后是指针传递。根据引用[3]、[5],指针传递实际上是传递指针的副本,这个副本指向原变量的地址。因此,通过指针可以修改原变量的值,但无法改变原指针的指向。指针允许传递空值,这在需要可选参数或动态内存管理时非常有用。例如,当函数需要处理可能为null的情况,或者需要重新分配内存时,指针更合适。 现在需要比较三者的区别。值传递和引用/指针传递的主要区别在于是否复制数据以及能否修改原数据。引用指针都能修改原数据,但引用更安全,因为不能为空,语法也更简洁。而指针则更灵活,允许空值和地址操作。 应用场景方面,值传递适合小型数据或不需修改的场景;引用传递适合需要修改原数据或大型数据;指针适合可选参数、动态内存或需要改变指向的情况。例如,动态数组处理可能用指针,而交换变量值用引用更直观。 需要注意,其他语言如Java的参数传递机制不同,可能只有值传递,但对象类型传递的是引用的副本。这部分可能需要简要提及,但用户主要关注C++,所以重点在C++的区别。 最后,需要检查是否有遗漏或错误。比如,确认引用不能为空是否正确,以及指针传递中能否改变原指针的指向。根据引用[3],指针传递时形参是实参的拷贝,因此修改形参的指向不会影响实参指针,但可以通过解引用修改指向的数据。 总结:结构上先分别介绍三种传递方式,再对比区别,最后说明应用场景,并附上示例代码。确保回答清晰,引用正确,符合用户要求的中文和格式要求。</think>### 指针传递 vs 值传递 vs 引用传递的区别与使用场景 在 C++ 中,函数参数传递方式主要分为**值传递**、**指针传递**和**引用传递**。它们的行为差异主要体现在内存操作、性能开销和应用场景上。 --- ### 一、核心区别 #### 1. **值传递** - **定义**:将实参的**副本**传递给函数,函数内操作的是副本,不影响原始数据。 - **特点**: - 适用于小型数据(如基本类型)。 - 无法通过形参修改实参的值。 - 默认的传递方式(无需特殊语法)。 - **示例**: ```cpp void modifyValue(int x) { x = 100; // 修改副本,不影响实参 } int main() { int a = 10; modifyValue(a); std::cout << a; // 输出 10 } ``` #### 2. **指针传递** - **定义**:传递变量的**内存地址**,函数通过指针间接操作原始数据。 - **特点**: - 允许修改实参的值(通过解引用)。 - 指针本身是值传递(传递的是地址的副本),因此无法通过形参指针修改实参指针的指向[^3]。 - 支持空值(`nullptr`),适合可选参数或动态内存管理[^5]。 - **示例**: ```cpp void modifyByPointer(int* ptr) { *ptr = 100; // 通过指针修改实参的值 } int main() { int a = 10; modifyByPointer(&a); std::cout << a; // 输出 100 } ``` #### 3. **引用传递** - **定义**:传递变量的**别名**,函数内操作直接影响原始数据。 - **特点**: - 语法简洁(无需解引用)。 - 引用必须初始化且不能为空。 - 无法通过形参引用修改实参引用绑定关系。 - 性能高效(避免大型对象的拷贝开销)[^4]。 - **示例**: ```cpp void modifyByReference(int& ref) { ref = 100; // 直接修改实参的值 } int main() { int a = 10; modifyByReference(a); std::cout << a; // 输出 100 } ``` --- ### 二、对比总结 | 特性 | 值传递 | 指针传递 | 引用传递 | |---------------------|---------------------|-----------------------|-----------------------| | **内存操作** | 操作副本 | 通过地址操作原始数据 | 直接操作原始数据 | | **空值支持** | 不适用 | 支持(`nullptr`) | 不支持 | | **语法复杂度** | 简单 | 需解引用(`*`) | 简洁(无特殊符号) | | **适用场景** | 小型数据、只读操作 | 动态内存、可选参数 | 大型对象、需修改实参 | --- ### 三、使用场景 1. **值传递**: - 函数无需修改原始数据(如数学计算)。 - 处理小型数据(如`int`, `char`)时性能更优[^2]。 2. **指针传递**: - 需要传递空值或可选参数(如链表节点)。 - 动态内存管理(如`new`/`delete`操作)[^5]。 - 需在函数内改变指针指向(需使用指针指针)。 3. **引用传递**: - 需要直接修改原始数据(如交换函数`swap`)。 - 传递大型对象(如类实例、容器)避免拷贝。 - 实现运算符重载(如`operator<<`)。 --- ### 四、其他语言中的差异 - **Java/Python**:仅支持值传递,但对象类型传递的是引用的副本(形似引用传递,实为值传递)。 - **Rust**:通过所有权机制限制引用指针的使用,避免内存安全问题。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值