因为最近在接触rust语言,所以随便记录下学习过程中遇到的一些有意思的点。实际上结构体在很多语言中都存在。rust的结构体和c/c++语言的结构体其实也是非常类似的。假如我们要定义一个存储帐号信息的结构体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
可以看到其实和c/c++语言并没有什么区别。不仅如此,rust的结构体初始化(实例化)也是和c/c++类似的。例如:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
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");
这里和c/c++的结构体的做法是一样的。在c/c++的结构体中同样是通过.
来访问数据的。有些人可能会提到->
,这里我也想简单讨论下者两个符号。其实.
和->
在功能上是相同的,都是用来访问成员变量(或成员函数)的。.
用于普通变量的操作而->
则用于指针变量的操作。即->
在一个对象指针上调用方法,这时需要先解引用指针。也就是说,如果object
是一个指针,那么object->something()
和(*object).something()
一样。这里要提的一点是,.
和->
也可以用在class
里。事实上,c++的class
和struct
并没有本质的区别,它和struct
的区别主要就在访问控制上。
为什么rust没有->
呢?实际上rust有一个自动引用和解引用功能。在rust的方法调用里就会用到。
对于rust
的结构体,还有一点需要注意的是数据的所有权。我们为什么选择自身拥有所有权的String
类型而不是&str
字符串slice
类型。这里很重要的一点就是所有权。如果我们想要这个结构体拥有它所有的数据,那么要使整个结构体有效,其数据也得是有效的才行。
不过,结构体也可以存储被其他对象拥有的数据的引用,不过这样做就必须引入生命周期了,原因也很简单,我们需要用生命周期来保证结构体引用的数据的有效性和结构体本身保持一致。否则,有可能出现结构体里实际存储了无效的引用的情况。因此,如果不指定声明周期,在结构体里存储引用是无效的。例如下面这段代码:
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,
};
}
编译器会报错,并提示需要生命周期标识符。
我们在编程中经常会通过打印数据来调试代码,然而,在c/c++中,我们是不能直接打印结构体数据的,然而rust提供了一个很实用的功能,能够直接通过println!
宏来打印信息。我们只需要在结构体定义之前加上#[derive(Debug)]
注解即可。这里Debug
是一个trait,暂且按下不表。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {:?}", rect1);
}
运行得到的结果为
rect1 is Rectangle { width: 30, height: 50 }
如果我们把println!
内的{:?}
替换成{:#}
,输出结果就会变为
rect1 is Rectangle {
width: 30,
height: 50
}
虽然只是个小功能,不得不说还是非常方便的。
方法 与函数类似:它们使用 fn 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义,并且它们第一个参数总是 self,它代表调用该方法的结构体实例。
对于方法,想必大家都很熟悉,事实上我之前也提过struct
和class
其实并没有本质的区别。而rust里的struct,从某种程度上来说就是c++里的class
。我们为struct
实现方法实际上和c++为class
实现方法基本是一致的
#[derive(Debug)]
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 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
可以看到我们为了使area
函数在Rectangle上下文中,我们开始了一个impl
块,并使area
函数位于impl
块中,并且area
函数的第一个参数是&self
。如此我们就可以通过.
号来调用area
方法了。
之前我们提到在c++里调用方法有.
和->
两种符号。当object
是一个对象指针时,我们需要用->
来解引用。但是rust里却没有->
,因为rust有自动引用和解引用功能。方法调用就是rust中少数拥有这种行为的地方。
当使用 object.something()
调用方法时,Rust 会自动为 object 添加&
、&mut
或 *
以便使 object
与方法签名匹配。即
p1.distance(&p2);
(&p1).distance(&p2);
是等价的。
这个比较特殊,它是在impl
块中,不以self
函数作为参数的函数,但是它与结构体相关联,所以被称为关联函数。,注意,关联函数虽然在impl
块中,但它是函数而不是方法,因为它并不作用于一个结构体实例。
通常使用结构体名字和::
来调用关联函数(这里和c++的命名空间有异曲同工之妙),典型的例子就是String::from
这个函数了。
最后引用一段rust程序设计方法的总结
结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。
零零散散的写了一些不知所云的东西,rust作为一个新语言,其实很多设计都借鉴了一些已有的语言,但是也解决了一些痛点。我有时候经常遇到一些说法"这个在c/c++/java…里也有,只要xxxx",实际上真的有人去xxxx吗?当然我作为一个比较浅薄的人来说,语言对我来讲就是一个工具,如果语言能够大大降低我写代码的心智负担,那就最好不过了。如果说现有的工具能通过某某方法就能实现某某功能,只是比较麻烦,这并不意味着新的在老工具基础上做了简单改进的新工具不该出现。毕竟,编程这件事本身也是为了偷懒而已啊!