Rust结构体

结构体

  struct,或者 structure,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,struct 就像对象中的数据属性。结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。也就是说元组和结构体类似,但是结构体有具体的代号

结构体的声明和实例化

#[derive(Debug)] // 必须带有这个

// 声明结构体
struct User {
    username: String,
    email: String,
    active: bool,
}

fn main() {
    // 实例化
    let mut user1 = User {
        // 使用mut可以修改里面的属性
        email: String::from("someone@qq.com"),
        username: String::from("wang"),
        active: true,
    };

    user1.email = String::from("666@qq.com");

    println!("{:?}", user1);
}

  

结构体的实例构造函数

  为了方便,我们可以写一个函数专门用来返回新实例化的对象,以及字段名和变量名一致可以简写,这里我们比如说是builder_user可以如下: 

#[derive(Debug)] // 必须带有这个

// 声明结构体
struct User {
    username: String,
    email: String,
    active: bool,
}

fn build_user(email: String, username: String) -> User {
    User {
        // 使用mut可以修改里面的属性
        email: email,
        username, // 一致就简写
        active: true,
    }
}

fn main() {
    let mut user1 = build_user(String::from("xxx"), String::from("erwqrq"));
    user1.email = String::from("666@qq.com");

    println!("{:?}", user1);
}

  

从另一个实例来更新新实例

  调用另一个实例的属性值来创建新实例,代码如下:

fn main() {
    let user1 = User {
        email: String::from("xxxx"),
        username: String::from("wang"),
        active: false,
    };

    let user2 = User {
        email: String::from("455"),
        username: user1.username,
        active: false,
    };

    let user3 = User {
        email:String::from("2232"),
        ..user2   // 支持结构赋值
    };

    println!("{:?}", user3);
}

  

使用没有命名字段的元组结构体来创建不同的类型

  要定义元组结构体,以 struct 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 Color 和 Point 元组结构体的定义和用法:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

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

}

 

结构体数据所有权

  User 结构体的定义中,我们使用了自身拥有所有权的 String 类型而不是 &str 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的,比如这样:

// 声明结构体
struct User {
    username: &str,
    email: &str, // 定义的时候就报错了,提示没有生命周期
    active: bool,
}

  

使用结构体创建一个demo:

// 声明结构体
fn area(width: u32, height: u32) -> u32 {
    width * height
}

fn area1(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

fn area2(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

// 结构体
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    // 最初版本
    let width = 30;
    let height = 50;
    println!("The area value :{}", area(width, height));

    // 元组重构
    let rect1 = (30, 50);
    println!("The area value :{}", area1(rect1));

    let rect2 = Rectangle {
        width: 32,
        height: 55,
    };
    println!("The area value :{}", area2(&rect2));
}

  

输出结构体实例

  由于Rust自带的println!不能打印出实例,所以我们需要在{}中添加#?来打印,同时需要在文件头部添加:#[derive(Debug)],在 {} 中加入 :? 指示符告诉 println! 我们想要使用叫做 Debug 的输出格式。 Debug 是一个 trait,它允许我们以一种对开发者有帮助的方式打印结构体,以便当我们调试代码时能看到它的值。

结构体方法实现

  为了使函数定义于 Rectangle 的上下文中,我们开始了一个 impl 块( impl 是implementation 的缩写)。接着将 area 函数移动到 impl 大括号中,并将签名中的第一个

(在这里也是唯一一个)参数和函数体中其他地方的对应参数改成 self 。然后在 main 中方法语法将我们先前调用 area 方法并传递 rect1 作为参数的地方,改成使用 方法语法(method

syntax)在 Rectangle 实例上调用 area 方法。方法语法获取一个实例并加上一个点号,后跟方法名、圆括号以及任何参数。

  例子中,使用 &self 来替代 rectangle: &Rectangle ,因为该方法位于 implRectangle 上下文中所以 Rust 知道 self 的类型是 Rectangle 。注意仍然需要在 self 前面加上 & ,就像 &Rectangle 一样。方法可以选择获取 self 的所有权,或者像我们这里一样不可变地借用 self ,或者可变地借用 self ,就跟其他参数一样。这里选择 &self 的理由跟在函数版本中使用 &Rectangle 是相同的:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self 。通过仅仅使用 self 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。

实例方法的调用:

  在 C/C++ 语言中,有两个不同的运算符来调用方法: . 直接在对象上调用方法,而 -> 在一个对象的指针上调用方法,这时需要先解引用(dereference)指针。换句话说,

如果 object 是一个指针,那么 object->something() 就像 (*object).something() 一样。Rust 并没有一个与 -> 等效的运算符;相反,Rust 有一个叫 自动引用和解引用(automatic referencing and dereferencing)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。他是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 & 、 &mut 或 * 以便使 object 与方法签名匹配。也就是说,这些代码是等价的:

#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn distance(&self, other: &Point) -> f64 {
        let x_squared = f64::powi(other.x - self.x, 2);
        let y_squared = f64::powi(other.y - self.y, 2);
        f64::sqrt(x_squared + y_squared)
    }
}

fn main() {
    let p1 = Point { x: 0.0, y: 0.0 };
    let p2 = Point { x: 5.0, y: 6.5 };
    println!("{}", p1.distance(&p2));
    println!("{}", (&p1).distance(&p2));
}

  

带有更多参数的方法

  rect1.can_hold(&rect2) 传入了 &rect2 ,它是一个 Rectangle 的实例rect2 的不可变借用。这是可以理解的,因为我们只需要读取 rect2 (而不是写入,这意味着我们需要一个不可变借用),而且希望 main 保持 rect2 的所有权,这样就可以在调用这个方法后继续使用它。

#[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,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };
    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

  

关联函数

  impl 块的另一个有用的功能是:允许在 impl 块中定义 不 以 self 作为参数的函数。这被称为 关联函数(associated functions),因为它们与结构体相关联。它们仍是函数而不是

方法,因为它们并不作用于一个结构体的实例。你已经使用过 String::from 关联函数了。(类似与结构体方法,类似与python的类方法)

#[derive(Debug)] // 必须带有这个

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    println!("Can rect1 area {}", Rectangle::square(30).area());
}

  

多个impl块

  每个结构体都允许拥有多个 impl 块。每个方法有其自己的 impl 块。