走进Rust:结构体

Rust大约 4068 字

定义和实例化结构体

结构体和元组类似。像元组一样,结构体的各个部分可以是不同的类型。与元组不同,你将为每个数据命名,以便清楚地知道这些值的含义。由于使用了这些名称,因此结构体比元组更灵活:你不必依赖数据的顺序来指定或访问实例的值。

要定义一个结构,我们输入关键字struct并命名整个结构体。结构体的名称应描述将数据分组在一起的特点。然后,在大括号内,定义数据段的名称和类型,我们将其称为字段。例如,下列代码显示了一个存储有关用户帐户信息的结构体。

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

要在定义结构体后使用结构体,我们可以通过为每个字段指定具体值来创建该结构的实例。我们通过指定结构体的名称来创建实例,然后添加包含key:value对的大括号,其中key是字段的名称,而value是我们要存储在这些字段中的数据。我们不必按照在结构体中声明它们的顺序来指定字段。换句话说,结构体定义就像该类型的通用模板,实例用特定的数据填充该模板以创建该类型的值。例如,我们可以声明一个特定的用户,如下所示。

let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

要从结构体中获取特定值,我们可以使用点表示法。如果只需要该用户的电子邮件地址,则可以在任何要使用此值的地方使用user1.email。如果实例是可变的,则可以通过使用点表示法并将其分配给特定字段来更改值。下列代码显示了如何更改可变User实例的email字段中的值。

let mut user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

user1.email = String::from("anotheremail@example.com");

注意整个实例必须是可变的。Rust不允许我们仅将某些字段标记为可变字段。与任何表达式一样,我们可以构造该结构体的新实例作为函数主体中的最后一个表达式,以隐式返回该新实例。

下列代码显示了一个build_user函数,该函数返回具有给定电子邮件和用户名的User实例。active字段的值为truesign_in_count的值为1

fn build_user(email: String, username: String) -> User {
    User {
        email: email,
        username: username,
        active: true,
        sign_in_count: 1,
    }
}

用与struct字段相同的名称来命名函数参数是有意义的,但是必须重复emailusername字段的名称和变量有点乏味。如果该结构体具有更多字段,则重复每个名称将变得更加烦人。幸运的是,这里有一个便捷的简写!

当变量和字段具有相同名称时,使用简写字段初始化

因为上述代码的参数名称和struct字段名称完全相同,所以我们可以使用简写字段初始化语法来重写build_user,使其行为完全相同,但不会重复emailusername,例如,如下所示。

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

在这里,我们正在创建User结构体的新实例,该实例具有一个名为email的字段。我们想将email字段的值设置为build_user函数的email参数中的值。因为email字段和email参数具有相同的名称,所以我们只需要写email而不是email: email

使用结构体更新语法从其他实例创建实例

创建结构体的新实例通常会很有用,该结构使用大多数旧实例的值,但会更改其中的一些值。你将使用结构体更新语法执行此操作。

首先,下列代码显示了如何在没有更新语法的情况下在user2中创建新的User实例。我们为emailusername设置了新值,但使用之前创建的user1相同的值。

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    active: user1.active,
    sign_in_count: user1.sign_in_count,
};

使用结构体更新语法,我们可以用更少的代码来达到相同的效果,如下所示。语法..指定未明确设置的其余字段应与给定实例中的字段具有相同的值。

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

上述代码创建了一个实例user2,该实例的emailusername值不同,但user1activesign_in_count字段的值相同。

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

你还可以定义看起来类似于元组的结构体,称为元组结构体。元组结构体具有附加的含义,即结构体名称提供了含义,但没有与其字段关联的名称;相反,它们只有字段的类型。当你想给整个元组起一个名字并使元组成为与其他元组不同的类型时,元组结构体很有用,并且像常规结构体中那样命名每个字段都是冗长或多余的。

要定义元组结构体,以struct关键字和结构体名称开头,后跟元组中的类型。例如,以下是两个名为ColorPoint的元组结构体的定义和用法:

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

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

let black0 = black.0;

请注意,blackorigin值是不同的类型,因为它们是不同元组结构体的实例。你定义的每个结构都是其自己的类型,即使该结构中的字段具有相同的类型。例如,即使两个类型都由三个i32值组成,但是采用Color类型参数的函数也不能将Point作为参数。另一方面,元组struct实例的行为类似于元组:你可以将它们分解为各自的片段,可以使用.后加索引以访问单个值,等等。

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

你还可以定义没有任何字段的结构!这些被称为类似单元的结构体,因为它们的行为类似于()的单位类型。在需要在某种类型上实现特性但又不想在类型本身中存储任何数据的情况下,类似单元的结构体很有用。我们将在第10章中讨论特性。

结构体数据的所有权

在上述代码中的User结构体定义中,我们使用了String类型而不是&str字符串切片类型。这是一个故意的选择,因为我们希望此结构体的实例拥有其所有数据,并且只要整个结构体有效,该数据就一直有效。结构体可以存储对其他人拥有的数据的引用,但这样做需要使用生命周期,生命周期是Rust的一个功能,我们将在第10章中讨论。生命周期可确保结构体引用的数据在结构体上一直有效。假设你尝试将引用存储在结构体中,但未指定生存期,例如这样,将无法正常工作:

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

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

编译器会说它需要生命周期说明符:

error[E0106]: missing lifetime specifier
 -->
  |
2 |     username: &str,
  |               ^ expected lifetime parameter

error[E0106]: missing lifetime specifier
 -->
  |
3 |     email: &str,
  |            ^ expected lifetime parameter

在第10章中,我们将讨论如何解决这些错误,以便你可以将引用存储在结构体中,但是现在,我们将使用诸如String之类的拥有类型而不是&str这样的引用来解决此类错误。

阅读 77 · 发布于 2020-07-08

————        END        ————

扫描下方二维码关注公众号和小程序↓↓↓

昵称:
随便看看换一批