Rust 学习笔记:结构体(struct)

Rust 学习笔记:结构体(struct)

  • Rust 学习笔记:结构体(struct)
    • 结构体的定义和实例化
    • 使用字段初始化简写
    • 用 Struct Update 语法从其他实例创建实例
    • 使用没有命名字段的元组结构来创建不同的类型
    • 没有任何字段的类单元结构
    • 结构体的所有权
    • 示例程序:Rectangle
    • 用派生特性添加有用的功能
      • Debug
      • Copy, Clone
    • 方法的语法
    • 有更多参数的方法
    • 关联函数
    • 多个 impl 块

Rust 学习笔记:结构体(struct)

struct 是一种自定义数据类型,它允许将多个相关的值打包在一起并命名,从而组成一个有意义的组。

结构体的定义和实例化

要定义结构,输入关键字 struct 并为整个结构命名。

然后,在花括号内定义数据块的名称和类型,我们称之为字段。

struct User {active: bool,username: String,email: String,sign_in_count: u64,
}

要在定义结构后使用它,需要为每个字段指定具体的值,从而创建该结构的实例。

我们通过声明结构体的名称来创建实例,然后添加包含键:值对的花括号,其中键是字段的名称,值是我们希望存储在这些字段中的数据。我们不必按照在结构体中声明字段的顺序指定字段。换句话说,结构定义就像是该类型的通用模板,实例用特定的数据填充该模板,以创建该类型的值。

fn main() {let user1 = User {active: true,username: String::from("someusername123"),email: String::from("someone@example.com"),sign_in_count: 1,};
}

为了从结构体中获得特定的值,我们使用点表示法。例如,要访问这个用户的电子邮件地址,我们使用 user1.email。如果实例是可变的,我们可以通过使用点表示法和赋值到一个特定的字段来改变值。

fn main() {let mut user1 = User {active: true,username: String::from("someusername123"),email: String::from("someone@example.com"),sign_in_count: 1,};user1.email = String::from("anotheremail@example.com");
}

注意,整个实例必须是可变的;Rust 不允许我们只将某些字段标记为可变的。

结构体的新实例可以作为函数体中的最后一个表达式,以隐式返回该新实例。

fn build_user(email: String, username: String) -> User {User {active: true,username: username,email: email,sign_in_count: 1,}
}

username: username 这种写法有点乏味。如果结构体有更多字段,重复每个名称会变得更加烦人。幸运的是,有一种方便的速记方法!

使用字段初始化简写

字段 init 速记语法允许将同名的字段和参数进行简化。

例如,因为 email 字段和 email 参数有相同的名字,所以我们只需要写 email 而不是 email: email。

fn build_user(email: String, username: String) -> User {User {active: true,username,email,sign_in_count: 1,}
}

用 Struct Update 语法从其他实例创建实例

创建一个结构体的新实例,该实例包含另一个实例的大部分值,但更改了一些值,这通常很有用。

不使用 Struct Update 语法,每个字段都需要设置。

fn main() {// --snip--let user2 = User {active: user1.active,username: user1.username,email: String::from("another@example.com"),sign_in_count: user1.sign_in_count,};
}

使用 Struct Update 语法,指定未显式设置的其余字段应具有与给定实例中的字段相同的值。

fn main() {// --snip--let user2 = User {email: String::from("another@example.com"),..user1};
}

..user1 必须在最后指定任何剩余的字段应该从 user1 中的相应字段获取它们的值。

请注意,struct update 语法就像使用 = 赋值一样,这是因为它移动数据,会发生所有权的转移。

在本例中,在创建 user2 之后,我们不能再将 user1 作为一个整体使用,因为 user1 的 username 字段中的 String 被移到了 user2 中。

如果我们只使用 user1 的 active 和 sign_in_count 值,那么在创建 user2 之后,user1 仍然有效。这是因为 active 和 sign_in_count 都是实现Copy Trait 的类型,这些变量会复制,并不移动。

使用没有命名字段的元组结构来创建不同的类型

Rust 还支持类似于元组的结构,称为元组结构。没有字段的名称,只有字段的类型。

要定义元组结构,首先使用struct关键字和结构名,然后是元组中的类型。例如,这里我们定义并使用了两个名为 Color 和 Point 的元组结构体:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);fn main() {let black = Color(0, 0, 0);let origin = Point(0, 0, 0);
}

注意,即使 Color 和 Point 这两种类型都由三个 i32 值组成,但它们的实例不能互相赋值。

没有任何字段的类单元结构

没有任何字段的结构体被称为类单元结构。

当需要在某些类型上实现 trait,但没有任何想要存储在类型本身中的数据时,类单元结构可能很有用。

下面是一个声明和实例化一个名为 AlwaysEqual 的类单元结构的例子:

struct AlwaysEqual;fn main() {let subject = AlwaysEqual;
}

结构体的所有权

在之前的 User 结构体定义中,我们使用了 String 类型而不是 &str 字符串切片类型。这是一个经过深思熟虑的选择,因为我们希望这个结构体的每个实例都拥有它的所有数据,并且只要整个结构体有效,这些数据就有效。

结构体也可以存储对其他对象拥有的数据的引用,但这样做需要使用生命周期,以确保结构体引用的数据在该结构体存在的时间内有效。假设在一个结构体中存储一个引用而不指定生命周期,如下所示。

struct User {active: bool,username: &str,email: &str,sign_in_count: u64,
}fn main() {let user1 = User {active: true,username: "someusername123",email: "someone@example.com",sign_in_count: 1,};
}

报错:缺少生命周期标识符

在这里插入图片描述

示例程序:Rectangle

创建一个名为 Rectangle 的结构体,存储矩形的宽度和高度。

再创建一个函数,入参为结构体对象,计算矩形的面积。

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

注意,访问借用的结构体实例的字段并不会移动字段值,这就是为什么经常看到借用结构体的原因。

用派生特性添加有用的功能

Debug

在调试程序时打印一个 Rectangle 实例并查看其所有字段的值是很有用的。

struct Rectangle {width: u32,height: u32,
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("rect1 is {}", rect1);
}

println! 宏可以进行多种格式化,默认情况下,大括号告诉 println! 使用称为显示的格式:用于直接最终用户使用的输出。到目前为止,我们看到的基本类型都默认实现了 Display,但是结构体没有提供 Display 的实现(与 println! 以及 {} 占位符)。

从报错信息中我们能得到提示:

= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

输入说明符 :? 在花括号内告诉 println! 我们希望使用一种名为 Debug 的输出格式。Debug 特性使我们能够以一种对开发人员有用的方式打印结构体,这样我们在调试代码时就可以看到它的值。

只是这样还不够,还是报错:

error[E0277]: `Rectangle` doesn't implement `Debug`
...
= help: the trait `Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`

Rust 确实包含打印调试信息的功能,但我们必须显式地选择使该功能对结构体可用。为此,我们在结构定义之前添加 #[derive(Debug)]

#[derive(Debug)]
struct Rectangle {width: u32,height: u32,
}fn area(rectangle: &Rectangle) -> u32 {rectangle.width * rectangle.height
}fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("rect1 is {rect1:?}");println!("The area of the rectangle is {} square pixels.",area(&rect1));
}

现在,当我们运行程序时,我们不会得到任何错误,并且我们将看到以下输出:

在这里插入图片描述

冒号和问号之间再加一个 #,即 :#?,可以使得打印的值更有格式,这在结构体中字段多时比较有用。

在这里插入图片描述

使用 Debug 格式打印值的另一种方法是使用 dbg! 宏,它接受一个表达式的所有权(与 println! 相反,它接受一个引用)。

注意,调用 dbg! 宏打印到标准错误控制台流(stderr),println! 则打印到标准输出控制台流(stdout)。

Copy, Clone

复制和克隆属性,可以让结构体的方法不再夺取结构体实例的所有权。

在这里插入图片描述

方法的语法

方法类似于函数:我们用 fn 关键字和一个名称来声明它们,它们可以有参数和返回值。

与函数不同,方法是在结构体(或 enum 或 trait 对象)的上下文中定义的,它们的第一个参数总是 self,它表示调用方法的结构体的实例。

#[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 的上下文中定义函数,我们为 Rectangle 启动了一个 impl(实现)块。这个 impl 块中的所有内容都将与 Rectangle 类型相关联。然后将 area 函数移动到 impl 花括号内,并将签名中的第一个(在本例中是唯一一个)参数更改为 self,并将函数体中的所有参数更改为 self。

在 area 的签名中,我们使用 &self 代替 rectangle: &Rectangle。&self 实际上是 self: &Self 的缩写。在一个 impl 块中,Self 类型是该 impl 块所针对的类型的别名。方法的第一个参数必须有一个名为 self 的 Self 类型的参数,因此 Rust 允许在第一个参数点只使用名称 self 来缩写它。

注意,我们仍然需要在 self 前面使用 & 来表示这个方法借用了 self 实例,就像我们在 rectangle: & rectangle 中所做的那样,因为这里我们不想占有所有权。

除了提供方法语法和不必在每个方法的签名中重复 self 的类型之外,使用方法而不是函数的主要原因是为了组织。我们把一个类型的实例所能做的所有事情都放在了一个 impl 块中,而不是让未来的代码用户在我们提供的库的各个地方搜索 Rectangle 的功能。

注意,我们可以选择给一个方法赋予与结构体的一个字段相同的名称。例如,我们可以在 Rectangle 上定义一个同样命名为 width 的方法:

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);}
}

这里,我们选择让 width 方法在实例的 width 字段值大于 0 时返回 true,在值小于等于 0 时返回 false。当我们使用带括号的 width, Rust 知道我们指的是方法宽度;当我们不使用括号时,Rust 知道我们指的是字段宽度。

通常(但并非总是),当我们给一个方法赋予与字段相同的名称时,我们希望它只返回字段中的值,而不做任何其他事情。像这样的方法被称为 getter, Rust 不像其他语言那样为 struct 字段自动实现它们。getter 很有用,因为您可以将字段设置为私有,而将方法设置为公共,从而使对该字段的只读访问成为类型的公共API的一部分。

在 C/C++ 中,调用方法使用两种不同的操作符:object->something() 或 (*object).something()。

Rust 没有与 -> 操作符等价的操作符;相反,Rust 有一个称为自动引用和解引用的特性。它是这样工作的:当你用 object.something() 调用一个方法时,Rust 会自动添加 &,&mut 匹配方法的签名。换句话说,以下内容是相同的:

p1.distance(&p2);
(&p1).distance(&p2);

第一个看起来干净多了。这种自动引用行为之所以有效,是因为方法有一个明确的接收者——self 类型。事实上,Rust 为方法接收者提供了隐含的借用。

有更多参数的方法

让我们通过在 Rectangle 结构体上实现第二个方法:can_hold。

这一次,我们想要一个矩形的实例接受另一个矩形的实例,如果第二个矩形可以完全适合 self(第一个矩形),返回 true;否则,它应该返回 false。

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}
}

can_hold 函数将以一个不可变的借用另一个矩形作为第二个参数,比较后返回一个布尔值。

关联函数

在 impl 块中定义的所有函数都称为关联函数,因为它们与以 impl 命名的类型相关联。我们可以定义不以 self 作为第一个参数的关联函数(因此不是方法),因为它们不需要使用该类型的实例。

我们已经使用过一个这样的函数:String::from 函数,它定义在 String 类型上。

非方法的关联函数通常用于将返回该结构的新实例的构造函数。这些通常被称为 new,但 new 并不是一个特殊的名称,也没有内置到语言中。例如,我们可以选择提供一个名为 square 的关联函数,它将有一个维度参数,并使用它作为宽度和高度,从而使创建一个正方形矩形更容易,而不必两次指定相同的值:

impl Rectangle {fn square(size: u32) -> Self {Self {width: size,height: size,}}
}

返回类型和函数体中的 Self 关键字是出现在 impl 关键字之后的类型的别名,在本例中是 Rectangle。

要调用这个关联函数,可以使用 结构体名+ :: + 方法名,比如:

let sq = Rectangle::square(3);

多个 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 块中,但这是有效的语法。

但在讨论泛型类型和特征时,多个 impl 块是有用的。

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

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

相关文章

Dify Agent节点的信息收集策略示例

Dify Agent节点的信息收集策略示例 0. 安装"对话 Agent"插件1. 创建一个 Chatflow2. 创建一个 Agent 节点3. 创建一个条件分支节点4. 在IF分支创建一个LLM节点5. 创建一个直接回复节点6. 在ELSE分支创建一个直接回复节点7. 分布并预览 0. 安装"对话 Agent"…

Qt/C++开发监控GB28181系统/获取设备信息/设备配置参数/通道信息/设备状态

一、前言 设备注册成功后,接下来要做的就是获取设备的信息,尤其是通道信息,根据国标协议,永远只有两个层级,一个是设备,然后就是设备下面多个通道,设备编码在整个系统中唯一,通道编…

金融风控的“天眼”:遥感技术的创新应用

在金融市场的复杂博弈中,风险管控一直是金融机构的核心竞争力。然而,传统的风控手段在应对现代金融市场的快速变化时,往往显得捉襟见肘。 如今,遥感技术的创新应用为金融风控带来了全新的视角和手段。星图云开放平台的遥感金融立体…

HFI笔记

高频分量: 载波频率的一半 选择alfabeta轴进行计算的原因 最终结果: 观测器方程 采样加减分离法-(高低频分离) 高频信号的评论高频载波 转子极性辨识

halcon关闭图形窗口

1、dev_close_window () 调用一次这个函数关闭一个图形窗口,并且先关闭最后打开的那个图形窗口,如果一共打开了N个图形窗口,那么就需要调用dev_close_window N次。

每日算法-250430

每日算法 - 2025年4月30日 记录下今天解决的两道题目。 870. 优势洗牌 (Advantage Shuffle) 题目描述 解题思路与方法 核心思想:贪心策略 (田忌赛马) 这道题的目标是对于 nums1 中的每个元素,找到 nums2 中一个比它小的元素进行配对(如果…

【MySQL】增删改查(CRUD)

目录 一. CRUD是什么 二. Create(新增数据) 2.1 单行数据全列插入 2.2 单行数据指定列插入 2.3 多行数据指定列插入 三. Retrieve (检索/查询) 3.1 全列查询 3.2 指定列查询 3.3 查询字段为表达式 3.4 为查询结果指定别名 3…

电商平台 API 开发实战:京东商品详情数据实时获取接口对接教程

在电商行业竞争日益激烈的当下,实时获取商品详情数据对于市场分析、竞品监控、商品推荐等业务场景至关重要。京东作为国内领先的电商平台,提供了强大的 API 接口,允许开发者获取丰富的商品信息。本文将详细介绍京东商品详情数据实时获取接口的…

YOLO视觉模型可视化训练与推理测试工具

推荐一款YOLO可视化训练测试工具: 对于yolo的训练,新手小白往往无从下手,本章推荐的这款工具可以非常轻易的帮您从模型训练到测试到部署。 下载地址http://www.voouer.com/yolo 可以点击此处跳转。 下载成功后打开这款工具,将会出现图形化界面,类似于下图所示: 当前页是可视…

微调 LLaMA 2:定制大型语言模型的分步指南

微调 LLaMA 2:定制大型语言模型的分步指南 深入了解如何运用新技术在 Google Colab 平台上对 Llama-2 进行微调操作,从而有效克服内存与计算方面的限制,让开源大型语言模型变得更加易于获取和使用。自从 Meta 发布了 LLaMA 的首个版本后&…

探秘明远智睿SSD2351开发板在HMI领域的独特魅力

人机界面(HMI)是人与机器进行交互的重要桥梁,其性能和用户体验直接影响到整个系统的使用效果。明远智睿的SSD2351开发板凭借其出色的性能和丰富的功能,在HMI领域展现出了独特的魅力。 SSD2351开发板的四核1.4GHz处理器具备强大的图…

Keysight万用表使用指南及基于Python采集数据生成Excel文件

文章目录 说明使用的库openpyxlpyvisa 代码说明效果展示参考代码 说明 本文介绍了 Keysight 34465A 的基本使用和 SCPI 指令设置,演示了使用 Python 的 PyVISA 库控制两台 34465A 同时采集数据的完整流程,包括设置采样参数、触发测量、读取数据、使用 O…

Docker 获取 Python 镜像操作指南

1. 安装 Docker 环境 1.1 上传安装脚本(Windows → Linux) 在 Windows 的 CMD 中执行: scp docker.sh root10.1.1.58:~ 可自行前往我的飞书下载docker.sh脚本 Docs 1.2 在 Linux 中检查文件 ls -l ~ # 确认 docker.sh 已上传到家目录…

JavaScript:从JS的执行机制到location对象

一、JS执行机制 (1)JS是单线程 JavaScript语言的一大特点就是单线程,也就是同一时间只能做一件事。因为JavaScript是为了处理页面中的用户交互,以及制作DOM二诞生的。比如我们对某个DOM元素进行添加和删除操作,这个不…

iVX:数字化转型全场景技术革新与生态构建实践

在数字经济蓬勃发展的当下,企业数字化转型需求日益迫切。iVX 凭借其独特的技术架构与创新解决方案,深度渗透工业互联网、元宇宙、智慧城市等领域,成为推动全场景数字化转型的重要力量。本文将重新梳理 iVX 的技术应用与生态价值,以…

生物化学笔记:神经生物学概论05 感受野 视觉中枢 高级视皮层中的信息走向

信息传递中的“击鼓传花” 新特性的突现 功能柱:简化节点 高级视皮层中的信息走向

StarRocks Lakehouse 如何重构大数据架构?

随着数据分析需求的不断演进,企业对数据处理架构的期望也在不断提升。在这一背景下,StarRocks 凭借其高性能的实时分析能力,正引领数据分析进入湖仓一体的新时代。 4 月 18 日,镜舟科技高级技术专家单菁茹做客开源中国直播栏目《…

【SpringBoot】基于mybatisPlus的博客系统

1.实现用户登录 在之前的项目登录中,我使用的是Session传递用户信息实现校验登录 现在学习了Jwt令牌技术后我尝试用Jwt来完成校验工作 Jwt令牌 令牌一词在网络编程一节我就有所耳闻,现在又拾了起来。 这里讲应用:令牌也就用于身份标识&a…

HCIP-security常见名词

缩略语英文全称解释3DESTriple Data Encryption Standard三重数据加密标准AESAdvanced Encryption Standard高级加密标准AHAuthentication Header报文认证头协议CACertification Authority证书颁发中心DESData Encryption Standard数据加密标准DHDiffie-Hellman密钥交换算法DPD…

合并多个Excel文件到一个文件,并保留格式

合并多个Excel文件到一个文件,并保留格式 需求介绍第一步:创建目标文件第二步:创建任务列表第三步:合并文件第四步:处理合并后的文件之调用程序打开并保存一次之前生成的Excel文件第五步:处理合并后的文件之…