一、前言
为通用范例赋予一个默认的范例,大部分的语言是没有这个特性的,但是也有例外的,例如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(&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怎样相加
[code]use std: ps::Add;#[derive(Debug,Clone,Copy)]struct Name{ type Output = Name |