rust学习——方法 Method

news2024/12/24 11:39:05

文章目录

  • 方法 Method
    • 定义方法
        • self、&self 和 &mut self
        • 方法名跟结构体字段名相同
    • 带有多个参数的方法
    • 关联函数
    • 多个 impl 定义
    • 为枚举实现方法
  • rust 结构体与枚举的区别
    • 回答1
    • 回答2

方法 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是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)
    }
}

我们这里先不详细展开讲解,只是先建立对方法定义的大致印象。下面的图片将 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()
    );
}

该例子定义了一个 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);
    }
}

当我们使用 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());
}

用这种方式,我们可以把 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);

第一行看起来简洁的多。这种自动引用的行为之所以有效,是因为方法有一个明确的接收者———— 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 {
        Rectangle { 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)实现方法,这将在下一章进行讲解,在此之前,先来看看泛型。

rust 结构体与枚举的区别

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

回答1

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

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

回答2

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

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

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

引用知乎网友回答

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1129096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx 动静分离 nginx防盗链

一、动静分离环境准备静态资源配置(10.36.192.169)安装nginx修改配置文件重启nginx 动态资源配置(192.168.20.135)yum安装php修改nginx配置文件重启nginx nginx代理机配置(192.168.20.134)修改nginx子自配置文件重启nginx 客户端访问 二、防盗链nginx防止…

【proteus】8086仿真/汇编:创建项目并添加汇编代码文件

1.创建好新项目 2.点击source code 弹出VSM 3. 4.注意两个都不勾选 可以看到schematic有原理图出现 5. 再次点击source code 6.project/project settings,取消勾选embed 7. add 8.输入文件名保存后: 注意:proteus不用写dos的相关语句 。

内存CACHE同步引起OSD时间戳显示异常

目前在用的这款芯片,图像翻转有专门的一个图像处理IP来完成,同时这个IP又支持叠加OSD的功能,但是在设计的时候叠加OSD的功能单元又在图像翻转单元的前面,导致了开启了图像翻转功能后,OSD就倒着显示、位置不在原来的坐标…

vue3 computed 和 watch 的差异

目录 前言 用法 computed watch 代码 理解 高质量的使用 Vue.js作为一种现代化的前端框架,提供了丰富的特性来帮助开发者构建高效和响应式的用户界面。在这其中,computed 和 watch 是两个非常重要的选项,它们都用于处理数据的变化&…

VUE到底有什么好处?

网上有许多前端开发框架的对比,相对的,VUE在综合评分方面还是优秀的。以下是一些State of JavaScript调查数据结果: 使用率:VUE使用者在调研开发者中占比 51%; 开发者满意度:VUE的综合开发者满意度达到64%…

【算法练习Day27】买卖股票的最佳时机 II跳跃游戏跳跃游戏 II

​📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:练题 🎯长路漫漫浩浩,万事皆有期待 文章目录 买卖股票的最佳时机 II跳跃…

智能视频监控平台EasyCVR接口调用注意事项汇总!

TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&…

汽车4S店如何在数字化管理下,提高市场竞争力

在所有人都认为疫情过后,经济形势会一路向阳,但是,实际情况出乎所有人的意料,各行各业举步维艰。 新闻爆出的各大房地产,恒大的2.4万亿让人瞠目结舌,还有碧桂园和融创,也是债台高筑了&#xff…

Access denied for user ‘root‘@‘localhost‘ (using password:YES) 解决方案(禅道相关)

如果是忘记Mysql密码或更改权限后访问不了的问题请直接跳转以下链接: MySQL登录时出现Access denied for user ‘root‘‘localhost‘ (using password: YES)无法打开的解决方法 关于这个问题,网上查到的解决方法基本都是因为忘记Mysql密码或者用户权限问…

利用Jpom在线构建Spring Boot项目

1 简介 前面介绍了运用Jpom构建部署Vue项目,最近研究了怎么部署Spring Boot项目,至此,一套简单的前后端项目就搞定了。 2 基本步骤 因为就是一个简单的自研测试项目,所以构建没有使用docker容器,直接用java -jar命令…

Go语言入门心法(十三): 反射认知升维

Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 Go语言入门心法(四): 异常体系 Go语言入门心法(五): 函数 Go语言入门心法(六): HTTP面向客户端|服务端编程 Go语言入门心法(七): 并发与通道 Go语言入门心法(八): mysql驱动安装报错o…

图像数据增强算法汇总(Python)

一、数据增强概述 数据增强是一种通过使用已有的训练样本数据来生成更多训练数据的方法,可以应用于解决数据不足的问题。数据增强技术可以用来提高模型的泛化能力,减少过拟合现象。比如在狗猫识别项目中,通过随机旋转、翻转和裁剪等数据增强…

什么是CDN?

目录 1.什么是CDN? 2.CDN工作原理 3.为什么要使用CDN? 4.CDN与传统网站访问的区别 5.CDN的应用场景 6.术语解释 1.什么是CDN? CDN英文全称Content Delivery Network,中文翻译即为内容分发网络。它是建立并覆盖在承载网之上…

【带头学C++】 ----- 1.基础知识 ---- 1.20 Typedef 为已有的类型取别名

1.20 Typedef 为已有的类型取别名 1.typedef语法格式 在 C 中,可以使用 typedef 关键字来为已有的类型创建一个别名。这对于提高代码可读性、简化类型声明以及增加代码的可维护性非常有用。 下面是 typedef 的语法格式: typedef 旧类型名 别名; 2.使…

Unity的碰撞检测

(一)测试前准备工作 1.创建两个游戏对象,分别取名为”Player”和”Enemy”,并且为名为”Player”的游戏对象设置Tag也为”Player”,二者在场景中如图1所示: 图1 绿为Enemy,红为Player 2.编写脚本…

《Linux从练气到飞升》No.22 Linux 进程间通信

🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…

kafka入门03——简单实战

目录 安装Java 安装Zookeeper 安装Kafka 生产与消费 主要是记录下Kafka的安装配置过程,前置条件需要安装jdk和zookeeper。 安装Java 1.Oracle官网下载对应jdk安装包 官网地址:Java Downloads | Oracle 好人分享了下载需要的oracle账号&#xff0c…

linux基本指令(Chapter 2)

linux基本指令(二) 1.date指令 语法:date [option] [format] date命令的作用是显示时间:使用date %Y:%m:%d可以指定格式显示时间.例如:(注意这里的和%之后不要有任何间隔)并且这里是加号,并不是减号 1.在显示方面,使用者可以设定欲显示的格式,格式设…

NeRF综述

文章目录 《Nerf: Neural radiance field in 3d vision, a comprehensive review 》一、数据集:二、基于方法的分类(Method-based Taxonomy)三、基于应用的分类(Application-based Taxonomy)四、未来展望 《Nerf: Neur…

盘点网安最好入手的10大岗位,最高月薪30K!

前段时间,知名机构麦可思研究院发布了《2022年中国本科生就业报告》,其中详细列出近五年的本科绿牌专业,信息安全位列第一。 对于网络安全的发展与就业前景已经说过很多,它是收入较高的岗位之一,在转行领域也占据着热…