Rust 入门 方法Method (十一)

从面向对象语言过来的同学,对于方法肯定不陌生, class 里面就充斥着方法的概念. 在Rust 中, 方法的概念也大差不差, 往往和对象成对出现:

object.method()

例如读取一个文件写入缓冲区,如果用函数的写法read(f ,buffer),  用方法的写发f.read(buffer). 不过与其他语言 class  跟方法的联动使用不同, ( 这里可能要修改下) ,Rust 的方法往往跟结构体, 枚举,特征 Trait  一起使用, 特征将在后面几章进行介绍. 

定义方法

Rust 使用 impl 来定义方法, 例如以下代码: 

struct  Circle {
    x: f64 ,
    y: f64,
    radius: f64,
}

impl Circle {
    //new  时 Cricle 的关联函数, 因为它的第一个参数不是self , 且 new 并不是关键字 
    // 这种方法往往用于初始化当前结构体的实例 
    fn new(x:f64,y:f64,radius: f64) -> Circle {
        Circle {
            x:x,
            y:y,
            radius: radius,
        }
    }

    // Circle 的方法, &self  表示借用省钱的 Circle 结构体 
    fn area( &self ) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

我们这里先不详细展开讲解,只是先建立对方法定义的大致隐形. 下面的图片将Rust方法定义与其它语言的方法定义做了对比

可以看出,其它语言中所有定义都在class 中, 但是Rust的对象定义和方法定义是分离的, 这种数据和使用分离的方式, 会给予使用者极高的灵活度.

再来看一个例子:

#[derive(Debug)]
struct Rectangle {
    width : u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle { width:30 , height:50};
        
    println!("The area of the rectangle is {} square pixels.", rect1.area() );
}

该例子定义了一个 Rectangle 结构体, 并且再其上定义了一个 area方法,用于计算该矩形的面积.

impl Rectangle {} 表示为 Rectangle 实现方法 (impl 是实现 implementation 的缩写) , 这样的写法表明 impl 语句块中的一切都是跟 Rectangle 相关联的. 

self , &self 和 &mut self 

接下来的内容非常重要, 请大家仔细看. 再area 的签名中, 我们使用&self 替代 rectangle: & Rectangle, &self 其实是self: &Self 的简写 (注意大小写). 在一个impl 块内, Self 指代被实现方法的结构体类型, self 指代此类型的实例, 欢聚话说, self 指代的是 Rectangle 结构体实例,这样的写法会让我们的代码简洁很多,而且非常便于理解: 我们为那个解构体实现方法,那么self 就是指代哪个解构体的实例. 

需要注意的是 , self  依然有所有权的概念: 

        1. self 表示Rectangle 的所有权转移到该方法中, 这种形式用的较少,

        2. &self  表示该方法对Rectangle 的不可变借用 

        3. &mut self  表示可变借用

总之, self  的使用 就跟函数参数一样, 要严格遵守Rust 的所有权规则. 

回到上面的例子中, 选择 & self 的理由跟再函数中 使用&Rectangle 是相同的: 我们并不像获取所有权, 也无需改变它, 只是希望能够读取结构体中的数据. 如果想要再方法中去改变当前的结构体, 需要将第一个参数改为 &mut self . 仅仅通过使用self 作为第一个参数来使方法获取实例的所有权是很少见的, 这种使用方式往往用于把当前的对象转成另外一个对象时使用, 转换完后, 就不再关注之前的对象. 且可以放置对之前对象的误调用. 

简单总结下,使用方法代替函数有以下好处: 

        1. 不用再函数签名中重复书写 self 对应的类型

        2. 代码的组织性和内举行更强, 对于代码维护和阅读来说,好处巨大

方法名跟结构体字段名相同

再Rust 中,允许方法名跟解构体的 字段名相同: 

impl Rectangle {
    fn width(&self) -> bool {
        self.width > 0
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        hegiht: 50 ,
    };
    
    if rect1.width() {
        println!("The rectangle has a nonzero width; it is {}", rect1.width);
    }
}

当我们使用rect1.width() 时, Rust知道我们调用的时它的方法,如果使用rect1.width , 则是访问它的字段. 

一般来说,方法跟字段同名,往往适用于实现getter 访问器,例如 

mod my {
    pub struct Rectangle {
         width: u32,
        pub height: u32,   
    }

    impl Rectangle {
        pub fn new(width: u32,height : u32 ) -> Self {
            Rectangle {width,height}
        }
        pub fn width(&self) -> u32 {
            return self.width;
        }
        pub fn height(&self) -> u32 {
            return self.height;
        }
    }
}

fn main() {
    let rect1 = my::Rectangle::new(30,50);
    
    println!("{}",rect1.width()); // ok 
    println!("{}", rect1.height()); // ok 
    // println!("{}",rect1.width); // Error - the visibility of field defaults to private
    println!("{}",rect1.height); // ok 
}

当从模块外部访问结构体时, 结构体的字段默认是私有的, 其目的是隐藏信息(封装).  我们如果想要从模块外部获取 Rectangle 的字段, 只需把它的new , width 和 height 方法设置为公开可见,那么用户就可以创建一个矩形,同时通过访问器 rect1.width() 和 rect1.height() 方法来获取矩形的宽度和高度. 

因为width 字段是私有的,  当用户访问 rect1.width 字段时,就会报错. 注意在此例中, Self 指代的就是被实现方法的结构体 Rectangle . 

特别的时.这种默认的可见性 (私有的) 可以通过pub 进行覆盖, 这样对于模块外部来说,就可以直接访问适用 pub 修饰的字段而无需通过访问器. 这种可见性仅当从定义解构的模块外部访问时才重要,并且具有隐藏信息 (封装) 的目的. 

-> 运算符到哪去了? 

在C / C++ 语言中, 有两个不同的运算符来调用方法: .  直接在对象上调用方法, 而 -> 在一个对象的指针上调用方法, 这时需要先解引用指针.  换句话说, 如果 object 是一个指针, 那么 object-> something()  和 (*object).something()  是一样的. 

Rust 并没有一个与 -> 等效的运算符; 相反, Rust 有一个叫自动引用和解引用的功能. 方法调用是Rust中少数几个拥有这种行为的地方. 

他是这样工作的: 当适用 object.something() 调用方法时, Rust 会自动为 object 添加 & (视可见性添加 &mut) , * 以使 object 与 方法签名匹配. 也就是说,这些代码使等价的; 

p1.distance(&p2);

(&p1).distance(&p2);

第一行看起来简洁的多. 这种自动引用的行为之所以有效,是因为方法有一个明确的接收者 ----- self 的类型. 在给出接收者和方法名的前提下, Rust 可以明确地计算出方法是仅仅读取 (&self ) , 做出修改(&mut self) 或者是获取所有权( self) . 事实上,Rust 对方法接收者的隐式借用让所有权在实践中更友好. 

带有多个参数的方法

方法和函数一样,可以适用多个参数: 

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    fn can_hold(&self,other:&Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main(){
    let rect1 = Rectangle {width:30,height: 50};
    let rect2 = Rectangle {width:10,height: 40};
    let rect3 = Rectangle {width:60,height: 45};

    println!("Can rect1 hold rect2? {}" , rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}" , rect1.can_hold(&rect3));
}

关联函数

现在大家可以思考一个问题, 如何为一个结构体定义一个构造器方法?  也就是接受几个参数,然后构造并返回该结构体的实例. 其实答案在开头的代码片段中就给出了, 很简单, 参数中不包含self 即可. 

这种定义在impl 中且没有self 的函数被称之为 关联函数:  因为它没有self , 不能用f.read() 的形式调用, 因此它是一个函数而不是方法. 它又在impl 中, 与结构体紧密关联, 因此称为关联函数. 

在之前的代码中,我们已经多次适用过关联函数, 例如String::from , 用于创建一个动态字符串 . 

impl Rectangle {
    fn new(w:u32,h:u32) -> Rectangle {
        Recangle { width: w,height: h}
    }
}

Rust 中有一个约定俗成的规则, 使用new 来作为构造器的名称, 出于设计上的考虑. Rust 特地没有用 new 作为关键字. 

因为是函数, 所以不能用 .  的方式来调用,我们需要用 :: 来调用, 例如 let sq = Rectangle::new(3,3);, 这个方法位于结构体的命名空间中 :  :: 语法用于关联函数和模块创建的命名空间. 

多个Impl 定义

Rust 语序我们为一个结构体定义多个impl 块, 目的是提供更多的灵活性和代码组织性,例如当方法多了后, 可以把相关的方法组织在同一个 impl 块中, 那么就可以形成多个impl 块, 各自完成一块儿目标: 

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

当然,就这个例子而言,我们没有必要使用两个impl块,这里只是为了演示方便. 

为枚举实现方法

枚举类型之所以强大, 不仅仅在于它好用,可以 同一化类型,还在于,我们可以像结构体一样,为枚举实现方法: 

#![allow(unused)]
enum Message {
    Quit,
    Move {x:i32,y:i32},
    Write(String),
    ChangeColor(i32,i32,i32),
}

impl Message {
    fn call(&self) {
        //在这里定义方法体
    }
}

fn main() {
    let m = Message::Write(String::from("hello");
    m.call();
}

除了结构体和枚举, 我们还能为特征 trait 实现方法, 这将在下一章进行讲解, 在此之前, 先来看看泛型.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值