【Rust 学习记录】5. 结构体
定义及实例化方式
定义和创建实例
定义方法和 C++ 是一模一样了,详见代码
1 | struct User{ |
实例化方法稍显不同,方法和定义差不多,指名道姓的赋值,优势是不用对应顺序,可读性强。访问方法就还是传统的点运算符
1 | struct User{ |
同理,结构体也有可变与不可变一说,可变结构体,结构体所有变量可变,不可变结构体,结构体所有变量不可变,Rust 没有让结构体部分变量可变,部分不可变的说法
一些语法糖
同名参数对应赋值
如果每次都要写一个 email: xxxx, username: xxxx 好像有点麻烦是吗?Rust 提供了一个简单的方法,当变量名和结构体内的字段名完全一样的时候,会对应赋值(有一说一,这个设计挺有想法的,语法糖+1)
所以我们可以很轻松的给 User 写一个创建用的函数,这样就可以实现带默认值,轻松的构建结构体实例了
1 | struct User{ |
用之前的实例构造现在的实例
在一些情况下其实结构体内的实例都不需要怎么变动,就像上面的例子里,sign_in_count
和 active
参数都是采用同一个默认值来赋给所有实例的,那有没有一些方法能简化这种情况的代码书写呢?有的。
1 | fn main() { |
..user1
就代表了剩下的值都和 user1
一样,把 user1
里对应字段的值赋给 user2
即可。(这个感觉不如函数封装性好吧,但可能也看情况)
元组结构体——没有字段名的结构体
其实就相当于给元组命个名字,适用于很多不需要给字段命名的情况下,例如颜色的RGB,大家都懂是吧,就用一个三元组命名 Color
就好了。定义方式如下
1 | struct Color(i8, i8, i8); |
值得一提的是,定义成结构体后也是用点运算符访问变量,而不是 [] 运算符了
空结构体——没有字段的结构体
当你想创建一个空结构体的时候,也是不会报错的,原理说是和空元组相似,然后在某些方面会有用,后续再介绍。
我也不太懂,就不多说了,后面再看吧。
结构体的所有权
在上面举的这个例子中
1 | struct User{ |
我们定义的所有字段都是具有值的所有权的,所以结构体实例能具有所有字段数据的所有权,能伴随着自己直到离开作用域,但也有不具有所有权的定义方式
1 | struct User{ |
但这种方式现在是没办法定义通过的,报错提示需要指定生命周期,这个涉及到了生命周期,所以就放到后面介绍了。
初试trait——为结构体增加更多有用的功能
说实话我不知道这个trait是什么意思,大概查了一下,是 特性(性状)的意思,用来定义一个类型可能和其他类型共享的功能,或许差不多相当于和 python 里的 xxx 差不多吗?但看样子还能自己定义的样子。先不管吧,大概了解一下概念,先学着。
打印结构体
用过 python 的应该都知道 python 类有个 __str__
函数,可以定义一个类的字符串格式,方便输出成人类能查看的格式,而不是一串地址,Rust 的结构体也有这个功能—— Display
。
我们可以先跑一下这段代码
1 | struct rectangle{ |
可以看到一个报错
1 | = help: the trait `std::fmt::Display` is not implemented for `rectangle` |
the trait std::fmt::Display is not implemented for rectangle
意思就是这个结构体还没实现 Display
这个方法,也就是说,println!
这个宏在输出的时候,还会调用一下类型的格式化函数,来进行指定的输出,之前我们用的基本类型都是默认实现了 Display
方法的,而这个 rectangle
是我们自己定义的,没有 Display
方法,println!
就不知道怎么格式化了,所以就报错。
但除了这个还有一个有意思的提示 in format strings you may be able to use {:?} (or {:#?} for pretty-print) instead
。这句话告诉我们,可以用 {:?}{:#?}
来整一个漂亮的输出?啥意思?写一下吧~
1 | = help: the trait `Debug` is not implemented for `rectangle` |
被骗了,还是报错。但我们发现了另一个有意思的 trait—— Debug
。也就是说,Rust 对格式化的方法区分了两种,Debug 是专门面向开发者调试时的输出用格式。我超,什么是现代化语法啊(后仰)。这种特性真能派上不少用途。
提示里说,要么添加[derive(Debug)]
要么自己实现一个 Debug
我们可以先添加一下这个试试
1 | #[derive(Debug)] |
添加在函数头,进行一个 Debug
注解就可以了,这次运行就不会报错了。我们来看看输出是怎样的
1 | rectangle is rectangle { w: 10, h: 20 } |
可见 Rust 标准的 Debug
格式化输出就是 结构体名{所有字段名: 对应的值},嗯,挺不错的,不用自己手动一个一个输出了。我们再来看看之前提示里提到的另一个 {:#?}
吧
1 | rectangle is rectangle { |
这个输出会更好看点,有了一定的排版,对于复杂的结构体会更有可读性。
好,书上的 trait 介绍就到这里结束了(是不是把一开始的 Format
忘了),它说到第 10 章的时候会更详细的介绍 trait 的时候,可以通过像这种对结构体进行 trait 注解的方式提供很多功能,包括自带的,甚至可以自己自定义,确实期待。
方法
结构体,或者说类,当然不能少了函数,书上对普通函数和结构体里的函数区分了一下概念,结构体内的函数叫方法,因为方法的定义有局限性,例如参数要有self,只能定义在结构体或trait之类的地方,属于是一个子集吧。我们也严谨点,区分一下吧。
定义
1 | #[derive(Debug)] |
(吐槽:这里把 rectangle
的首字母大写了,因为 Rust 的编译器居然会警告我的命名不规范,牛)
可以看见,方法和函数定义差不多,也是用 fn
来定义,指定哪个函数里的话倒是比较意外,居然不是写在 Rectangle
定义的花括号里,而是另开一个 impl Rectangle
再来定义方法。另外,Rust 方法和 Python 也有点相似之处,也是通过 self
来指代当前实例,self
可以用三种方式来定义
&self
:不可变引用,这个是最常见的,我们只要读取数据,什么也不干,所以不需要用到所有权,也最方便self
:获取所有权,应该最不常见,有时方法需要用来转换self
类型的话,需要用到所有权,获取所有权后再进行返回;如果不返回的话,所有权在调用完方法就被回收了,实例就销毁了。&mul self
:可变引用,也没什么好说的,就是有时要改变实例内字段的值时会用。
然后书上介绍了 Rust 为什么没有 ->
运算符的问题,我没太看懂,就简述一下我的理解,具体感兴趣可以自行看书或查阅资料。
C++在对于一个指针类型的结构体变量里,需要对变量进行一次解引用,也就是 ractangle->area()= (*rectangle).area()
所以额外定义了一个 ->
运算符写起来方便些。而 Rust 里,self的类型被显式定义了,所以编译器可以自动的根据你定义的 self
类型,去自动推理出 self
是否需要自动引用还是解引用,所以就不需要 ->
运算符了。
关联函数
在 impl
块里,除了带 self
的方法之外,Rust 还允许在块里定义不含 self
的函数,这些函数因为和结构体有关联,又不太需要 self
所以称为关联函数。和 Python 的 @staticmethod
差不多吧
这里用一个定义函数来举例
1 | #[derive(Debug)] |
使用也就是指定命名空间调用就可以了。
多个impl块
使用多个 impl 块也是合法的,可以编译通过。书上说后面会有应用场景介绍,那就后面再看吧,目前感觉还派不上用场?
1 | #[derive(Debug)] |
本章到此结束!没什么好总结的,是比较基础的,也很重要的一部分,下一章继续干。
- 标题: 【Rust 学习记录】5. 结构体
- 作者: TwoSix
- 创建于 : 2023-03-30 21:05:00
- 更新于 : 2024-07-04 23:52:28
- 链接: https://twosix.page/2023/03/30/【Rust-学习记录】5-结构体/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。