寇秀娟 发表于 4 天前

rust学习二十.6、RUST通用类型参数默认类型和运算符重载

一、前言

为通用类型赋予一个默认的类型,大部分的语言是没有这个特性的,但是也有例外的,例如TypeScript(可能还有其它)。
例如TypeScript可以这样使用:
class MyClass<T = number> {
    value: T;
    constructor(value: T) {
      this.value = value;
    }
    printValue(): void {
      console.log(`Value is ${this.value}`);
    }
}
const obj1 = new MyClass(42);// 使用默认类型 number
const obj2 = new MyClass<string>("Hello");// 使用指定类型 string而运算符重载,则不少语言也支持,最典型的莫过于C++,C#.
但是rust的运算符重载是比较特别的一种,该怎么说了?
rustc做了太多的工作,而且我觉得有点违背一些通用的设计规则。这是因为例子中的方法必须要求对象实现Copy,但是方法的参数又没有带&,会让人误会!
不喜欢有太多默认约定的设计,更喜欢每个东西都明明白白地定义。
二、通用类型参数默认类型

读取来有点拗口,意思就是:
1.在有关对象(struc,特质等)或者方法中使用通用参数T
2.可以为T指定一个默认的类型,语法是T=xxx,其中xxx是某个具体类型
如果你不喜欢T,也可以换成任意合法的rust标识符.
就目前来看,通用参数的默认参数的作用有两点:运算符重载+方便
三、运算符重载和其它作用

3.1、运算符重载

所谓运算符重载就是除了运算符最原始的功能(编译器默认支持的)之外,还可以支持其它类型的运算数。
例如+通常用于整数、浮点数等的相加,但通过重载,其它类型对象实例也可以使用+。
以此类推,-*/等运算符号也可以。
不管怎么说,这算是一个好东西!
只不过类型参数的默认类型好像就是为了运算符重载而存在。
3.2、其它作用

查了一些资料,据说可以结合条件编译。其它作用就是无关紧要的。
条件编译示例
// 定义一个特性标志,用于条件编译

#
type DefaultNumType = f64;
#
type DefaultNumType = i32;
struct Point<T = DefaultNumType> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
      Point { x, y }
    }

四、示例

4.1、示例代码

由于例子涉及到Add,Sub两个特质,所以先列出此二特质的定义:
pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
pub trait Sub<Rhs = Self> {
    type Output;
    fn sub(self, rhs: Rhs) -> Self::Output;
}对书本上的例子稍微改造了下:
use std::ops::{Add,Sub};

#
struct Point {
    x: i32,
    y: i32,
}
/**
* 这个使用默认类型,来自rust编程语言官方文档的例子
*/
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
      Point {
            x: self.x + other.x,
            y: self.y + other.y,
      }
    }
}

/**
* 实现相减运算符(从而实现Point的-重载),需要实现Sub trait
*/
impl Sub for Point {
    type Output = Point;
    /**
   * 需要特别注意的是两个参数的定义
   * self -没有使用引用
   * other - 没有要求引用
   * 这种不引用的方式,不同于一般的方法定义
   */
    fn sub(self, other: Point) -> Point {
      Point {
            x: self.x - other.x,
            y: self.y - other.y,
      }
    }
}


fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    //使用重载的方式调用
    println!("{:?}+{:?}={:?}",p1,p2, p1 + p2);
    println!("{:?}-{:?}={:?}",p1,p2, p1 - p2);

    //不使用重载的方式调用
    let p3 = p1.add(p2).sub(p2);
    let p4 = (p1.sub(p2)).add(p2);
    println!("{:?}+{:?}-{:?}={:?}",p1,p2, p2,p3);
    println!("{:?}-{:?}+{:?}={:?}",p1,p2,p2, p4);

    let lml= Person {name: "lml".to_string()};
    let ww= Animal {name: "ww".to_string()};
    lml.attack(ww);
    println!("{:?}",lml);
}

// --------------------------------------------------------
// 以下的代码是为了演示 参数不带&是什么情况

trait Fight {
    type Item;
    //fn attack(&self, other: &Self::Item);
    fn attack(self, other: Self::Item);
}
#
struct Person {name: String}
#
struct Animal {name: String}

impl Fight for Person {
    type Item = Animal;
    fn attack(self, other: Self::Item) {
      println!(
            "{}攻击了{}",
            self.name,
            other.name
      );
    }   
}这个例子做了三件事情:
1.重载+
2.重载-
3.如果不使用Copy特质会怎么样
特质Fight和结构体Person,Animal就是为了验证第3点。
4.2、名词解释

在开始执行代码前,先解释两个重要的内容
Rhs
Rhs是 "Right-Hand Side"(右侧操作数)的缩写
关键字self和Self
仔细看看,才发现是两个,不是一个,要知道rust中是区分大小写的。
self-全小写,表示对象实例本身
Self-首字母大写,其它小写,表示类型本身
例如以下代码中:
trait Fight {    type Item;    fn attack(&self, other: &Self::Item);    fn defend(&self, danger: &T)    where T: Danger; }在方法attack中,第一个self表示具体对象实例,第二个Self则表示具体对象类型。
pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
pub trait Sub<Rhs = Self> {
    type Output;
    fn sub(self, rhs: Rhs) -> Self::Output;
}现在代码应该容易看了。
4.3、执行

看看输出:

只要把示例中如下一部分:
trait Fight {
    type Item;
    //fn attack(&self, other: &Self::Item);
    fn attack(self, other: Self::Item);
}
#
struct Person {name: String}
#
struct Animal {name: String}


impl Fight for Person {
    type Item = Animal;
    fn attack(self, other: Self::Item) {
        println!(
            "{}攻击了{}",
            self.name,
            other.name
        );
    }    
} 修改为:
trait Fight {
    type Item;
    fn attack(&self, other: &Self::Item);
    //fn attack(self, other: Self::Item);
}
#
struct Person {name: String}
#
struct Animal {name: String}


impl Fight for Person {
    type Item = Animal;
    fn attack(&self, other: &Self::Item) {
        println!(
            "{}攻击了{}",
            self.name,
            other.name
        );
    }    
}再把main的调用修改为:
lml.attack(&ww);那么就可以正确输出:

为什么在Point上没有这个问题了?这是因为Point实现了Copy特质,看下面的代码:#
rust的Copy特质奇怪的作用:允许类型进行隐式的、按位的复制,适用于简单数据类型,避免不必要的所有权移动,提升代码效率和便利性。同时,强调其使用条件和限制,帮助用户正确理解和应用
什么是按位复制?
也就是说,当赋值或作为函数参数传递时,不需要移动所有权,而是直接复制。不过,只有满足某些条件的类型才能实现Copy,比如所有字段都实现了Copy,并且类型本身没有实现Drop trait
所以,上例中,即使在方法中没有定义为引用类型,它也不会报错。而Person并没有实现Copy特质,所以会发生这个问题。
五、示例2

以下的示例演示了一个只包含字符串切片的struct如何相加
use std::ops::Add;#struct Name{    type Output = Name
页: [1]
查看完整版本: rust学习二十.6、RUST通用类型参数默认类型和运算符重载