练习题来自:https://practice-zh.course.rs/compound-types/struct.html
1 对于结构体,我们必须为其中的每一个字段都指定具体的值
// fix the error
struct Person {
name: String,
age: u8,
hobby: String
}
fn main() {
let age = 30;
let p = Person {
name: String::from("sunface"),
age,
};
}
结构体的初始化,需要将每个字段都赋值才行。
struct Person {
name: String,
age: u8,
hobby: String
}
fn main() {
let age = 30;
let p = Person {
name: String::from("sunface"),
age,
hobby:String::from("sunface")
};
}
题外话,Rust的结构体显然和C或者C++的结构体不太一样,对于C的结构体来说,不存在批量初始化这个说法,结构体都是基本类型,自带初值,赋值需要逐个字段;C++的结构体其实就是public class,初始化方式多种多样,还有面向对象的一系列特征。
而Rust并不是一个面向对象语言(至少它自己不觉得是)
2 🌟 单元结构体没有任何字段。
struct Unit;
trait SomeTrait {
// ...定义一些行为
}
// 我们并不关心结构体中有什么数据( 字段 ),但我们关心它的行为。
// 因此这里我们使用没有任何字段的单元结构体,然后为它实现一些行为
impl SomeTrait for Unit { }
fn main() {
let u = Unit;
do_something_with_unit(u);
}
// 填空,让代码工作
fn do_something_with_unit(u: __) { }
trait
是特征,后续会学到这个语法,目前来说可以当它不存在。这里u
的类型是Unit
fn do_something_with_unit(u: Unit) { }
3 🌟🌟🌟 元组结构体看起来跟元组很像,但是它拥有一个结构体的名称,该名称可以赋予它一定的意义。由于它并不关心内部数据到底是什么名称,因此此时元组结构体就非常适合。
// 填空并修复错误
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let v = Point(__, __, __);
check_color(v);
}
fn check_color(p: Color) {
let (x, _, _) = p;
assert_eq!(x, 0);
assert_eq!(p.1, 127);
assert_eq!(__, 255);
}
复习一下元组的两种访问方式:
- 使用索引访问
- 指定别名后,使用别名访问
元组结构体也一样,只是类似于C
的typedef
和C++
的using
,给元组起了一个别名。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let v = Point(0, 127, 255);
check_color(v);
}
fn check_color(p: Point) {
let Point(x, y, z) = p;
assert_eq!(x, 0);
assert_eq!(p.1, 127);
assert_eq!(z, 255);
}
4 🌟 你可以在实例化一个结构体时将它整体标记为可变的,但是 Rust 不允许我们将结构体的某个字段专门指定为可变的.
// 填空并修复错误,不要增加或移除代码行
struct Person {
name: String,
age: u8,
}
fn main() {
let age = 18;
let p = Person {
name: String::from("sunface"),
age,
};
// how can you believe sunface is only 18?
p.age = 30;
// 填空
__ = String::from("sunfei");
}
需要让p可变
fn main() {
let age = 18;
let mut p = Person {
name: String::from("sunface"),
age,
};
// how can you believe sunface is only 18?
p.age = 30;
// 填空
p.name = String::from("sunfei");
}
Rust 不允许我们将结构体的某个字段专门指定为可变的
对应C++中的mutable
,它的作用是,即使类的成员函数已经声明了const
,使用mutable
修饰的成员依然可以被修改,也就是“将结构体的某个字段专门指定为可变的”
我们有如下的代码:
struct testMut
{
int changeNum() const
{
num++;
return num;
}
private:
int num = 0;
};
编译失败,原因:
testMut.cpp: In member function 'int testMut::changeNum() const':
testMut.cpp:9:9: error: increment of member 'testMut::num' in read-only object
9 | num++;
| ^~~
再给num加上mutable修饰:
mutable int num = 0;
就不会编译失败了。
很难说Rust的这个改进是否有意义,mutable的变量如果不看头文件的定义,再加上你用的IDE比较垃圾(点名eclipse C++),有时候真的意识不到变量被修改了。
5 🌟 使用结构体字段初始化缩略语法可以减少一些重复代码
// 填空
struct Person {
name: String,
age: u8,
}
fn main() {}
fn build_person(name: String, age: u8) -> Person {
Person {
age,
__
}
}
答案如下:
struct Person {
name: String,
age: u8,
}
fn main() {}
fn build_person(name: String, age: u8) -> Person {
Person {
age,
name
}
}
6 🌟 你可以使用结构体更新语法基于一个结构体实例来构造另一个
// 填空,让代码工作
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let u1 = User {
email: String::from("someone@example.com"),
username: String::from("sunface"),
active: true,
sign_in_count: 1,
};
let u2 = set_email(u1);
}
fn set_email(u: User) -> User {
User {
email: String::from("contact@im.dev"),
__
}
}
有点像C++的继承,说实话我第一次知道继承还是从python,Rust的这个更加简洁一点。
fn set_email(u: User) -> User {
User {
email: String::from("contact@im.dev"),
..u
}
}
7 🌟🌟 我们可以使用 #[derive(Debug)] 让结构体变成可打印的.
// 填空,让代码工作
#[__]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale), // 打印 debug 信息到标准错误输出 stderr,并将 `30 * scale` 的值赋给 `width`
height: 50,
};
dbg!(&rect1); // 打印 debug 信息到标准错误输出 stderr
println!(__, rect1); // 打印 debug 信息到标准输出 stdout
}
对于未继承Debug接口的struct,需要强制支持打印。
// 填空,让代码工作
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale), // 打印 debug 信息到标准错误输出 stderr,并将 `30 * scale` 的值赋给 `width`
height: 50,
};
dbg!(&rect1); // 打印 debug 信息到标准错误输出 stderr
println!("{:?}", rect1); // 打印 debug 信息到标准输出 stdout
}
题外话,C++的format
直到最近几代标准才加入,之前各个C++项目基本是在自己用第三方库打印,其实不如这种开箱即用的体验。至于cout
?我觉得那个语法挺搞笑的,唯一优点是不需要像printf
一样非得声明类型了。
8
// 修复错误
#[derive(Debug)]
struct File {
name: String,
data: String,
}
fn main() {
let f = File {
name: String::from("readme.md"),
data: "Rust By Practice".to_string()
};
let _name = f.name;
// 只能修改这一行
println!("{}, {}, {:?}",f.name, f.data, f);
}
f
的name
所有权已经转移走了,因此尝试打印name
或者f
本身都是不被允许的,但是f
的data
还是可用的。
#[derive(Debug)]
struct File {
name: String,
data: String,
}
fn main() {
let f = File {
name: String::from("readme.md"),
data: "Rust By Practice".to_string()
};
let _name = f.name;
// 只能修改这一行
println!("{}, {}",_name, f.data);
}