趣味学RUST基础篇(结构体方法)
Rust 的“超级英雄”:结构体(struct)完全指南
你还记得dota里面的英雄吗?每个英雄都有独特的身份和能力。在 Rust 世界里,**结构体 **(struct) 就是你创建这些超级英雄的“设计模板”!
第一步:设计你的英雄模板(定义结构体)
你想设计一个“剑圣”这样的英雄。这个英雄需要哪些信息?你需要给每个信息贴上标签。
// struct = "设计一个英雄"
struct User {active: bool, // 标签1:是否活跃? (是/否)username: String, // 标签2:用户名 (比如 "朱比瑟斯")email: String, // 标签3:电子邮件 (比如 "flash@justiceleague.com")sign_in_count: u64, // 标签4:登录次数 (比如 100 次)
}
struct
: 就像游戏里“新建英雄”的按钮。User
: 英雄的类型名。所有按这个模板做的英雄都是User
类型。- 字段 (Fields) 比如
active
,username
… 这些就是卡牌上的属性标签。它们有明确的名字和类型(bool
,String
,u64
)。
为什么不用元组? 你可以用元组
(true, "剑圣", "flash@...", 100)
,但你能记住第3个元素是邮箱吗?标签化(结构体)让信息一目了然,不依赖顺序,超灵活!
第二步:创建你的英雄!(实例化结构体)
有了模板,就可以造英雄了!这叫“创建实例”。
fn main() {// let user1 = ... 创建一个叫 user1 的英雄// User { ... } 按照 User 模板来造let user1 = User {active: true,username: String::from("BM"),email: String::from("MBJ@justiceleague.com"),sign_in_count: 100,};// 注意:字段顺序可以和定义时不同!// let user1 = User {// email: String::from("..."),// active: true,// sign_in_count: 100,// username: String::from("BM"),// };// 这样也完全没问题!
}
User { ... }
:就像拿着模板,往里面填具体信息。key: value
:标签名(key)和具体值(value)用冒号连接。- 顺序无关:填信息时,顺序不重要!只要标签对了就行。
第三步:查看英雄信息(访问字段)
造好英雄后,怎么查看他的信息?用点(.)!
fn main() {let user1 = User {active: true,username: String::from("BMJ"),email: String::from("BMJ@justiceleague.com"),sign_in_count: 100,};println!("用户名是:{}", user1.username); // 输出:用户名是:BMJprintln!("邮箱是:{}", user1.email); // 输出:邮箱是:BMJ@justiceleague.com
}
user1.email
就像说“user1 这个英雄的 email 属性”。
第四步:给英雄升级!(修改字段)
英雄可以升级!但有个重要规则:必须是“可编辑”状态。
fn main() {// let mut user1 = ... 注意这里的 mut!let mut user1 = User {active: true,username: String::from("BMJ"),email: String::from("BMJ@justiceleague.com"),sign_in_count: 100,};// 升级!修改邮箱user1.email = String::from("newBMJ@speedforce.com");println!("新邮箱:{}", user1.email); // 输出:新邮箱:newflash@speedforce.com// user1.username = String::from("超级剑圣"); // 也可以改用户名// user1.sign_in_count += 1; // 登录次数+1
}
mut
是关键!没有mut
,你不能修改任何字段。Rust 说:“英雄默认是锁定的,防止意外修改!”- 不能只锁一部分:你不能让
email
可改,而username
不可改。要么整张卡牌都可改 (mut
),要么都不可改。
第五步:批量制造英雄!(结构体更新语法)
想造一个新英雄,大部分信息和旧英雄一样,只改一两个?用“复制粘贴”大法!
fn main() {let user1 = User {active: true,username: String::from("恶魔猎手"),email: String::from("ghost@justiceleague.com"),sign_in_count: 100,};// 创建 user2,只改邮箱,其他都和 user1 一样!let user2 = User {email: String::from("barry@speedforce.com"), // 新邮箱..user1 // 其他所有字段,都从 user1 那里复制!};println!("user2 用户名:{}", user2.username); println!("user2 邮箱:{}", user2.email);
}
..user1
:就像“其余字段,统统抄 user1 的!”。..user1
必须放在最后。- 你可以改任意多个字段,顺序随意。
这个“复制”其实是“移动”(move)!
user1
的username
和String
)被搬到了user2
,所以user1
的username
和user1.username
。但active
和sign_in_count
(是Copy
类型)是复制过去的,所以user1.active
还能用。
额外彩蛋:特殊类型
Rust 还支持几种特殊的类型:
-
**元组结构体 **(Tuple Struct):像元组一样简洁的对象。
// struct Color(R, G, B); 没有字段名,只有类型 struct Color(i32, i32, i32); struct Point(i32, i32, i32); // 和 Color 类型不同!fn main() {let red = Color(255, 0, 0);let origin = Point(0, 0, 0);// 访问:用 .索引println!("红色值:{}", red.0); // 输出:255// 解构:必须带类型名let Color(r, g, b) = red;println!("RGB: {r}, {g}, {b}"); }
Color
和Point
虽然都是(i32, i32, i32)
,但它们是不同类型!不能混用。
-
**类单元结构体 **(Unit-Like Struct):没有属性的结构体。
// 就是一个名字,代表一种“状态”或“行为” struct AlwaysEqual; // 没有花括号!只有一个分号!fn main() {let _x = AlwaysEqual; // 创建一个实例let _y = AlwaysEqual; // 再创建一个// 想象一下,这两个实例总是“相等”的,虽然它们没数据。// 这在实现某些高级功能(trait)时很有用。 }
核心思想总结
-
结构体是“带标签的数据包”:用
struct
定义模板,给每个数据起名字(字段)。 -
实例是具体对象:用
let 变量 = 结构体名 { 字段: 值, ... }
创建。 -
访问用点(.):
实例.字段名
。 -
修改要
mut
:整个实例必须声明为可变。 -
批量复制用
..
:..旧实例
复制剩余字段,但注意所有权转移(move)。 -
String
vs&str
:结构体里通常用String
(拥有数据),用&str
(引用)需要复杂的“生命周期”(以后再说)。从“数字堆”到“完美矩形”:用 Rust 结构体打造你的专属工具箱
假设老板让你写个程序,计算一个矩形的面积。很简单,对吧?你二话不说,撸起袖子就干:
fn main() {let width = 30;let height = 50;println!("面积是:{}", width * height); // 1500 }
搞定!老板看了一眼,说:“嗯,能算,但…这代码读起来像天书。
width
和height
就是两个孤零零的数字,谁知道它们是描述同一个矩形的?要是我再画一个矩形,岂不是要搞一堆width2
,height2
… 乱死了!”老板说得对!这就像把乐高积木的零件(30块板子,50根柱子)散落在地上,谁知道它们是拼成一个房子的?
改造1:用“元组”打包
你灵机一动:“我打包一下!” 于是你用了元组:
fn main() {let rect = (30, 50); // 把宽高打包成一个“元组包”println!("面积是:{}", rect.0 * rect.1); // 用索引0和1取值 }
进步:现在
rect
代表一个矩形了,不再是两个散装数字。问题:这个“包”太简陋了!里面的零件没贴标签。
rect.0
是宽还是高?万一你手滑写成rect.1 * rect.0
虽然结果一样,但要是以后要画图,搞反了可就出大错了!这就像打包时只写了“零件1”、“零件2”,谁能记得清?
改造2:用“结构体”造专属工具箱!
你终于找到了终极武器——结构体 (struct)。这就像为矩形量身定做一个带标签的工具箱!
// 设计一个叫 Rectangle 的工具箱模板 struct Rectangle {width: u32, // 工具箱左边贴个标签:“宽度”height: u32, // 工具箱右边贴个标签:“高度” }fn main() {// 造一个具体的工具箱实例let rect1 = Rectangle {width: 30,height: 50,};// 计算面积的函数,现在只需要一个“工具箱”参数fn area(rect: &Rectangle) -> u32 { // & 表示借用,不拿走所有权rect.width * rect.height // 直接看标签取值,清晰明了!}println!("面积是:{}", area(&rect1)); }
优点:
- 意义明确:
rect1.width
比rect.0
好懂得多! - 关联性强:
width
和height
被牢牢地“绑定”在Rectangle
这个概念下。 - 函数签名更清晰:
area
函数现在明确说:“我需要一个Rectangle
来算面积!”
调试神器:让工具箱“显形”!
代码写好了,但运行时出了 bug,你想看看
rect1
里面到底装了啥。你尝试:println!("rect1 是 {}", rect1); // 编译错误!
Rust 会告诉你:“
Rectangle
没有实现Display
特性,我不知道怎么把它变成人类能读的字符串。”别慌!Rust 给了你一个“开发者专用透视镜”——
Debug
特性。#[derive(Debug)] // 给 Rectangle 工具箱装上“调试透视镜” struct Rectangle {width: u32,height: u32, }fn main() {let rect1 = Rectangle { width: 30, height: 50 };// 使用 {:?} 告诉 println! 用“调试模式”看println!("rect1 是 {:?}", rect1); // 输出:rect1 是 Rectangle { width: 30, height: 50 }// 想看更整齐?用 {:#?}!println!("rect1 是 {:#?}", rect1);/* 输出:rect1 是 Rectangle {width: 30,height: 50,}*/ }
dbg!
宏:更强大的调试显微镜dbg!
宏更厉害,它不仅能打印值,还能告诉你这个值是在哪一行代码里出现的!fn main() {let scale = 2;let rect1 = Rectangle {width: dbg!(30 * scale), // 在这里调试表达式height: 50,};dbg!(&rect1); // 调试整个 rect1 }
输出:
[src/main.rs:10:16] 30 * scale = 60 [src/main.rs:14:5] &rect1 = Rectangle {width: 60,height: 50, }
看!它直接告诉你
30 * scale
这个计算发生在main.rs
文件的第 10 行第 16 列,结果是 60!&rect1
的信息在第 14 行第 5 列。这在复杂代码里找 bug 简直是神器!
总结:结构体的“设计哲学”
- 从“数字堆”到“概念包”:结构体让你把零散的数据(
width
,height
)组合成一个有意义的整体(Rectangle
)。 - 标签化访问:通过
实例.字段名
访问数据,比元组的索引 (实例.0
) 清晰、安全得多。 #[derive(Debug)]
是必备品:开发时一定要给结构体加上这个,方便调试。{:?}
和{:#?}
:println!
的调试好搭档。dbg!
宏:终极调试武器,自带“案发现场”定位。
Rust 方法语法:给你的“数据积木”加上“技能按钮”
假设,你有一堆乐高积木,拼出了一个电视屏幕。现在你想知道这个屏幕有多大,或者想问问它:“嘿,你能装下另一个小屏幕吗?”
在编程世界里,这个“长方形”就是一个结构体(struct),它记录了宽和高。而“计算面积”、“能不能装下另一个”这些动作,就是它的方法(method)。
方法 vs 函数:谁更懂“自己”?
我们可以写一个叫
calculate_area(rect)
的函数,把长方形传进去,它算完再告诉你结果。这样就像——你把一个机器人拆开,扔给一个程序员,说:“帮我算算这个机器人外壳的面积。” 程序员算完,再告诉你。
方法就不一样了。它是直接“贴”在机器人身上的一个按钮,上面写着“面积”。你只要按下这个按钮:
my_robot.area()
机器人自己就会说:“我知道!我宽30,高50,面积是1500!” 这就是方法的魅力:它知道“我是谁”,因为它属于这个结构体。
怎么给“机器人”贴按钮?用
impl
块!在 Rust 里,我们用
impl
(implementation,实现)块来给结构体“贴技能”。struct Rectangle {width: u32,height: u32, }impl Rectangle {fn area(&self) -> u32 {self.width * self.height} }
这里的
&self
就是关键!它就像机器人说:“我来算我的面积。”
&
表示我只是借一下自己,不会把你拆了(不拿走所有权)。self
有三种用法:&self
:只读我(最常见)&mut self
:可以修改我self
:直接把我拿走(比如把我变成一堆零件)
同名字段和方法?没问题!
想象你的机器人还有一个“宽度检测仪”按钮,按下它会告诉你:“我宽度大于0吗?”
impl Rectangle {fn width(&self) -> bool {self.width > 0} }
这时候你有两个“宽度”:
rect.width
→ 直接看它的宽是多少(字段)rect.width()
→ 按下按钮,问它“宽度正常吗?”(方法)
Rust 很聪明,看有没有()
就知道你要哪个。
这就像:
- 问:“你多高?” →
person.height
- 问:“你够高吗?” →
person.is_tall()
更酷的方法:can_hold
现在,你想让机器人比较一下:“我能不能把另一个小机器人完全装进我的肚子里?”
impl Rectangle {fn can_hold(&self, other: &Rectangle) -> bool {self.width > other.width && self.height > other.height} }
然后你就可以这么用:
let big_screen = Rectangle { width: 30, height: 50 }; let small_screen = Rectangle { width: 10, height: 40 }; let huge_screen = Rectangle { width: 60, height: 45 };println!("我能装下小屏吗?{}", big_screen.can_hold(&small_screen)); // true println!("我能装下大屏吗?{}", big_screen.can_hold(&huge_screen)); // false
输出:
我能装下小屏吗?true
我能装下大屏吗?false
看,big_screen
自豪地说:“我能吞下小屏,但那个巨无霸?想都别想!”
静态方法:工厂按钮
有时候,我们想要一个“造机器人”的按钮,而不是让已有机器人做动作。这种不依赖具体实例的方法,叫关联函数。
比如,你想造一个正方形,每次都写宽高一样太麻烦。于是你加一个“造正方形”按钮:
impl Rectangle {fn square(size: u32) -> Self {Self { width: size, height: size }} }
注意:这里没有
self
!因为它不是某个具体机器人的技能,而是整个“机器人类型”的技能。怎么用?
let sq = Rectangle::square(10); // 造一个 10x10 的正方形
看到
::
了吗?这就像说:“喂,Rectangle 工厂!给我造个边长10的正方形!”String::from("hello")
也是这么用的!它是String
类型的“造字符串”工厂。
多个
impl
块?随你喜欢!你可以把所有方法写在一个
impl
块里,也可以分开写:impl Rectangle {fn area(&self) { /*...*/ } }impl Rectangle {fn can_hold(&self, other: &Rectangle) { /*...*/ } }
这就像给机器人贴贴纸:你可以一次性贴完,也可以今天贴一个“面积”,明天贴一个“能装下谁”。
虽然现在看不出啥好处,但以后学“泛型”和“trait”时,这招就神了!
Rust 的“魔法”:没有
->
运算符!如果你学过 C++,你可能会想:“那
->
呢?不是要用指针调用方法吗?”Rust 说:不需要!我自动帮你搞定!
当你写
robot.start()
,Rust 会自动判断:- 如果
robot
是个实例,直接调 - 如果
robot
是个引用,自动解引用调用
这叫自动引用和解引用,让你写代码更清爽,不用满屏&
和*
。
总结:方法就是“对象的技能包”
概念 通俗解释 例子 方法 (method) 属于某个结构体的函数,知道“我是谁” rect.area()
&self
“我”来操作“我自己”,只读 fn area(&self)
&mut self
“我”来修改“我自己” fn grow(&mut self)
关联函数 类型的“工厂方法”,不依赖实例 Rectangle::square(5)
::
调用“类型级”功能(工厂、常量) String::from()
生活小例子:披萨店机器人
struct Pizza {size: String,is_delicious: bool, }impl Pizza {// 方法:机器人自己报告状态fn status(&self) {println!("我是{}披萨,好吃吗?{}", self.size, self.is_delicious);}// 关联函数:工厂造披萨fn new_deluxe() -> Self {Self {size: "大号".to_string(),is_delicious: true,}} }fn main() {let my_pizza = Pizza::new_deluxe(); // 工厂造一个豪华披萨my_pizza.status(); // 披萨自己说:“我是大号披萨,好吃吗?true” }
输出:
我是大号披萨,好吃吗?true所以,方法就是让你的数据“活”起来,不再只是冷冰冰的字段,而是能“说话”、能“行动”的小机器人!
- 意义明确: