定义
需要用一个函数解决多种不同数据类型的同一个功能的时候,就会用到泛型,比如不同类型的数据要分别相加,如果不使用泛型,就会很繁琐,例如:
fn add_i8(a:i8, b:i8) -> i8 {
a + b
}
fn add_i32(a:i32, b:i32) -> i32 {
a + b
}
fn add_f64(a:f64, b:f64) -> f64 {
a + b
}
fn main() {
println!("add i8: {}", add_i8(2i8, 3i8));
println!("add i32: {}", add_i32(20, 30));
println!("add f64: {}", add_f64(1.23, 1.23));
}
如果使用泛型,就会变得简洁:
fn add<T>(a:T, b:T) -> T {
a + b
}
fn main() {
println!("add i8: {}", add(2i8, 3i8));
println!("add i32: {}", add(20, 30));
println!("add f64: {}", add(1.23, 1.23));
}
上面这段代码仅用于形象的解释泛型,并不能编译通过,在详解中会解释
详解
定义中的代码中的T,就是泛型参数,这个参数名可以随便定义,但一般使用T,因为Type。
在上面的代码中,为什么会报错呢?是因为上面的代码中T没有做限制,T可以是任何类型的值,但不是所有的值都可以相加,比如abc这种字符串,需要使用 std::cmp::PartialOrd
特征(Trait)对 T
进行限制,特征请在特征章节学习,这里只需要理解是让这里的T实现可以比较的功能,也就是对类型做了限制,使其仅允许为可进行运算or比较的数值
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
这是一段标准的错误例子,在 char_list = vec!['y', 'm', 'a', 'q'];这个数组的值并不能进行largest函数中的比较,所以会报错
结构体中使用泛型
在之前的方法章节涉及到了结构体,在结构体中也可以使用泛型:
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
这里有两点需要特别的注意:
- 提前声明,跟泛型函数定义类似,首先我们在使用泛型参数之前必需要进行声明
Point<T>
,接着就可以在结构体的字段类型中使用T
来替代具体的类型 - x 和 y 是相同的类型,如果不是相同的类型,代码就会报错
如果想让x和y既可以类型相同又可以类型不同,就需要定义两个泛型函数:
struct Point<T,U> {
x: T,
y: U,
}
fn main() {
let p = Point{x: 1, y :1.1};
}
枚举中使用泛型
enum Option<T> {
Some(T),
None,
}
Option<T>
是一个拥有泛型 T
的枚举类型,它第一个成员是 Som(T)
,存放了一个类型为 T
的值。得益于泛型的引入,我们可以在任何一个需要返回值的函数中,去使用 Option<T>
枚举类型来做为返回值,用于返回一个任意类型的值 Some(T)
,或者没有值 None
。
enum Result<T, E> {
Ok(T),
Err(E),
}
这个枚举和 Option
一样,主要用于函数返回值,与 Option
用于值的存在与否不同,Result
关注的主要是值的正确性。
如果函数正常运行,则最后返回一个 Ok(T)
,T
是函数具体的返回值类型,如果函数异常运行,则返回一个 Err(E)
,E
是错误类型。例如打开一个文件:如果成功打开文件,则返回 Ok(std::fs::File)
,因此 T
对应的是 std::fs::File
类型;而当打开文件时出现问题时,返回 Err(std::io::Error)
,E
对应的就是 std::io::Error
类型。
方法中使用泛型
方法中依然需要提前声明Point<T>
不再是泛型声明,而是一个完整的结构体类型,因为我们定义的结构体就是 Point<T>
而不再是 Point
。也就是说,impl
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
方法中的结构体也可以额外定义新的泛型函数
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
这个例子中,T,U
是定义在结构体 Point
上的泛型参数,V,W
是单独定义在方法 mixup
上的泛型参数,它们并不冲突
仅登录用户可评论,点击 登录