趣味学RUST基础篇(结构体&方法)

发布于:2025-09-01 ⋅ 阅读:(17) ⋅ 点赞:(0)

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); // 输出:用户名是:BMJ
    println!("邮箱是:{}", 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)!user1usernameemail(都是 String)被搬到了 user2,所以 user1usernameemail 已经失效了!你不能再用 user1.username。但 activesign_in_count(是 Copy 类型)是复制过去的,所以 user1.active 还能用。


额外彩蛋:特殊类型

Rust 还支持几种特殊的类型:

  1. **元组结构体 **(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}");
    }
    
    • ColorPoint 虽然都是 (i32, i32, i32),但它们是不同类型!不能混用。
  2. **类单元结构体 **(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
    }
    

    搞定!老板看了一眼,说:“嗯,能算,但…这代码读起来像天书。widthheight 就是两个孤零零的数字,谁知道它们是描述同一个矩形的?要是我再画一个矩形,岂不是要搞一堆 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.widthrect.0 好懂得多!
    • 关联性强widthheight 被牢牢地“绑定”在 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 简直是神器!


    总结:结构体的“设计哲学”

    1. 从“数字堆”到“概念包”:结构体让你把零散的数据(width, height)组合成一个有意义的整体(Rectangle)。
    2. 标签化访问:通过 实例.字段名 访问数据,比元组的索引 (实例.0) 清晰、安全得多。
    3. #[derive(Debug)] 是必备品:开发时一定要给结构体加上这个,方便调试。
    4. {:?}{:#?}println! 的调试好搭档。
    5. 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

    所以,方法就是让你的数据“活”起来,不再只是冷冰冰的字段,而是能“说话”、能“行动”的小机器人!


网站公告

今日签到

点亮在社区的每一天
去签到