Rust语言学习笔记

本文介绍了Rust编程语言的基本语法,包括条件分支(match)、循环结构(loop,for),特殊的移动和克隆行为,所有权规则,引用的概念,以及字符串切片和结构体、枚举的使用。作者强调了Rust中所有权的独特性以及避免空值问题的Option枚举。
摘要由CSDN通过智能技术生成
println!("The value of number is: {number}");

}


基本使用语法,没什么特殊的。像其他语言一样,只会执行第一个条件为 true 的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。



fn main() {
let number = 6;
if number % 4 == 0 {
println!(“number is divisible by 4”);
} else if number % 3 == 0 {
println!(“number is divisible by 3”);
} else {
println!(“number is not divisible by 4 or 3”);
}
}


### 分支 —— match


强大的 Rust 分支结构 match 还没学到


### 循环 —— loop


第一次遇见这个(除了汇编),还没想明白这么设计的原因  
 1、基本语义  
 一直执行,除非遇到`break`,或者控制台手动`Ctrl+C`  
 2、循环返回值  
 `break`后面可以跟返回值



> 
> 这个挺特殊的,有返回值传参,可以发挥一些作用,**但我还没想清楚**  
>  官方举例说:比如检查线程是否完成了任务。然而你可能会需要将操作的结果传递给其它的代码。
> 
> 
> 


3、循环标签与多重循环  
 一般来说,最内层循环不用定义标签,(我测试了一下,写上标签也是可以的,但是没啥用)。因为**标签的作用**是,在最内层循环里,**break加上标签来指明跳出外层循环**,否则默认是跳出当前循环。  
 注意标签的定义,`'counting_up: loop{}`,最前面一个`'`,且**没有闭合**,最后有`:`



fn main() {
let mut count = 0;
'counting_up: loop {
println!(“count = {count}”);
let mut remaining = 10;
loop {
println!(“remaining = {remaining}”);
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!(“End count = {count}”);
}



> 
> //输出是  
>  count = 0  
>  remaining = 10  
>  remaining = 9  
>  count = 1  
>  remaining = 10  
>  remaining = 9  
>  count = 2  
>  remaining = 10  
>  End count = 2
> 
> 
> 


### 循环 —— while


一点特殊的都没有,像Python一样用就行


### 循环 —— for


像Python一样,可以用直接用for遍历集合元素,  
 要循环指定次数,像下面一样用,也和Python一样属于[a,b),取不到b。  
 这里的`..`,可以看作一个符号,不能在中间加个数用来指定步长,语法不允许。  
 要制定步长可以采用 `for i in (0..3).rev().step_by(1)`,.rev()是反转,.step\_by()是指定步长,步长不能为负值。



fn main() {
for number in (1…4).rev() {//反转range
println!(“{number}!”);
}
println!(“LIFTOFF!!!”);
//指定步长
for i in (0…3).step_by(1){
print!(“{}”, i);
}
}


## 所有权(最与众不同的特性)


首先明确3条规则



> 
> Rust 中的每一个值都有一个 所有者(owner)。  
>  值在任一时刻有且只有一个所有者。  
>  当所有者(变量)离开作用域,这个值将被丢弃。(个人理解:一个作用域包括用{}括起来的;函数等)
> 
> 
> 



{                      // s 在这里无效,它尚未声明
    let s = "hello";   // 从此处起,s 是有效的

    // 使用 s
}                      // 此作用域已结束,s 不再有效

### 变量与内存的交互方式


#### 堆上分配的数据:移动


对于String类型这种在堆上分配的数据,离开作用域后,Rust会自动调用drop()函数释放堆内存。(类似C++自动调用析构函数)  
 在采用正常的等号赋值时,如下:



let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);//报错

实际上是将s2指向hello的实际内存位置,没有另外分配内存,此时s1和s2都指向同一位置,而为了保证不出现二次释放的错误,Rust机制使得s1无效,等价于将s1**移动**到s2。  
 如果一定要重新分配堆内存的话,需要用到.clone()方法。



let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);

#### 栈上分配的数据:克隆


对于在栈上分配的数据(就是整型、浮点型、字符型、布尔型这种内存大小确定的),因为给变量分配一个内存的开销很小,只是再执行压栈操作,所以不采用像上文String类型那种移动的方式,而是相当于直接克隆的。


#### 在函数中使用:


根据作用域的知识,我们可以知道,存在堆上的数据,如果直接作为参数传入函数,在函数调用时发生作用域切换,在函数返回时,会被drop掉。  
 而这个参数很有可能在函数返回后还会被用到,我们不希望它被drop。以及我们在函数中创建的一些变量,我们也希望能回到主函数后继续使用。有一个方法,**通过return将值传回去**,使得可以继续使用,但这样未免太麻烦。因此我们要介绍引用机制。


### 引用


**引用**(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与**指针不同**,引用**确保指向某个特定类型的有效值**。指针指向一个实际内存地址,引用只是一个内存单元的别名。(对概念的解释可能不太完整,目前知道怎么用就行了,暂不深究)  
 对于Rust里的引用,**它指向变量,但不拥有所有权,释放引用对变量不产生影响。**  
 创建引用:**&s1**语法让我们**创建**一个指向值s1的引用



fn calculate_length(s: &String) -> usize { // s 是 String 的引用
s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生


变量 s 有效的作用域与函数参数的作用域一样,不过当 s 停止使用时并不丢弃引用指向的数据,因为 s 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。  
 **可变引用**  
 定义变量和传引用时,都加上mut修饰。



fn main(){
let mut s = String::from(“hello”);
change(&mut s);
}

fn change(some_string: &mut String) {
some_string.push_str(“, world”);
}


**出现了新的限制**,  
 1.不能**同时**拥有,两个可变引用,以免出现数据竞争,例如用一个引用修改了原变量,但数据没有及时同步到内存中。(参考体系结构缓存一致性问题)  
 这里同时的意思是:可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用。因为大括号内的引用在退出大括号时会被销毁。  
 2.不能出现可变引用和不可变引用同时存在,同样是因为缓存一致性问题  
 3.可以同时有多个不可变引用,不涉及到修改,有几个都没问题



> 
> 在任意给定时间,**要么** 只能有一个可变引用,**要么**只能有多个不可变引用。  
>  引用必须总是有效的。
> 
> 
> 


**悬垂指针——如何返回一个在函数内创建的变量?**  
 悬垂指针是其指向的内存可能已经被分配给其它持有者。在编译时会报错。



fn dangle() -> &String { // dangle 返回一个字符串的引用
let s = String::from(“hello”); // s 是一个新字符串
&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!返回的引用s 指向的内存被释放了!

//正确使用,直接返回 String
fn no_dangle() -> String {
let s = String::from(“hello”);
s
}


### slice—切片


slice 是一种**引用**,所以它没有**所有权**。  
 **String slice**  
 1.基本使用:返回一个引用,可以是部分的引用,也可以是全部的引用



let s = String::from("hello world");
//部分引用
let hello = &s[0..5];
let world = &s[6..11];
let len = s.len();
//全部引用
let slice = &s[0..len];
let slice = &s[..];

**2.从一个例子体会slice是不可变引用&str**  
 返回一个slice 解决index与内容不匹配导致内存问题,本质上因为slice是一个&str,不可变引用,由于引用间的限制,使得内存不会出错。



fn main() {
let mut s = String::from(“hello world”);

let word = first\_word(&s);

s.clear(); // 错误!

println!("the first word is: {}", word);

}


错误原因:clear 需要清空 String,它尝试获取一个可变引用。  
 在调用 clear 之后的 println! 使用了 word 中的引用,所以这个不可变的引用在此时必须仍然有效。Rust 不允许 clear 中的可变引用和 word 中的不可变引用同时存在,因此编译失败。  
 **3.写函数签名时,参数用&str,因为String可以轻松转换为&str**  
 当**传参要传一个String时**,可以用slice传一个&str,这样函数参数就兼容String类型和字符串字面量



fn main() {
let my_string = String::from(“hello world”);
// first\_word 适用于 String(的 slice),部分或全部
let word = first_word(&my_string[0…6]);
let word = first_word(&my_string[…]);
// first\_word 也适用于 String 的引用,
// 这等价于整个 String 的 slice
let word = first_word(&my_string);

let my_string_literal = "hello world";
// `first\_word` 适用于字符串字面值,部分或全部
let word = first\_word(&my_string_literal[0..6]);
let word = first\_word(&my_string_literal[..]);
// 因为字符串字面值已经字符串 slice 了,这也是适用的,无需 slice 语法!
let word = first\_word(my_string_literal);

}


**4.要想在函数里修改string,不能用&str**  
 传入&mut String可变引用,类似上文可变引用部分



fn main(){
let mut my_string = String::from(“Hello, world!”);
my_string = myreplace(&mut my_string);
println!(“{my_string}”);
}
//这里之所以有返回值,是因为replace()方法会重新分配一段内存
//如果用push_str()方法,可以去掉返回值
fn myreplace(s:&mut String) -> String{
s.replace(“,”,“:”)
}


**字符串字面量本质上是slice**  
 基本数据类型里提到的**字符串字面量**,就是一个**slice**,类型&str,它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;因为&str 是一个不可变引用。  
 **怎么在函数里修改字符串字面量(&str,不可变引用)**



//来自copilot,测试有效
fn main() {
let original_string = “Hello, world!”;
let modified_string = replace_char_at(original_string, 5, ‘X’);
println!(“{}”, modified_string); // prints “HelloX world!”
}
//就是在函数里手动复制这个字符串,并在需要的地方修改
fn replace_char_at(s: &str, idx: usize, c: char) -> String {
let mut result = String::with_capacity(s.len());
for (i, d) in s.char_indices() {//s.char_indices()返回迭代器(index, char)
result.push(if i == idx { c } else { d });
}
result
}


**数组的slice**



let a = [1, 2, 3, 4, 5];
let slice = &a[1…3];


## 结构体


整个结构体都是可变的。  
 没什么特别的,就是当结构体包含引用时,要指明生命周期。还没学到  
 定义:



struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}


例化:



fn main() {
let mut user1 = User {
active: true,
username: String::from(“someusername123”),
email: String::from(“someone@example.com”),
sign_in_count: 1,
};
user1.email = String::from(“anotheremail@example.com”);
}
//通过函数返回一个结构体的实例,以及初始化简写(字段名和值名一样,可以简写,顺序无关)
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
//用其他实例的值,新建一个实例
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
};


省略元组结构体、没有字段的类单元结构体  
 方法:



#[derive(Debug)] //为了便于调试,一般都会写上
struct Rectangle {
width: u32,
height: u32,
}
//用impl关键字,定义在Rectangle上下文中,
//方法的第一个参数一定是&self,可用来访问结构体定义的字段
//如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
//使用函数
let rec = Rectangle{width:1,height:2};
let area = rec.area();


关联函数  
 self不作为第一参数  
 经常被用作返回一个结构体新实例的构造函数。



//例如square关联函数,它接受一个维度参数并且同时作为宽和高,这样可以更轻松的创建一个正方形 Rectangle
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
//使用关联函数
let sq = Rectangle::square(3);


打印结构体的内容  
 在结构体定义之前**加上外部属性** #[derive(Debug)]



#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
//方式1 println!宏接收一个引用,打印到标准输出控制台流(stdout)
println!(“rect1 is {:?}”, rect1);
//方式2 dbg! 宏接收一个表达式的所有权,打印到标准错误控制台流(stderr),还会打印出所在行号
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale),//打印过程值,便于调试
height: 50,
};
dbg!(&rect1);
}


## 枚举


### 枚举类似结构体,成员可以任意类型


每一个枚举的成员,都可以是不同的类型,可以将数据附加到枚举的每个成员上



enum Message {
Quit, //没有关联任何数据
Move { x: i32, y: i32 }, //类似结构体。
Write(String),//包含单独一个 String
ChangeColor((i32, i32, i32),(i8,i8)),//包含两个元组
}


### 枚举的方法,适用于所有成员


可以定义枚举的方法,这样有一个好处,就是写一个方法适用于这个枚举里的所有成员



impl Message {
    fn call(&self) {
        // 在这里定义方法体
    }
}
//通过 :: 例化一个枚举成员
let m = Message::Write(String::from("hello"));
m.call();

### Option 枚举和其相对于空值的优势


使用Option枚举可以避免由空值引发的错误。  
 只要一个值不是 Option 类型,你就 可以安全的认定它的值不为空。  
 必须显式的将一个可能为空的值,放入对应类型的 Option 中。接着,当使用这个值时,**必须明确的处理值为空的情况**。  
 如何使用,待补充


## match 与 if let


match用法很类似switch to,但是match是穷尽的,所以分支里要考虑所有的情况,否则会编译失败。  
 如果不是所有的情况都有不同的处理,可以用占位符集中处理。  
 可以匹配枚举成员中的值



#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
Alabama,
Alaska,
// --snip–
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

24年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-nOiqK24g-1712950174304)]

[外链图片转存中…(img-uHEC3xwZ-1712950174304)]

[外链图片转存中…(img-fcR3qBxg-1712950174304)]

[外链图片转存中…(img-QekUUkoS-1712950174305)]

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

img
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值