结构体类似于其他面向对象语言的类,它包含了一些表达某类特性的属性组合,内容是一组属性名和属性值的集合。结构体还包含了对应相关联的函数方法和行为。它和元祖的区别是:
- 元祖不包含属性名称,结构体包含属性名称。
- 元祖使用小括号,结构体使用大括号。
- 结构体的定义和实例化
使用关键字struct加上名称来制定类型为结构体,名称一般使用大写字母开头的英文,名称最好是有含义的英文单词或组合,其后使用大括号包含属性名称,属性名称后面跟着冒号,冒号后面指定属性名称的类型,每个属性组合之间使用逗号进行分割。具体的示例如下所示:
struct User { name: String, email: String, active: bool, sign_in_count: u64, }为了使用上面定义的结构体,还必须将其实例化,即给每一个定义的属性进行赋值。赋值的语法类似定义,只不过将属性类型替换为值,形成key: value的键值对。属性的赋值顺序不一定要严格按照结构体内定义的顺序。
fn main() { let user = User { active: true, name: String::from("caocao"), email: String::from("caocao@example.com"), sign_in_count: 1, }; println!("{user:?}"); }如果需要获取结构体中的某个属性的值,使用小数点符号(“.”)来进行声明,例如需要访问用户的电子邮件可以使用user.email的方式。如果需要修改属性的值,实现化结构体的时候需要将其声明为可变变量,使用符号“mut”作为修饰符。如下面的示例所示:
fn main() { let mut user = User { active: true, name: String::from("caocao"), email: String::from("caocao@example.com"), sign_in_count: 1, }; println!("{0}",user.email); user.email = String::from("liubei@example.com"); println!("{0}",user.email) }注意:整个结构体实例必须都是声明为可变的,Rust不允许只标注某一个属性是可变的。
和其他表达式一样,我们也可以将实例一个结构体作为一个表达式在某个函数的结尾作为返回值。如下所示:
fn build_user(email: String, name: String)->User { User { active: true, email: email, name: name, sign_in_count: 1, } }从上面的示例可以看到email: email,name: name,似乎显得有点冗余,Rust提供了一种简写形式,如果参数名和属性名一致,可以只写一个。如下所示:
fn build_user(email: String, name: String)->User { User { active: true, email, name, sign_in_count: 1, } }是不是简约了许多。
- 两个结构体实例移植的更新语法
我们经常会遇到从一个结构体复制一些值到一个新的结构体。这时就可以使用结构体的更新语法。下面的示例是创建user2,从user实例内复制一个值,没有使用更新语法的示例:
let user2 = User { active: user.active, name: user.name, email: String::from("zhouyu@example.com"), sign_in_count: user.sign_in_count, };我们可以使用符号“..”来获取源机构体的其余属性值。下面是使用更新语法的示例:
let user2 = User { email: String::from("zhouyu@example.com"), ..user };..user表明该结构体其他属性值来自user。它必须在结构体的最后。
- 使用元祖结构体
Rust也提供了一种“元祖结构体”,类似与元祖类型,但是它使用了struct关键字进行了声明。元祖机构图没有使用属性名,而是使用类型来定义成员,但是它指定了整个结构的名称,用于表明此元祖非彼元祖。可能是如果给每个属性指定名称显得冗长而且多余,适用于人们清楚的知道每个成员属性的名称,例如:颜色值,或者坐标值。
#[derive(Debug)] struct Color(i32,i32,i32); #[derive(Debug)] struct Point(i32,i32,i32); fn main() { let black = Color(0,0,0); let origin = Point(0,0,0); println!("{black:?}"); println!("{origin:?}"); }注意:black和origin是不同的类型,因为它们使用了不同的元祖结构进行了实例化。每种结构都定义了它们自己的类型,虽然它们的值是一样的,且它们的结构体内容也是一致的。你也可以使用元祖的解构方式对元祖机构进行解构;例如:let Point(x, y, z) = origin;。如果要访问器成员也可以使用“.”加上其索引值;例如black.0。
- 单元类型的结构体
你也可以定义不包含任何属性的结构体,我们称之为单元类结构体。这就像类型“()”,单元类结构体适用于只需要函数和方法的定义,这就像其他语言中的接口。
struct AlwaysEqual; fn main() { let subject = AlwaysEqual; }我们使用struct定义了结构体AlwaysEqual,后面直接使用分号结尾,没有花括号,也没有属性。如果要实例化,直接将其复制给变量就可以了,也不用对其的属性进行赋值。
- 结构体中数据的所有权
在之前的User结构体中,我们使用了String类型,而不是&str的字符串切片类型,这是故意而为之,因为我们需要实例化这个User之后,拥有它所有数据的所有权,这样整个结构体在整个生命周期中的数据都是有效的。
当然,有时也是需要保存数据的引用值,这时就需要使用生命周期的概念了。它可以确保其和结构体一样的生命周期。看看下面使用引用而没有规定其声明周期的示例,它在编译期间就会报错:
struct User { active: bool, name: &str, email: &str, sign_in_count: u64, } fn main() { let user1 = User { active: true, name: "caocao", email: "caocao@example.com", sign_in_count: 1, }; }系统报错信息:
在报错的提示信息中已经提示了如何修改。