rust学习十一.1、泛型(通用类型)

打印 上一主题 下一主题

主题 909|帖子 909|积分 2727

这是和大部分的语言差不多的一个概念,只不过实现上有一些区别而已。
所以,如果学过java,c#,c++,那么这应该很好理解。
虽然如此,还是有不少内容需要记录,只不过内容有一点小多。
注意:这是入门级别内容,只能涉及到一些基本的方面。
一、定义

英文 Generic /generics, 中文翻译为通用类型/泛型, 和java等语言是差不多的一个概念。
注意:现实指的是对象(这里引用了java等oop的概念)的属性和方法可以接收通用类型,不是指对象本身是通用的。只不过
通过对属性和方法的通用化,实现某种水平的对象通用。
 
通用类型的引入产生了巨大的方便
在么有通用类型的情况下,一个带有一个参数的函数/方法只能适用于一种类型,如果类似的函数有多个,那么一定照成了一定的工作量。
所以,通用类型的存在就是为了让一个对象(属性和此中方法)、方法可以适用于多种类型。 具体实现方式见后文。
如果您学过其它语言,这个就非常轻易理解了。
 
二、实现通用类型

和java类似,rust可以在对象(struct,enum等)和方法中使用通用类型符号。
本文的例子基本泉源于相关册本。
以下那么的例子,其实归结起来就是两个:方法中使用通用类型、变量/属性中使用通用类型。
2.1 函数中使用通用类型

特别注意
rust自己把其它语言中的函数、方法做了区分

  • 函数(function)  -  不属于结构、摆列的功能体
  1. fn this_is_function()->i32{
  2.    10;
  3. }
  4. fn main(){
  5.   this_is_function();
  6. }
复制代码
 
      大体上可以这么理解。

  • 方法(method)-属于结构、媒体的功能体
  1. #[derive(Debug)]
  2. struct Cube{
  3.     length: u32,
  4.     width: u32,
  5.     height: u32,
  6. }
  7. impl Cube{
  8.     fn volume(&self) -> u32{
  9.         return self.length * self.width * self.height;
  10.     }
  11.     fn is_bigger_than(&self, other: &Cube) -> bool{
  12.         return self.volume() > other.volume();
  13.     }
  14. }
复制代码
 
虽然二者的语法一致,包括关键字等,但是核心的区别是:函数不属于某个对象(结构、摆列等),只会属于某个模块。
 
这个例子,对于从来没有接触过通用类型的工程师而言,非常有效,由于它说清楚了使用了通用类型的函数和一般函数的区别。
这是典型的:口水说干了,不如一个例子
例_2.1
  1. fn largest_i32(list: &[i32]) -> &i32 {
  2.     let mut largest = &list[0];
  3.     for item in list {
  4.         if item > largest {
  5.             largest = item;
  6.         }
  7.     }
  8.     largest
  9. }
  10. fn largest_char(list: &[char]) -> &char {
  11.     let mut largest = &list[0];
  12.     for item in list {
  13.         if item > largest {
  14.             largest = item;
  15.         }
  16.     }
  17.     largest
  18. }
  19. //使用上通用类型后的取最大值函数,这里只要一个即可
  20. //如果没有对T进行限定,会报告异常: binary operation `>` cannot be applied to type `&T`
  21. //所以必须使用 T: xxxx的方式进行限定
  22. fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
  23.     let mut largest = &list[0];
  24.     for item in list {
  25.         if item > largest {
  26.             largest = item;
  27.         }
  28.     }
  29.     largest
  30. }
  31. fn main() {
  32.     let number_list = vec![34, 50, 25, 100, 65];
  33.     let char_list = vec!['y', 'm', 'a', 'q'];
  34.     let result = largest_i32(&number_list);
  35.     println!("最大数值 of {:?} is {result}", number_list);
  36.     let result = largest_char(&char_list);
  37.     println!("最大字符 of {:?} is {result}", char_list);
  38.     println!("使用带通用类型参数的函数来计算最大值");
  39.     let result = largest(&number_list);
  40.     println!("最大数值 of {:?} is {result}", number_list);
  41.     let result = largest(&char_list);
  42.     println!("最大字符 of {:?} is {result}", char_list);
  43. }
复制代码
 在上例中,函数largest利用通用类型T实现了整数取最大、字符取最大的功能。
如果有返回值,且返回类型也是通用类型,则必须在方法名后带上之类的,这一点和java等类似
2.2 结构体中使用通用类型

道理同2.1,稍微列举下。
  1. #[derive(Debug)]
  2. struct Family<数字,字符>{
  3.     qty:数字,
  4.     name:字符
  5. }
  6. fn main() {
  7.     let family = Family{
  8.         qty: 10,
  9.         name: "A".to_string(),
  10.     };
  11.     println!("{:#?}", family);
  12.     let mf= Family{
  13.         qty:"10个",
  14.         name:"爸爸,妈妈,儿子,女儿"
  15.     };
  16.     println!("{:#?}", mf);
  17. }
复制代码
 
如果有结构方法,且 impl后的结构有带上通用类型符号,则必须在impl后直接带上之类的,同结构体自身一样
  1. impl<数字,String>  Family<数字,String> {
  2.     fn double(&self){
  3.         &self.qty;
  4.     }
  5. }
复制代码
也就是说如果Family有限定类型,则impl后必须带上一样的
 
如果Family不用通用类型符号,则impl也不必带
  1. impl  Family<i32,String> {
  2.     fn get(&self){
  3.         &self.qty;
  4.     }
  5. }
复制代码
 
2.3 摆列中使用通用类型

道理同2.1,稍微列举下。
  1. #[derive(Debug)]
  2. enum Position<A,B,C>{
  3.     普通成员(A),
  4.     组长(B),
  5.     经理(C),
  6.     总监(C),
  7.     老板(A,B,C)
  8. }
  9. fn main() {
  10.     //如果你只使用部分通用类型符号,那么必须使用":xxx"语法来列明其它通用符号的类型
  11.     //这种情况应该是使用于其它情况下
  12.     let p:Position<i32, i32, i32> = Position::普通成员(10); //如果直接定义为 let p=Position::普通成员(10);会报错 type annotations needed for `Position<i32, _, _>`
  13.     println!("{:?}",p);
  14.     let boss=Position::老板("lml",2045,[10,20,30,80,90,95,99,100]);
  15.     println!("{:?}",boss);
  16. }
复制代码
 
这里需要注意是:如果实例变量只是使用部分通用类型符号,那么必须使用":xxx"语法来列明其它通用符号的类型。
否则会报告类似这样的错误: type annotations needed for `Position`
这是由于rust必须在编译期间确定具体类型。
 
摆列定义方法,如果不用通用类型,如下,否则同结构。
  1. impl Position<i32, i32, i32> {
  2.     fn get_position(&self)->i32{
  3.         match self {
  4.             Position::普通成员(a) => *a,
  5.             Position::组长(a) => *a,
  6.             Position::经理(a) => *a,
  7.             Position::总监(a) => *a,
  8.             _ => 100
  9.         }
  10.     }
  11. }
复制代码
 
2.4 其它类型中使用通用类型

除了广泛使用的结构、摆列类型,rust的其它类型也可以使用通用类型。
只不过,这些类型通常需要作为结构摆列等复杂类型(对象)的属性成员,才可以使用这些通用类型符号。
  1. struct Box<A,B,C>{
  2.     names:(A,B,C),
  3.     scores:HashMap<B,C>,
  4.     days:[C;5],
  5.     tools:Vec<A>
  6. }
复制代码
 
它们自己单独定义是没有的,例如不能定义 let name:Vec=Vec::new();
三、限定类型范围

在例子2.1中,函数largest的,必须定义为:
largest(list: &[T]) -> &T 这是为了限定T的类型范围,否则会报告异常,具体什么异常,需要看情况而定。 这个例子告诉我们:rust也面对其它语言一样的题目,这个题目就是在某些场合需要限定通用类型的类型范围,由于有的行为不适合于所有类型。 在本文中,限定类型范围的方式只有这个。但现实上,rust有多种方式,后续会补充完善!四、在运行时判断通用类型的现实类型

rust好像不支持这个。
rust是静态类型语言,这意味着它在编译的时间,就需要确定变量实例的类型。
而这也意味着,它不怎么需要在运行时间:动态判断一个变量实例的具体的类型。
如果它那么做,可能会违法它的核心理念:安全之上。
 
说到这个题目,不得不提到java。
java虽然是名义上的静态类型语言,但是由于java的特殊性(一切都是类),它有办法通过为对象设置属性来判断一个实例的类型。
java可以利用instaneof来判断。
rust没有这种机制,至少学到这里是没有的!
然而rust也有变通的方式改变这个,此处岂论!
五、编译与性能

这个比较有意思。
在入门级的册本中,我们就能了解到这个究竟:

  • 泛型并不会使程序比具体类型运行得慢
  • Rust 通过在编译时进行泛型代码的 单态化monomorphization)来保证服从。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程
monomorphization这是rust人制造的一个词汇,大体由mono(单声道)+morph(变化)+zation(化,使得...成立)构成,外貌意思是:单声道变化
只有结合例子才知道,其实就是中文"具体化"的意思:针对适配的具体类型,逐个编译,使得每一种情况都被思量到。
这种过程很类似设计模式中工厂模式-对外公布一个接口,对内则实现各种工厂。
还是结合例子(来自原册本),再看例_2.1:
  1. fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
  2.     let mut largest = &list[0];
  3.     for item in list {
  4.         if item > largest {
  5.             largest = item;
  6.         }
  7.     }
  8.     largest
  9. }
复制代码
 在上例中,如果只有一个通用类型函数,而没有具体的,那么,在编译的时间,会根据相关情况,编译出若干个函数出来(这里是两个):
  1. fn largest_i32(list: &[i32]) -> &i32 {
  2.     let mut largest = &list[0];
  3.     for item in list {
  4.         if item > largest {
  5.             largest = item;
  6.         }
  7.     }
  8.     largest
  9. }
  10. fn largest_char(list: &[char]) -> &char {
  11.     let mut largest = &list[0];
  12.     for item in list {
  13.         if item > largest {
  14.             largest = item;
  15.         }
  16.     }
  17.     largest
  18. }
复制代码
 
固然,大体是这么一个意思。这也是我自己的猜测。
书本上的例子更加直接,是摆列的。
  1. enum Option_i32 {
  2.     Some(i32),
  3.     None,
  4. }
  5. enum Option_f64 {
  6.     Some(f64),
  7.     None,
  8. }
  9. fn main() {
  10.     let integer = Option_i32::Some(5);
  11.     let float = Option_f64::Some(5.0);
  12. }
复制代码
 
意思就是,根据main的情况,自动反编译出Option的两个具体。
思量到这种,需求,所以,不难理解为什么rust采用静态实现,为什么不支持动态判断变量实例的类型(待确认)。
六、小结


  • 通用类型针对的是对象属性,方法/函数参数,不是对象、方法本身。这个基本同java等语言
  • 可以使用合法的标识符来表现通用类型,不必一定是K,T,V之类的,包括单词,汉语词语等。这个方面和JAVA等语言类似
  • rust并没有对方法/函数一大概对象可以定义的通用类型进行个数限定
  • 如果要对函数返回类型使用通用类型,则必须在函数名后跟上诸如这样的字符,这个方式和java的类似。当方法不需要(注意函数和方法的区别)
  • rust一样存在通用类型的常见题目:要么定义的时间限定通用类型的类型,要么是运行时在方法/函数体中使用类似 instanceof(java)之类的判断泛型的现实类型。rust只能在编码的时间进行限定
  • 但rust可以使用类似 T:xxxx的方式限定类型范围,大概T:xxxx+****+???+..,,此中xxxx,****,???都是表现特定类型/操纵
  • 截止本章为止,rust并没有?这样的通配符,可以用于通用类型中。java有类似 ? super/extends Grade这样的用法,可用于限定参数类型
  • rust通过在编译阶段的行为(单态化/具体化)来实现通用。但这种机制是不同于java之类的语言的,虽然它们都是静态类型语言。
注意:由于本文是按照相关册本次序编写的,所以很多内容是具有一定局限性的。 例如rust用于限定通用类型范围的方式不是只有一种。后续会补充有关内容。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表