引语
根据之前的课程进度,第15课是泛型 Generics。这是一个文本创作类任务,我需要按照之前课程的格式来编写泛型的教学内容,包括知识点讲解、代码示例和练习题。
知识点1:泛型函数
泛型让你写出适用于多种类型的代码,避免重复:
// 不用泛型:需要写多个函数fnlargest_i32(list:&[i32])->i32{letmutlargest=list[0];for&itemin&list[1..]{ifitem>largest{largest=item;}}largest}// 用泛型:一个函数搞定所有类型// T: PartialOrd 表示 T 必须支持比较操作fnlargest<T:PartialOrd>(list:&[T])->&T{letmutlargest=&list[0];foritemin&list[1..]{ifitem>largest{largest=item;}}largest}fnmain(){letnumbers=vec![34,50,25,100,65];println!("最大数: {}",largest(&numbers));letchars=vec!['y','m','a','q'];println!("最大字符: {}",largest(&chars));letfloats=vec![1.1,3.3,2.2];println!("最大浮点: {}",largest(&floats));}知识点2:泛型结构体
// 泛型结构体:Point 可以存储任何类型的坐标structPoint<T>{x:T,y:T,}impl<T>Point<T>{fnnew(x:T,y:T)->Self{Point{x,y}}fnx(&self)->&T{&self.x}fny(&self)->&T{&self.y}}// 可以为特定类型实现额外方法implPoint<f64>{fndistance_from_origin(&self)->f64{(self.x.powi(2)+self.y.powi(2)).sqrt()}}fnmain(){letinteger_point=Point::new(5,10);println!("整数点: ({}, {})",integer_point.x(),integer_point.y());letfloat_point=Point::new(3.0,4.0);println!("浮点点: ({}, {})",float_point.x(),float_point.y());println!("到原点距离: {}",float_point.distance_from_origin());// 5.0}知识点3:多个泛型参数
structPair<T,U>{first:T,second:U,}impl<T,U>Pair<T,U>{fnnew(first:T,second:U)->Self{Pair{first,second}}}impl<T:std::fmt::Display,U:std::fmt::Display>Pair<T,U>{// 只有当 T 和 U 都实现了 Display 时,这个方法才可用fndisplay(&self){println!("({}, {})",self.first,self.second);}}fnmain(){letp=Pair::new("hello",42);p.display();// (hello, 42)letp2=Pair::new(3.14,true);p2.display();// (3.14, true)}知识点4:trait 约束
用 where 子句让约束更清晰:
usestd::fmt;// 普通写法fnnotify1<T:fmt::Display+Clone>(item:T){println!("通知: {}",item.clone());}// where 写法(更清晰)fnnotify2<T,U>(t:T,u:U)whereT:fmt::Display+Clone,U:fmt::Debug+Clone,{println!("T: {}, U: {:?}",t.clone(),u.clone());}fnmain(){notify1(String::from("hello"));notify2(String::from("hello"),42);}知识点5:返回实现了 trait 的类型
usestd::fmt;// 返回实现了 Display 的任意类型fnreturns_displayable(flag:bool)->implfmt::Display{ifflag{String::from("hello")}else{42}// 注意:上面这样写会报错!// impl Trait 要求返回类型必须一致// 正确写法如下:}// 正确:两个分支返回相同类型fnreturns_string(flag:bool)->implfmt::Display{ifflag{String::from("true")}else{String::from("false")}}fnmain(){println!("{}",returns_string(true));println!("{}",returns_string(false));}核心规则
概念 写法
泛型函数 fn func<T: Trait>(param: T)
泛型结构体 struct Name { field: T }
多泛型参数 struct Name<T, U>
trait 约束 T: Display + Clone
where 子句 where T: Display, U: Debug
返回 trait 类型 fn func() -> impl Trait
调用泛型函数 类型自动推断,或 func::(…)
动手试试
补全下面的代码:
usestd::fmt;// 补全:定义一个泛型结构体 Stack<T>,内部用 Vec<T> 存储数据// 实现以下方法:// 1. new() -> Stack<T> 创建空栈// 2. push(&mut self, item: T) 入栈// 3. pop(&mut self) -> Option<T> 出栈// 4. peek(&self) -> Option<&T> 查看栈顶(不弹出)// 5. is_empty(&self) -> bool 是否为空// 6. size(&self) -> usize 栈中元素个数// 补全:为 Stack<T> 实现 Display trait(T 需要实现 Display)// 格式:[底 -> 顶],例如栈中有 1,2,3(3是栈顶),输出 "[1, 2, 3]"fnmain(){// 补全:创建一个整数栈,依次压入 10, 20, 30// 打印栈内容(期望: [10, 20, 30])// 打印栈大小(期望: 3)// 查看栈顶(期望: Some(30))// 弹出栈顶并打印(期望: 30)// 再次打印栈内容(期望: [10, 20])// 弹出所有元素,再打印是否为空(期望: true)// 补全:创建一个字符串栈,压入 "hello", "world"// 打印栈内容(期望: [hello, world])}期望输出:
[10, 20, 30]
大小: 3
栈顶: Some(30)
弹出: 30
[10, 20]
栈为空: true
[hello, world]
写好了贴过来,没问题就进入第16课:生命周期 Lifetimes 👋
贴代码
usestd::fmt;// 泛型栈结构体structStack<T>{data:Vec<T>,}// 方法实现impl<T>Stack<T>{// 1. 创建空栈fnnew()->Stack<T>{Stack{data:Vec::new()}}// 2. 入栈fnpush(&mutself,item:T){self.data.push(item);}// 3. 出栈fnpop(&mutself)->Option<T>{self.data.pop()}// 4. 查看栈顶(借用,不获取所有权)fnpeek(&self)->Option<&T>{self.data.last()}// 5. 判断栈是否为空fnis_empty(&self)->bool{self.data.is_empty()}// 6. 获取栈元素数量fnsize(&self)->usize{self.data.len()}}// 实现 Display,约束 T 必须实现 fmt::Displayimpl<T:fmt::Display>fmt::DisplayforStack<T>{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{// 将每个元素转字符串,用 ", " 连接letitems:Vec<String>=self.data.iter().map(|x|format!("{}",x)).collect();write!(f,"[{}]",items.join(", "))}}fnmain(){// 整数栈测试letmutint_stack=Stack::new();int_stack.push(10);int_stack.push(20);int_stack.push(30);println!("{}",int_stack);println!("栈大小: {}",int_stack.size());println!("栈顶: {:?}",int_stack.peek());lettop=int_stack.pop();println!("弹出: {:?}",top);println!("{}",int_stack);// 清空栈int_stack.pop();int_stack.pop();println!("栈是否为空: {}",int_stack.is_empty());// 字符串栈测试letmutstr_stack=Stack::new();str_stack.push("hello");str_stack.push("world");println!("{}",str_stack);}运行输出
plaintext
[10, 20, 30]
栈大小: 3
栈顶: Some(30)
弹出: Some(30)
[10, 20]
栈是否为空: true
[hello, world]
核心知识点讲解
1. 泛型 Stack
T 是类型参数,栈可以存放任意类型: Stack 、 Stack<&str> 、 Stack 。
泛型让一份代码支持多种数据类型,不需要重复写多份栈实现。
2. Vec 作为栈底层存储
- push :尾部追加 → 入栈;
- pop :尾部删除 → 出栈;
- last() :获取尾部元素的引用 → peek,不会拿走所有权。
3. 借用 vs 所有权
- pop(&mut self) -> Option :返回元素本身,所有权转移,栈里面就没有了;
- peek(&self) -> Option<&T> :返回引用,栈里面元素还在,只是看一看。
4. Display trait 实现
impl<T:fmt::Display>fmt::DisplayforStack<T>T: fmt::Display 是trait 约束:只有栈里面的元素类型T支持Display,Stack才能被 {} 打印。
如果T没有实现Display,就不能用println!(“{}”, stack),编译报错。
5. 迭代器 map + collect
self.data.iter().map(|x|format!("{}",x)).collect()遍历栈里每一个元素,转为字符串,收集到一个字符串Vec,最后用 join 拼接。
拓展:
Rust标准库没有单独的Stack容器,日常开发直接用Vec模拟栈(push/pop/last),和我们这道题一模一样。
做完泛型栈,泛型基础就过关了。需要一道泛型队列Queue练习题吗?
练习题:泛型队列 Queue(FIFO 先进先出)
题目要求
使用 Rust 实现一个泛型队列 Queue,底层用 Vec 存储,实现队列的基础操作。
队列:先进先出(FIFO),队尾入队,队头出队。
1. 结构体定义
Queue ,内部字段 data: Vec
2. 需要实现的方法
2.1 new() -> Self :创建空队列
2.2 enqueue(&mut self, item: T) :入队,元素加到队尾(vec.push)
2.3 dequeue(&mut self) -> Option :出队,从队列头部删除并返回
注意:Vec 的 remove(0),删除索引0的元素;数组头部删除性能差,但初学练习就用这个简单实现
2.4 front(&self) -> Option<&T> :查看队首元素(不删除,返回引用)
2.5 is_empty(&self) -> bool :队列是否为空
2.6 len(&self) -> usize :返回队列元素个数
3. 实现 Display trait
约束: T: fmt::Display
打印格式样例:队列存 10,20,30(10队首、30队尾)→ [10, 20, 30]
4. main 测试逻辑
- 创建一个 i32 队列,依次入队:10、20、30
- 打印队列内容 → [10, 20, 30]
- 打印队列长度 → 3
- 获取队首 front → Some(10)
- 出队 dequeue,打印弹出的值 → 10
- 打印当前队列 → [20, 30]
- 全部元素出队,打印 is_empty → true
- 新建字符串队列,入队 “a” , “b” ,打印队列 → [a, b]
提示
4.1. vec.remove(0) :移除第一个元素,返回该元素;队列为空调用remove(0)会panic,不能直接用,需要先判断队列是否为空再返回Option。
4.2. 获取队首引用: self.data.first()
4.3. Display 的实现思路和上一题 Stack 几乎一样。
贴代码
先自行编写,完成后再看参考答案👉完整参考答案
usestd::fmt;structQueue<T>{data:Vec<T>,}impl<T>Queue<T>{fnnew()->Self{Queue{data:Vec::new()}}// 入队:队尾添加fnenqueue(&mutself,item:T){self.data.push(item);}// 出队:队头弹出,空队列返回Nonefndequeue(&mutself)->Option<T>{ifself.is_empty(){None}else{Some(self.data.remove(0))}}// 获取队首元素的引用fnfront(&self)->Option<&T>{self.data.first()}fnis_empty(&self)->bool{self.data.is_empty()}fnlen(&self)->usize{self.data.len()}}// Display 实现impl<T:fmt::Display>fmt::DisplayforQueue<T>{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{letstrs:Vec<String>=self.data.iter().map(|v|format!("{}",v)).collect();write!(f,"[{}]",strs.join(", "))}}fnmain(){// i32队列测试letmutq=Queue::new();q.enqueue(10);q.enqueue(20);q.enqueue(30);println!("{}",q);println!("队列长度: {}",q.len());println!("队首: {:?}",q.front());letout=q.dequeue();println!("出队元素: {:?}",out);println!("{}",q);q.dequeue();q.dequeue();println!("队列是否为空: {}",q.is_empty());// 字符串队列letmutstr_q=Queue::new();str_q.enqueue("a");str_q.enqueue("b");println!("{}",str_q);}运行输出
plaintext
[10, 20, 30]
队列长度: 3
队首: Some(10)
出队元素: Some(10)
[20, 30]
队列是否为空: true
[a, b]
知识点总结
- 栈(Stack):LIFO后进先出,push/pop操作vec尾部,性能很好 O(1)
- 本版队列: remove(0) ,数组头部删除,时间复杂度O(n),元素很多的时候很慢;工业级实现一般用双端队列VecDeque( std::collections::VecDeque ),两端增删都是O(1)。
- first() :获取第一个元素的引用,对应栈的 last() 。
- 泛型代码复用:Queue一份实现,支持i32、&str、String等任意类型。
拓展
标准库双端队列 VecDeque 的基础用法(工业版队列)
usestd::collections::VecDeque;fnmain(){letmutdq=VecDeque::new();dq.push_back(10);// 入队尾部dq.pop_front();// 队头出队,O(1)高性能!}