rust基础

rust基础

前言

本篇文章是《Rust程序设计语言》第一~六章笔记

一、编译命令与包管理工具cargo

1、不使用cargo的情况下,用命令rustc进行编译:

$ rustc hello.rs

2、使用cargo的情况下

$ cargo new demo2     # 此命令用于新建一个cargo项目
     Created binary (application) `demo2` package

$ cargo build #此命令用于编译
$ cargo run #此命令可先编译后运行该项目

#build或run的运行要在项目目录或src目录下,
#其中run后面可以空着,也可以跟上项目名或main.rs。
#build命令后面什么都不用跟,跟着反而报错。

总结:
可以使用 cargo build 构建项目。
可以使用 cargo run 一步构建并运行项目。
可以在不生成二进制文件的情况下构建项目并使用 cargo check 检查错误。
有别于将构建结果放在与源码相同的目录,Cargo 会将其放到 target/debug 目录。
可以使用 cargo build --release 来优化编译项目。Cargo 会将其放到 target/release 目录。

二、实现猜数游戏

1、如何从控制台输入一个数字?如何进行类型转换?

首先应声明并初始化一个String类型对象
let mut str =String::new();

核心方法是
io::stdin().read_line(&mut str)//输入
str.trim().parse()//转换:关键在于trim方法,输入的数字字符串结尾处带一个回车,要trim修剪掉

rust是链式编程,在方法后面可直接继续.expect(“xx exception”)进行异常处理

2、如何引入随机数的依赖并生成随机数?

在Cargo.toml文件的[dependencies]标签下写rand的依赖:
rand = “0.8.3”
然后在不改动代码的情况下cargo build构建,首次构建cargo会自动帮下载依赖。

main.rs中引入:use rand::Rng;//todo:vscode能否用快速修复来引入依赖?
核心方法是:
rand::thread_rng().gen_range(1…101);//todo:这里的1…101是什么意思?

3、rust的控制语句:

和其他语言一样也有while和if else
但如果用while true 实现永远循环会报警告,提示应该使用loop关键字。

todo:除了使用if else实现分支,还可用match

use std::io;
use rand::Rng;

//1. 输入数字,String转int
fn get_number()->i32{
    println!("Please input your number:");
    let mut guess_number = String::new();
    io::stdin().read_line(&mut guess_number).expect("Failed to read line");
    println!("your guessed : {} ",guess_number);
    let guess_number :i32= guess_number.trim().parse().expect("ClassCastException");//这里运用rust的shadowing特性
    return guess_number;
}

fn main() {
    println!("Welcome to guess game!");
    //2.生成随机数,声明猜测的数
    let target = rand::thread_rng().gen_range(1..101);
    let mut guess_int :i32;
    //3. 判断是否猜对
   loop{
        guess_int = get_number();
        if guess_int>target {
            println!("too big");
        }else if guess_int<target{
            println!("too small");    
        }else if guess_int==target{
            println!("right");
            break;
        }
    }
    println!("Game over,bye!")
}

三、基本概念

1.变量

1.1、什么是可变/不可变?

let num = 3;
num = 4;//编译无法通过:num是不可变的变量

let mut str = "hello";//mut关键字让变量可被再次赋值
str = "institution";//可以再次赋值

1.2、常量和不可变变量的区别是什么?

const i :i32 = 7;
// let mut k = 5;//报错
fn main() {
    let mut num = 5;
    const j :i32 = 19;
}

常量的数据类型不可被省略。
常量可以放到全局作用域(变量不可),常量不能mut,常量的值在编译期必须是明确的,不允许在运行时赋值。

1.3、什么是shadowing隐藏/重影?

rust把赋值叫做绑定,所以shadowing重影也可以叫做重新绑定
经常有这样的场景:某个string类型的变量我们要转换为int类型,要为他们分别命名,如price_str 、price_int
在rust中可以用shadowing巧妙地避免这样的冗余。

let mut guess = String::new();
let mut guess = 10;

2、数据类型

2.1、标量/基本类型有哪几种?

整型、浮点型、布尔、字符四种

2.2、整型有哪几种?默认是哪种?各自的取值范围是什么?

有符号:i8-128,无符号:u8-128;范围分别是-2^(n-1) 到[2^(n-1)]-1 和0到(2^n)-1
isize 和usize是取决于计算机硬件架构的类型,可能是32位或64位。

2.3、浮点型有哪几种?默认是哪种?运算时要注意什么?

f32和f64两种,默认是f64,因为现代CPU架构大多是64位了。
运算时要注意精度问题。

2.4、布尔太简单了,略过

2.5、字符涉及到编码比较复杂,第8章会讨论。

2.6、复合类型有哪些?复合类型存在堆还是栈中?

复合类型有:元组、数组。复合类型存在栈中。(todo:一定是这样的吗?如果元组、数组的元素是引用类型呢?)

2.7、如何声明并初始化元组,如何解构元组?如何理解元组?

let t :(i32,f64,u8)=(90,8.5,2);
let (x,y,z)=t;//解构元组

元组可以被理解为简易的结构体和对象,可用于作为函数的多返回值。

2.8、元组是否有索引?

有索引。上一条的例子中可用下标访问元组的元素。如t.0

2.9、数组的长度可变吗?

数组长度不可变。Vector长度可变,更灵活。

2.10、如何声明和初始化数组?

let arr :[i32;5]=[1,2,3,4,5];

2.11、如何访问数组的元素?

arr[0];//中括号里的数字是下标

2.12、数组的元素可变吗?

在声明的时候用了mut关键字就是可变的,可重新赋值,如

let mut arr :[i32:3]=[1,2,3];
arr[0] = 98;
println!("{}",arr[0]);//输出98

3、函数

3.1、rust中的函数可以省略return,直接写一个表达式用于返回
3.2、函数是否支持多个返回值?可以用元组实现多返回值的效果,调用后需要解构。

4、控制

4.1、打印99乘法表
4.2、打印n阶斐波那契数列
4.3、打印火箭发射倒计时

use std::time::Duration;
use std::thread;
const ci :i32 = 7;//全局作用域允许声明常量,常量必须显式声明其类型
// let mut k = 5;//报错,全局作用域不可声明变量
fn main() {
    let mut num = 5;
    const j :i32 = 19;//在函数内的常量也必须显式声明其类型


    let mut arr :[i32;5]=[6,7,8,9,0];//数组是基本类型中的复合类型,存在栈中,长度不可变
    println!("first element of arr is {} ",arr[0]);//可以用下标查询数组中的元素
    arr[0]=2;//如果数组是mut,则可以修改数组中的元素值,但数组长度是不可改变的
    println!("first element of arr is {} ",arr[0]);
    multi_table();
    fibonacci(30);
    rocket();
}

fn multi_table(){
    for i in  1..10{
        for j in 1..i+1{
            print!("{} × {} = {}     ",i,j,i*j);
        }
        println!();
    }
}

fn fibonacci(n :i32){
    let mut i:i32 = 0;
    let mut j:i32 = 0;
    let mut ni:i32 = 1;
    while ni <= n{
        if i == 0 {
            print!("0 , ");
            i = 1;
        }else if i == 1{
            print!("1 , 1 , ");
            i = 2;
            j = 1;
        }else {
            print!("{} , ",i);
            let k = j;
            j = i;
            i = i +k;
        }
        ni += 1;
    }
    println!();
}

fn rocket(){
    for i in (1..11).rev(){
        println!("{}",i);
        thread::sleep(Duration::from_millis(1000));
    }
    println!("0 !   rocket lift!");
}

四、所有权

1、所有权概念及其规则

1.1~1.4 : 什么是所有权?

1.1、所有权规则系统要解决的是堆中变量的内存分配和回收的问题。

1.2、对于String这样未知大小的变量,且运行时大小可能会改变的变量,必须在堆中开辟空间进行存储。
其大小改变时,可能还需要重新开辟空间。

1.3、其他语言对于String的处理,主流的做法是两种:
一是由程序员为复杂变量手动请求并分配内存,用完此变量后再释放/回收内存。
二是使用垃圾回收机制,由GC自动回收内存。

1.4、手动回收内存曾经是一个容易导致问题的操作,如果忘记回收会浪费内存,如果过早回收会使变量无效,如果重复回收还会导致bug。

1.5、rust采用了与其他语言完全不同的策略:所有权规则,所有权规则是什么?

1.5.1、rust的每个值都有一个被称为其所有者的变量。
1.5.2、值在任意时刻有且只有一个所有者。
1.5.3、当所有者离开作用域,这个值将被丢弃。

1.6、基本类型(标量和复合类型)和引用类型都适用所有权规则,那么区别是什么?

基本类型变量存在栈上,使用时是值传递。
引用类型变量存在堆上,所有者的本质是个指针,使用时传递了指针。
尝试用代码和图来说明。

//基本类型---------------------------------------
fn main() {
    {
        let a = 5;
        let b = a;
        println!("{}",a);//a仍然拥有所有权
    }
    //println!("{}",a);此处编译报错
}
//-----------------------------------------------

//引用类型---------------------------------------
fn main() {
    {
        let a = String::from("hello");
        let b = a;//a会把所有权移交给b
        // println!("{}",a);此处编译报错:a已被moved
        println!("{}",b);
    }//b被丢弃
}
//-----------------------------------------------

1.7、如果确实需要拷贝一个引用类型变量,需要用clone方法

基本类型都实现了Copy trait,直接用=赋值就能实现完全的拷贝,原来的变量不会被丢弃。包括元组和数组。

1.8、函数传参与赋值类似,也会移交引用类型变量的所有权。

1.9、总结:

fn main() {
        let a = 5;//基本类型
        let b = a;//基本类型实现了Copy trait,在赋值后能直接实现拷贝,原来的a还能继续使用
        println!("{}",a);//这里是可以使用的
        println!("{}",b);
        prt_i(b);
        println!("{}",b);
        //----------------------------------------------------------------------------------------------------

        let a = String::from("hello");
        let b = a;//引用类型在赋值操作之后,a移交给了b,a就失效了。
        let c = b.clone();//想真的实现拷贝需要用clone方法
        // println!("{}",a);//a已经被移交moved,不能再使用
        println!("{}",b);
        println!("{}",c);
        prt_str(b);//这里传参操作之后,b移交给了函数,b 就失效了。
        // println!("{}",b);//b已经移交moved,不能再使用
        

        //---------------------------------------------------------------------------------------------------
        let arr = [1,2,3];//数组和元组也是存于栈中的,属于基本类型中的复合类型。
        let brr = arr;//数组的赋值操作也能实现拷贝
        println!("{},{}",arr[0],brr[1]);
}

fn prt_str(str :String){
    println!("prt : {}",str);
}

fn prt_i(i:i32){
    println!("prt : {}",i);
}

2、引用与借用

2.1、什么是引用与借用?

引用就是&,允许使用但不获取所有权的一个指针。
借用就是创建引用的过程。

2.2、创建一个函数,传入一个字符串的引用,返回该字符串的长度。

fn main() {
    let str = String::from("hello");
    let len_of_str = get_len(&str);
    println!("len_of_str is {}",len_of_str);
}
fn get_len(str :&String) -> usize {
    str.len()
}

2.3、引用默认不可变,如何允许借用过程中修改变量?

创建引用和传参时使用关键字mut。

2.4、总结

2.4.1、一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。
2.4.2、在给定的时间内,要么只有一个可变引用,要么只有多个不可变引用。
2.4.3、引用必须总是有效的。不允许出现悬垂引用(空指针)。

fn main() {
    let mut mystr = String::from("hello");
    let len_of_str = get_len(&mystr);//借用并传入函数,不移交所有权
    println!("len of {} is {}",mystr ,len_of_str);//str的所有权还在,这里能直接使用

    let borrow =&mut mystr;//创建可变引用
    change(borrow);//把可变引用传入
    change(borrow);//把可变引用传入,这里是最后一次使用,borrow的作用域到此为止
    println!("after change , str is {}",mystr);//这里是不可变引用,为什么?println!宏实际生成的代码使用了&x,不可变借用
    //  println!("{}",borrow);//如果最后一次使用在这行会导致同时存在可变和不可变引用,编译错误。
}
fn get_len(mystr :&String) -> usize {
    mystr.len()
}

fn change(mystr :&mut String){
    mystr.push_str(",world");
}

// fn dangle()-> &String{//返回一个字符串的引用
//     let s = String::from("null pointer~~");//生成一个字符串的变量
//     return &s//返回字符串变量的引用
// }//这里s离开作用域,将被丢弃,而其引用就会变成悬垂引用/空指针,危险。

fn no_dangle()-> String{//返回一个字符串
    let s = String::from("not pointer~~");//生成一个字符串的变量
    return s//返回字符串,移交所有权
}

3、slice

3.1、slice是什么?

slice是一个集合一部分的引用,slice没有所有权。

3.2、字符串slice的用法

    let s = String::from("lotus miner");
    let s1 :&str = &s[0..5];
    let s2 = &s[5..];
    println!("{} and {}",s1,s2);
//字符串字面量就是字符串slice,所以默认是不可变的。字符串slice的类型是 &str

3.3、实现一个函数,返回一段话中的第一个单词。

fn main() {
    let s = String::from("lotus miner");
    let s1 :&str = &s[0..5];
    let s2 = &s[5..];
    println!("{} and {}",s1,s2);
    let res = first_word(&s);
    println!("first word is {}",res);
}
//字符串字面量就是字符串slice,所以默认是不可变的。字符串slice的类型是 &str

fn first_word(mystr:&String)-> &str{
    let bs = mystr.as_bytes();
    for (i,&item) in bs.iter().enumerate(){
        if item ==b' '{
            return &mystr[0..i];
        }
    }
    &mystr[..]
}

五、结构体

1、定义并实例化结构体

1.1、定义一个User结构体并实例化,如何打印?

fn main() {
    let user1 =User{
        user_name: String::from("fengbaobao"),
        email: String::from("fbb@163.com"),
        active: true,
        balance: 10086.9
    };
    println!("{:?}",user1)//:?告诉println!宏需要按Debug格式打印结构体
}
#[derive(Debug)]//此外部属性允许println!宏打印Debug格式的结构体对象
struct User{
    user_name: String,
    email: String,
    active: bool,
    balance: f64,
}

1.2、user对象的可变性

上面的user1是不可变的,用mut令其可变:

    let mut user1 =User{//mut关键字使user1可变
        user_name: String::from("fengbaobao"),
        email: String::from("fbb@163.com"),
        active: true,
        balance: 10086.9,
    };
    user1.balance = 9988.9;//更改其中某个字段的值

1.3、为User结构体实现构造函数,并提供简写方法

fn build_user(user_name: String,email: String)-> User{
    User{
        user_name,//简写
        email:email,//完整写法
        active: true,
        balance: 0.0,
    }
}

1.4、如何使用结构体更新语法从user1创建user2?这个过程中user1的所有权是怎样变化的?

    let user2 = User{
        user_name:String::from("jay chou"),
        ..user1
    };//因为email是引用类型,user1的email赋值给user2之后,user1的所有权就会移交给user2
    // println!("{:?}",user1)//所有权已经移交,报错
    println!("{:?}",user2);
    let user3 = User{
        user_name:String::from("zhangchulan"),
        email:String::from("zcl@163.com"),
        ..user2
    };//因为user3的引用类型字段都是新生成的值,其他基本类型字段拷贝了user2的值,所以user2的所有权还在
    println!("{:?}",user2);
    println!("{:?}",user3);

1.5、结构体数据能否使用&str,为什么?

User中的user_name和email字段,刻意使用了拥有所有权的String类型而非&str类型,这是因为&str是一种slice,是对值的引用,不拥有所有权,可以使用&str但需要标注生命周期,此功能在第十章讨论。

1.6、什么是元组结构体和类单元结构体?

只有字段类型,没有字段名的与元组类似的结构体,叫做元组结构体,例如:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

没有任何字段的结构体称为类单元结构体,可用于实现trait(第十章介绍),实现方法,例如:

struct UserService

2、示例:定义一个矩形结构体并提供计算其面积的方法

fn main() {
    let r1 = Rectangle{
        width:9,
        height:8,
    };
    println!("r1.area is {}",r1.get_area());
}

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

impl Rectangle{
    fn get_area(&self)->i32{
        self.width * self.height
    }
}

六、枚举和match

1、枚举

1.1、如何定义枚举,其结构是怎样的,如何调用?

fn main() {
    let homeIpKind = IpAddrKind::V4;
    let move1 = Message::Move{x:7,y:23};
    let s1 = Message::Show(String::from("institution"));
}

enum IpAddrKind{
    V4,//枚举的成员
    V6,//注意枚举的成员没有数据类型
}//todo:标准库中的IpAddr枚举是怎样的?

enum Message{//枚举成员可以包含任意类型和数量的数据
    Quit,//没有关联数据的成员
    Move{x:i32,y:i32},//此成员包含了一个匿名结构体
    Show(String),//此成员包含一个String
    ChangeColor(i32,i32,i32),//此成员包含三个i32
}

标准库中的IpAddr

1.2、Option枚举和其相对于空值的优势是什么?prelude是什么?

prelude是标准库中默认导入的crate。Option就是包括在其中的。
Option是标准包中的枚举,包含Some和None两个成员。
Some不能和i8进行运算,必须进行类型转换才可以。
只要一个值不是Option,就可以认定它的值非空。

举个栗子:
从链上获取某个账户的余额,可能获取到了,也可能获取不到。
所以我们可以用Option去接这个余额。
接到之后必须处理可能为空的情况。

let balance: Option<f64> = getBalanceFromChain(); 

1.3、如何从 Some 成员中取出 T 的值来使用它呢?

Option 枚举拥有大量用于各种情况的方法:你可以查看它的文档。熟悉 Option 的方法将对你的 Rust 之旅非常有用。

2、match

2.1、match基本使用示例

rust中有表达式和语句的概念。
match是一个表达式,可以把整个match视为一个值或一个对象。
match的每个分支的最后一句是一个表达式,中间可以有语句。
match分支可以从枚举成员的绑定数据中取出数据并在match分支内的语句中使用。

enum WorkDay{
    Monday(String),
    Tuesday,
    Wednesday,
    Thursdag,
    Friday(Lunch),
}

fn value_of_workday(wd: WorkDay)-> u8{
    match wd {
        WorkDay::Monday(plan) => {
            println!("Plan is {} this week!",plan);    
            1
        },
        WorkDay::Tuesday => 2,
        WorkDay::Wednesday => 3,
        WorkDay::Thursdag => 4,
        WorkDay::Friday(lunch) => {
            println!("Friday is relaxing. Eat some {:?}",lunch);
            5
        },
    }
}

#[derive(Debug)]
enum Lunch{
    Noodles,
    Rice,
}

2.2、match最重要的特性是什么?

match最重要的特性是穷尽,必须考虑所有可能性。例如上面的例子中,必须考虑周一到周五的所有情况,才算穷尽了工作日的所有可能性。下面的代码会报错:

fn value_of_workday(wd: WorkDay)-> u8{
    match wd {
        WorkDay::Tuesday => 2,
        WorkDay::Wednesday => 3,
        WorkDay::Thursdag => 4,
    }
}

2.3、match与Option的惯用套路是什么?

假设空投规则是:从链上获取用户余额,如果有余额就加100,没有就不做操作。

fn air_drop (balance: Option<i32>)->Option<i32>{
    match balance{
        None => None,
        Some(i) => Some(i + 100),
    }
}

这样的好处是,因为match会穷尽所有情况,所以必须处理空值,避免出现空指针。

2.4、通配符和占位符在match中如何使用?

比如跨城通勤,周一要从家里去公司,周五要从公司回家,其他日子都在公司或酒店待着。

fn action(day: WorkDay){
    match day{
        WorkDay::Monday(_) => go_to_company(),//不需要接收绑定数据,用占位符
        WorkDay::Friday(_) => go_home(),
        other => work(other),//除了周一周五特殊,其他日子都执行同样操作,用通配符
    }
}
fn go_home(){}
fn go_to_company(){}
fn work(w:WorkDay){}

再比如只有周一需要做计划,实现一个计划方法。

fn work_plan(day: WorkDay){
    match day{
        WorkDay::Monday(plan) => handle_plan(plan),
        _ =>(),//通配符
    }
}
fn handle_plan(s:String){}

2.5、if let是如何简化match的?

上面的计划方法可以用if let简写,如下所示:

fn work_plan(day: WorkDay){
    if let WorkDay::Monday(plan) = day {
        handle_plan(plan);
    }
}

3、综合运用match、枚举、结构体的小例子

实现如下需求:定义职业枚举,包含战法牧三种职业结构体。

fn main() {
    let sun_shangxiang = new_player(Profession::Doctor);
    let guo_jia = new_player(Profession::Master);
    let zhang_liao = new_player(Profession::Warrior);
    show_player(&sun_shangxiang);//引用,不移交所有权
    // reward_player(sun_shangxiang);//移交所有权,下面vector存储报错。
    //用vector存储这些角色
    let mut player_group = vec![sun_shangxiang,guo_jia,zhang_liao];//发现特点:所有权移交的时候可以把原先不可变的变量变成可变的
    //遍历这些角色,给他们buff:hp加5
    for p in &mut player_group{
        p.hp +=5;
    }
    //遍历角色,打印
    for h in player_group{
        println!("{:?}",h);
    }
    // show_player(&sun_shangxiang);//所有权已经移交,无法再次使用
    
}

//一、首先定义Player结构体,字段包括hp、mp、攻、防、敏、智等
#[derive(Debug)]
struct Player{
    hp:i32,
    mp:i32,
    attack:i32,
    defense:i32,
    agility:i32,
    intelligence:i32,
}
//二、定义枚举,里面规定了战法牧三种Player
enum Profession{
    Warrior,
    Master,
    Doctor,
}
//三、构造方法可以匹配枚举中的职业,实例化对应的对象
fn new_player(player:Profession)->Player{
    match player{
        Profession::Warrior => build_player(100, 30, 100, 80, 30, 20),
        Profession::Master => build_player(70, 70, 10, 10, 50, 100),
        Profession::Doctor =>build_player(80, 40, 60, 50, 50, 70),
    }
}
fn build_player(hp:i32,mp:i32,attack:i32,defense:i32,agility:i32,intelligence:i32)->Player{
    Player{
        hp,//形参与字段同名,可简写
        mp,
        attack,
        defense,
        agility,
        intelligence,
    }
}

fn show_player(p:&Player){
    println!("Hello, I am {:?}",p);
}
fn reward_player(p:Player){
    print!("{:?} is rewarded",p);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值