Rust学习笔记_01——基础
文章目录
- 1. Hello world
- 2. 变量
- 1. 变量声明
- 2. 类型推断
- 3. 变量绑定
- 4. 重绑定(Shadowing)
- 3. 值
- 4. 算术
- 4.1 基础运算
- 4.2 整数 && 浮点数运算
- 4.3 标准库函数
- `std::cmp` 模块
- `num_traits` 模块
- 4.4 注意事项
- 5. 类型推导
- 5.1 工作原理
- 5.2 限制
- 5.3 类型注解的作用
1. Hello world
fn main() {
println!("hello world!")
// 函数以 fn 开头。
// 像 C 和 C++ 一样,块由花括号分隔。
// main 函数是程序的入口点。
// Rust 有卫生宏 (hygienic macros),println! 就是一个例子。
// Rust 字符串是 UTF-8 编码的,可以包含任何 Unicode 字符。
}
在Rust中,宏是一种元编程工具,允许你在编译时生成代码。宏可以用来创建新的语法结构,简化重复的模式,或者实现其他语言特性。Rust的宏系统设计得非常强大且灵活,同时尽量保持安全性和可预测性。
卫生宏是Rust宏的一个重要特性,它确保了宏的使用不会意外地干扰到程序的其他部分。
println!()
是 Rust 中一个非常常用的宏,用于格式化字符串并将其打印到标准输出(通常是控制台)。这个宏是卫生宏的一个很好的例子,因为它展示了宏如何在不影响调用者代码的情况下安全地执行其功能。
println!()
宏的基本用法
println!(fmt, arg1, arg2, ...);
fmt
是一个格式字符串,类似于 C 语言中的printf
。arg1, arg2, ...
是要插入到格式字符串中的参数。
格式字符串
格式字符串中可以包含占位符,这些占位符会被后面的参数替换。常见的占位符包括:
{}
:自动选择合适的格式。{:?}
:调试格式(Debug
trait)。{:<width>}
:左对齐,宽度为width
。{:>width}
:右对齐,宽度为width
。{:^width}
:居中对齐,宽度为width
。{:.precision}
:设置浮点数的小数位数或字符串的最大长度。
卫生宏的特点
- 作用域隔离:
println!()
宏内部定义的任何变量或标签都不会与调用者的作用域中的变量或标签冲突。例如,宏内部可能会使用临时变量来构建格式化的字符串,但这些变量不会泄漏到调用者的代码中。
- 防止捕获外部变量:
println!()
宏只会捕获显式传递给它的参数。例如,如果你在格式字符串中使用了{}
, 那么你需要提供相应的参数。宏不会捕获调用者作用域中的其他变量,除非你明确地将它们传递给宏。
- 避免名称泄露:
println!()
宏的内部实现细节对调用者是隐藏的。你不需要关心宏内部的具体实现,只需要知道如何使用它即可。
2. 变量
1. 变量声明
在Rust中,变量默认是不可变的(immutable),这意味着一旦赋值后就不能再修改。要声明一个不可变变量,使用 let
关键字:
let x = 5; // 不可变变量
如果需要声明一个可变变量(mutable),可以在 let
关键字后面加上 mut
关键字:
let mut y = 10; // 可变变量
y = 15; // 修改变量的值
2. 类型推断
Rust具有类型推断功能,编译器可以根据赋值表达式推断出变量的类型。不过,你也可以显式地指定类型:
let z: i32 = 20; // 显式指定类型
3. 变量绑定
变量绑定是指将一个值绑定到一个变量名上。Rust中的变量绑定是不可变的,但可以通过 mut
关键字使其可变:
let a = 1; // 不可变绑定
let mut b = 2; // 可变绑定
b = 3; // 修改可变变量的值
4. 重绑定(Shadowing)
Rust允许在同一作用域内重新声明一个变量,这称为重绑定(shadowing)。重绑定会创建一个新的变量,原来的变量仍然存在,但不可访问:
let x = 5; // 第一次绑定
let x = x + 1; // 重绑定,创建一个新的变量 x
let x = x * 2; // 再次重绑定
println!("The value of x is: {}", x); // 输出 "The value of x is: 12"
3. 值
- 有符号整数
- 类型:
i8
、i16
、i32
、i64
、i128
、isize
- 字面量:
-10
、0
、1_000
、123_i64
- 类型:
- 无符号张数
- 类型
u8
、u16
、u32
、u64
、u128
、usize
- 字面量:
123
、10_u16
、0
- 类型
- 浮点数
- 类型:
f32
、f64
- 字面量:
3.14
、-10.0e20
、2_f32
- 类型:
- Unicode标量类型
- 类型:char
- 字面量:
'a'
- 布尔值
- 类型:bool
- 字面量:
true
、false
一些说明
iN
,uN
和fN
占用 N 位,isize
和usize
占用一个指针大小的空间,char
占用 32 位空间,bool
占用 8 位空间。- 数字中的所有下划线均可忽略,它们只是为了方便辨识。因此,
1_000
可以写为1000
(或10_00
),而123_i64
可以写为123i64
。
4. 算术
4.1 基础运算
-
加法 (
+
):let a = 5; let b = 10; let sum = a + b; // sum 的值是 15
-
减法 (
-
):let a = 10; let b = 5; let difference = a - b; // difference 的值是 5
-
乘法 (
\*
):let a = 3; let b = 4; let product = a * b; // product 的值是 12
-
除法 (
/
):let a = 20; let b = 4; let quotient = a / b; // quotient 的值是 5
-
取余 (
%
):let a = 9; let b = 4; let remainder = a % b; // remainder 的值是 1
4.2 整数 && 浮点数运算
整数运算遵循上述基本运算符的规则。需要注意的是,整数除法会舍弃小数部分,例如:
let a = 7;
let b = 3;
let quotient = a / b; // quotient 的值是 2,而不是 2.333...
浮点数运算同样遵循基本运算符的规则,但由于浮点数的表示精度问题,可能会出现一些细微的误差:
let a = 7.0;
let b = 3.0;
let quotient = a / b; // quotient 的值是大约 2.3333333333333335
4.3 标准库函数
Rust标准库提供了一些额外的数学函数,主要位于 std::cmp
和 num_traits
等模块中。
std::cmp
模块
std::cmp
模块提供了比较函数,例如 max
和 min
,这些函数可以用于获取两个数值中的最大值或最小值:
use std::cmp;
let a = 10;
let b = 20;
let max_value = cmp::max(a, b); // max_value 的值是 20
let min_value = cmp::min(a, b); // min_value 的值是 10
num_traits
模块
num_traits
是一个外部库,提供了丰富的数值运算功能,包括求绝对值、幂运算、平方根等。要使用这个库,需要在 Cargo.toml
文件中添加依赖:
[dependencies]
num-traits = "0.2"
然后在代码中导入并使用相关函数:
use num_traits::{Pow, Abs};
let a = -5i32;
let b = 2;
let power = a.pow(b); // power 的值是 25,因为 (-5)^2 = 25
let abs_value = a.abs(); // abs_value 的值是 5
4.4 注意事项
- 整数溢出:Rust的整数类型在运算时可能会发生溢出。为了避免溢出,可以使用
checked_add
,checked_sub
,checked_mul
, 和checked_div
等方法,这些方法在溢出时会返回Option<T>
类型 - 浮点数精度:浮点数运算可能会受到精度限制,导致一些细微的误差。
5. 类型推导
Rust中的类型推导(Type Inference)是一种编译器功能,它允许程序员在编写代码时省略变量或表达式的类型注解,而编译器则会自动推断出这些类型。这种功能使得Rust代码更加简洁和易于编写,同时仍然保持了Rust的强类型系统所提供的类型安全性。
5.1 工作原理
Rust编译器通过以下几个步骤来推断类型:
- 局部类型推断:编译器首先会查看变量的初始值(如果有的话)来推断其类型。例如,如果有一个变量被初始化为一个整数字面量,那么编译器就会推断这个变量的类型为
i32
(在大多数情况下,整数字面量的默认类型)。 - 上下文依赖:编译器还会考虑变量在表达式中的使用上下文来推断类型。例如,如果一个变量被用作函数参数,并且该函数期望一个特定类型的参数,那么编译器就会推断该变量的类型为该函数参数所期望的类型。
- 泛型约束:当使用泛型时,编译器会根据泛型参数的使用方式和提供的类型约束来推断具体的类型。
- 类型统一:在推断过程中,编译器会尝试统一不同类型的表达式或变量,以确保它们在使用时具有兼容的类型。如果类型无法统一,编译器就会报错。
5.2 限制
- 在某些情况下,编译器可能无法推断出变量的类型,这时就需要程序员显式地提供类型注解。
let some_number; // 错误! 无法进行类型推导
let some_number: i32; // OK
let some_number = 123; // OK
- 当函数返回类型或泛型参数的类型无法从上下文推断出来时,也需要显式地提供类型注解
5.3 类型注解的作用
- 提高代码可读性:类型注解可以帮助其他程序员(或未来的你)更容易地理解代码。
- 避免潜在的错误:在某些复杂的表达式中,类型推导可能会失败或导致意外的结果。显式地提供类型注解可以避免这些错误。
- 与C/C++等语言的互操作性:当与C/C++等语言进行互操作时,可能需要显式地指定类型以确保正确的数据表示和传递。