网站服务内容怎样选,网站建设费用设计,软件开发就业前景好吗,网页制作基础教程实验报告“解引用”(Deref)是“取引用”(Ref)的反操作。取引用#xff0c;我们有、mut等操作符#xff0c;对应的#xff0c;解引用#xff0c;我们有操作符#xff0c;跟C语言是一样的。示例如下#xff1a; 比如说#xff0c;我们有引用类型p:i32;,那么可以用符…“解引用”(Deref)是“取引用”(Ref)的反操作。取引用我们有、mut等操作符对应的解引用我们有操作符跟C语言是一样的。示例如下 比如说我们有引用类型p:i32;,那么可以用符号执行解引用操作。上例中v1的类型是i32,p的类型是i32,*p的类型又返回i32。
自定义解引用
解引用操作可以被自定义。方法是实现标准库中的std::ops::Deref或者std::ops::DerefMut这两个trait。
Deref的定义如下所示。DerefMut的唯一区别是返回的是mut型引用都是类似的因此不过多介绍了。 这个trait有一个关联类型Target,代表解引用之后的目标类型。比如标准库中实现了String向str的解引用转换 deref()方法返回的类型是Target,而不是Target。
如果说有变量s的类型为string,*s的类型并不等于s.deref()的类型。
*s的类型实际上是Target,即str。*s的类型才是str。
s.deref()的类型为Target,即str。 比如我们可以这样理解这几个类型
Box是“指针”,指向一个在堆上分配的对象Vec是“指针”,指向一组同类型的顺序排列的堆上分配的对象且携带有当前缓存空间总大小和元素个数大小的元数据string是“指针”,指向的是一个堆上分配的字节数组其中保存的内容是合法的utf8字符序列。且携带有当前缓存空间总大小和字符串实际长度的元数据。 以上几个类型都对所指向的内容拥有所有权管理着它们所指向的内存空间的分配和释放。Rc和Arc也是某种形式的、携带了额外元数据的“指针”,它们提供的是一种“共享”的所有权当所有的引用计数指针都销毁之后它们所指向的内存空间才会被释放。
自动解引用
Rust提供的“自动解引用”机制是在某些场景下“隐式地”“自动地”帮我们做了一些事情。什么是自动解引用呢?下面用一个示例来说明 编译成功。查文档我们可以知道len()这个方法的签名是
fn len(self)-usize它接受的receiver参数是str,因此我们可以用UFCS语法调用
println!(length:{},str::len(s));但是如果我们使用str类型来调用成员方法也是可以的。原因就是Rust编译器帮我们做了隐式的deref调用当它找不到这个成员方法的时候会自动尝试使用deref方法后再找该方法一直循环下去。
编译器在str类型里面找不到len方法尝试将它deref,变成str类型后再寻找len方法还是没找到继续deref,变成str,现在找到len方法了于是就调用这个方法。
自动deref的规则是如果类型T可以解引用为U,即T:Deref,则T可以转为U:
自动解引用的用处
用Rc这个“智能指针”举例。Rc实现了Deref: 它的Target类型是它的泛型参数T。这么设计有什么好处呢?我们看下面的用法 我们创建了一个指向string类型的Rc指针并调用了bytes()方法。这里是不是有点奇怪?
这里的机制是这样的Rc类型本身并没有bytes()方法所以编译器会尝试自动deref,试试s.deref().bytes()。
String类型其实也没有bytes()方法但是string可以继续deref,于是再试试s.deref().deref().bytes()。
这次在str类型中找到了bytes()方法于是编译通过。
我们实际上通过Rc类型的变量调用了str类型的方法让这个智能指针透明。这就是自动Deref的意义。
实际上以下写法在编译器看起来是一样的 这就是为什么String需要实现Deref trait,是为了让string类型的变量可以在必要的时候自动转换为str类型。所以string类型的变量可以直接调用str类型的方法。
比如
let s String::from(hello);
let len s.bytes();虽然s的类型是string,但它在调用bytes()方法的时候编译器会自动查找并转换为s.deref().bytes()调用。
所以String类型的变量就可以直接调用str类型的方法了。
同理Vec类型也实现了Deref trait,目标类型是[T],Vec类型的变量就可以在必要的时候自动转换为[T]数组切片类型Rc类型也实现了Deref trait,目标类型是T,Rc类型的变量就可以直接调用T类型的方法。
*两个操作符连写跟分开写是不同的含义。以下两种写法是不同的 fn joint()是可以直接编译通过的而fn separate()是不能编译通过的。
因为编译器很聪明它看到*这两个操作连在一起的时候会直接把*s表达式理解为s.deref(),这时候p只是s的一个借用而已。
而如果把这两个操作分开写会先执行*s把内部的数据move出来再对这个临时变量取引用这时候s已经被移走了生命周期已经结束。
同样的let p{*s};这种写法也编译不过。
这个花括号的存在创建了一个临时的代码块在这个临时代码块内部先执行解引用同样是move语义。
从这里我们也可以看到默认的“取引用”、“解引用”操作是互补抵消的关系互为逆运算。但是在Rust中只允许自定义“解引用”,不允许自定义“取引用”。
如果类型有自定义“解引用”,那么对它执行“解引用”和“取引用”就不再是互补抵消的结果了。先后以及先后的结果是不同的。
有时候需要手动处理
如果智能指针中的方法与它内部成员的方法冲突了怎么办呢?编译器会优先调用当前最匹配的类型而不会执行自动deref,在这种情况下我们就只能手动deref来表达我们的需求了。
比如说Rc类型和String类型都有clone方法但是它们执行的任务不同。
Rc::clone()做的是把引用计数指针复制一份把引用计数加1。String::clone()做的是把字符串深复制一份。示例如下 智能指针
Rust语言提供了所有权、默认move语义、借用、生命周期、内部可变性等基础概念。但这些并不是Rust全部的内存管理方式在这些概念的基础上我们还能继续抽象、封装更多的内存管理方式而且保证内存安全。
引用计数
到目前为止我们接触到的示例中都是一块内存总是只有唯一的一个所有者。
当这个变量绑定自身消亡的时候这块内存就会被释放。
引用计数智能指针给我们提供了另外一种选择一块不可变内存可以有多个所有者当所有的所有者消亡后这块内存才会被释放。
Rust中提供的引用计数指针有std::rc::Rc类型和std::sync::Arc类型。Rc类型和Arc类型的主要区别是Rc类型的引用计数是普通整数操作只能用在单线程中Arc类型的引用计数是原子操作可以用在多线程中。
这一点是通过编译器静态检查保证的。
首先我们用示例展示Rc智能指针的用法 编译运行结果显示 $./testvalue :4242address :0x13958abdf200x13958abdf20 这说明owner1 owner2里面包含的数据不仅值是相同的而且地址也是相同的。
这正是Rc的意义所在。
从示例中可以看到Rc指针的创建是调用Rc::new静态函数与Box类型一致(将来会允许使用box关键字创建)。如果要创建指向同样内存区域的多个Rc指针需要显式调用clone函数。
请注意Rc指针是没有实现Copy trait的。
如果使用直接赋值方式会执行move语义导致前一个指针失效后一个指针开始起作用而且引用计数值不变。
如果需要创造新的Rc指针必须手工调用clone()函数此时引用计数值才会加1。
当某个Rc指针失效会导致引用计数值减1。当引用计数值减到0的时候共享内存空间才会被释放。
这没有违反我们前面讲的“内存安全”原则它内部包含的数据是“不可变的”,每个Rc指针对它指向的内部数据只有读功能和共享引用一致因此它是安全的。
区别在于共享引用对数据完全没有所有权不负责内存的释放Rc指针会在引用计数值减到0的时候释放内存。
Rust里面的Rc类型类似于C里面的shared_ptr类型且强制不可为空。
从示例中我们还可以看到使用Rc访问被包含的内部成员时可以直接使用小数点语法来进行与T T Box类型的使用方法一样。
原因我们在前面已经讲过了这是因为编译器帮我们做了自动解引用。我们查一下Rc的源码就可以知道 可见Rc类型重载了“解引用”运算符而且恰好Target类型指定的是T。
这就意味着编译器可以将Rc类型在必要的时候自动转换为T类型于是它就可以访问T的成员变量调用T的成员方法了。因此它可以被归类为“智能指针”。
下面我们继续分析Rc类型的实现原理。它的源代码在src/liballoc/rc.rs中Rc类型的定义如下所示 其中RcBox是这样定义的 其中Shared类型我们暂时可以不用管它当它是一个普通指针就好。
同时它实现了Clone和Drop这两个trait。在clone方法中它没有对它内部的数据实行深复制而是将强引用计数值加1,如下所示 在drop方法中也没有直接把内部数据释放掉而是将强引用计数值减1,当强引用计数值减到0的时候才会析构掉共享的那块数据。
当弱引用计数值也减为0的时候才说明没有任何Rc/Weak指针指向这块内存它占用的内存才会被彻底释放。