rust 学习(9):结构体

文章目录

返回首页 | 上篇 | 下篇


掌握 Rust 中的自定义数据类型 ------ 结构体定义、实例化、方法以及关联函数


一、回顾与导入

前面八篇文章我们学习了 Rust 的基础语法:

  • 变量、数据类型、函数、控制流
  • 所有权、引用、借用、切片

这些都是 Rust 的"基石"。从今天开始,我们将进入 Rust 的面向对象特性 ------ 首先是结构体(Struct)

💡 结构体是一种自定义数据类型,允许你将多个相关的值组合成一个有意义的整体。


二、定义与实例化结构体

2.1 定义结构体

使用 struct 关键字,花括号内定义字段(field):

rust 复制代码
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

2.2 创建实例

rust 复制代码
fn main() {
    let user1 = User {
        active: true,
        username: String::from("rustacean"),
        email: String::from("rust@example.com"),
        sign_in_count: 1,
    };

    println!("用户名: {}", user1.username);
}

2.3 修改字段(需要 mut)

rust 复制代码
fn main() {
    let mut user1 = User {
        active: true,
        username: String::from("rustacean"),
        email: String::from("rust@example.com"),
        sign_in_count: 1,
    };

    user1.email = String::from("newemail@example.com");
    println!("新邮箱: {}", user1.email);
}

⚠️ Rust 不支持单独字段的可变性,要么整个实例可变,要么整个不可变。


三、字段初始化简写

当变量名与字段名相同时,可以使用简写:

rust 复制代码
fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,      // 等同于 username: username
        email,         // 等同于 email: email
        sign_in_count: 1,
    }
}

四、结构体更新语法(Struct Update Syntax)

从已有实例创建新实例,未指定的字段使用原实例的值:

rust 复制代码
fn main() {
    let user1 = User {
        active: true,
        username: String::from("user1"),
        email: String::from("user1@example.com"),
        sign_in_count: 1,
    };

    // 创建 user2,email 是新值,其他字段从 user1 复制
    let user2 = User {
        email: String::from("user2@example.com"),
        ..user1  // 剩余字段使用 user1 的值
    };

    println!("user2: {}, {}", user2.username, user2.email);
    // println!("user1.username: {}", user1.username);  // ❌ user1 的 username 已被移动!
}

⚠️ 注意所有权..user1 语法会移动字段的所有权。上面的例子中,user1.username(String 类型)被移动到了 user2,所以 user1 之后不能再使用 username 字段。但 activesign_in_countCopy 类型,仍然可用。


五、元组结构体(Tuple Struct)

没有字段名,只有字段类型,适合用于创建类型别名:

rust 复制代码
// 定义元组结构体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

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

    // 类型不同,即使内部结构相同也不能混用
    // let p = black;  // ❌ 编译错误!

    println!("Color: ({}, {}, {})", black.0, black.1, black.2);
    println!("Point: ({}, {}, {})", origin.0, origin.1, origin.2);
}

六、类单元结构体(Unit-Like Struct)

没有字段的结构体,适用于需要在某个类型上实现 trait 但不需要存储数据:

rust 复制代码
struct AlwaysEqual;  // 不需要花括号

fn main() {
    let subject = AlwaysEqual;
    // 通常配合 trait 使用
}

七、为结构体添加方法

使用 impl 块(implementation)为结构体定义方法。

7.1 定义方法

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // &self 是 self: &Self 的语法糖
    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 rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("面积: {}", rect.area());

    let rect2 = Rectangle { width: 20, height: 40 };
    let rect3 = Rectangle { width: 40, height: 60 };

    println!("rect 能容纳 rect2? {}", rect.can_hold(&rect2));
    println!("rect 能容纳 rect3? {}", rect.can_hold(&rect3));
}

7.2 方法参数类型

接收者 简写 完整形式 所有权变化
不可变引用 &self self: &Self 只读借用
可变引用 &mut self self: &mut Self 可变借用
获取所有权 self self: Self 转移所有权,方法结束后被 drop
rust 复制代码
impl Rectangle {
    // 只读方法
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 修改方法
    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }

    // 消耗方法(调用后实例不再有效)
    fn destroy(self) {
        println!("销毁一个 {}x{} 的矩形", self.width, self.height);
        // self 在这里被 drop
    }
}

八、关联函数(Associated Functions)

impl 块中定义不带 self 参数的函数,称为关联函数。通常用作构造函数。

rust 复制代码
impl Rectangle {
    // 关联函数,通常用于构造新实例
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

fn main() {
    // 使用 :: 语法调用关联函数
    let sq = Rectangle::square(25);
    println!("正方形面积: {}", sq.area());
}

九、多个 impl 块

Rust 允许将方法分散在多个 impl 块中:

rust 复制代码
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 块主要用于泛型和 trait 实现(后续章节)。


十、完整代码示例

rust 复制代码
#[derive(Debug)]
struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

impl User {
    // 构造函数关联函数
    fn new(username: String, email: String, age: u32) -> Self {
        Self {
            username,
            email,
            age,
            active: true,
        }
    }

    // 方法:不可变引用
    fn greet(&self) -> String {
        format!("你好,我是 {},{} 岁", self.username, self.age)
    }

    // 方法:可变引用
    fn deactivate(&mut self) {
        self.active = false;
    }

    // 方法:获取所有权(消耗)
    fn into_string(self) -> String {
        format!("{} ({})", self.username, self.email)
    }
}

// 元组结构体
struct Point(i32, i32);

impl Point {
    fn distance_from_origin(&self) -> f64 {
        ((self.0.pow(2) + self.1.pow(2)) as f64).sqrt()
    }
}

// 类单元结构体
struct Logger;

impl Logger {
    fn log(&self, msg: &str) {
        println!("[LOG] {}", msg);
    }
}

fn main() {
    println!("=== 1. 结构体实例化 ===");
    let mut user = User::new(
        String::from("alice"),
        String::from("alice@example.com"),
        25,
    );
    println!("{}", user.greet());

    println!("\n=== 2. 修改字段 ===");
    user.age = 26;
    println!("修改年龄后: {}", user.greet());

    println!("\n=== 3. 方法调用 ===");
    user.deactivate();
    println!("激活状态: {}", user.active);

    println!("\n=== 4. 消耗方法 ===");
    let user_info = user.into_string();
    println!("{}", user_info);
    // println!("{}", user.greet());  // ❌ user 已经被消耗

    println!("\n=== 5. 元组结构体 ===");
    let p = Point(3, 4);
    println!("点 ({}, {}) 到原点距离: {:.2}", p.0, p.1, p.distance_from_origin());

    println!("\n=== 6. 类单元结构体 ===");
    let logger = Logger;
    logger.log("程序启动");

    println!("\n=== 7. 结构体更新语法 ===");
    let user1 = User::new(
        String::from("bob"),
        String::from("bob@example.com"),
        30,
    );
    let user2 = User {
        email: String::from("bob_new@example.com"),
        ..user1
    };
    println!("user2 邮箱: {}", user2.email);
    println!("user2 用户名: {}", user2.username);
    // user1.username 已经被移动到 user2,不能再使用
}

十一、常见陷阱与最佳实践

陷阱 1:忘记使用 mut 导致无法修改

rust 复制代码
struct Point { x: i32, y: i32 }

fn main() {
    let p = Point { x: 0, y: 0 };
    // p.x = 10;  // ❌ 编译错误:p 不可变
}

陷阱 2:结构体更新导致所有权转移

rust 复制代码
struct Person {
    name: String,
    age: i32,
}

fn main() {
    let p1 = Person { name: String::from("Alice"), age: 20 };
    let p2 = Person { age: 21, ..p1 };

    println!("{}", p1.name);   // ❌ p1.name 已被移动
    println!("{}", p1.age);    // ✅ age 是 Copy 类型,仍然可用
}

最佳实践

场景 推荐做法
打印结构体 添加 #[derive(Debug)],使用 {:?}{:#?}
构造函数 使用关联函数 newfrom
不需要修改的方法 使用 &self
需要修改的方法 使用 &mut self
需要消耗实例的方法 使用 self

添加 Debug 输出

rust 复制代码
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle { width: 30, height: 50 };

    // 调试打印
    println!("{:?}", rect);    // 单行
    println!("{:#?}", rect);   // 多行(pretty print)
}

十二、总结与速查

概念 语法 示例
定义结构体 struct Name { fields } struct User { name: String }
创建实例 Name { fields } User { name: "Alice".into() }
访问字段 instance.field user.name
更新语法 Name { field: val, ..other } User { age: 18, ..user1 }
元组结构体 struct Name(Types) struct Color(i32, i32, i32)
类单元结构体 struct Name; struct AlwaysEqual;
方法 impl Name { fn (&self) } impl User { fn greet(&self) }
关联函数 fn new() -> Self User::new()

核心要点

  • ✅ 结构体将相关数据组合在一起
  • ✅ 使用 mut 才能使实例可变
  • ✅ 方法定义在 impl 块中
  • &self 是只读方法,&mut self 是可写方法
  • ✅ 关联函数(如 new)通常用作构造函数
  • ✅ 使用 #[derive(Debug)] 可以方便地打印结构体

十三、思考题

  1. 下面的代码能否编译?为什么?
rust 复制代码
struct Book {
    title: String,
    pages: u32,
}

fn main() {
    let book = Book {
        title: String::from("Rust Book"),
        pages: 500,
    };
    let book2 = Book { pages: 600, ..book };
    println!("{}", book.title);
}
  1. 方法关联函数 有什么区别?

  2. 为什么 Rust 要区分 self&self&mut self


参考链接

相关推荐
此生决int1 小时前
深入理解C++系列(11)——stack、queue和priority_queue
开发语言·c++
周杰伦fans1 小时前
数列极限的定义
java·开发语言
上下求索,莫负韶华2 小时前
切面学习笔记
笔记·python·学习
2601_963869952 小时前
【计算机毕业设计】基于Java的潮牌购物网站系统的设计与实现
java·开发语言·课程设计
名字还没想好☜3 小时前
Go 的 io.Reader/Writer 组合实战:io.Copy、TeeReader、MultiWriter 优雅处理数据流
开发语言·后端·golang·go·iphone
学掌门3 小时前
超级菜鸟怎么学习数据分析?
python·学习·数据分析
郝学胜-神的一滴3 小时前
Qt 高级编程 042:进度条从入门到自定义美化
开发语言·c++·qt·软件开发·用户界面
铅笔侠_小龙虾3 小时前
rust 学习(12):常用集合(上)-- Vec
rust
鸿芯微控科技3 小时前
压电纳米定位分辨率怎么测?噪声、带宽、最小可检测位移与Python分析
开发语言·python·噪声分析·压电执行器·纳米定位·位移测量