rust入坑之旅-struct

Scroll Down

Struct

结构体和我们在“元组类型”部分论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
元组
 let tup: (i32, f64, u8) = (500, 6.4, 1);

要想修改结构体实例的某个字段,则整个实例必须是可变的;Rust 并不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。(默认不可变模式)

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn test_Struct() -> User {
    let user = User {
        username: "丽丽".to_string(),
        email: "1116258181@qq.com".to_string(),
        sign_in_count: 0,
        active: false,
    };
    user
}

    let mut ret = test_Struct();
    println!("User username is {}", ret.username);
    ret.username = String::from("dadasdadsadas@aa.com");
    println!("User email is {}", ret.email);
    println!("User username is {}", ret.username);

output :
User username is 丽丽
User email is 1116258181@qq.com
User username is dadasdadsadas@aa.com

struct update syntax

使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有用的。这可以通过 结构体更新语法(struct update syntax)实现。

fn test_Struct_update() {
    let user1 = User {
        username: "丽丽".to_string(),
        email: "1116258181@qq.com".to_string(),
        sign_in_count: 0,
        active: false,
    };
    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
    println!("User1 email is {}", user1.email);
    println!("User2 username is {}", user2.username);
    let user3 = User {
        email: String::from("another@example.com"),
        ..user2
    };
    println!("user3 username is {}", user3.username);
    // println!("User2 username is {}", user2.username);
}
output:
User username is 丽丽
User email is 1116258181@qq.com
User username is dadasdadsadas@aa.com
User1 email is 1116258181@qq.com
User2 username is 丽丽
user3 username is 丽丽

基于 user2 中创建了一个新实例,其有不同的 email 值不过 username、 active 和 sign_in_count 字段的值与 user1 相同。..user1 必须放在最后,以指定其余的字段应从 user1 的相应字段中获取其值,但我们可以选择以任何顺序为任意字段指定值,而不用考虑结构体定义中字段的顺序。 使用结构体更新之后就等于赋值=操作 所有权就发生转移。

元组结构体

元组结构体,以 struct 关键字和结构体名开头并后跟元组中的类型。元组结构体在于特殊的标识变量,用于展示不同元组的区别命名。


fn main() {
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}

类单元结构体

没有任何字段的类单元结构体


fn main() {
struct AlwaysEqual;

let subject = AlwaysEqual;
}

定义 AlwaysEqual,我们使用 struct 关键字,我们想要的名称,然后是一个分号。不需要花括号或圆括号!然后,我们可以以类似的方式在 subject 变量中获得 AlwaysEqual 的实例:

求长方形的面积

  • 结构体版本
// #[derive(Debug)] 显示增加外部属性,派生Debug trait
 struct Rectangle {
        width: i32,
        height: i32,
    }
fn main(){
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("area  is {}", area(rect1.width, rect1.height));
    println!("area  is {}", area_struct(&rect1));
//  println!("area  is {}", rect1); 打印结构体报错
}
fn area(wight: i32, height: i32) -> i32 {
    wight * height
}
fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}
  • 元组版本
fn main(){
    struct RectangleTub(i32, i32);
    let rect2 = RectangleTub(30, 50);
    println!("area  is {}", area(rect2.0, rect2.1));
}
fn area(wight: i32, height: i32) -> i32 {
    wight * height
}

当需要打印结构体时候,则会提示如下错误,这里得提下宏println!,标准输出。

= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

println! 宏能处理很多类型的格式,占位符可支持大多数可以直接展示的变量,不能打印元组和结构体,因为机构体和元组的类型不确定,所以无法使用标准打印输出结构体。提示我们使用{:?}来打印,我们在打印元组的时候发现是可以,在对结构体使用时则提示the trait Debug is not implemented for Rectangle,为啥针对机构体则不行,观察错误提示则是Rust中打印结构体必须为结构体显式选择这个功能,在结构体定义之前加上外部属性 #[derive(Debug)],

  • 成员方法
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    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 rect1 = Rectangle { width: 30, height: 50 };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

通过impl块来和结构体Rectangle关联, 关联函数的调用则更为简单,直接使用变量.area()方法即可。
再来看看变成成员方法之后方法的签名的处理,这利用&self用引用处理和之前方法的参数&Rectangle一样,只是读取变量 而不是写入,如果要修改则需要改为可变引用。如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self。通过仅仅使用 self 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。可以使用多个impl块来标识关联函数。

总结

结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。在impl块中,你可以定义与你的类型相关联的函数,而方法是一种相关联的函数,让你指定结构体的实例所具有的行为。