小工具知识网

当前位置: 首页 > 行业知识 > Rust 数据类型 之 结构体(Struct)

行业知识

Rust 数据类型 之 结构体(Struct)

2026-11-11 23:27:52 来源:互联网转载

���录

结构体(Struct)

定义与声明

结构体定义

结构体实例

结构体分类

单元结构体(Unit Struct)

元组结构体(Tuple Struct)

具名结构体(Named Struct)

结构体嵌套

结构体方法

例1:结构体转换为字符串描述

例2:矩形的周长和面积

例3:结构体字段的更新与输出

关联函数

结构体方法与关联函数的区别

参数传递方式的区别

使用方式的区别

结构体的trait

#[derive(Debug)]

例1:

例2:

自定义打印宏 

1. impl fmt::Debug for Student

2. impl fmt::Display for Student

#[derive(PartialEq)]

例1:

例2:

#[derive(Default)]

例1:

例2:

#[derive(Clone)]

其他相关内容

模式匹配

例1:

例2:

结构体大小

1. std::mem::size_of

2. std::mem::size_of_val

本文总结


结构体(Struct)

是一种自定义数据类型,允许将多个相关的值组合在一起,形成一个更复杂的数据结构。结构体被广泛应用于组织和管理数据,具有灵活性和强大的表达能力。

定义与声明

结构体定义

在Rust中,定义和声明结构体的语法如下:

struct Name {      field1: Type1,      field2: Type2,      // ...      fieldN: TypeN,  }

其中,Name是结构体的名称,每个数据名及其对应的数据类型组成一个字段,field1到fieldN是结构体的字段名称,Type1到TypeN是字段的数据类型。

通过关键字 struct 定义,指定结构体名称,结构体内用 field:type, 表示字段名称及数据类型,注意rust语言不能在定义的同时进行赋值,且用逗号分隔各字段,不像c/c++用分号。

结构体中可以根据需要定义字段个数,理论上要多少就定义多少;但实际上字段太多,结构体也会变得很占空间,对程序的空间效率是个负担。

结构体实例

如以下代码定义了一个名为Point的结构体,包含x和y两个字段,类型分别为i32和f64:

struct Point {

    x: i32,

    y: f64,

}

定义结构体后,可以像使用其他类型一样使用它。例如,可以声明一个Point类型的变量,并为其字段赋值:

let my_point = Point { x: 10, y: 20.0 };

使用结构体时,用成员运算符 my_point.x 来调用对应字段的值:

println!("({},{})", point.x, point.y);    // 输出:(10,20)

可变实例

需要变动字段的值,在声明时需要用 let mut,如:

struct Point {      x: i32,      y: f64,  }fn main() {let mut point = Point { x: 10, y: 20.0 }; point.x = 5;    println!("({},{})", point.x, point.y);  // 输出:(5,20)}

结构体分类

在Rust中,结构体(Struct)可以按照不同的方式进行分类,以下是一些常见的分类方式:

单元结构体(Unit Struct)

这种结构体没有任何字段,它只是用于表示一个空的类型。这种结构体通常用于作为其他结构体的组成部分或返回类型。例如:

struct UnitStruct;

元组结构体(Tuple Struct)

这种结构体包含一组字段,可以通过元组语法来访问每个字段。元组结构体可以用于表示简单的数据集合,不使用大括号{},而是使用元组的小括号()。例如:

struct TupleStruct(i32, String);

相当字段数据没有名称的结构体,访问时使用索引。如:

struct Point (i32, f64);fn main() {let mut point = Point(10, 20.0); point.0 = 5;    println!("({},{})", point.0, point.1);  }

具名结构体(Named Struct)

这种结构体有一个显式的名称,并且包含一组字段。具名结构体可以用于表示复杂的数据结构,例如一个包含多个字段的对象,本文的示例大多数都为具名结构体,用法已在本文开头讲过:

struct MyStruct {  

    field1: i32,  

    field2: String,  

    // ...  

}

除了以上三种常见的结构体类型,Rust还支持其他特殊类型的结构体,例如带有泛型参数的结构体、具名元组结构体(Named Tuple Struct)和结构体路径(Struct Type Alias)等。

需要注意的是,在Rust中,结构体的分类并不是强制性的,也就是说,一个结构体可以包含任意类型的字段,并且可以在任何地方使用。这使得结构体非常灵活,可以用于实现各种复杂的数据结构。

结构体字段的数据类型可以是以下常见的rust数据,甚至可以是函数、引用、指针类型。

  1. 标量类型(Scalar Types):

    • 整数类型(Integer Types):包括有符号整数类型和无符号整数类型。常见的整数类型有 i8、i16、i32、i64、i128 表示有符号整数,u8、u16、u32、u64、u128 表示无符号整数。此外,还有 isize 和 usize,它们根据平台的位数自动调整大小。
    • 浮点数类型(Floating-Point Number Types):包括 f32 和 f64 两种类型,表示单精度和双精度浮点数。
    • 布尔类型(Boolean Type):只有两个取值,true 和 false。
    • 字符类型(Character Type):表示单个 Unicode 字符,通常存储为 4 个字节。
  2. 复合类型(Composite Types):

    • 数组类型(Array Types):由相同类型的元素组成的有限集合。可以通过固定长度或动态长度来定义数组。
    • 切片类型(Slice Types):对一个连续的内存块进行引用,可以看作是动态数组。切片类型提供了访问和操作数据的一种高效方式。
    • 元组类型(Tuple Types):一种将多个不同类型的值组合在一起的数据结构,用圆括号和逗号分隔的元素序列表示。元组可以包含不同类型的元素,例如整数、浮点数、布尔值、字符串等。
    • 结构体类型(Struct Types):一种自定义的数据类型,可以包含多个不同类型的字段。结构体可以通过定义来指定其字段和属性。
    • 枚举类型(Enum Types):表示一个可能取多个值的变量。在 Rust 中,枚举类型使用 enum 关键字定义,每个可能的取值都是一个不同的枚举成员。

结构体嵌套

一个结构体可以包含任意类型的字段,当然也包括结构体。

在以下这个例子中,Address 结构体包含了 street、city 和 state 三个字段,而 Person 结构体则包含了 name、age 和 address 三个字段,其中 address 字段的类型是 Address 结构体。

struct Address {      street: String,      city: String,      state: String,  }    struct Person {      name: String,      age: u8,      address: Address,  }

结构体方法

方法(method)是在结构体上定义的功能,可以访问结构体的字段并执行一些操作。使用关键字impl,结构体可以对应一个或多个impl代码块。

例1:结构体转换为字符串描述

struct Student {    name:String,    age:u32,    school:String,    major:String,    grade:String,    state:bool}  impl Student {      fn to_string(&self) -> String {          format!("Student {{ name: {}, age: {}, school: {}, major: {}, grade: {}, state: {} }}",             self.name, self.age, self.school, self.major, self.grade, self.state)      }  } fn main() {    let school = String::from("东南大学");    let major = String::from("土木工程学院");    let s = Student{        name:String::from("杨程"),        age:22,        school,        major,        grade:String::from("大三"),        state:true    };    println!("{}", s.to_string());}

输出:

Student { name: 杨程, age: 22, school: 东南大学, major: 土木工程学院, grade: 大三, state: true }

注意:上例中有一个rust结构体的特殊用法,使用同名变量在结构体外为对应字段赋值。

例2:矩形的周长和面积

struct Rectangle {      width: f32,      height: f32,  }    impl Rectangle {      // 构造函数      fn new(width: f32, height: f32) -> Rectangle {          Rectangle { width, height }      }        // 计算矩形的面积      fn area(&self) -> f32 {          self.width * self.height      }        // 计算矩形的周长      fn perimeter(&self) -> f32 {          (self.width + self.height) * 2.0    }  }impl Rectangle {      // 判断矩形是否相等      fn is_equal(&self, other: &Rectangle) -> bool {          self.width == other.width && self.height == other.height      }  }    fn main() {      let rect1 = Rectangle::new(5.0, 6.0);      let rect2 = Rectangle::new(5.0, 6.0);        println!("Rectangle 1 area: {}", rect1.area());      println!("Rectangle 1 perimeter: {}", rect1.perimeter());        println!("Rectangle 2 area: {}", rect2.area());      println!("Rectangle 2 perimeter: {}", rect2.perimeter());        if rect1.is_equal(&rect2) {          println!("Rectangles are equal");      } else {          println!("Rectangles are not equal");      }  }

输出:

Rectangle 1 area: 30

Rectangle 1 perimeter: 22

Rectangle 2 area: 30

Rectangle 2 perimeter: 22

Rectangles are equal 

例3:结构体字段的更新与输出

struct Person {      name: String,      age: u32,  }    impl Person {      // 这是构造函数,用于创建一个新的 Person 实例      fn new(name: String, age: u32) -> Person {          Person { name, age }      }        fn say_hello(&self) {          println!("Hello, my name is {} and I'm {}.", self.name, self.age);      }        fn update_age(&mut self, new_age: u32) {          self.age = new_age;      }     fn update_name(&mut self, new_name: String) {          self.name = new_name;      }  }    fn main() {      // 创建一个新的 Person 实例      let mut person = Person::new("Tom".to_string(), 5);        // 调用 say_hello 方法,输出 Person 的信息      person.say_hello();        // 调用 update_age 方法,更新 Person 的年龄      person.update_age(3);        // 再次调用 say_hello 方法,输出更新后的信息      person.say_hello();     person.update_age(5);    person.update_name(String::from("Jerry"));      person.say_hello();  }

输出:

Hello, my name is Tom and I'm 5.

Hello, my name is Tom and I'm 3.

Hello, my name is Jerry and I'm 5.

关联函数

之所以"结构体方法"不叫"结构体函数"是因为"函数"这个名字留给了这种函数:它在 impl 块中却没有 &self 参数。这种函数不依赖实例,但是使用它需要声明是在哪个 impl 块中的,比如上小节例2和例3中的构造函数new()就是关联函数,类似于字符串函数String::new(),String::from("Jerry")。

示例:

#[derive(Debug,Clone)]struct Rectangle {    width: u32,    height: u32,}impl Rectangle {    fn create(width: u32, height: u32) -> Rectangle {        Rectangle { width, height }    }    fn area(self) -> u32 {        self.width * self.height    }    fn area2(&self) -> u32 {        self.width * self.height    }}fn main() {    let rect = Rectangle::create(30, 50);    println!("{:?}", rect);    println!("Area: {}", Rectangle::area(rect.clone()));    println!("Area: {}", rect.area2());}

输出:

Rectangle { width: 30, height: 50 }

Area: 1500

Area: 1500

结构体方法与关联函数的区别

参数传递方式的区别

结构体方法:结构体方法默认情况下是可变的(mutable),也就是说可以修改结构体的字段。在调用方法时,可以通过引用(&self)或可变引用(&mut self)来传递结构体实例,以便修改其字段。例如:my_struct.my_method(&mut my_struct)。

关联函数:关联函数默认情况下是不可变的(immutable),也就是说无法修改结构体的字段。在调用函数时,只能通过常量引用(&self)来传递结构体实例,因为常量引用是只读的。例如:let my_struct = MyStruct {...}; my_struct.my_function()。

使用方式的区别

结构体方法:结构体方法可以直接在结构体实例上调用,无需显式传递结构体实例。例如:my_struct.my_method()。

关联函数:关联函数需要显式传递结构体实例作为参数。例如:MyStruct::my_function(my_struct)。

结构体的trait

Rust 中的 trait 是一种抽象类型,用于定义泛型行为,trait 可以理解为一种接口。trait 使用关键字 derive 来自动生成实现。通过使用 derive,可以避免手动编写冗长的代码,提高代码的可读性和可维护性。trait 有很多,比如Copy,Clone,Debug,Default,Drop,Hash,Ord,PartialOrd,Send,Sync等等,先挑几种最常用的学一下:

#[derive(Debug)]

在 Rust 语言中用于自动生成一个结构体的 Debug 实现,Debug 是 Rust 标准库中的一个 trait,用于在控制台打印调试信息。

使用 #[derive(Debug)] 属性可以为结构体自动生成一个 Debug 实现,这样在需要打印调试信息时,就可以使用 {:?} 格式化字符串来打印该结构体的内容。例如,在上面的代码中,s 结构体的 Debug 实现已经被自动生成,因此可以使用 println!("{:?}", s) 来打印出结构体 s 的内容。

例1:
#[derive(Debug)]struct Point {    x: i32,    y: i32,}impl Point {    fn distance(&self, other: &Point) -> f32 {        let x_diff = self.x - other.x;        let y_diff = self.y - other.y;        ((x_diff * x_diff + y_diff * y_diff) as f32).sqrt()    }}fn main() {    let p1 = Point { x: 3, y: 0 };    let p2 = Point { x: 0, y: 4 };    println!("Distance between {:?} and {:?} is {}.", p1, p2, p1.distance(&p2));}

输出:

Distance between Point { x: 3, y: 0 } and Point { x: 0, y: 4 } is 5. 

例2:
#[derive(Debug)]struct Student {    name: String,    age: u32,    school: String,    major: String,    grade: String,    state: bool,}  impl Student {    fn new() -> Student {        return Student {            age: 0,            name: String::new(),            school: String::from(""),            major: "".to_string(),            grade: "".to_string(),            state: false,        };    }}fn main() {      let mut s = Student::new();    s.name = String::from("杨程");    s.age = 22;    s.school = String::from("东南大学");    s.major = String::from("土木工程学院");    s.grade = String::from("大三");    s.state = true;    println!("{:?}", s);}

输出:

Student { name: "杨程", age: 22, school: "东南大学", major: "土木工程学院", grade: "大三", state: true } 

与上一小节的例2对比,输出内容基本一致,就多了String的引号标记。相比自动生成 Debug 实现可以简化代码编写过程,并且可以避免手动实现 Debug 时可能出现的错误。

在本例中,使用宏打印结构体println!("{:?}", s);时,第一行的代码#[derive(Debug)]是必须的,如果去掉就会报错:

error[E0277]: `Student` doesn't implement `Debug`

  --> E:\.rs\struct2.rs:31:22

   |

31 |     println!("{:?}", s);

   |                      ^ `Student` cannot be formatted using `{:?}`

   |

   = help: the trait `Debug` is not implemented for `Student`

   = note: add `#[derive(Debug)]` to `Student` or manually `impl Debug for Student`

   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

help: consider annotating `Student` with `#[derive(Debug)]`

   |

1  + #[derive(Debug)]

2  | struct Student {

   |

error: aborting due to previous error

自定义打印宏 

1. impl fmt::Debug for Student

返回值:fmt::Result; 调用:println!("{:?}", s);

use std::fmt;struct Student {    name: String,    age: u32,    school: String,    major: String,    grade: String,    state: bool,}    impl fmt::Debug for Student {      fn fmt(&self, f: &mut fmt::Formatter

上一篇:SpringBoot项目无缝集成SkyWalking:实现分布式追踪与日志透传实战

下一篇:Redis 和 Mysql 如何保证两者数据一致性