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、其它作用

查了一些资料,据说可以结合条件编译。其它作用就是无关紧要的。

条件编译示例

// 定义一个特性标志,用于条件编译

#[cfg(feature = "use_f64")]
type DefaultNumType = f64;
#[cfg(not(feature = "use_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};

#[derive(Debug, Copy, Clone, PartialEq)]
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);
}
#[derive(Debug)]
struct Person {name: String}
#[derive(Debug)]
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<T>(&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);
}
#[derive(Debug)]
struct Person {name: String}
#[derive(Debug)]
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);
}
#[derive(Debug)]
struct Person {name: String}
#[derive(Debug)]
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特质,看下面的代码:#[derive(Debug, Copy, Clone, PartialEq)]

rust的Copy特质奇怪的作用:允许类型进行隐式的、按位的复制,适用于简单数据类型,避免不必要的所有权移动,提升代码效率和便利性。同时,强调其使用条件和限制,帮助用户正确理解和应用

什么是按位复制?

也就是说,当赋值或作为函数参数传递时,不需要移动所有权,而是直接复制。不过,只有满足某些条件的类型才能实现Copy,比如所有字段都实现了Copy,并且类型本身没有实现Drop trait

所以,上例中,即使在方法中没有定义为引用类型,它也不会报错。而Person并没有实现Copy特质,所以会发生这个问题。

五、示例2

以下的示例演示了一个只包含字符串切片的struct如何相加

use std::ops::Add;
#[derive(Debug,Clone,Copy)]
struct Name<'a>{
    name:&'a str
}

impl<'a> Add for Name<'a>{
    type Output = Name<'a>;
    fn add(self, other: Self) -> Self {
        let tmp=format!("{} {}",self.name,other.name);
        //Box::leak的作用是 将堆上的内存转换为 'static 生命周期的引用
        let leaked_str: &'static str = Box::leak(tmp.into_boxed_str());
        Self{name: leaked_str }
    }
}

fn main() {
    let name1 = Name{name:"lu"};
    let name2 = Name{name:" mula"};
    let name3=name1+name2;
    println!("name1={:?},name2={:?}", name1,name2);
    println!("name3={:?}", name3);

    let name4=String::from("lu");
    let name5=String::from("lu");
    let name6=add(name4,name5);
    println!("name6={:?}", name6);
}

fn add(name1:String,name2:String)->String{
    format!("{} {}",name1,name2)
}

运行结果:

本例主要说明以下几个问题:

1.字符串切片可以Copy.所以包含字符串切片的类型也可以实现+等重载

2.实现和字符串切片有关的内容太麻烦,导出是奇怪的生命周期符号

六、小结

1.rust通过定义通用类型参数的默认类型来实现运算符重载

2.但也不是什么对象都可以重载,最好是能够实现Copy特质的类型,否则可能失败

posted @ 2025-03-21 17:18  正在战斗中  阅读(77)  评论(0)    收藏  举报