Rust的数据类型

【图书介绍】《Rust编程与项目实战》-CSDN博客

《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Rust到底值不值得学,之一 -CSDN博客

Rust到底值不值得学,之二-CSDN博客

3.5 数据类型的定义和分类

在Rust编程中,所谓数据类型,就是对数据存储的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的数据类型分配不同的长度和存储形式。

在编程时,我们将变量存储在计算机的内存中,但是计算机要知道我们要用这些变量存储什么样的值,因为一个简单的数值,一个字符或一个巨大的数值在内存中所占用的空间是不一样的。

在Rust中,每个值都属于某一个数据类型,用来告诉Rust它被指定为何种数据,以便明确数据处理方式。Rust基本数据类型主要有两类子集:标量(Scalar)类型和复合(Compound)类型。标量类型是单个值类型的统称。Rust中内建了4种标量类型:整数、浮点数、布尔值及字符类型。复合类型包括数组、元组、结构体和枚举等。

这里所讲的基本数据类型都是Rust原生的数据类型,它们都是创建在栈上的数据结构。Rust 标准库还提供了一些更复杂的数据类型,它们有些是创建在堆上的数据结构,这里先不讲。

Rust是静态类型语言,因此在编译时就必须知道所有变量的类型。通常,根据值及其使用方式,Rust 编译器可以推断出我们想要用的类型,当多种类型均有可能时,必须增加类型注解,否则编译会报错。

3.6 标量数据类型

3.6.1 整型

整型也叫整数类型,是专门用来定义整数变量的数据类型。按照整型变量占用的内存空间大小来讲,整型可以划分为 1字节整型、2字节整型、4字节整型、8字节整型、16字节整型。

按最高位是否当作符号位来讲,Rust 中的整型又分为有符号整型(Unsigned)和无符号整型(Signed)。有符号整型的左边最高位为0表示正数,为1则表示负数。有符号整型可以用来定义存储负数的变量,当然也可以定义存储非负数的变量。而无符号整型定义的变量只存储非负整数。

整型数据在内存中的存储方式:用整数的补码形式存放,原因在于,使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。一个整数X的补码计算方式可以用以下公式得到:

  • 当X≥0时,X的补码=X的二进制形式。
  • 当X < 0时,X的补码=(X + 2n)的二进制形式,n是补码的位数。

可以看出,一个非负整数的补码就是该数本身的二进制形式,负整数稍微复杂一些。

比如0,如果用1字节的存储单元存储一个整数,则其补码就是00000000,在内存中存储的形式就是00000000;又比如8,其二进制形式是1000,如果用1字节的存储单元存储一个整数,则在内存中的数据就是00001000。对于−8,假设用1字节的存储单元存储一个整数,根据公式,−8的补码就是(−8+28=−8+256=248)的二进制形式,即11111000,也就是它在内存中存放的形式。

  1. 有符号的8位整型i8

有符号的8位整型的类型名是i8,为了方便,经常会省略有符号3个字,直接称8位整型,或超短整型。编译器分配1字节长度的存储单元给i8定义的变量,最高位是符号位,0表示正数,1表示负数。比如定义一个i8类型的变量n:

复制代码
let n: i8 = -6;

这个变量n在内存中占据1字节的存储单元,其在内存中的补码位(−6+256=250)的二进制形式为11111010。如果不信,当场验证一下,代码如下:

复制代码
fn main() {

    let n:i8=-6;                   //定义一个i8类型的变量n

    println!("{:b}",n);           //:b的意思是以二进制形式输出变量n的值

}

结果输出:11111010。

下面我们来看i8类型所能定义的数据的最小值和最大值,这个问题很多书都没讲清楚。i8类型整数的补码有8位,表示的范围为0000 0000~1111 1111,我们通过补码计算公式可以得到表3-2。

从表3-2中不难看出,最大值只能表示到127,因为128的补码最高位(符号位)是1,不能用来表示一个正数。而最小值是−128,因为−129的补码最高位(符号位)是0,不能用来表示一个负数。所以,我们得出i8类型所表示的最小值是−128,最大值是127。这里使用列表法得到最小值和最大值,似乎有点笨笨的。笔者再介绍一个更简便的方法得到最小值和最大值。

根据补码计算公式,非负数的二进制形式和补码相同,我们可以这样考虑来得到最大值,i8类型定义的变量所占用的存储空间长度是1字节,且最高位是符号位,则能存储的最大数据是01111111,左边第一位是0表示正数,后面7位表示数值,7位全为1时最大,因此最大数据就是27−1=127。对于最小值,则根据负数的补码计算公式,我们知道,正整数的补码是其本身的二进制数,负整数的补码就是(X + 2n)的二进制数,现在,0~127的二进制数已经用作正整数的补码了,那么一个负整数X的补码只能从128的二进制数开始找,也就是有这样的关系:X+256≥128,即X≥−128,从而得出最小值Xmin=−128。这个方法方便多了。如果不信,当场验证一下,代码 如下:

复制代码
fn main() {

    assert_eq!(i8::MIN, -128); 

    assert_eq!(i8::MAX, 127);  

    println!("{},{}",i8::MIN,i8::MAX);

}

结果输出就是−128,127。其中,Rust提供了i8::MIN来表示i8变量的最小值(−128),用i8::MAX表示i8变量的最大值(127)。而assert_eq!表示传入的两个参数如果不相等,则抛出异常,也就是会在输出窗口打印一行语句:thread 'main' panicked at 'assertion failed: `(left == right)`。

  1. 无符号8位整型u8

无符号8位整型的类型名是u8,又称无符号超短整型,它占据1字节长度的存储单元,最高位不是符号位。这里的u表示unsigned,u8定义的变量取值是非负整数,存储形式依旧是补码,根据补码计算公式,非负整数的补码和变量本身的二进制形式相同,比如变量值是255,那么其在内存中的存储形式就是11111111,它就是补码,也是255的二进制形式。

定义一个u8变量示例如下:

复制代码
let n: u8 = 100;

u8的最高位不是符号位,是有效的数值位,因此u8定义的变量,其最小值是0,最大值是11111111,即28−1=255,我们可以用下列代码来验证:

复制代码
fn main() {

    println!("{},{}",u8::MIN,u8::MAX);

}

输出结果:0,255。

  1. 有符号16位整型i16

有符号16位整型的类型名是i16,占据2字节长度的存储单元,最高位是符号位。i16有时又称为短整型。定义一个i16变量示例如下:

复制代码
let n: i16 = -100;

i16的最高位是符号位,若为0则表示正数,若为1则表示负数。i16定义的变量,其最大值是0111111111111111,即215−1=32767,最小值这样计算:X+65536≥32768,即X≥−32768,从而得出最小值Xmin=−32768,对应补码为(−32768+65536)的二进制形式,即32768的二进制数 1 000 000 000 000 000。得到最小值和最大值的原理这里不再赘述,因为已经在讲i8的时候详述过了。我们可以用下列代码来验证:

复制代码
fn main() {

    println!("{},{}",i16::MIN,i16::MAX);

    println!("{:b},{:b}",i16::MIN,i16::MAX);

}

运行结果如下:

复制代码
-32768,32767

1000000000000000,111111111111111
  1. 无符号16位整型u16

无符号16位整型的类型名是u16,占据2字节长度的存储单元,最高位不是符号位。u16有时又称无符号短整型。定义一个u16变量示例如下:

复制代码
let n: u16 = 100;

u16定义的变量取值范围是[0, 65535]。

  1. 32位、64位、128位整型

一理通百理融。了解了8位、16位整型后,32位、64位、128位整型与之类似。i32是默认的整型,如果直接说出一个数字而不说它的数据类型,那么它默认就是i32。i64通常称为长整型,i128称为超长整型。

我们可以用一张表来归纳这些整型,Rust内建的整数类型如表3-3所示。

整型的长度还可以是arch。arch是由CPU构架决定大小的整型类型。大小为arch的整数在x86机器上为32位,在x64机器上为64位。arch整型通常用于表示容器的大小或者数组的大小,或者数据在内存上存储的位置。

有符号整型所表示的范围如表3-4所示。

无符号整型所表示的范围如表3-5所示。

3.6.2 布尔型

Rust使用关键字bool表示布尔数据类型,布尔型变量共有两个值:true和false。比如定义一个bool变量:

复制代码
let checked:bool = true;

布尔变量占用1字节,使用bool类型的场景主要是条件判断。下列代码将输出bool变量的值:

复制代码
fn main() {

    let checked:bool = true;

    println!("{}", checked);//输出true

}

输出结果:true。

3.6.3 字符类型

字符类型是Rust的一种基本数据类型,使用关键字char来表示字符类型。字符类型变量用于存放单个Unicode字符,这意味着ASCII字母、重音字母、中文、日文、韩文、表情符号和零宽度空格都是Rust中的有效字符值。Unicode 标量值的范围为U+0000~U+D7FF和U+E000~U+10FFFF(含)。然而,"字符"在Unicode中并不是一个真正的概念。在存储char类型数据时,会将其转换为UTF-8编码的数据(即Unicode代码点)进行存储。

char定义变量占用4字节空间(32bit),且不依赖于机器架构。我们可以用代码验证一下:

复制代码
fn main() {

    println!("{}", std::mem::size_of::<char>());

}

结果输出:4。看来的确占用了4字节。std是Rust的标准库,mem是std中的一个模块,size_of是模块mem中的函数,它返回某种数据类型占用的字节数。关于库、模块和函数的概念后面详述,现在只要知道这样调用是可以得到某个类型所占用的字节数的。

char类型变量的值是单引号包围的任意单个字符,例如'a'、'我'。注意:char和单字符的字符串String是不同的类型。比如下列代码定义字符类型变量并输出:

复制代码
fn main() {

    let a = 'z';

    let b = '\n';                          //赋值转义字符'\n'

    let c = '我';

    print!("{},{},{}",a,b,c);              //输出

}  

输出结果:

复制代码
z,

,我

b的值是'\n',因此会出现换行。

另外,可使用关键字as将char转为各种整数类型,目标类型小于4字节时,将从高位截断,这种转换叫作显式转换。注意:Rust不会自动将char类型转换为其他类型,必须使用as进行显式转换。比如:

复制代码
fn main() {

   // char -> Integer

println!("{}", '我' as i32);              // 25105=0x6211

println!("{}", '是' as u16);              // 26159=0x662f

println!("{}", '是' as u8);                // 47=0x2f,被截断了,因此66就没输出

}

结果输出:

复制代码
25105

26159

47

我们以十进制形式输出了3个字符的Unicode值,第三行中的'是'转为u8类型,只能把0x2f存于u8中,66就被截断了。如果想在线查询某个字符的Unicode编码值,可以到网站https://www.unicodery.com上查询

关于整型转为char类型,将用到标注库的char模块,我们到讲标准库的时候再讲。

3.6.4 浮点型

浮点型变量用来表示具有小数点的实数。为何在Rust中把实数称为浮点数呢?这是因为在Rust中,实数是以指数形式存放在存储单元中的。一个实数表示为指数可以有多种形式,比如5.1234可以表示为5.1234×100、51.234×10-1、0.51234×101、0.051234×102、51234×10-5、512340×10-6等。可以看到,小数点的位置可以在5、1、2、3、4这几个数字之间、之前或之后(需添加0)浮动,只要在小数点位置浮动的同时改变指数的值,就可以保证表示的是同一个实数。因为小数点位置可以浮动,因此以指数形式表示的实数称为浮点数。Rust 编程语言按照 IEEE 754 二进制浮点数表示与算术标准存储浮点数。IEEE 754这里就不展开了,如果以后大家从事这方面的底层开发,可以深入研读这个标准。这里只是让大家心里有个数。

与大多数编程语言一样,Rust也拥有两种不同精度的浮点类型,分为单精度浮点类型f32和双精度浮点类型f64。f32的数据使用32位来表示,f64的数据使用64位来表示。Rust中的默认浮点类型是f64,因为现在的CPU几乎为64位的,因此在处理f64和f32类型的数据时所耗的时间基本相同,但f64可表示的精度更高。值得注意的是,所有的浮点类型都是有符号的。下列代码输出3个浮点类型变量:

复制代码
fn main() {

    let x = 2.01;                 // 默认f64

    let y: f32 = 3.14;            // f32

    let z:f64=6.28;                //f64

    println!("{},{},{}",x,y,z);

}

结果输出:2.01,3.14,6.28。

值得注意的是,Rust中不能将0.0赋值给任意一个整型,也不能将0赋值给任意一个浮点型,但可以将0.0赋值给浮点类型变量。

当数字很大的时候,Rust可以用下画线(_)来分段数字,这样可以使数字的可读性变得更好。比如:

复制代码
fn main() {

    let a=1_000_000;

    let b:i64 =1_000_00088000;

    let x:f64=1_000_000.666_123;

    println!("{},{},{}",a,b,x);

}

结果输出:1000000,100000088000,1000000.666123。

3.6.5 得到变量的字节数

我们可以用std::mem::size_of::<类型>得到类型所占的字节数,比如:

复制代码
println!("{}", std::mem::size_of::<char>());

输出结果是4。

除此之外,还可以通过std::mem::size_of_val获取变量所占用的字节数。比如:

复制代码
fn main() {

    //明确指定类型

    let a:i64=100;



    // 通过变量类型后缀指定变量的类型

    let x = 1u8;

    let y = 2u32;

    let z = 3f32;



    // 没有变量类型后缀,通过怎么使用变量来进行推断

    let i = 1;

    let f = 1.0;



    println!("size of `a` in bytes: {}", std::mem::size_of_val(&a));

    println!("size of `x` in bytes: {}", std::mem::size_of_val(&x));

    println!("size of `y` in bytes: {}", std::mem::size_of_val(&y));

    println!("size of `z` in bytes: {}", std::mem::size_of_val(&z));

    println!("size of `i` in bytes: {}", std::mem::size_of_val(&i));

    println!("size of `f` in bytes: {}", std::mem::size_of_val(&f));

}
相关推荐
双叶83627 分钟前
(C语言)超市管理系统(测试版)(指针)(数据结构)(二进制文件读写)
c语言·开发语言·数据结构·c++
PXM的算法星球29 分钟前
使用CAS操作实现乐观锁的完整指南
开发语言
TDengine (老段)38 分钟前
基于 TSBS 标准数据集下 TimescaleDB、InfluxDB 与 TDengine 性能对比测试报告
java·大数据·开发语言·数据库·时序数据库·tdengine·iotdb
lgily-122541 分钟前
常用的设计模式详解
java·后端·python·设计模式
意倾城1 小时前
Spring Boot 配置文件敏感信息加密:Jasypt 实战
java·spring boot·后端
火皇4051 小时前
Spring Boot 使用 OSHI 实现系统运行状态监控接口
java·spring boot·后端
rylshe13142 小时前
在scala中sparkSQL连接mysql并添加新数据
开发语言·mysql·scala
小宋加油啊2 小时前
Mac QT水平布局和垂直布局
开发语言·qt·macos
薯条不要番茄酱2 小时前
【SpringBoot】从零开始全面解析Spring MVC (一)
java·spring boot·后端
MyhEhud2 小时前
kotlin @JvmStatic注解的作用和使用场景
开发语言·python·kotlin