一、结构体的定义
Rust 中的结构体与元组(Tuple)都可以将若干个类型不一定相同的数据捆绑在一起形成整体,但结构体的每个成员和其本身都有一个名字,这样设计有利于访问其成员。元组常用于非定义的多值传递,而结构体用于规范常用的数据结构,且结构体内的成员被称为字段
结构体定义的语法:
struct User{ username:String, sex:String, age:u32, height:u32 }
乍一看跟C/C++很相似,但是Rust里的结构体每定义一个字段都要加上逗号且最后一个字段不需要加任何符号。
每个字段需要指定具体的数据类型。
同时值得注意的是:结构体不允许在定义的时候实例化。 我们知道C/C++语言是可以在最后的大括号后面实例化的,但是Rust没有这一特点且大括号后面不能加分号。
二、结构体实例化
Rust 很多地方受 JavaScript 影响,在实例化结构体的时候用 JSON 对象的 key: value 语法来实现定义:
为每个字段指定具体值
无需按照声明的顺序指定
例如:
let demo=User{ username: String::from("微凉秋意"), sex: String::from("男"), age:22, height:183 };
记住一般格式:
结构体类名 {
字段名 : 字段值,
…
}
如果正在实例化的结构体有字段名称和现存变量名称一样的,可以简化书写:
let username=String::from("微凉秋意"); let sex=String::from("男"); let demo=User{ username, // 等同于 username : username, sex, age:22, height:183 };
有这样一种情况:你想要新建一个结构体的实例,其中大部分属性需要被设置成与现存的一个结构体属性一样,仅需更改其中的一两个字段的值,可以使用结构体更新语法:
let demo1=User{ username:String::from("叶落秋白"), sex:demo.sex.clone(), ..demo };
注意:
..demo 后面不可以有逗号。这种语法不允许一成不变的复制另一个结构体实例,意思就是说至少重新设定一个字段的值才能引用其他实例的值。
sex:demo.sex.clone()这里如果不单独写出来编译器会报错:borrow of moved value
意思就是租借了没有所有权的变量,由于无法租借所有权,报错也很正常。而导致错误的原因就是demo.sex的所有权已经在赋值的时候转移给了demo1.sex,这点要尤其注意。因此写成demo.sex.clone()就可以解决所有权转移的问题了。
如果想要修改实例的成员属性,需要在定义的时候把结构体改为可变,也就是加上mut关键字
一旦struct实例是可变的,那么实例中所有的字段都是可变的
无法单独为某一个字段设置为可变
三、元组结构体
元组结构体是一种形式是元组的结构体,与元组的区别是它有名字和固定的类型格式。
它存在的意义是为了处理那些需要定义类型(经常使用)又不想太复杂的简单数据:
//定义: struct Color(u8,u8,u8); struct Point(u8,u8,u8); //实例: let black=Color(0,0,0); let origin=Point(0,0,0);
上面定义的black和origin尽管字段类型一致,但是二者是不同的类型,属于两个元组结构体实例。
访问元组结构体与访问元组方法一致,通过点标记法即可
补充一个特殊的单元结构体
例如:
struct UnitStruct;
我们称这种没有身体的结构体为单元结构体(Unit Struct)
可以定义没有任何字段的struct
适用于需要在某个类型上实现某个trait(接口,没有具体实现)
四、结构体所有权
结构体必须掌握字段值所有权,因为结构体失效的时候会释放所有字段。
这也是上面字符串类型使用String而不是用&str(字符串切片)的原因
不过结构体是可以定义引用类型字段的,这需要结合后面的”生命周期“实现
五、结构体方法
Rust中 的方法和函数类似,只不过它是用来操作结构体实例的。在C++语言中,我们知道类内的this指针指向的是对象自身,而Rust 语言不是面向对象的,从它所有权机制的创新可以看出这一点。但是面向对象的珍贵思想可以在 Rust 实现。
结构体方法的第一个参数必须是 &self,不需声明类型,因为 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:15, length:20 }; println!("rect.area={}",rect.area()); println!("rect = {:#?}",rect); }
这里的#[derive(Debug)]是Rust提供的调试库
在程序前面加上之后在println和print宏中就可以用 {:?}或{:#?} 占位符输出一整个结构体
不带#的占位符是输出一行,而带的占位符输出和实例化一样形式的结构体
为结构体添加方法需要在impl里进行,格式就是impl+结构体类型{}
在调用结构体方法的时候不需要填写 self ,这是出于对使用方便性的考虑。
一个多参数的例子:
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn wider(&self, rect: &Rectangle) -> bool { self.width > rect.width } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle { width: 40, height: 20 }; println!("{}", rect1.wider(&rect2)); } //运行结果为false,证明rect1没有rect2宽度大
六、结构体关联函数
之所以"结构体方法"不叫"结构体函数"是因为"函数"这个名字留给了这种函数:它在 impl 块中没有 &self 参数。
这种函数不依赖实例,但是使用它需要声明是在哪个 impl 块中的。
一直使用的 String::from 函数就是一个"关联函数"。
接下来看一个实例:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn create(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } } fn main() { let rect = Rectangle::create(30, 50); println!("{:?}", rect); }
可以看到在Rectangle块中定义了一个create函数,返回值是一个Rectangle类型,而内部实现就是实例化结构体的一个过程。那么这时候在主函数调用该关联函数就可以创建不同的Rectangle结构体实例了。