参考教材 --》 Rust权威指南
一、入门
1.安装Rust
2.helloworld
fn main(){
println!("helloworld"); // 通常以 ;结尾
}
3.Cargo
|_____cargo.toml 配置文件格式
|_____src 存放源代码
cargo build -创建可执行文件
cargo run -构建并运行可执行文件
cargo check 检查代码确保能通过编译, 但是不产生任何可执行文件
二、猜数游戏
项目描述:随机生成1-100的数与用户输入的数进行比较并给用户提示
use std::cmp::Ordering;
use std::io;
use rand::Rng; // trait
// prelude 预导入
fn main(){
println!("猜数");
let secret_number = rand::thread_rng().get_range(1, 101);
pringln!(" input a number");
// 使用 let 声明变量 如果没有mut 则表明该变量不可变
// :: new()关联函数
loop{ // 循环
let mut guess = String :: new();
// io::result 如果是err expect会中断当前运行
io::stdin().read_line(&mut guess).expect("无法读取");
let guess:u32 = guess.trim().parse().expect("please enter a number");
println!("number is {}", guess);
match guess.cmp(&secret_number){
Ordering::Less=>println!("too small"),
Ordering::Equal=>{println!("too Equal"); break}, // 相等时跳出循环
Ordering::Greater=>println!("too big"),
}
}
}
三、通用的变成概念
3.1变量与可变性
声明变量使用let关键字。默认是不可变的。如果想更改变量的字可以使用mut关键字
常量:绑定值以后也是不可变的 与不可变的变量的区别
不可以使用mut 常量永远都是不可变的
声明常量使用const关键字,他的类型必须被标注
常量可以再任何作用域内进行声明 包括全局作用域
常量只可以绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值
3.1.2 shadowing
可以使用相同的名字声明新的变量 新的变量就会shadow之前声明的同名变量
fn main(){
let x = 1;
let x = x + 1;
// 之后的x就变成了第二次声明的x的值 我们可以输出验证一下
println!("{}",x);
// 现在的x的类型是 u32 如果我们执行下面一段代码后x的类型会变成什么呢?
let x = "AAAA";
// 是的,是string
// 也就是说在Rust 中我们可以通过shadowing来突破类型的限制 但是下面的一段代码的执行
// 结果呢?
let mut xa = " ";
xa = xa.len();
}
3.2数据类型
标量和复合类型
Rust是静态编译语言,在编译时必须知道所有变量的类型
基于使用的值,编译器通常可以推断出他的具体类型
整数类型 默认是i32
无符号整数
有符号整数
浮点类型 f32 f64(默认类型)
数值操作 + - * / %
布尔类型 true \ false
字符类型 char 四字节 使用单引号
3.3复合类型
复合类型可以将多个值放在一个类型里
Rust提供了两种基础的复合类型元组(Tuple)、数组
Tuple 可以放入多个不同类型的值 但是长度是固定的
fn main(){
let tup: (i32, f64, u8) = (1,2.0,3);
let (x,y,z) = (1,2.0,3);
println!("{},{},{}",x,y,z);
let x = tup;
println!("{}",x.0)
}
数组:相同类型的集合 数组的长度是固定的
数组的类型以这种形式表示: [ 类型;长度 ]
如果数组的每个元素值都相同 例如: let a = [3;5] => let a = [3,3,3,3,3]
let a = [3;5];
println!("{}",a[1]);
3.4函数
声明函数使用fn关键字 必须指明参数的类型
函数的返回值
在->符号后边声明函数返回值的类型,但是不可以为返回值命名
在Rust里面,返回值就是函数体里面最后一个表达式(简单来说就是没有分号)的值
也可以使用return 进行返回
fn plus(x:i32,y:i32)->i32{
x + y;
return x + y;
// 与 x + y 等价
}
3.5 if 表达式 条件必须是bool类型 if 与else 中的返回值的类型必须一样的类型
3.6循环
loop 重复执行作用域的代码 类似于 while true
while条件循环 每次执行循环体之前都要判断一次条件
for
let a = [1,2,3,4,5,6];
for element in a.iter(){
println!("the value is :{}", element)
}
Range 制定一个开始和一个结束 不包含结束 rev可以反转range
for number in (1..4).rev(){
println!("{}",number)
}
四、所有权
Rust的核心特性就是所有权
所有程序在运行时都必须管理它们使用计算机内存的方式
有些语言有垃圾收集机制,在程序运行时,它们会不断地寻找不再使用的内存
在其他语言中,程序员必须显式地分配和释放内存
Rust采用了第三种方式
内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则。
当程序运行时,所有权特性不会减慢程序的运行速度
栈内存:按值的接收顺序来存储,按相反的顺序将他们移除
-添加数据叫做压入栈
-移除数据叫做弹出栈
所有存储在栈伤的数据必须拥有已知的固定的大小
编译时大小未知的数据或运行时大小可能发生变化的数据必须存在堆上
堆内存:当你把数据放入堆上,你会请求一定数量的空间
操作系统在堆上找到一块足够大的空间,把他标记为在用,并返回一个指针,也就是这个空间的地址
这个过程叫做在堆上进行分配
指针是已知固定大小可以把指针存在栈上
访问堆上的数据要比访问栈上的数据慢因为需要通过指针才能找到堆上的数据
所有权解决的问题
跟踪代码的哪些部分正在使用堆上的哪些数据
最小化堆上的重复数据量
清理堆上未使用的数据以避免空间不足
所有权规则
每个值都有一个变量,这个变量是该值的所有者
每个值同时只能有一个所有者
当所有者超出作用域(scope)时,该值将被删除
stack上的数据:复制
Copy trait 可以用于像整数这样完全存放在stack上面的数据
如果一个类型实现了Copy这个trait,那么旧的变量在赋值后仍然可用
如果一个类型或者该类型的一部分实现了Drop trait 那么Rust不允许让在再去实现Copy trait 一些拥有Copy trait的类型
任何简单标量的组合类型都可以是Copy的
任何需要分配内存或某种资源的都不是Copy的
一些拥有Copy的类型
所有的整数类型 bool char 所有的浮点类型
Tuple(如果其所有元素是可以copy的)
所有权与函数
use std::collections::btree_map::Range;
fn main() {
let s = String::from("hello world");
take_ownership(s);
// println!("s:{}",s); 报错
let x = 5;
makes_copy(x);
println!("X:{}",x);
}
fn take_ownership(some_string: String){
println!("some_string:{}",some_string);
}
fn makes_copy(some_number: i32){
println!("some_number:{}",some_number);
}
返回值 与 作用域
函数在返回值的过程中同样也会发生所有权的转移
一个变量的所有权总是遵循同样的模式:
把一个值赋给其他变量时就会发生移动
当一个包含堆数据的变量离开作用域时,他的值就会被drop函数清理,除非数据的所有权移动到另一个变量上了。
4.2 引用与借用
fn main(){
let s1 = String :: from ("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1,len);
}
fn calculate_length(s : &String) -> usize{
s.len()
}
参数的类型是&String 而不是 String
&符号表示引用,允许你引用某些值而不取得其所有权
借用: 把引用作为函数参数这个行为叫做借用
不可修改借用的东西
可变引用
fn main(){
let mut s1 = String :: from ("hello");
let len = calculate_length(&mut s1);
println!("The length of '{}' is {}.", s1,len);
}
fn calculate_length(s : &mut String) -> usize{
s.push_str(" world");
s.len()
}
可变引用有一个重要的限制: 在特定作用域内,对某一块数据,只能有一个可变的引用
-这样做的目的是为了防止在编译时发生数据竞争
以下三种行为会发生数据竞争
两个或多个指针同时访问同一个数据
至少有一个指针写入数据
没有使用任何机制来同步数据的访问
不可以同时有可变引用和不可变引用 但是可以有多个不可变引用
4.3切片
Rust的另外一种不持有所有权的数据类型
字符串切片是指向字符串中一部分内容的引用
形式:[开始索引..结束索引]
开始索引就是切片开始位置的索引值
结束索引是切片终止位置的下一个索引值
五、struct
5.1定义并实例化struct
struct结构体 自定义的数据类型
定义struct
使用struct关键字,并为整个struct命名
在花括号内,为所有字段(Field)定义名称和类型
实例化struct\
取得struct里面的某个值:使用点标记法
struct更新语法
基于某个struct实例创建一个新的实例的时候可以使用更新语法
let user1 = User{
email: String::from("abc@126.com"),
username: String::from("nikky"),
active: true,
sign_in_count: 556,
};
let user2 = User{
email: String::from("AAAA@qq.com"),
..user1
};
Tuple Struct 可定义类似tuple的struct 叫做tuple struct
Tuple struct 整体有名,但是里面的元素没有名
适用:想给整个Tuple起名并让他不同于其他tuple而且又不需要给每个元素起名
定义Tuplestruct:使用struct关键字,后边是名字,以及里面元素的类型
例如:struct color(i32,i32,i32);
5.3 struct的方法
方法和函数类似: fn关键字、名称、参数、返回值
方法与函数不同之处:
方法是在struct的上下文中定义
第一个参数是self 表示方法被调用的struct实例
语法:
impl 结构体名{
fn 方法名(参数)-> 返回值{
代码
}
}
Rust会自动应用或解引用
关联函数:第一个参数不是self
通过结构体名 :: 符号进行调用
六、枚举与模式匹配
6.1 定义枚举
6.2 Option 枚举
定义与标准库中
在预导入模块中
描述了某个值可能存在或不存在的情况
enum Option<T>{
Some(T),
None,
}
若想使用Option<T>中的T 必须将他转换成T
6.3match匹配
允许一个值与一系列模式进行匹配,并执行匹配的代码
使用match必须穷举所有可能性
可以根据实际需求 对其他情况使用_
6.4 if let
if let 只针对一种模式匹配
八、常用的集合(存储在heap伤)
8.1 vector
Vec<T>叫做 vector
由标准库提供
可存储多个值
只能存储相同类型的数据
值在内存中连续存放
创建Vector
Vec::new函数
使用初始值创建Vec<T> 使用vec!宏
更新Vector
向Vector添加元素使用 push方法
删除Vector
与任何struct一样 当Vector离开作用域后
他就被清理掉了
他所有的元素也被清理掉了
读取Vector元素
索引 get方法
fn main(){
let mut v : Vec<i32> = Vec::new(); // 使用了泛型
// let v = vec![1,2,3];
v.push(11);
v.push(12);
v.push(12);
v.push(12);
v.push(12);
// 使用索引方法读取下标元素的值可能会引发异常
let third: &i32 = &v[2];
println!("The Third element is {}", third);
// 使用match不会
match v.get(2){
Some(third) => println!("The Third element is {}", third),
None => println!("no element"),
}
for i in v {
println!("{}",i);
}
}
8.2 String
String类型
来自标准库而不是核心语言
可增长可修改可拥有
创建一个新的字符串
String::new()
也可以使用初始值来创建字符串
to_string()方法
String::from() 函数,从字面值创建String
更新String
push_str()方法 把一个字符串切片添加 不会获得所有权
push() 添加一个字符