news 2026/9/6 8:29:15

【Rust入门知识点学与练】第18课:智能指针 Smart Pointers

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust入门知识点学与练】第18课:智能指针 Smart Pointers

知识点1:Box — 堆上分配

Box 把数据放在堆上,栈上只存一个指针:

fnmain(){// 在堆上分配一个整数letboxed=Box::new(42);println!("值: {}",boxed);// 42println!("解引用: {}",*boxed);// 42,* 解引用// 在堆上分配一个字符串lets=Box::new(String::from("hello"));println!("字符串: {}",s);// Box 最常见的用途:递归类型// 编译器不知道递归类型的大小,用 Box 包装后大小就确定了#[derive(Debug)]enumList{Cons(i32,Box<List>),// 一个节点:值 + 指向下一个节点的指针Nil,// 空节点(链表结尾)}letlist=List::Cons(1,Box::new(List::Cons(2,Box::new(List::Cons(3,Box::new(List::Nil))))));println!("链表: {:?}",list);}

知识点2:Deref trait 和解引用

Box 实现了 Deref trait,所以可以自动解引用:

fnmain(){letboxed=Box::new(5);// 手动解引用assert_eq!(5,*boxed);// 自动解引用:函数参数是 &i32,传入 &Box<i32> 也能工作fntakes_ref(x:&i32){println!("收到: {}",x);}takes_ref(&boxed);// ✅ 自动解引用 Box<i32> -> i32// 解引用强制转换(Deref Coercion)// String 实现了 Deref<Target = str>// 所以 &String 可以自动转为 &strlets=Box::new(String::from("hello"));fntakes_str(s:&str){println!("字符串: {}",s);}takes_str(&s);// ✅ Box<String> -> String -> str// 自定义类型实现 DerefstructMyBox<T>(T);impl<T>MyBox<T>{fnnew(x:T)->MyBox<T>{MyBox(x)}}impl<T>std::ops::DerefforMyBox<T>{typeTarget=T;fnderef(&self)->&T{&self.0}}letmb=MyBox::new(42);println!("MyBox 值: {}",*mb);// 42,自动解引用}

知识点3:Drop trait — 清理资源

当值离开作用域时,自动调用 drop:

structCustomSmartPointer{data:String,}implDropforCustomSmartPointer{fndrop(&mutself){println!("清理: {} 被销毁了",self.data);}}fnmain(){leta=CustomSmartPointer{data:String::from("第一个")};letb=CustomSmartPointer{data:String::from("第二个")};println!("创建完成");// 可以用 drop 函数提前释放drop(a);// 手动提前释放 aprintln!("a 已被释放");// b 在这里自动释放}// 输出:// 创建完成// 清理: 第一个 被销毁了// a 已被释放// 清理: 第二个 被销毁了

知识点4:Rc — 多所有权引用计数

Rc(Reference Counted)允许多个所有者共享同一份数据:

usestd::rc::Rc;fnmain(){leta=Rc::new(String::from("共享数据"));println!("引用计数: {}",Rc::strong_count(&a));// 1letb=Rc::clone(&a);// 增加引用计数(不是深拷贝)println!("引用计数: {}",Rc::strong_count(&a));// 2{letc=Rc::clone(&a);println!("引用计数: {}",Rc::strong_count(&a));// 3println!("c 读到: {}",c);// c 在这里被释放,引用计数 -1}println!("引用计数: {}",Rc::strong_count(&a));// 2// 多个所有者共享同一个数据letdata=Rc::new(vec![1,2,3,4,5]);letowner1=Rc::clone(&data);letowner2=Rc::clone(&data);println!("owner1: {:?}",owner1);println!("owner2: {:?}",owner2);println!("引用计数: {}",Rc::strong_count(&data));// 3}

知识点5:RefCell — 内部可变性

RefCell 允许在持有不可变引用时修改内部数据(运行时检查借用规则):

usestd::cell::RefCell;fnmain(){letdata=RefCell::new(vec![1,2,3]);// borrow() 返回不可变借用println!("数据: {:?}",data.borrow());// borrow_mut() 返回可变借用data.borrow_mut().push(4);println!("修改后: {:?}",data.borrow());// ⚠️ 运行时检查借用规则// 如果同时存在 borrow 和 borrow_mut,程序会 panic// let r1 = data.borrow();// let r2 = data.borrow_mut(); // ❌ panic! 同时存在不可变和可变借用// 配合 Rc 使用:多个所有者共享可变数据usestd::rc::Rc;letshared_data=Rc::new(RefCell::new(vec![1,2,3]));letowner1=Rc::clone(&shared_data);letowner2=Rc::clone(&shared_data);owner1.borrow_mut().push(4);println!("owner2 看到: {:?}",owner2.borrow());// [1, 2, 3, 4]}

知识点6:组合使用 Rc>

usestd::rc::Rc;usestd::cell::RefCell;// 用 Rc<RefCell<T>> 实现一个共享的可变计数器structCounter{count:RefCell<u32>,}implCounter{fnnew()->Self{Counter{count:RefCell::new(0),}}fnincrement(&self){*self.count.borrow_mut()+=1;}fnget(&self)->u32{*self.count.borrow()}}fnmain(){letcounter=Rc::new(Counter::new());// 多个所有者共享同一个计数器letc1=Rc::clone(&counter);letc2=Rc::clone(&counter);c1.increment();c1.increment();c2.increment();println!("计数: {}",counter.get());// 3println!("引用计数: {}",Rc::strong_count(&counter));// 3}

核心规则

智能指针 用途 所有权 可变性
Box 堆分配、递归类型 单一所有权 可变/不可变
Rc 多所有权共享 共享所有权 仅不可变
RefCell 内部可变性 单一所有权 运行时检查
Rc<RefCell> 共享 + 可变 共享所有权 运行时检查
检查时机 说明
Box / Rc 编译时检查借用规则
RefCell 运行时检查,违反则 panic

动手试试

补全下面的代码:

usestd::rc::Rc;usestd::cell::RefCell;// 补全:定义一个 Student 结构体// 字段:name: String, grades: RefCell<Vec<f64>>// 补全:为 Student 实现以下方法:// 1. new(name: String) -> Student// 2. add_grade(&self, grade: f64) 添加成绩// 3. average(&self) -> f64 计算平均分(无成绩返回 0.0)// 4. highest(&self) -> Option<f64> 最高分// 5. display(&self) -> String 格式: "学生: 名字, 成绩: [x, y, z], 平均: x.xx"fnmain(){// 补全:创建一个学生 "张三",用 Rc 包装// 创建两个共享引用 s1 和 s2// 补全:通过 s1 添加成绩 85.0, 92.0, 78.0// 通过 s2 添加成绩 95.0// 补全:通过原始引用打印学生信息// 期望输出: 学生: 张三, 成绩: [85, 92, 78, 95], 平均: 87.50// 补全:打印最高分// 期望输出: 最高分: Some(95)// 补全:打印引用计数// 期望输出: 引用计数: 3}

期望输出:
学生: 张三, 成绩: [85, 92, 78, 95], 平均: 87.50
最高分: Some(95)
引用计数: 3

写好了贴过来,没问题就进入第19课:并发 Concurrency 👋

贴代码

usestd::rc::Rc;usestd::cell::RefCell;// Student结构体定义structStudent{name:String,grades:RefCell<Vec<f64>>,}implStudent{// 1. 构造函数fnnew(name:String)->Student{Student{name,grades:RefCell::new(Vec::new()),}}// 2. 添加成绩:&self(不可变引用),借助RefCell实现内部可变性fnadd_grade(&self,grade:f64){self.grades.borrow_mut().push(grade);}// 3. 平均分,无成绩返回0.0fnaverage(&self)->f64{letgrades=self.grades.borrow();ifgrades.is_empty(){return0.0;}letsum:f64=grades.iter().sum();sum/(grades.len()asf64)}// 4. 最高分,返回Option<f64>fnhighest(&self)->Option<f64>{letgrades=self.grades.borrow();grades.iter().copied().max_by(|a,b|a.partial_cmp(b).unwrap())}// 5. 格式化输出字符串fndisplay(&self)->String{letgrades=self.grades.borrow();format!("学生: {}, 成绩: {:?}, 平均: {:.2}",self.name,grades,self.average())}}fnmain(){// 创建Rc包裹的学生对象letstudent=Rc::new(Student::new(String::from("张三")));lets1=Rc::clone(&student);lets2=Rc::clone(&student);// 通过不同共享引用添加成绩,RefCell实现内部可变性s1.add_grade(85.0);s1.add_grade(92.0);s1.add_grade(78.0);s2.add_grade(95.0);// 打印学生信息println!("{}",student.display());// 打印最高分println!("最高分: {:?}",student.highest());// 获取Rc当前引用计数println!("引用计数: {}",Rc::strong_count(&student));}

运行输出

plaintext

学生: 张三, 成绩: [85.0, 92.0, 78.0, 95.0], 平均: 87.50
最高分: Some(95.0)
引用计数: 3

核心知识点讲解(Rc + RefCell,Rust高频组合)

1. Rc (单线程共享所有权)

  • Rc::new :在堆上创建对象;
  • Rc::clone() :增加引用计数,只是复制指针,不复制底层数据;
  • Rc::strong_count() :查看当前强引用的数量;
  • Rc是单线程专用,多线程需要 Arc 。

2. RefCell(内部可变性)

正常情况下, &self (不可变引用)不能修改结构体字段;
RefCell 绕开这个限制,实现内部可变性:

  • .borrow() :获取不可变借用;
  • .borrow_mut() :获取可变借用;

RefCell在运行时检查借用规则,而不是编译期;违反借用规则程序会panic。

3. Rc 黄金组合

多个所有者(Rc) + 运行时内部可变性(RefCell),
多个共享引用可以修改同一份数据,本题: s1 和 s2 都能添加成绩。

4. highest 里面的细节

grades.iter().copied().max_by(|a,b|a.partial_cmp(b).unwrap())```f64没有实现Ord(存在NaN),不能直接用.max(),要用 partial_cmp 。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 8:28:32

AI大模型高效学习路线:以项目驱动,拒绝无效刷教程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 8:27:37

RAG_Techniques实战指南:检索增强生成从入门到进阶

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 8:26:29

Codex+Relay:移动端AI全栈开发从原型到交付的完整链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 8:24:07

初学者补充知识

C语言基础全套整理笔记1.1 计算机硬件根据冯.诺依曼体系&#xff1a;输入设备、输出设备、存储器、CPU&#xff08;控制器 运算器&#xff09;cpu不能和硬盘进行数据交换&#xff0c;只能和内存进行数据交换。&#xff08;cpu打开应用程序时会进行加载&#xff0c;这个速度取决…

作者头像 李华
网站建设 2026/9/6 8:19:45

Rust vs Python 在用户认证模块中的实现:底层

在当前微服务架构盛行的开发环境中&#xff0c;用户认证与权限管理是保障系统安全的核心环节。面对日益复杂的业务场景&#xff0c;开发者常常陷入选择技术栈的纠结之中。Rust 以其内存安全和性能优势赢得了越来越多关注&#xff0c;而 Python 则因易用性和生态丰富性依旧广泛使…

作者头像 李华