bool
布尔类型(bool)代表的是“是”和“否”的二值逻辑。它有两个值:true和false。
一般用在逻辑表达式中,可以执行“与”“或”“非”等运算。
char
字符类型由char表示。它可以描述任何一个符合unicode标准的字符值。在代码中,单个的字符字面量用单引号包围。
字符类型字面量也可以使用转义符:
因为char类型的设计目的是描述任意一个unicode字符,因此它占据的内存空间不是1个字节,而是4个字节。
对于ASCⅡ字符其实只需占用一个字节的空间,因此Rust提供了单字节字符字面量来表示ASCⅡ字符。我们可以使用一个字母b在字符或者字符串前面,代表这个字面量存储在u8类型数组中,这样占用空间比char型数组要小一些。示例如下:
整数类型
Rust有许多的数字类型,主要分为整数类型和浮点数类型。本节讲解整数类型。各种整数类型之间的主要区分特征是:有符号/无符号,占据空间大小。
需要特别关注的是isize和usize类型。
它们占据的空间是不定的,与指针占据的空间一致,与所在的平台相关。如果是32位系统上,则是32位大小;如果是64位系统上,则是64位大小。
在C++中与它们相对应的类似类型是int_ptr和uint_ptr。Rust的这一策略与C语言不同,C语言标准中对许多类型的大小并没有做强制规定,比如int、long、double等类型,在不同平台上都可能是不同的大小,这给许多程序员带来了不必要的麻烦。相反,在语言标准中规定好各个类型的大小,让编译器针对不同平台做适配,生成不同的代码,是更合理的选择。
数字类型的字面量表示可以有许多方式:
字面量后面可以跟后缀,可代表该数字的具体类型,从而省略掉显示类型标记:
我们可以为任何一个类型添加方法,整型也不例外。比如在标准库中,整数类型有一个方法是pow,它可以计算n次幂,于是我们可以这么使用:
我们甚至可以不使用变量,直接对整型字面量调用函数:
对于整数类型,如果Rust编译器通过上下文无法分析出该变量的具体类型,则自动默认为i32类型。比如:
整数溢出
在整数的算术运算中,有一个比较头疼的事情是“溢出”。
在C语言中,对于无符号类型,算术运算永远不会overflow,如果超过表示范围,则自动舍弃高位数据。
对于有符号类型,如果发生了overflow,标准规定这是undefined behavior,也就是说随便怎么处理都可以。
未定义行为有利于编译器做一些更激进的性能优化,但是这样的规定有可能导致在程序员不知情的某些极端场景下,产生诡异的bug。
Rust的设计思路更倾向于预防bug,而不是无条件地压榨效率,Rust设计者希望能尽量减少“未定义行为”。
比如彻底杜绝“Segment Fault”这种内存错误是Rust的一个重要设计目标。
当然还有其他许多种类的bug,即便是无法完全解决,我们也希望能尽量避免。
整数溢出就是这样的一种bug。
Rust在这个问题上选择的处理方式为:默认情况下,在debug模式下编译器会自动插入整数溢出检查,一旦发生溢出,则会引发panic。
在release模式下,不检查整数溢出,而是采用自动舍弃高位的方式。示例如下:
Rust编译器还提供了一个独立的编译开关供我们使用,通过这个开关,可以设置溢出时的处理策略:
$rustc -C overflow-checks=no test.rs
“-C overflow-checks=”可以写“yes”或者“no”,打开或者关闭溢出检查。
如果在某些场景下,用户确实需要更精细地自主控制整数溢出的行为,可以调用标准库中的checked_*、saturating_*和wrapping_*系列函数。
输出结果为:
checked_*系列函数返回的类型是Option<_>,当出现溢出的时候,返回值是None。
saturating_*系列函数返回类型是整数,如果溢出,则给出该类型可表示范围的“最大/最小”值。
wrapping_*系列函数则是直接抛弃已经溢出的最高位,将剩下的部分返回。
在对安全性要求非常高的情况下,强烈建议用户尽量使用这几个方法替代默认的算术运算符来做数学运算,这样表意更清晰。
在Rust标准库中就大量使用了这几个方法,而不是简单地使用算术运算符,值得大家参考。
在很多情况下,整数溢出应该被处理为截断,即丢弃最高位。
浮点类型
Rust提供了基于IEEE 754-2008标准的浮点类型。按占据空间大小区分,分别为f32和f64,其使用方法与整型差别不大。浮点数字面量表示方式有如下几种:
Infinite和Nan是带来更多麻烦的特殊状态。Infinite代表的是“无穷大”,Nan代表的是“不是数字”(not a number)。
指针类型
- 同一个类型,某些时候可以指定它在栈上,某些时候可以指定它在堆上。内存分配方式可以取决于使用方式,与类型本身无关。
- 既可以直接访问数据,也可以通过指针间接访问数据。可以针对任何一个对象取得指向它的指针。
- 既可以在复合数据类型中直接嵌入别的类型的实体,也可以使用指针,间接指向别的类型。
- 甚至可能在复合数据类型末尾嵌入不定长数据构造出不定长的复合数据类型。Rust里面也有指针类型,而且不止一种指针类型。
在标准库中还有一种封装起来的可以当作指针使用的类型,叫“智能指针”(smart pointer)。
类型转换
Rust对不同类型之间的转换控制得非常严格。即便是下面这样的程序,也会出现编译错误:
Rust提供了一个关键字as,专门用于这样的类型转换:
tuple
tuple指的是“元组”类型,它通过圆括号包含一组表达式构成。tuple内的元素没有名字。tuple是把几个类型组合到一起的最简单的方式。比如:
元组内部也可以一个元素都没有。这个类型单独有一个名字,叫unit(单元类型):let empty:()=();
可以说,unit类型是Rust中最简单的类型之一,也是占用空间最小的类型之一。
struct
结构体(struct)与元组类似,也可以把多个类型组合到一起,作为新的类型。区别在于,它的每个元素都有自己的名字。举个例子:
tuple struct
Rust有一种数据类型叫作tuple struct,它就像是tuple和struct的混合。区别在于,tuplestruct有名字,而它们的成员没有名字:
enum
如果说tuple、struct、tuple struct在Rust中代表的是多个类型的“与”关系,那么 enum类型在Rust中代表的就是多个类型的“或”关系。