Rust基础语法及数据类型
文章目录
前言
一、Rust的基础语法
变量,基本类型,函数,注释和控制流,这些几乎是每种编程语言都具有的编程概念。
1.1 变量(variable)
Rust 是强类型语言,但具有自动判断变量类型的能力。这很容易让人与弱类型语言产生混淆。
如果要声明变量,需要使用 let 关键字。例如: let a = 1
;
但是在Rust中,此时声明的a是一个不可变的变量,即a的值不能更改。
声明let a = 1
之后如下三种语句都是错误的
a = “hello;
a = 1.21;
a = 2;
第一行的错误在于当声明 a 是 1 以后,a 就被确定为整型数字,不能把字符串类型的值赋给它。
第二行的错误在于自动转换数字精度有损失,Rust 语言不允许精度有损失的自动数据类型转换。
第三行的错误在于 a 不是个可变变量。
前两种错误很容易理解,但第三个是什么意思?难道 a 不是个变量吗?Rust 语言为了高并发安全而做的设计:在语言层面尽量少的让变量的值可以改变。所以 a 的值不可变。但这不意味着 a 不是"变量"(英文中的 variable),官方文档称 a 这种变量为"不可变变量"。
如果我们编写的程序的一部分在假设值永远不会改变的情况下运行,而我们代码的另一部分在改变该值,那么代码的第一部分可能就不会按照设计的意图去运转。由于这种原因造成的错误很难在事后找到。这是 Rust 语言设计这种机制的原因。
当然,使变量变得"可变"(mutable)只需一个 mut 关键字。
如下是声明可变变量,并更改变量的值:
let mut a = 1;
a = 2;
1.2 常量与不可变量的区别
既然不可变变量是不可变的,那不就是常量吗?为什么叫变量?
变量和常量还是有区别的。在 Rust 中,以下程序是合法的:
let a = 11;
let a = 22;
a如果定义是常量则不合法:
const a: i32 = 11;
let a = 22;
变量的值可以"重新绑定",但在"重新绑定"以前不能私自被改变,这样可以确保在每一次"绑定"之后的区域里编译器可以充分的推理程序逻辑。 虽然 Rust 有自动判断类型的功能,但有些情况下声明类型更加方便:
let a:u64 = 11;
这里定义a为无符号64为整型的变量,如果没有声明类型,比如let a = 11;
a将被自动判断为有符号的32位整型变量,即等同于let a:i32 = 11;
1.3 重影(Shadowing)
重影的概念与其他面向对象语言里的"重写"(Override)或"重载"(Overload)是不一样的。重影就是刚才讲述的所谓"重新绑定",之所以加引号就是为了在没有介绍这个概念的时候代替一下概念。
重影就是指变量的名称可以被重新使用的机制:
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
}
这段程序的运行结果:
The value of x is: 12
重影与可变变量的赋值不是一个概念,重影是指用同一个名字重新代表另一个变量实体,其类型、可变属性和值都可以变化。但可变变量赋值仅能发生值的变化。
let mut s = "123";
s = s.len();
这段程序会出错:不能给字符串变量赋整型值。
二、基本数据类型
Rust的基本数据类型包括布尔类型、整数类型、浮点类型和字符类型。
2.1 布尔(bool)类型
布尔类型在Rust中表示逻辑值,取值范围为true
和false
,Rust语言中布尔类型的大小为1个字节。
fn main() {
let istrue: bool = true;
let isfalse: bool = false;
println!("istrue: {}", is_true);
println!("isfalse: {}", is_false);
}
2.2 整数类型
Rust提供了多种整数类型,包括有符号和无符号整数类型。有符号整数类型可以表示正数、负数和零,而无符号整数类型只能表示非负数和零。
i8
:有符号8位整数类型
u8
:无符号8位整数类型
i16
:有符号16位整数类型
u16
:无符号16位整数类型
i32
:有符号32位整数类型
u32
:无符号32位整数类型
i64
:有符号64位整数类型
u64
:无符号64位整数类型
i128
:有符号128位整数类型
u128
:无符号128位整数类型
isize
:有符号指针大小整数类型
usize
:无符号指针大小整数类型
整型变量使用示例:
fn main() {
let a: i8 = 42;
let b: u16 = 100;
let c: i32 = -500;
let d: u64 = 1000;
println!("a: {}", a);
println!("b: {}", b);
println!("c: {}", c);
println!("d: {}", d);
}
2.3 浮点类型
Rust提供了两种浮点类型:f32和f64,分别表示单精度和双精度浮点数。f32是32位浮点数,f64是64位浮点数。
浮点类型使用的示例:
fn main() {
let x: f32 = 3.14;
let y: f64 = 2.71828;
println!("x: {}", x);
println!("y: {}", y);
}
2.4 字符类型
在Rust中,字符类型用char表示,它是Unicode标量值的32位表示。字符类型的大小为4个字节。
以下是一个字符类型的示例:
fn main() {
let c: char = 'A';
let heart_emoji: char = '❤';
println!("c: {}", c);
println!("heart_emoji: {}", heart_emoji);
}
三 复合数据类型
Rust提供了几种复合数据类型,包括元组、数组和字符串。
3.1 元组(Tuple)
元组是Rust中的一种复合数据类型,它可以将多个不同类型的值组合在一起。元组使用圆括号()表示,其中的值可以通过索引访问。
以下是一个元组的示例:、
//元组, 多类型,每个位置类型必须一一对应
let person:(String, i32, bool,i64) = ("张三".to_string(), 100, false, 100);
println!("Name = {}", person.0);
println!("Age = {}", person.1);
println!("Yes or No = {}", person.2);
println!("weight = {}", person.3);
3.2 数组(Array)
数组是一种固定长度的数据结构,它可以存储相同类型的多个值。在Rust中,数组的长度是固定的,且数组的类型由元素类型和长度决定。
以下是一个数组的示例:
//数组
let array: [i32; 5] = [1,2,3,4,5];
//for 循环遍历
for num in array.iter() {
println!("Number : {}", num);
}
3.3 字符串(String)
字符串是一种文本数据类型,它由一系列Unicode字符组成。在Rust中,字符串类型使用String表示,它是一个可增长的、可变的字符串类型。
以下是一个字符串的示例:
fn main() {
let message: String = String::from("Hello, Rust!");
println!("Message: {}", message);
//字符串
let mut str1 = String::new();//创建一个空的 可变的 字符串变量
str1.push_str("hello");//向String 对象追加字符串
println!("str1 : {}", str1);
}
四 自定义数据类型
Rust允许用户自定义数据类型,包括结构体和枚举。
4.1 结构体(Struct)
结构体是一种自定义的数据类型,它可以将多个不同类型的值组合在一起形成一个新的类型。结构体使用struct关键字定义,并可以包含字段(field)和方法(method)。
结构体(structure,缩写成 struct)有 3 种类型,使用 struct 关键字来创建:
1.元组结构体(tuple struct),事实上就是具名元组。
struct Pair(i32, f32);
fn main() {
let pair = Pair(1, 0.1);
println!("pair contains {:?} and {:?}", pair.0, pair.1);
//pair contains 1 and 0.1
// 元组结构体的解构
let Pair(integer, decimal) = pair;
println!("pair contains {:?} and {:?}", integer, decimal);
//pair contains 1 and 0.1
}
2.经典的 C 语言风格结构体。
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
fn main() {
let name = String::from("Peter");
let age = 27;
let peter = Person { name, age };
println!("{:?}", peter);
println!("person name: {}, person age: {}", peter.name, peter.age);
}
//Person { name: "Peter", age: 27 }
//person name: Peter, person age: 27
3.单元结构体(unit struct),不带字段,在泛型中很有用。
#[derive(Debug)]
struct Unit;
fn main() {
let unit = Unit;
println!("{:?}", unit)
//Unit
}
以下是利用impl关键字来定义结构体成员方法 示例1:
// 利用结构体定义成员变量
struct Fruit {
color: String,
weight: f32
}
// 利用impl关键字来定义结构体成员方法
impl Fruit {
fn printInfo(&self) {
println!("{},{}",self.color,self.weight);
}
}
// 调用
fn main() {
let f = Fruit{color:String::from("green"), weight:12.5};
f.printInfo();
}
问题来了,一般定义类前面都会有个关键字new,那么这个该怎么实现呢?看下面的例子。
Rust 定义标准类
// 利用结构体定义成员变量
struct Fruit {
color: String,
weight: f32
}
// 利用impl关键字来定义结构体成员方法
impl Fruit {
// 相当于方法Fruit::new()调用
fn new(color: String, weight:f32) -> Fruit {
Fruit {
color: color,
weight: weight
}
}
fn printInfo(&self) {
println!("{},{}",self.color,self.weight);
}
}
// 调用
fn main() {
let f = Fruit::new(String::from("green"), 12.5);
f.printInfo();
}
为什么在定义类方法printInfo(&self)里面传的参数是&self ?
解释:
impl关键字在struct、enum或者trait对象上实现方法调用语法
关联函数 (associated function) 的第一个参数通常为self参数。
有几种变体:
self,允许实现者移动和修改对象,对应的闭包特性为FnOnce
&self,既不允许实现者移动对象也不允许修改,对应的闭包特性为Fn
&mut self,允许实现者修改对象但不允许移动,对应的闭包特性为FnMut
不含self参数的关联函数称为静态方法 (static method
以下是结构体的示例2:
struct Girl {
name: String,
age: u8,
height: f32,
}
fn struct_example1() {
//创建一个可变的结构体对象 girl。
let mut girl = Girl { name: "jack".to_string(), age: 12, height: 1.8};
girl.name = "mark".to_string();
println!("name: {}, age: {}, height :{}", girl.name, girl.age, girl.height);
girl.name = String::from("mark");//String 变量的两种不同初始化方式
println!("name: {}, age: {}, height :{}", girl.name, girl.age, girl.height);
//创建一个不可变的结构体对象 girl2, Rust不允许只将一个不可变的结构体中某个字段标记为可变。
let girl2 = Girl { name: "jack".to_string(), age: 13, height: 1.85};
girl2.name = "mark".to_string();//这里会报错,不能通过不可变结构体对象修改结构体中字段的值
println!("name: {}, age: {}, height :{}", girl2.name, girl2.age, girl2.height);
}
fn main() {
struct_example1();
}
以下是结构体的示例3:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
// 构建结构体User
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("username123@example.com"),
String::from("username123"),
);
println!("email :{}, username: {}, active: {}, sign_in_count: {}", user1.email, user1.username, user1.active, user1.sign_in_count);
}
从其他实例创建实例
通过旧的结构体实例创建一个新的实例,使用了大多数旧实例的值,但更改了一些值。可以使用结构体更新语法来完成此操作。如下所示:
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
使用结构体更新语法,我们可以使用更少的代码实现同样的效果。语法..
。指定未显式设置的其余字段应具有与给定实例中的字段相同的值。如下所示:
// 使用更新语法..构建结构体实例
let user2 = User {
email: String::from("another@example.com"),
..user1
};
4.2 枚举(enum)
rust的枚举比C/C++的枚举要更加强大。它允许你列举可能的成员来定义一个枚举类型,而这些成员的类型并不要求一致。枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。
枚举的值存储为整数,默认从0开始,后面的值依次 +1。如果想更改某个枚举的值,需要告诉它用什么整数。示例代码如下:
// 为了可以格式化打印枚举值,需要加入下面这行代码,具体有关 trait 的知识将在后面介绍。
#[derive(Debug)]
enum Week {
Mon,
Tue,
Wed,
Thu = 300,
Fri,
Sat,
Sun
}
fn main() {
// 打印枚举类型
println!("{:?}", Week::Wed);
// 打印枚举的值
println!("{}", Week::Wed as i32);
println!("{}", Week::Mon as i32);
// 由于Thu 赋值维 300, 则后面的值依次+1
println!("{}", Week::Fri as i32);
}
// 输出结果:
// Wed
// 2
// 0
// 301
枚举 match:
enum Fruit {
Apple,
Banana,
Orange,
}
fn fruit_match(fruit: Fruit) {
// match 必须列出所有可能;
match fruit {
Fruit::Apple => println!("It's an apple!"),
Fruit::Banana => println!("It's a banana!"),
Fruit::Orange => println!("It's an orange!"),
}
}
fn main() {
let fruit: Fruit = Fruit::Apple;
fruit_match(fruit);
let fruit: Fruit = Fruit::Banana;
fruit_match(fruit);
let fruit: Fruit = Fruit::Orange;
fruit_match(fruit);
}
/*输出
It's an apple!
It's a banana!
It's an orange!
*/
五 其他数据类型
除了基本数据类型、复合数据类型和自定义数据类型,Rust还提供了其他一些常用的数据类型。
5.1 切片
切片是对数组或字符串的引用,它允许我们引用集合中的一部分而不用拷贝整个集合。切片使用&符号和范围表示。
以下是一个切片的示例:
// 左闭右开[ )。包含start_index下标位置元素,不包含end_index下标位置的元素
fn main() {
let numbers: [i32; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let slice: &[i32] = &numbers[0..1];//索引从下标 0开始;
println!("Slice : {:?}", slice);
let slice: &[i32] = &numbers[..1];//缺省start_index; 默认从 0 开始
println!("Slice : {:?}", slice);
let slice: &[i32] = &numbers[5..10];
println!("Slice : {:?}", slice);
let slice: &[i32] = &numbers[5..];//缺省end_index; 默认到最后一个元素截止;
println!("Slice : {:?}", slice);
//字符串切片
let str = String::from("helloworld");
let slice = &str[0..5];
println!("str = {}", slice);
/*输出
Slice : [0]
Slice : [0]
Slice : [5, 6, 7, 8, 9]
Slice : [5, 6, 7, 8, 9]
str = hello */
}
5.2 Option类型
Option类型在Rust中用于表示可能为空的值。它有两个可能的取值:Some(value)表示有值,None表示无值。Option类型可以帮助我们处理可能出现空值的情况。
Option是Rust的为了解决函数返回null问题而产生的。
Option 类型是 Rust 标准库中的一个枚举类型,定义如下:
pub enum Option<T> {
None,
Some(T),
}
fn divide(x: f64, y: f64) -> Option<f64> {
if y != 0.0 {
Some(x / y)
} else {
None
}
}
fn main() {
let result = divide(10.0, 2.0);
match result {
Some(value) => println!("Result: {}", value),
None => println!("Cannot divide by zero"),
}
}
//Result: 5
5.3 Result类型
Result类型在Rust中用于处理可能发生错误的操作。它有两个可能的取值:Ok(value)表示操作成功,返回一个值,Err(error)表示操作失败,返回一个错误。
以下是一个Result类型的示例:
fn divide(x: f64, y: f64) -> Result<f64, String> {
if y != 0.0 {
Ok(x / y)
} else {
Err("Cannot divide by zero".to_string())
}
}
fn main() {
let result = divide(10.0, 2.0);
match result {
Ok(value) => println!("Result: {}", value),
Err(error) => println!("Error: {}", error),
}
}
//Result: 5
总结
本文介绍了Rust的基础语法以及Rust的各种数据类型,包括布尔类型、整数类型、浮点类型、字符类型、元组、数组、字符串、结构体、枚举、切片、Option类型和Result类型。