rust学习——方法 Method

文章目录

  • 方法 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.mzph.cn/news/116996.shtml

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

相关文章

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

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

C++第一篇--关键字以及命名空间

📙 作者简介 :RO-BERRY 📗 学习方向:致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持 目录 🎄 前言 …

31一维信号滤波(限幅滤波、中值滤波、均值滤波、递推平均滤波),MATLAB程序已调通,可直接运行。

一维信号滤波(限幅滤波、中值滤波、均值滤波、递推平均滤波),MATLAB程序已调通,可直接运行。 31matlab、中值滤波、信号处理 (xiaohongshu.com)

螺旋矩阵[中等]

优质博文:IT-BLOG-CN 一、题目 给你一个m行n列的矩阵matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5] 示例 2&#xf…

性能优化:JIT即时编译与AOT提前编译

优质博文:IT-BLOG-CN 一、简介 JIT与AOT的区别: 两种不同的编译方式,主要区别在于是否处于运行时进行编译。 JIT:Just-in-time动态(即时)编译,边运行边编译:在程序运行时,根据算法计算出热点代码&#xf…

【题解 单调队列优化dp】 简单的加法乘法计算题

题目描述: 分析: 由于对于每一步而言,我们都需要的是最小步数 所以我们很显然的可以写出一个dp方程: 设 f [ i ] f[i] f[i]表示达到i时的最小步数 我们有两种操作,也就是说我们可以通过一下两种方式转移过来&#xff…

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET “/**“

解决使用WebTestClient访问接口报[185c31bb] 500 Server Error for HTTP GET "/**" 问题发现问题解决 问题发现 WebTestClient 是 Spring WebFlux 框架中提供的用于测试 Web 请求的客户端工具。它可以不用启动服务器,模拟发送 HTTP 请求并验证服务器的响…

力扣刷题 day54:10-24

1.十进制整数的反码 每个非负整数 N 都有其二进制表示。例如, 5 可以被表示为二进制 "101",11 可以用二进制 "1011" 表示,依此类推。注意,除 N 0 外,任何二进制表示中都不含前导零。 二进制的反…

CPU眼里的C/C++:1.2 查看变量和函数在内存中的存储位置

写一个很简单的 c 代码,打印一些“地址”, 也就是变量、函数的“存储位置”:当程序被加载到内存后,它们具体是存在哪里,可以用精确的数值来表示,这就是内存地址。 https://godbolt.org/z/Ghh9ThY5Y #inc…

Java基础篇 | Java8流式编程

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏&#xf…

自然语言处理---Transformer模型

Transformer概述 相比LSTM和GRU模型,Transformer模型有两个显著的优势: Transformer能够利用分布式GPU进行并行训练,提升模型训练效率。 在分析预测更长的文本时,捕捉间隔较长的语义关联效果更好。 Transformer模型的作用 基于seq…

Ai写作创作系统ChatGPT网站源码+图文搭建教程+支持GPT4.0+支持ai绘画(Midjourney)/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统,支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

一文了解AIGC与ChatGPT

关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、AIGC简介 1.AIGC基础 (1)AIGC是什么 AIGC是人工智能图形计算的缩写,是一种基于图形处理器(GPU)的计算技术,可以加速各种…

业务出海、高效传输、动态加速,尽在云栖大会「CDN与边缘计算」专场

2023杭州云栖大会,即将热力来袭。 一场云计算盛会,500前沿话题,3000科技展品,与阿里云一起,共赴72小时的Tech沉浸之旅。 今日,「CDN与边缘计算」Tech专场,重磅议题抢先知晓! 01 「…

Elasticsearch的增删查改详细操作

目录标题 一、创建索引二、查看索引三、修改索引四、删除索引五、向索引增加数据 一、创建索引 单独创建索引 PUT /test1 # test1 为索引名称自定义{"settings":{ # 创建index 需要有效的xcontent字节及Json格式 否则创建不成功 "index":{"number_…

log函数解释

log函数是指数函数y bx 的反函数,用于求数字以某个数为底的对数。log函数的定义:设b>0,b≠1,对于任意实数x > 0,如果存在唯一的实数y,使得 b^y x,则称y为以b为底x的对数,记为:y log_b(x)这里b称为对数的底数。对数运算的底数通常取10和e。常见的对数运算有:1. 常用对数…

前后端交互—跨域与HTTP

跨域 代码下载 同源策略 同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。 MDN 官方给定的概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这 是一个用于隔离潜在恶意文件的重要安全机制。 通俗的理解:浏览器规定&a…

测试C#调用Windows Media Player组件

新建基于.net framework的Winform项目,可以通过添加引用的方式选择COM组件中的Windows Media Player组件,如下图所示:   也可以在VS2022的工具箱空白处点右键,选择“选择项…”菜单。   在弹出的选择工具箱项窗口中&#xf…

小样本学习(2)--LibFewShot使用

目录 一、LibFewShot安装 1、LibFewShot代码仓库 2、配置环境 3、测试安装是否正确 二、LibFewShot结构 1、config文件夹 2、core文件夹 3、reproduce文件夹 4、results文件夹 三、如何训练自己的数据集 1、调用主配置文件 2、修改主配置文件 一、LibFewShot安装…

Html -- 文字时钟

Html – 文字时钟 文字时钟&#xff0c;之前在Android上实现了相关效果&#xff0c;闲来无事&#xff0c;弄个网页版的玩玩。。。直接上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><titl…