亲宝软件园·资讯

展开

2022最新Rust变量与数据类型讲解

暴风雨中的白杨 人气:0

变量与数据类型

常用的三大数据结构:

Rust标准库std::collections提供了4种通用的容器类型,其中包含8种数据结构。

动态数组可细分为普通动态数组Vec和双端队列VecDeque

映射包括HashMap

字符串包括String等类型

变量和可变性

Rust的变量不同于其他编程语言的变量,其本质上是一种绑定语义,即将一个变量名与一个值绑定在一起。变量名和值建立关联关系。

变量默认是不可改变的

变量声明

使用let关键字声明变量,先声明后使用

let x :i8 =1
let x = 1 ; // 等价于 let x: i32 = 1;

变量声明以let关键字开头,x为变量名,变量名后紧跟冒号和数据类型

Rust编译器具有变量类型的自动推导功能

在可以根据赋值类型或上下文信息推导出变量类型的情况下,冒号和数据类型可以省略。

变量命名

Rust中下划线是一种特殊的标识符,其含义是“忽略这个变量”

变量的可变性

let声明的变量默认是不可变的,在第一次赋值后不能通过再次赋值来改变它的值,即声明的变量是只读状态

在变量名的前面加上mut关键字就是告诉编译器这个变量是可以重新赋值的

let mut x = 3;
x = 5;
println!("x: {}",x);

Rust编译器保证了如果一个变量声明为不可变变量,那它就真的不会变

变量遮蔽

Rust允许在同一个代码块中声明一个与之前已声明变量同名的新变量,新变量会遮蔽之前的变量,即无法再去访问前一个同名的变量,这样就实现了变量遮蔽

fn main(){
    let x = 3;
    let x = x+2;
    let x = x*2;
    let x = "Hello, World";
}

变量遮蔽的实质是通过let关键字声明了一个新的变量,只是名称恰巧与前一个变量名相同而已,但它们是两个完全不同的变量,处于不同的内存空间,值可以不同,值的类型也可以不同

常量

常量是指绑定到一个标识符且不允许改变的值,一旦定义后就没有任何方法能改变其值了

const MAX_NUM : u32 = 1024;

使用const关键字来声明常量

常量名通常是大写字母,且必须指定常量的数据类型

常量与不可变变量的区别主要在于:

在编译阶段就要确定其值

基本数据类型

强类型的静态编译语言

Rust的基本数据类型有整数类型、浮点数类型、布尔类型、字符类型、范围类型等。

整数类型

整数可以分为有符号整型和无符号整型

按照存储大小,整数类型可以进一步分为1字节、2字节、4字节、8字节、16字节

Rust默认的整数类型是i32

isize和usize主要作为数组或集合的索引类型使用,其长度依赖于运行程序的计算机系统。在64位计算机系统上,其长度是64位;在32位计算机系统上,其长度是32位。

长度有符号无符号
8 biti8u8
16 biti16u16
32 biti32u32
64 biti64u64
128 biti128u128
archisizeusize
let integer1 : u32 = 17 ; // 类型声明
let integer2 = 17u32;     // 类型后缀声明
let integer3 = 17 ;       // 默认i32
let integer4 : u32 = 0b1001; // 二进制
let integer5 : u32 = 0o21;   // 八进制
let integer6 : u32 = 0x11 ;  // 十六进制
let integer7 = 50_000;   // 数据可读性分隔符_

Rust允许使用下划线“_”作为虚拟分隔符来对数字进行可读性分隔

Rust在编译时会自动移除数字可读性分隔符“_”。

如果某个变量的值超出了给定的数值范围,将会发生整型溢出。编译器将其视为一种错误。

浮点数类型

浮点数分为f32和f64两类。Rust默认的浮点数类型是f64

浮点数支持使用数字可读性分隔符“_”

let float1 : f32 = 1.1;  // 类型声明
let float2  = 2.2f32;  // 类型后缀声明
let float3 = 3.3  ;  // 默认f64类型
let float4 = 11_00.555_01;  // 数字可读性分隔符

布尔类型

使用bool来声明布尔类型的变量

let t : bool = true;  //显式类型声明
let f = false ;       // 隐式类型声明

字符类型

Rust使用UTF-8作为底层的编码。

字符类型代表的是一个Unicode标量值(Unicode Scalar Value),包括数字、字母、Unicode和其他特殊字符。

每个字符占4个字节。

字符类型char由单引号来定义

let z = 'z' ;  // 使用单引号
let hz = '中';

范围类型

范围类型常用来生成从一个整数开始到另一个整数结束的整数序列,有左闭右开全闭两种形式

范围类型自带一些方法

fn main(){
    print!("(1..5):")
    for i in 1..5 {
        print!("{} ",i);
    }
    println!();
    
    print!("(1..=5).rev:");
       for i in (1..=5).rev() {
        print!("{} ",i);
    }
    println!();
    
    let sum :i32 = (1..=5).sum();
    println!("sum(1..=5)={}",sum);
}

//(1..5):1 2 3 4 
//(1..=5).rev:5 4 3 2 1 
//sum(1..=5)=15

复合数据类型

复合数据类型是由其他类型组合而成的类型

Rust的复合数据类型有元组、数组、结构体、枚举等

元组类型

元组类型是由一个或多个类型的元素组合成的复合类型,使用小括号“()”把所有元素放在一起。元素之间使用逗号“,”分隔

元组中的每个元素都有各自的类型,且这些元素的类型可以不同。

元组的长度固定,一旦定义就不能再增长或缩短。

如果显式指定了元组的数据类型,那么元素的个数必须和数据类型的个数相同。

可以使用元组名.索引的方式来访问元组中相应索引位置的元素。

当元组中只包含一个元素时,应该在元素后面添加逗号来区分是元素,而不是括号表达式

let tup1 :(i8,f32,bool) = (-10,7.7,false);
let tup2 = (7.7,(false,10));
let tup3 = (100,);
println!("{},{}",tup1.0,tup2.1)
// 解构赋值
let (x,y,z) = tup1;

数组类型

由相同类型的元素组合成的复合类型

使用[T;n] 表示,T代表元素类型,n代表长度即元素个数

// 指定数组类型,为每一个元素赋值
let arr:[i32;5] = [1,2,3,4,5];
// 省略数组类型 --> 编译器可以从初始值推断出数组类型
let arr = [1,2,3,4];
// 省略数组类型,为所有元素使用默认值初始化
let arr = [1;5]; // -> 等价let arr = [1,1,1,1,1]

可以使用"数组名[索引]"来访问数组中相应索引位置的元素,元素的索引从0开始计数。

动态数组Vec, Vec是允许增长和缩短长度的容器类型,其提供的get方法在访问元素时可以有效避免索引越界

结构体类型

结构体类型是一个自定义数据类型,通过struct关键字加自定义命名,可以把多个类型组合在一起成为新的类型。

结构体中以"name: type"格式定义字段,name是字段名称,type是字段类型。

字段默认不可变,并要求明确指定数据类型,不能使用自动类型推导功能。

// 指定数组类型,为每一个元素赋值
let arr:[i32;5] = [1,2,3,4,5];
// 省略数组类型 --> 编译器可以从初始值推断出数组类型
let arr = [1,2,3,4];
// 省略数组类型,为所有元素使用默认值初始化
let arr = [1;5]; // -> 等价let arr = [1,1,1,1,1]

使用"实例名.字段名"形式更改和访问结构体实例某个字段的值。

结构体实例默认是不可变的,且不允许只将某个字段标记为可变,如果要修改结构体实例必须在实例创建时就声明其为可变的。

struct Student {
    name : &'static str,
    score: i32,
}

fn main(){
    let score = 59;
    let username = "wkk";
    
    let mut student = Student{
        score, // 变量和字段同名,可以简写
        name : username,
    };
    
    student.score = 60;
    //结构体更新语法,对除字段name外未显式设置值的字段以student实例对应字段的值来赋值。
    let student2 = Student {
        name : "yyr",
        ..student
    };
}

特殊结构体:

字段只有类型,没有名称

struct Color(i32,i32,i32);

没有任何字段的结构体

struct Solution;

枚举类型

使用enum关键字加自定义命名来定义

含若干枚举值,可以使用“枚举名::枚举值”访问枚举值。

变量的值限于枚举值范围内

根据枚举值是否带有类型参数,枚举类型还可以分成无参数枚举类型和带参数枚举类型。

无参枚举类型

// #[derive(Debug)] 让ColorNoParam自动实现Debug trait
// 只有实现了Debug trait的类型才拥有使用{:?}格式化打印的行为
#[derive(Debug)]
enum ColorNoParam {
	Red,
    Yellow,
    Blue,
}

fn main(){
    let color_no_param = COlorNoParam::Red;
    match color_no_param{
		ColorNoParam :: Red => println!("{:?}",ColorNoParam::Red),
        ColorNoParam :: Yellow => println!("{:?}",ColorNoParam::Yellow),
        ColorNoParam :: Blue => println!("{:?}",ColorNoParam::Blue),
    }
}

带参枚举类型

#[derive(Debug)]
enum ColorParam{
    Red(String), //带有String类型参数
    Yellow(String),
    Blue(String),
}

fn main(){
    //使用这种枚举值需要传入实参
    println!("{:?}",ColorParam::Blue(String::from("blue")));
}

容器类型

Rust标准库std::collections提供了4种通用的容器类型,包含8种数据结构

image-20221101142431588

Vec

动态可变长数组, 在运行时可以增长或者缩短数组的长度

动态数组在内存中开辟了一段连续内存块用于存储元素,且只能存储相同类型的元素。

新加入的元素每次都会被添加到动态数组的尾部

// 创建空的动态数组
let mut v: Vec<i32> = Vec::new();
// 创建指定容量的动态数组
let mut v: Vec<i32> = Vec::with_capacity(10);
// 使用vec!宏在创建动态数组的同时进行初始化,并且根据初始值自动推断动态数组的元素类型
let mut v: Vec<i32> = vec![]; // 没有初始值,需要声明元素类型
let mut v = vec![1,2,3]; // 自动推断元素个数
let mut v = vec![0;10]; // 10个元素,元素的初始值都是0

应该尽可能根据初始元素个数以及增长情况来指定合理的容量。

// 使用push方法在动态数组尾部添加新元素
v.push(1);
// 使用数组名[索引] 获取元素
v[1] = 5;
// 使用pop方法删除并返回动态数组的最后一个元素,如果数组为空返回None
v.pop();
// remove方法删除并返回动态数组指定索引的元素,同时后面的所有元素向前移动一位
// 索引越界将导致程序错误
v.remove(1);

访问

//使用索引访问
v[2];
// 使用get方法以索引作为参数访问
v.get(1);
// 遍历
for i in v {
    print!("{}",i);
}
// 可变引用
for i in &mut v {
    *i += 50;
    print!("{}",i);
}

VecDeque

双端队列是一种同时具有栈(先进后出)和队列(先进先出)特征的数据结构,适用于只能在队列两端进行添加或删除元素操作的应用场景

定义在标准库的std::colllections::VecDeque中,使用前需要显式导入std::collections::VecDeque;

// 创建空的VecDeque
let mut v : VecDeque<u32> = VecDeque::new();
// 创建指定容量的VecDeque
let mut v : VecDeque<u32> = VecDeque :: with_capacity(10);
// push_front 在队列头部添加新的元素
v.push_front(1);
// push_back 在尾部添加新元素
v.push_back(2);
// 使用索引修改元素
v[1] = 5;
// pop_front 删除并返回队列的头部元素
v.pop_back();
// pop_back 删除并返回尾部元素
v.pop_front();
// remove 删除并返回队列指定索引的元素,同时后面的所有元素向左移动一位
// 索引越界返回None
v.remove(1);
// 使用索引访问
v[0];
// 使用get方法以索引作为参数访问元素
v.get(0);

HashMap

哈希表(HashMap)是基于哈希算法来存储键-值对的集合,其中所有的键必须是同一类型,所有的值也必须是同一类型,不允许有重复的键

定义在标准库std::collections 模块中,使用前要显式导入std::collection::HashMap

// 创建空的HashMap
let mut map: HashMap<&str,i32> = HashMap::new();
// 创建指定容量
let mut map: HashMap<&str,i32> = HashMap::with_capacity(10);

修改

// insert 执行插入或者更新
// 键不存在,执行插入并返回None
// 键存在,执行更新,并返回旧值
let zhangsan = map.insert("zhangsan",16);
// 使用entry 和 or_insert 方法检查是否有对应值,没有对应值就插入,有对应值不执行操作
// entry方法以键为参数,返回值是一个枚举类型Entry
// Entry类型的or_insert 方法以值为参数,在键有对应值时不执行任何操作,没有对应值时,将键与值组成键值对插入
map.entry("zhangsan").or_insert(23);
// iter_mut 方法会返回由所有键值对的可变引用组成的迭代器
for( _, val ) in map.iter_mut() {
    *var += 2; // 所有的值都加2
}
// remove 删除并返回指定键值对的值,不存在返回None
let result = map.remove("wkk");
// 使用实例名[键] 访问指定键值对, 键不存在会导致程序错误
map["wkk"]
// 使用get 方法,以键作为参数访问指定的键值对,存在返回值,不存在返回None
map.get("wkk");

字符串

字符串的本质是一种特殊的容器类型,是由零个或多个字符组成的有限序列。

字符串常被作为一个整体来关注和使用

常用的字符串有两种:

创建

1.&str的创建

内置的字符串类型是str, 通常以引用的形式&str出现。

字符串字面量&str是字符的集合,代表的是不可变的UTF-8 编码的字符串的引用,创建后无法再追加内容或者更改内容

// 使用双引号创建字符串字面量
let s1 = "hello,wkk";
// 使用as_str方法将字符串对象转换为字符串字面量
let str = String::from("hello,wkk");
let s2 = str.as_str();

2.String 的创建

字符串对象String 由Rust 标准库提供。

创建后可以为其追加内容或者更改内容。

本质是一个字段为Vec<u8> 类型的结构体,把字符内容放在堆上,由指向堆上字节序列的指针(as_ptr方法)、记录堆上字节序列的长度(len方法)和堆分配容量(capacity) 3部分组成。

// 创建空的字符串对象
let mut s = String::new();
// 根据指定的字符串字面量创建字符串对象
let s = String::from("wkk");
// 使用to_string 方法将字符串字面值转换为字符串对象
let str = "wkk";
let s = str.to_string();

修改

1.使用push方法在字符串后面追加字符,使用push_str方法在字符串后追加字符串字面量

都是在原字符串上追加,不会返回新的字符串

let mut s = String::from("wkk");
s.push('R');
s.push_str("111");

要追加,字符串必须是可变的,使用mut关键字

2.使用insert方法在字符串中插入字符,使用insert_str方法在字符串中插入字符串字面量

第1个参数是插入位置的索引,第2个参数是插入字符或者字符串字面量

都是在原字符串上插入,并不是返回新的字符串

索引非法会导致程序错误

s.insert(5,',');
s.insert_str(7,"Rust ");

3.使用 “+” 或者"+="运算符将两个字符串连接成一个新的字符串,要求运算符的右边必须是字符串字面量

不能对两个String 类型的字符串使用

连接与追加的区别在于,连接会返回新的字符串,而不是在原字符串上的追加

let s = "hello " + "wkk";

4.对于较为复杂的带有格式的字符串连接,可以使用格式化宏format!

对于String 类型 和 &str类型的字符串都适用

let s = format!("{}-{}-{}",s1,s2,s3);

5.replace 和 replacen() 方法将字符串中指定的子串替换为另一个字符串。

replace 接收两个参数,第1个参数为要被替换的子串,第2个参数为新的字符串,会替换所有匹配的子串。

replacen 方法除了上述两个参数外,还接受第3个参数来指定替换的个数

let s1 = s.replace("aa","77");
let s2 = s.replace("aa","77",1);

6.适用pop , remove , truncate 和 clear 方法删除字符串中的字符

删除并返回字符串的最后一个字符,返回类型为Option<char>, 如果字符串为空,返回None

删除并返回字符串中指定位置的字符,参数是该字符的起始索引位置。

remove方法是按字节处理字符串的,如果给定的索引位置不是合法的字符边界,将会导致程序错误。

删除字符串中从指定位置开始到结尾的全部字符,参数是起始索引位置。

truncate 也是按照字节处理字符串,如果给定的索引位置不是合法的字符边界,会导致程序错误。

删除字符串中所有字符

s.pop();
s.remove(9);
s.truncate(9);
s.clear();

字符串的访问

len 方法获取以字节为单位的字符串长度

UTF-8 中字母1字节,特殊字符2字节,汉字3字节,不同字符的长度是不一样的。

s.len();
// 按字节遍历
let bytes = s.bytes();
for b in bytes {
    print!("{} |",b);
}
// 按字符遍历
let chars = s.chars();
for c in chars{
    print!("{} |",c);
}

字面量和运算符

字面量

由文字,数字或者符号构成的值

可以在字面量后面追加类型后缀进行类型说明:

单元类型,单元类型的值叫做单元值,以()表示,一个函数无返回值,实际上是以单元值作为函数的返回值了。

运算符

支持算术运算符、关系运算符、逻辑运算符、位运算符

算术运算符

+ - * / % 

不支持 ++ 和 –

关系运算符

比较两个值之间的关系,并返回一个布尔类型的值

> < >= <= == != 

逻辑运算符

组合两个或者多个条件表达式,返回一个布尔类型的逻辑运算结果

&& || !

位运算符

对二进制格式的数据进行操作

& | ^ ! << >>

<< 和 >> 空白位都是补0

加载全部内容

相关教程
猜你喜欢
用户评论