The Rust programming language 读书笔记——结构体(Struct)

结构(Struct)是一种自定义数据类型。允许我们命名多个相关的值并将它们组成一个有机的结合体。

定义与实例化

关键字 struct 被用来定义并命名结构体,一个良好的结构体名称需反映出自身数据组合的意义。

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

结构体就像是类型的通用模板,将具体的数据填入模板时就创建了新的实例

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

在创建了结构体实例后,可以通过点号来访问实例中的特定字段。假如这个实例是可变的,还可以通过点号来修改字段的值。

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");

需要注意的是,一旦结构体实例定义为可变,那么实例中的所有字段都将是可变的

可以在函数体的最后一个表达式中构建结构体实例,来隐式的将这个实例作为结果返回。

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

在变量名与字段名相同时,可以使用简化版的字段初始化方法重构上面的 build_user 函数。

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

在许多情况下,新创建的实例中,除了需要修改的小部分字段以外,其余字段的值与旧实例完全相同。可以使用结构体更新语法快速实现此类新实例的创建。

使用结构体更新语法来为一个 User 实例设置新的 email 和 username 字段的值,并从 user1 实例中获取剩余字段的值:

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

.. 表示剩下的那些还未被显式赋值的字段都与给定实例拥有相同的值。

元组结构体

可以使用一种类似元组的方式定义结构体,这种结构体也被称作元组结构体。元组结构体同样拥有表明自身含义的名称,但无需在声明时对其字段进行命名,只标注类型即可。

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

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

这里的 black 和 origin 是不同的类型,因为它们两个分别是不同元组结构体的实例。
每一个结构体都拥有自己的类型

示例程序

使用 cargo 命令创建一个名为 rectangles 的项目:
cargo new rectangles
这个程序会接收以像素为单位的宽度和高度作为输入,并计算出对应的长方形面积。

编辑项目中的 src/main.rs 源代码文件:

fn main() {
    let width1 = 30;
    let height1 = 50;

    print!("The area of the rectangle is {}", area(width1, height1));
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

运行 cargo run 命令查看输出:

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 2.53s
     Running `target/debug/rectangle`
The area of the rectangle is 1500

area 函数用来计算长方形的面积,接收宽和高两个参数。这两个参数是相互关联的,但程序中没有任何地方可以体现这一点。将宽和高放在一起能够使代码更加易懂和易于维护。

使用元组关联长方形的宽和高

fn main() {
    let rect1 = (30, 50);
    print!("The area of the rectangle is {}", area(rect1));
}

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

在上面的代码中,元组使输入的参数结构化了,现在只需要传递一个参数就可以调用函数 area
但元组不会给出自身元素的名称,只能通过索引访问。这使得程序变得难以阅读。
比如当需要将该长方形绘制到屏幕上时,混淆宽度和高度就容易出现问题。

使用结构体增加有意义的描述信息

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

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    print!("The area of the rectangle is {}", area(&rect1));
}

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

Rectangle 结构体表明了宽度和高度是相互关联的两个值,并为这些值提供了描述性的名字。因此代码看起来会更加清晰。

方法

方法与函数十分相似,它们都使用 fn 关键字及一个名称进行声明;它们都可以拥有参数和返回值;它们都包含了一段在调用时执行的代码。
方法总是被定义在某个结构体(或者枚举类型、trait 对象)的上下文中,且它们的第一个参数都是 self,用于指代调用该方法的结构体实例

area 函数定义为 Rectangle 结构体中的方法:

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

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

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    print!("The area of the rectangle is {}", rect1.area());
}

由于方法的声明被放置在 impl Rectangle 块中,因此 Rust 能够将 self 的类型推导为 Rectangle,我们才可以在 area 的签名中使用 &self 来替代 &Rectangle
使用方法替代函数不仅能够避免在每个方法的签名中重复编写 self 的类型,还有助于程序员组织代码的结构。可以将某个类型的实例需要的功能放置在同一个 impl 块中,避免用户在代码库中盲目地搜索它们。

添加 can_hold 方法检测当前的 Rectangle 实例能否完整地包含传入的另一个 Rectangle 实例:

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,
    };

    print!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    print!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
关联函数

除了方法,impl 块还允许我们定义不用接收 self 作为参数的函数。这类函数与结构体(而不是实例)相互关联,因此也被称为关联函数。
它们不会作用于某个具体的结构体实例。
之前用到的 String::from 就是关联函数的一种。

关联函数常被用作构造器来返回一个结构体的新实例。例如可以编写一个 square 关联函数,只接收一个参数,该参数同时用作宽度与高度来构造正方形实例。

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

这样就可以使用 let sq = Rectangle::square(3); 类似的语法来创建正方形实例。

总结

结构体可以让我们基于特定领域的规则创建有意义的自定义类型
通过使用结构体,可以将相互关联的数据组合起来,并为每条数据赋予有含义的名称,从而使代码更加清晰。
方法可以让我们为结构体实例指定特殊的行为,而关联函数则可以将那些不需要实例的特定功能放置到结构体的命名空间中。

参考资料

The Rust Programming Language

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容