rust 面向对象、trait

目录

一,结构体、成员方法

二,trait(特征)

三,同名函数的覆盖、冲突

1,trait中的默认函数(低级)

2,有无self的函数的多种调用方法

3,引用升级

4,给结构体实现的trait中的函数(中级)

5,直接实现的函数(高级)

6,2个trait之间的同名函数

四,trait继承

五,trait的孤儿规则

六,trait的关联类型(type成员)

七,常见trait

0,trait的特殊规则总结

1,Clone、Copy

2,PartialEq、Eq、PartialOrd、Ord、Hash

3,Display、ToString、Debug

4,Write

5,Sized、From、Into

6,Default

7,Drop

8,Borrow、BorrowMut

9,Deref、DerefMut


一,结构体、成员方法

给结构体添加方法有2种,一种是直接实现,一种的带trait的实现。

直接实现的方式中,数据和方法是对应的。带trait的实现中,trait是一组可以被共享的行为,只要实现了特征,你就能使用这组行为。


struct SearchData{
    
}
impl SearchData{
    fn f(&self)-> i32{
        return 123;
    }
    fn f2()-> i32{
        return 456;
    }
}


fn main() {
    let x=SearchData{};
    assert_eq!(x.f(), 123);
    assert_eq!(SearchData::f2(), 456);
}

二,trait(特征)

带trait的实现方式中,数据、接口描述、接口实现要分开。

trait Solution{
    fn find(&self)-> i32;
}
struct SearchData{
    
}
impl Solution for SearchData{
    fn find(&self)-> i32{
        return 123;
    }
}
fn main() {
    let x=SearchData{};
    println!("{}",x.find());
}

三,同名函数的覆盖、冲突

按照覆盖关系,可以分为三个级别。

1,trait中的默认函数(低级)

trait MyTrait
{
    fn my_func()->i32{
        -1
    }
}

trait中的方法是无法直接调用的

trait MyTrait
{
    fn my_func()->i32{
        -1
    }
}

struct S{
}
impl MyTrait for S{

}

fn main() {
    let x=S{};
    assert_eq!(S::my_func(), -1);
    println!("end");
}

给结构体S实现了trait,S就自动拥有了默认函数

2,有无self的函数的多种调用方法

trait MyTrait
{
    fn my_func()->i32{
        -1
    }
    fn my_func2(&self)->i32{
        -2
    }
}

struct S{
}
impl MyTrait for S{

}

fn main() {
    let mut x=S{};
    assert_eq!(S::my_func(), -1);
    assert_eq!(<S as MyTrait>::my_func(), -1);
    assert_eq!(x.my_func2(), -2);
    assert_eq!((&x).my_func2(), -2);
    assert_eq!((&mut x).my_func2(), -2);
    assert_eq!(S::my_func2(&x), -2);
    assert_eq!(S::my_func2(&mut x), -2);
    println!("end");
}

对于无self的函数,可以用类型体名去调用,也可以用类型名转化成Trait名去调用

对于有self的函数,可以用数据或者数据的各种引用去调用,也可以用类型名或Trait名去调用但是要带上引用形参

3,引用升级

self形参只允许有& T、&mut T两种

对于实参,遵循以下几条规则:

(1)带self形参有四种调用方式,分别是数据调用、数据引用调用、类型调用、Trait名调用

PS:这里的引用包括各种各样的引用

PS:这里的类型包括数据结构类型和dyn Trait类型。

还是以这个代码为例:

trait MyTrait
{
    fn my_func()->i32{
        -1
    }
    fn my_func2(&self)->i32{
        -2
    }
    fn my_func3(&mut self)->i32{
        -3
    }
}

struct S{
}
impl MyTrait for S{

}

(2)数据调用,自动把数据转换成引用,前提是满足引用规则。

PS:引用规则就是不可变数据的1种引用和可变数据的2种引用。

    let mut x=S{};    
    assert_eq!(x.my_func2(), -2);
    assert_eq!(x.my_func3(), -3);

(3)数据引用调用

&self入参的函数,可以在&x或者&mut x前面随便加引用

&mut self入参的函数,可以在&mut x前面随便加&mut。

PS:满足引用级别指的是级别足够高,&mut的级别高于&的级别

    assert_eq!((&x).my_func2(), -2);
    assert_eq!((&mut x).my_func2(), -2);
    assert_eq!((&&&x).my_func2(), -2);
    assert_eq!((&mut x).my_func3(), -3);
    //assert_eq!((&mut &x).my_func3(), -3); 引用级别不够

(4)类型调用,规则和数据引用调用完全一致


    assert_eq!(S::my_func2(&x), -2);
    assert_eq!(S::my_func2(&mut x), -2);
    assert_eq!(S::my_func2(&&&x), -2);
    assert_eq!(S::my_func3(&mut x), -3);
    //assert_eq!(S::my_func3(&mut & x), -3); //引用级别不够

(5)Trait调用,规则和数据引用调用完全一致


    assert_eq!(<S as MyTrait>::my_func2(&x), -2);
    assert_eq!(<S as MyTrait>::my_func2(&mut x), -2);
    assert_eq!(<S as MyTrait>::my_func2(&&&x), -2);
    assert_eq!(<S as MyTrait>::my_func3(&mut x), -3);
    //assert_eq!(<S as MyTrait>::my_func3(&mut & x), -3); //引用级别不够

(6)完整代码

trait MyTrait
{
    fn my_func()->i32{
        -1
    }
    fn my_func2(&self)->i32{
        -2
    }
    fn my_func3(&mut self)->i32{
        -3
    }
}

struct S{
}
impl MyTrait for S{

}

fn main() {
    let mut x=S{};
    //数据调用
    assert_eq!(x.my_func2(), -2);
    assert_eq!(x.my_func3(), -3);
    //数据引用调用
    assert_eq!((&x).my_func2(), -2);
    assert_eq!((&mut x).my_func2(), -2);
    assert_eq!((&&&x).my_func2(), -2);
    assert_eq!((&mut x).my_func3(), -3);
    //assert_eq!((&mut &x).my_func3(), -3); //引用级别不够
    //数据类型调用
    assert_eq!(S::my_func2(&x), -2);
    assert_eq!(S::my_func2(&mut x), -2);
    assert_eq!(S::my_func2(&&&x), -2);
    assert_eq!(S::my_func3(&mut x), -3);
    //assert_eq!(S::my_func3(&mut & x), -3); //引用级别不够
    //Trait类型调用
    assert_eq!(<S as MyTrait>::my_func2(&x), -2);
    assert_eq!(<S as MyTrait>::my_func2(&mut x), -2);
    assert_eq!(<S as MyTrait>::my_func2(&&&x), -2);
    assert_eq!(<S as MyTrait>::my_func3(&mut x), -3);
    //assert_eq!(<S as MyTrait>::my_func3(&mut & x), -3); //引用级别不够
    println!("end");
}

4,给结构体实现的trait中的函数(中级)

trait的具体实现会覆盖默认实现

trait MyTrait
{
    fn my_func()->i32{
        -1
    }
    fn my_func2(&self)->i32{
        -2
    }
    fn my_func3(&mut self)->i32{
        -3
    }
}

struct S{
}
impl MyTrait for S{
    fn my_func()->i32{
        1
    }
    fn my_func2(&self)->i32{
        2
    }
    fn my_func3(&mut self)->i32{
        3
    }
}

fn main() {
    let mut x=S{};
    assert_eq!(x.my_func2(), 2);
    assert_eq!(x.my_func3(), 3);
    assert_eq!(S::my_func(), 1);
    assert_eq!(S::my_func2(&x), 2);
    assert_eq!(S::my_func3(&mut x), 3);
    assert_eq!(<S as MyTrait>::my_func(), 1);
    assert_eq!(<S as MyTrait>::my_func2(& x), 2);
    assert_eq!(<S as MyTrait>::my_func3(&mut x), 3);
    println!("end");
}

这是一种彻底的覆盖,对于一个结构体来说,实现了某个trait中的函数,trait中的默认函数就彻底失去了

从语法来说,两个函数其实是同一个函数,所以用法也是完全一样

所以,下文的“Trait中的函数”同时指代这2个函数。

5,直接实现的函数(高级)

直接实现的函数有数据调用、数据引用调用、类型调用三种方法。而Trait名调用是Trait中的函数的独有方法。

当同名函数冲突时,这三种方法都会调到优先级更高的直接实现的函数

PS:这是可以共存的2个不同的函数,都是可以调用到的。


trait MyTrait
{
    fn my_func()->i32{
        -1
    }
    fn my_func2(&self)->i32{
        -2
    }
    fn my_func3(&mut self)->i32{
        -3
    }
}

struct S{
}
impl MyTrait for S{
    fn my_func()->i32{
        1
    }
    fn my_func2(&self)->i32{
        2
    }
}
impl S{
    fn my_func()->i32{
        11
    }
    fn my_func2(&self)->i32{
        22
    }
    fn my_func3(&mut self)->i32{
        33
    }
}

fn main() {
    let mut x=S{};
    assert_eq!(x.my_func2(), 22);
    assert_eq!(x.my_func3(), 33);
    assert_eq!((& x).my_func2(), 22);
    assert_eq!((&mut x).my_func3(), 33);
    assert_eq!(S::my_func(), 11);
    assert_eq!(S::my_func2(&x), 22);
    assert_eq!(S::my_func3(&mut x), 33);
    assert_eq!(<S as MyTrait>::my_func(), 1);
    assert_eq!(<S as MyTrait>::my_func2(& x), 2);
    assert_eq!(<S as MyTrait>::my_func3(&mut x), -3);
    println!("end");
}

6,2个trait之间的同名函数

2个trait之间有同名函数,只能用trait调用。

struct S{
    
}
trait MyTrait{
    fn f1(&self)-> i32;
}

impl MyTrait for S{
    fn f1(&self)-> i32{
        return 111;
    }
}
trait MyTrait2{
    fn f1(&self)-> i32;
}

impl MyTrait2 for S{
    fn f1(&self)-> i32{
        return 222;
    }
}

fn main() {
    let x=S{};
    assert_eq!(MyTrait::f1(&x), 111);
    assert_eq!(<S as MyTrait>::f1(&x), 111);
    assert_eq!(<S as MyTrait2>::f1(&x), 222);
    println!("end");
}

四,trait继承

trait继承是规定,一个类型实现子trait之前,必须先实现父trait。

trait A{
    fn funa();
}
trait B:A{
    fn funb();
}
trait C:A+B{
    fn func();
}
struct S{
}
impl A for S{
    fn funa(){
        println!("run a");
    }
}
impl B for S{
    fn funb(){
        println!("run b");
    }
}
impl C for S{
    fn func(){
        S::funa();
        println!("run c");
    }
}

注意,虽然有继承关系,但是可见性却是平级的,父特征中的方法也可以调用子特征中的方法

例如,自定义排序是这么写的:

#[derive(Eq,PartialEq)]
struct Node{
    id:i32,
    num:i32
}
impl Ord for Node {
    #[inline]
    fn cmp(&self, other: &Node) -> Ordering {
        if self.num != other.num{
            return (*other).num.cmp(&self.num);
        }else{
            return self.id.cmp(&(*other).id);
        }
    }
}
impl PartialOrd for Node{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        return Some(self.cmp(other)); 
    }
}

Ord依赖PartialOrd,但实现上却建议用partial_cmp调用cmp。

我是这么理解的,trait继承表达的是顶层的依赖关系,即设计层面的依赖关系,而trait内的函数调用是底层的依赖关系,即实现层面的依赖关系

五,trait的孤儿规则

孤儿规则:如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的。

这条规则保证了代码的安全性,使得无关的代码不会互相影响。

六,trait的关联类型(type成员)

 以减法为例

pub trait Sub<Rhs = Self> {
    type Output;
    fn sub(self, rhs: Rhs) -> Self::Output;
}

用法:

struct S{
    x:i32,
    y:f32
}

impl Sub for S{
    type Output = Self;
    fn sub(self,rhs:Self)->Self::Output{
        S{x:self.x-rhs.x,y:self.y-rhs.y}
    }
}

七,常见trait

0,trait的特殊规则总结

特殊背景规则:

(1)部分类型自动具有Clone、Copy特征

(2)任何类型都不能同时实现Drop和Copy,但是可以同时实现Drop和Clone。

(3)模板类型是默认包含Sized这个特征的,需要 ?Sized 才能说明可以不具有该特征。

(4)只要trait中有一个函数的入参或返回值是Self类型,这个trait就需要继承Sized

特殊潜规则:

(1)永远不要修改ToString中的to_string函数。

(2)永远不要修改Into中的into函数。

1,Clone、Copy

Clone、Copy

2,PartialEq、Eq、PartialOrd、Ord、Hash

PartialEq、Eq、PartialOrd、Ord、Hash

3,Display、ToString、Debug

ToString中的to_string有默认的泛型实现,里面会调用Display中的fmt,永远不要修改to_string函数

use std::fmt::Display; 
use std::string::ToString;
use std::fmt::Formatter;
use std::fmt::Result;
struct S{
    x:i32,
    y:f32
}
impl Display for S {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result{
        write!(f, "x={},y={}", self.x, self.y)
    }
}

fn main() {
    let x = S{x:3,y:5.5};
    assert_eq!(x.to_string(), "x=3,y=5.5");
}

同时也要注意,实现fmt时一定不要调用self.to_string(),否则会造成无限递归。

Debug里面也是fmt函数,一般会比Display的fmt函数的输出内容更详细。

println中的{}会调用Display的fmt,而{:?}和{:#?}会调用Debug的fmt。其中{:#?}是段落格式。

对于原始指针类型,{:?}和{:p}都可以把指针本身的值打出来,不需要特征。

print和println的唯一区别就是,println的末尾多打印一个换行符。

4,Write

use std::io::Write;

和输入输出流有关

5,Sized、From、Into

Sized表示一个类型是否具有确定的大小。

如i32的大小是确定的,而序列 [i32] 的大小是不确定的。

pub trait From<T>: Sized {
    fn from(value: T) -> Self;
}

因为返回值类型需要具有Sized,所以只要是有Self类型的返回值函数的Trait,都需要继承Sized

pub trait Into<T>: Sized {
    fn into(self) -> T;
}

因为入参类型需要具有Sized,所以只要是有有Self类型的入参的函数的Trait,都需要继承Sized

PS:只针对单纯的Self类型,各种引用类型不算,因为引用本身肯定是Sized的。

Into的into有默认的泛型实现:

#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> Into<U> for T
where
    U: From<T>,
{
    #[inline]
    fn into(self) -> U {
        U::from(self)
    }
}

所以永远不要修改into函数

6,Default

default函数类似于new函数,用于申请一个对象:

pub trait Default: Sized {
    #[stable(feature = "rust1", since = "1.0.0")]
    fn default() -> Self;
}

需要显式地调用default函数才能获取具有默认值的对象,而new函数用于获取任意值的对象。

7,Drop

Drop用于定义一个类型的析构函数:

pub trait Drop {
    fn drop(&mut self);
}

这是系统自动调用的函数,用户可以实现这个函数,但不能调用它,如果写了调用代码,则会编译报错:explicit use of destructor method

但是,rust提供一个泛型的drop函数

pub fn drop<T>(_x: T) {}

函数实现是空的,作用是释放一个变量的所有权,函数调用完后立刻析构。

任何类型都不能同时实现Drop和Copy,但是可以同时实现Drop和Clone

系统自动调用Drop中的drop的规则:

(1)一个数据的所有权丢失了,不被任何变量持有,则立刻调用Drop中的drop

(2)同一个函数内的栈变量,按照后定义的先释放,依次调用Drop中的drop

示例:

struct S{
    x:i32
}

impl Drop for S{
    fn drop(&mut self){
        print!("x={} ", self.x);
    }
}

fn main() {
    let mut a=S{x:1};
    let b=S{x:2};
    let c=S{x:3};
    drop(b);
    a.x=1;
    println!("end");
}

输出:

x=2 end
x=3 x=1 

无论有没有最后的a.x=1;这一句,都是这个输出。

(3)如果一个数据的所有权发生了转移,则按照最后持有所有权的变量的定义位置进行排序,依次调用Drop中的drop

示例:

struct S{
    x:i32
}

impl Drop for S{
    fn drop(&mut self){
        print!("x={} ", self.x);
    }
}

fn main() {
    let a=S{x:1};
    let b=S{x:2};
    let c=S{x:3};
    let d=a;
    drop(b);
    println!("end");
}

输出:

x=2 end
x=1 x=3 

(4)嵌套数据结构,先调用外层的drop,再逐层往里调用drop,同一级成成员,按照定义的顺序依次析构

示例:

struct SS(&'static str);
impl Drop for SS{
    fn drop(&mut self){
        println!("Dropping {}",self.0);
    }
}
struct S{
    a:SS,
    b:SS
}
impl Drop for S{
    fn drop(&mut self){
        println!("Dropping S");
    }
}

fn main() {
    let x=S{
        a:SS("a"),
        b:SS("b")
    };
}

输出:

Dropping S
Dropping a
Dropping b

(5)结构体的成员变量,按照声明顺序析构。枚举、元组等都类似

(6)闭包通过移动(move)语义捕获的变量的销毁顺序未明确指定,这是一个未定义行为

8,Borrow、BorrowMut

参考引用

9,Deref、DerefMut

参考隐式转换

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值