rust中ref及引用的使用

由C++指针和引用引发的思考

#include<iostream>
using namespace std;
void c1(int a) {
    a = 6;
}
void c2(int* a) {
    *a = 7;
}
void c3(int& a) {
    a = 8;
}
int main() {
    int a = 5;
    printf("a is %d\n",a);
    c1(a);
    printf("a is %d\n",a);
    c2(&a);
    printf("a is %d\n",a);
    c3(a);
    printf("a is %d\n",a);

}
显然,只有c2和c3函数可以修改a的值,并在主函数生效

等效的rust代码

use std::any::type_name;
//输出变量类型
fn print_type_of<T>(_: &T) {
    println!("{}", type_name::<T>());
}

fn c1(mut a:i32) {
    a = 5;
    println!("c1里面a的类型");
    println!("{:p}",&a as *const i32);
    print_type_of(&a);
}
fn c2(a:&mut i32) {
    *a = 7;
    println!("c2里面a的类型");
    println!("{:p}",a as *const i32);
    print_type_of(&a);
}
//指针,自动从引用转换过来了
fn c4(a:*mut i32) {
    unsafe {
        *a = 8;
    }
    println!("c4里面a的类型");
    println!("{:p}",a as *const i32);
    print_type_of(&a);
}
fn main() {
    let mut a = 5;
    println!("{:p}",&a as *const i32);
    println!("{}",a);
    c1(a);
    println!("{}",a);
    c2(&mut a);
    println!("{}",a);
    c4(&mut a);
    println!("{}",a);
}
输出当然和上面的C++是一样的,但是,rust里面C2和C4函数只是等价于C++C2函数而已。那rust里面有没有和C++引用一样的效果呢,有,但不是完全一样。
再加上一个c3函数
fn c3(ref mut a:i32) {
    *a = 100;
    println!("c3修改后函数中的a {}",a);
    println!("{:p}",a as *const i32);
    println!("c3里面a的类型");
    print_type_of(&a);
}
fn main() {
    //末尾填写如下语句
    c3(a);
    println!("{}",a);
}
但是,它只是改变了函数里面a的值而已,对main没有变化,虽然,它和C2一样,都是&mut i32类型。

输出如下

0x467ff5f2dc
1
c1里面a的类型
0x467ff5f20c
i32
1
c2里面a的类型
0x467ff5f2dc
&mut i32
7
c4里面a的类型
0x467ff5f2dc
*mut i32
8
c3修改后函数中的a 100
0x467ff5f1a4
c3里面a的类型
&mut i32
8
可见,函数头使用ref,就像是复制了原来的值到新的内存,然后再来一个指针指向新分配的空间

这个问题,可以通过打印指针地址的方式进行查看。(上面已经加上了)

那么,rust里面可不可以使用ref的修改原值呢,当然是可以的。不过rust的ref主要是用于模式匹配。
let mut a = 1;
    match a {
        ref mut v=> {
            *v = 100;//等效于let ref mut v = a;*v=100;
            print_type_of(&v);
        }
        _ =>{},
    }
    println!("{}",a);
    match &mut a {
        v => {
            *v = 66;
            print_type_of(&v);
        }
        _=>{},
    }
    println!("{}",a);
    print_type_of(&a);
输出如下
&mut i32
100
&mut i32
66
i32
let mut s = Some(10);
    match s {
        Some(ref mut v) => {
            print_type_of(&v);
            *v = 100;   //为什么这里修改,也有效呢,按照下文就很好理解了。let ref mut v = num; *v=999;num就变成999了。
            // println!("{}",v);
        }
        
        _ =>{},
    }
    println!("{:?}",s);
输出如下:
&mut i32
Some(100)
结论:使用match模式匹配时,ref的用法就像C++里面的C3函数那样

再来一些模式匹配中关于ref的例子

#[derive(Debug)]
struct Point {
    x:i32,
    y:i32,
}
#[derive(Debug)]
enum Mess {
    Start {id:i32},
    End (i32),
}
fn main() {
    let mut p = Point{x:8,y:9};
    println!("before {:?}",p);
    let Point {x: ref mut x_ref,mut y} = p;
    *x_ref = 10;
    y = 10; //没有ref,y的修改丝毫不影响原来的p Point
    println!("after {:?}",p);

    let mut m = Mess::Start{id:9};
    //枚举不能这样解构,只能通过if let,或者match匹配
    // let Mess::Start{id: ref mut id1} = m;
    if let Mess::Start{id: ref mut id1} = m {
        *id1 = 200;
    } 
    let mut n = Mess::End(10);
    match n {
        Mess::End(ref mut id2) => {
            *id2 = 66;
        }
        _ => {},
    }
    println!("{:?}",m);
    println!("{:?}",n);
}
输出如下:
before Point { x: 8, y: 9 }
after Point { x: 10, y: 9 }
Start { id: 200 }
End(66)

再来一些例子

fn main() {
    let mut num = 10;
    let mut value = Some(&mut num); // Option<&i32> 类型

    match value {
        Some(ref mut v) => {
            **v = 66;
            print_type_of(&v);
        }
        _ => (),
    }
    println!("{:?}",value);
    println!("{}",num);
    //这里,为了便于理解match里面匹配的类型。
    let ref mut v = &mut num;
    print_type_of(&v);
    **v = 1000;
    println!("{}",num);
}
输出如下:
&mut &mut i32
Some(66)
66
&mut &mut i32
1000
更有意思的是(绕晕了~~)
fn main() {
    let mut num = 10;
    let mut value = Some(&mut num);
    //match1
    match value {
        Some(ref v) => {
            print_type_of(&v);
        }
        _ => {},
    }
    //match2
    match value {
        Some(&mut v) => {
            print_type_of(&v);
        }
        _ => {},
    }
    //match3
    match value {
        Some(v) => {
            *v = 166;
            print_type_of(&v);
        }
        _ => {},
    }
}
/*
输出如下:
&&mut i32
i32
&mut i32
166
*/
如果把match3移到match2前面,编译就不能通过了,原因是Some里面的内容是具有所有权的,match改变了它的所有权。所以,使用ref还是有好处的,可以避免所有权的转移。
这个例子,倒是在闭包参数中很常见,能直接打印变量的实际类型,对学习很有帮助
再来个例子,这次是闭包里面的参数。
// let s = "A man, a plan, a canal: Panama";
    //只保留字母数字,并且大写字母变成小写
    // let s:String = s.chars().filter(|c| c.is_alphanumeric()).map(|c|c.to_ascii_lowercase()).collect();
    let s = "1";
    let s:String = s.chars().filter(|c|{
        print_type_of(&c);
        c.is_alphanumeric()
    }).collect();
    println!("{}",s);
    let s = "2";
    let s:String = s.chars().filter(|ref c|{
        print_type_of(&c);
        c.is_alphanumeric()
    }).collect();
    println!("{}",s);
    let s = "3";
    let s:String = s.chars().filter(|&c|{
        print_type_of(&c);

        c.is_alphanumeric()
    }).collect();
    println!("{}",s);
/*
输出如下,&char
1
&&char
2
char
3
*/

可见,闭包里面,参数的使用规则,也和match匹配一样。

posted @ 2024-10-28 23:48  念秋  阅读(20)  评论(0编辑  收藏  举报