rust学习 -- 第八章 rust中的struct

93 阅读5分钟

第八章 rust中的struct

什么是struct? 结构体,自定义的数据类型,为相关联的值命名,打包->有意义的组合

定义struct

struct User{  // 使用struct关键字
    username: String, // 字段名称和类型
    email: String, 
    sign_in_count: u64,
    active: bool, // 最后的字段也要有逗号
}

实例化struct

想要使用必须先实例化.

  • 为每个字段指定具体的值
  • 无需按声明顺序进行指定
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
​
fn main() {
    let user1 = User {
        email: String::from("abc@123.com"),
        username: String::from("Nikky"),
        active: true,
        sign_in_count: 556, // 初始化时要给每个成员都赋值
    };
}

实例化字段的简写

当字段名与字段值对应的变量名相同时,就可以使用字段初始化简写的方式:

struct User{
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}
​
fn build_user(email: String, username: String)->User{
    User {
        email, // 使用了简写的方式
        username,  // 使用了简写的方式
        active: true,
        sign_in_count: 0,
    }
}

struct 中的更新语法

当想要基于一个struct实例创建一个新实例的时候,可以使用struct更新语法:

let user2 = User{
    email: String::from("anotheremail@123.com")
    username: String::from("anothername"),
    active: user1.active,  // 如果不使用更新的语法,需要从之前的struct指定值
    sign_in_count: user1.sign_in_count,
};
​
let user2 = User{
    email: String::from("anotheremail@123.com")
    username: String::from("anothername"),
    ..user1  // struct更新语法,使用两个...剩下的值使用user1的值
};

获取struct里面的某个值

使用点 标记法

let mut user1 = User {  // 如果使用mut标记,标记struct是可变的,那么整个struct的字段都是可变的
        email: String::from("abc@123.com"),
        usernameL: String::from("Nikky"),
        active: true,
        sign_in_count: 556,
};
user1.email = String::from("anotheremail@123.com");

struct作为函数返回值

将struct作为函数最后的表达式, 就可以将struct作为函数的返回值使用.

tuple struct

定义类似tuple的struct.需要:

  1. tuple struct整体有个名字,但里面的元素没有名字
  2. 适用范围:想给整个tuple起名,并让它不同于其他tuple,而且又不需要给每个元素起名.

定义tuple struct: struct + 名称(元素类型)

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0,0,0);
let origin = Point(0,0,0);

没有任何字段的struct (unit-Like Struct)

可以定义没有任何字段的struct,叫做unit-like struct

适用于需要在某个类型上实现某个trait,但是在里面又没有想要存储的数据.

struct Empty;
let x = Empty;
println!("{:p}", &x);

struct数据的所有权

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

上述结构体的字段使用了String而不是&str,并且u64等基础类型

  • 所以该struct实例拥有其所有的数据的所有权
  • 只要struct的实例是有效的,那么里面的字段数据也是有效的.

struct里面也可以存放引用,但这需要使用生命周期(以后再讲)

  • 生命周期保证只要struct实例是有效的,那么里面的引用也是有效的

  • 如果struct里面存储引用,而不使用生命周期,就会报错.

    struct ImportantExcerpt<'a> {  // 声明周期标注
        part: &'a str,
    }
    

使用struct的例子

假如要计算一个矩形的面积,我们需要长宽进行计算,也就是我们需要两个参数.

fn main()
{
    let w = 30;
    let l = 50;
    prinfln!("{}", area(w, l));
}
​
fn area(wigth: u32, length: u32) -> u32{
    wigth * length
}

上面的例子的问题是矩形的长宽是独立的,没有将他们联系起来,也就是不统一.

使用元组可以将两个参数组合起来,但是它的问题的是哪个是长,哪个是宽,容易混淆

fn main()
{
    let rect = (30, 50);
​
    println!("{}", area(rect));
}
​
fn area(dim: (u32, u32)) ->u32
{
    dim.0 * dim.1
}

使用struct对每个参数起名字

struct Rectangle {
    width: u32,
    length: u32,
} // 使用结构体来保存长宽的数据fn main() {
    let rect = Rectangle {
        width: 30,
        length: 50,
    };
    println!("{}", area(&rect)); // 保留了所有权
    println!("{}", rect); // 这里打印会报错, 需要实现 Display trait
}
​
fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.length
}

struct的调试模式

如上面例子所示

println!("{}", rect);

这里直接打印一个struct是不行的,因为这个struct并没有实现std::fmt::Display这个接口,类似于python中没有实现__str__这个方法,无法打印出struct中具体的数据.

修改提示可以使用{:?}进行格式化

println!("{:?}", rect);
println!("{:#?}", rect);

但是这样也会报错,因为没有开启struct的debug模式,我们需要显示的开启这个功能

#[derive(Debug)]
struct Rectangle
{
    width: u32,
    length: u32,
} 

其中derive是一种派生,这是实现一种trait的方式.

struct的方法

方法和函数类似:fn关键字、名称、参数、返回值相似

不同之处:

  • 方法是在struct(或者enumtrait对象)的上下文中定义
  • 第一个参数是self,表示方法被调用的struct实例

方法的定义

  • 使用impl关键字,定义在impl块中,
  • 方法的第一个参数可以是&self,也可以获得其所有权或可变借用,和其他参数一样.
#[derive(Debug)]
struct Rectangle {
    width: u32,
    length: u32,
}
​
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.length
    }
}
​
fn main() {
    let rect = Rectangle {
        width: 30,
        length: 30,
    };
    println!("{}", rect.area());
    println!("{:#?}", rect);
}

方法调用的运算符

rust没有->运算符

rust会自动解引用或者自动引用:在调用方法时就会发生这种行为

在调用方法时,rust根据情况自动添加& &mut *,以便object可以匹配方法的签名.

下面两行代码效果相同:

  • p1.distance(&p2);
  • (&p1).distance(&p2);
impl Rectangle{
    fn area(&self) -> u32
    {
        self.width * self.length
    }
​
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.length > other.length
    }
}

如上我们添加一个方法之后.试着调用函数

fn main()
{
    let rect1 = Rectangle{
        width: 30,
        length: 50,
    };
​
    let rect2 = Rectangle{
        width: 20,
        length: 10,
    };
​
    println!("{}", rect1.can_hold(&rect2));  // rect1 会自动 添加&rect1
}

关联函数

可以在impl块里定义不把self作为第一个参数的函数,他们叫关联函数例如 String::from()

关联函数通常用于构造器,用来生成关联类型的实例.

impl Rectangle{
    fn area(&self) ->u32
    {
        self.width * self.length
    }
​
    fn can_hold(&self, other: &Rectangle)->bool{
        self.width > other.width && self.length > other.length
    }
​
    fn square(size: u32) -> Rectangle{
        Rectangle {
            width: size,
            length: size,
        }
    }
}
​
fn main(){
    let s = Rectangle::square(20);
}

每个struct可以有多个impl