• rust学习——方法 Method


    方法 Method

    在这里插入图片描述

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

    object.method()
    
    • 1

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

    定义方法

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

    struct Circle {
        x: f64,
        y: f64,
        radius: f64,
    }
    
    impl Circle {
        // new是Circle的关联函数,因为它的第一个参数不是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)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    img

    可以看出,其它语言中所有定义都在 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()
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    该例子定义了一个 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 依然有所有权的概念:

    • self 表示 Rectangle 的所有权转移到该方法中,这种形式用的较少
    • &self 表示该方法对 Rectangle 的不可变借用
    • &mut self 表示可变借用

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

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

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

    • 不用在函数签名中重复书写 self 对应的类型
    • 代码的组织性和内聚性更强,对于代码维护和阅读来说,好处巨大
    方法名跟结构体字段名相同

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

    impl Rectangle {
        fn width(&self) -> bool {
            self.width > 0
        }
    }
    
    fn main() {
        let rect1 = Rectangle {
            width: 30,
            height: 50,
        };
    
        if rect1.width() {
            println!("The rectangle has a nonzero width; it is {}", rect1.width);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

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

    pub struct Rectangle {
        width: u32,
        height: u32,
    }
    
    impl Rectangle {
        pub fn new(width: u32, height: u32) -> Self {
            Rectangle { width, height }
        }
        pub fn width(&self) -> u32 {
            return self.width;
        }
    }
    
    fn main() {
        let rect1 = Rectangle::new(30, 50);
    
        println!("{}", rect1.width());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    用这种方式,我们可以把 Rectangle 的字段设置为私有属性,只需把它的 newwidth 方法设置为公开可见,那么用户就可以创建一个矩形,同时通过访问器 rect1.width() 方法来获取矩形的宽度,因为 width 字段是私有的,当用户访问 rect1.width 字段时,就会报错。注意在此例中,Self 指代的就是被实现方法的结构体 Rectangle

    -> 运算符到哪去了?

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

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

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

    p1.distance(&p2);
    (&p1).distance(&p2);
    
    • 1
    • 2

    第一行看起来简洁的多。这种自动引用的行为之所以有效,是因为方法有一个明确的接收者———— 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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    关联函数

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

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

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

    impl Rectangle {
        fn new(w: u32, h: u32) -> Rectangle {
            Rectangle { width: w, height: h }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当然,就这个例子而言,我们没必要使用两个 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    rust 结构体与枚举的区别

    枚举都有方法了,那么rust 结构体与枚举有什么区别呢

    回答1

    使用上,枚举定义其中一项就可以使用了,每一项之间是相对立的。而结构体主要是为了将有联系的数据放在一起,便于将数据归类。

    枚举是一个萝卜一个坑,但结构体是一块地。

    回答2

    第一:枚举是列举所有可能的成员 ,而结构体是一个多种类型的数据组合;

    第二:实例化结构体,必须为每个成员指定一个具体的值,而枚举的成员是各种数据类型,包括字符串、数值、结构体甚至另一个枚举,通过模式匹配具体的指令

    当枚举的成员是非结构体类型时,自然清晰了

    引用知乎网友回答

  • 相关阅读:
    尚硅谷SpringBoot3笔记 (二) Web开发
    物联网助力智慧农业,农民也能成为科技工作者
    秘钥泄露(使用kali得到密码)
    网络数据帧中的(Jumbo Frame)巨帧、超长帧
    ASP.NET Core - 选项系统之源码介绍
    想知道图片转表格用什么软件?不妨试试这些软件
    【开源】基于Vue和SpringBoot的超市商品管理系统
    jQuery的使用,下载以及一些小案例
    自定义MVC增查
    在 M1/M2 芯片的Mac上安装最新原生 ruby 的超简单方法(无需rvm)
  • 原文地址:https://blog.csdn.net/e891377/article/details/133945863