文章目录
- 0、引入
- 1、泛型
- 1.1、在函数中使用
- 1.2、在结构体中使用
- 2、特性
- 2.1 默认特性
- 2.2 特性做参数
- 2.3 特性做返回值
- 2.4 有条件实现方法
- 3、总结
0、引入
泛型是一个编程语言不可或缺的机制。
- C++ 语言中用"模板"来实现泛型,而 C 语言中没有泛型的机制,这也导致 C 语言难以构建类型复杂的工程。
- 泛型机制是编程语言用于表达类型抽象的机制,一般用于功能确定、数据类型待定的类,如链表、映射表等。
1、泛型
1.1、在函数中使用
//数组排序,只针对i32数据
fn max(array: &[i32]) -> i32 {
let mut max_index = 0;
let mut i = 1;
while i < array.len() {
if array[i] > array[max_index] {
max_index = i;
}
i += 1;
}
array[max_index]
}
fn main() {
let a = [2, 4, 6, 3, 1];
println!("max = {}", max(&a));
}
//要支持其他数据类型就可以使用函数泛型
fn max<T>(array: &[T]) -> T {
let mut max_index = 0;
let mut i = 1;
while i < array.len() {
if array[i] > array[max_index] {
max_index = i;
}
i += 1;
}
array[max_index]
}
1.2、在结构体中使用
在之前我们学习的 Option 和 Result 枚举类就是泛型的。
Rust 中的结构体和枚举类都可以实现泛型机制。
struct Point<T> {
x: T,
y: T
}
//使用
let p1 = Point {x: 1, y: 2};
let p2 = Point {x: 1.0, y: 2.0};
也可以多个T
struct Point<T1, T2> {
x: T1,
y: T2
}
/结构体方法也可以使用
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("p.x = {}", p.x());
}
注:
1、结构体与枚举类都可以定义方法,那么方法也应该实现泛型的机制,否则泛型的类将无法被有效的方法操作。
2、使用时并没有声明类型,这里使用的是自动类型机制,但不允许出现类型不匹配的情况如下。
2、特性
特性(trait)概念接近于 C+中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。**特性在 Rust 中用 trait 表示:**格式如下
trait Descriptive {
fn describe(&self) -> String;
}
//我们用它实现一个结构体
struct Person {
name: String,
age: u8
}
impl Descriptive for Person {
fn describe(&self) -> String {
format!("{} {}", self.name, self.age)
}
}
注:Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。
2.1 默认特性
这是特性与接口的不同点:接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法,因为是"默认",所以对象既可以重新定义方法,也可以不重新定义方法使用默认的方法,有点像虚函数。
trait Descriptive {
fn describe(&self) -> String {
String::from("[Object]")
}
}
struct Person {
name: String,
age: u8
}
impl Descriptive for Person { //2重新定义
fn describe(&self) -> String {
format!("{} {}", self.name, self.age)
}
}
fn main() {
let cali = Person {
name: String::from("Cali"),
age: 24
};
println!("{}", cali.describe());
}
运行结果:Cali 24
如果去掉 //2重新定义
则结果:[Object]
该处使用的url网络请求的数据。
2.2 特性做参数
很多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Java 中函数必须以接口实现的类实例来传递,在 Rust 中可以通过传递特性参数来实现:
fn output(object: impl Descriptive) {
println!("{}", object.describe());
}
//一种等效的语法糖格式
fn output<T: Descriptive>(object: T) {
println!("{}", object.describe());
}
//在多个参数可以很简洁
fn output_two<T: Descriptive>(arg1: T, arg2: T) {
println!("{}", arg1.describe());
println!("{}", arg2.describe());
}
//特性作类型表示时如果涉及多个特性,可以用 + 符号表示,
fn notify(item: impl Summary + Display)
fn notify<T: Summary + Display>(item: T)
//复杂的实现关系可以使用 where 关键字简化
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)
//简化为
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
所以rust学到后面之后越来越难看得懂了,因为很多都是简化,这是他写法上的便利,但是阅读上的困惑,只有真正习惯的人才会得心应手。
2.3 特性做返回值
格式如下:
fn person() -> impl Descriptive {
Person {
name: String::from("ok"),
age: 50
}
}
注意:但是有一点,特性做返回值只接受实现了该特性的对象做返回值且在同一个函数中所有可能的返回值类型必须完全一样。
2.4 有条件实现方法
impl 功能十分强大,我们可以用它实现类的方法。但对于泛型类来说,有时我们需要区分一下它所属的泛型已经实现的方法来决定它接下来该实现的方法:
//这段代码声明了 A<T> 类型必须在 T 已经实现 B 和 C 特性的前提下才能有效实现此 impl 块。
struct A<T> {}
impl<T: B + C> A<T> {
fn d(&self) {}
}
3、总结
Rust泛型和特性虽然很强大,但是就和C++中泛型一样,大多数的情况下用不到,可以作为高级特性掌握。