泛型
有了impl 的基础我们可以来看RUST 的泛型实现了,RUST中的泛型和其他面向对象语言 相比较 最大特点是采用了 零抽象的原则 为什么说是零抽象?
泛型本质上就是在代码阶段 通过类型符号(严格意义上不是变量) 实现了类型占位符
其实C里面也有实现泛型的类似方法,想想是什么?
#define min(a,b) (a)<(b)? (a) : (b)
作为一名C语言开发工程师,之前我是很烦面向对象的,原因在于接触了java语言后,似乎程序员的工作从编写 代码变成了需要熟知各种类型的API,更重要的原因我总结如下
- C语言中的API接口,只要你看到源码,可以清楚知道它在做什么 是怎么实现的
- 面向对象中的API接口,你看到源码,会看到它又引用或者继承了更高一个层级的(一般叫父类型)的API,又因为 这些代码中大量使用了更高级别的抽象语言,导致如果你没有把它的语法学明白,几乎无法读基础库的源码
这形成了一个悖论,java 这种提高生产力的开发语言,目标是希望开发人员可以用极低的学习成本加速开发过程, 如果你想成为高深的java程序员,你需要把java的语言特性和语法学习明白(大部分人是不行的,因为本来就是上层应用开发人员)
什么是类型系统?类型是对模型的抽象,这其实对我们并不遥远,即使是C语言开发人员 也知道 u8 是对0-255数字的抽象
类型泛型
知识点: 泛型是一个类型占位符,没有实例化(使用该泛型的解构、函数等)的泛型是没有意义的 RUST会在编译阶段,根据具体使用泛型的代码,完成泛型类型的替换,生成实际的类型、函数
练习1: 结构体泛型
struct GenricA<T> (T); // 定义了一个元组结构体,包含类型T
// 定义了一个POINT 泛型结构体,包含类型T U
// 代码中并没有使用到这个类型,因此该类型不会被实例化
struct GenricPoint<T,U> {
x:T,
y:U,
}
fn main() {
//使用两种方法修复它
let a: GenricA<i32> = GenricA(12_u32);
let GenricA(x) = a; //解构元组结构体
println!("{}",x);
}
练习2: 函数泛型
//先不用关注这里了 std::ops::Add 后面会讲
fn add <T: std::ops::Add<Output = T>> (a:T, b:T) -> T {
a+b
}
fn main() {
let c = add(10_i32,20_i32);
let d = add(10_f32,20_f32);
}
练习3: 方法实现泛型
- 在使用impl 为泛型类型实现方法时,必须要显示为impl声明泛型
struct GenricPoint<T,U> {
x:T,
y:U,
}
//必须在impl 之后增加泛型声明
impl<T,U> GenricPoint<T,U> {
//函数需要用到其他泛型,需要在函数继续声明泛型
fn mix<V,W>(self: GenricPoint<T,U> , mixval: GenricPoint<V,W>) -> GenricPoint<T,W> {
GenricPoint{
x: self.x,
y: mixval.y,
}
}
}
fn main() {
let a : GenricPoint<i32,i32> = GenricPoint{x:10, y:10};
let b : GenricPoint<f32,f32> = GenricPoint{x:1.0_f32, y:2.0_f32};
let _c : GenricPoint<i32,f32> = a.mix(b);
//上述代码在真正运行的时候,想的简单一点,会为 每个类型 实例化一个函数
//GenricPoint_mix(GenricPoint<i32,i32>,GenricPoint<f32,f32>)
}
const值泛型
基于泛型类型本质上就是一个占位符,在编译阶段会被常量化,RUST也支持值的常量化
//声明了一个N占位符,N 在使用的时候 必须是一个常量,或者是一个在编译阶段可以处理的常量表达式
//由于它的静态性,该值初始化不应该依赖运行态 比如需要调用函数
struct Arr<T, const N: usize> ([T;N]);
fn main(){
let _a: Arr<i32,12> = Arr([0;12]);
}