8万字带你入门Rust

Rust

?? 学习建议:

  • 先从 整体出发,不要让自己陷入到细节中去
  • 和自己已知的知识建立联系
  • rust 和go一样采用 组合的手段实现代码复用,不要深思为什么不是继承
  • 学会阅读源码,从源码中学习
  • Rust设计哲学

使用 cargo new 项目名

在终端中构建项目

使用 cargo build 来构建和运行项目

也可以使用 cargo run 来完成编译和运行任务

第一个程序:

fn main(){
    println!("hello world!");
}

Rust中变量默认都是不可变的,如果要改变使用mut关键字来修饰变量就可以改变了。

用let创建变量:

let foo = 5;//foo是不可变的
let mut bar = 5; //bar是可变的

& 引用在默认情况也是不可变的

&mut guess //声明一个可变的引用 
&guess //声明一个不可变的引用

示例:

use std::io; // 导入包
fn main() {
println!("Guess the number!"); //输出
println!("Please input your guess:");
let mut guess =String::new();//声明一个可变的变量并且绑定一个空白字符串
io::stdin().read_line(&mut guess).expect("Failed to read line");//从键盘获取输入
//read_line它读取的同时还会返回一个值 io::Result值(它一个枚举类型),它有OK和ERR两个变体,如果读取失败就会返回expect并输出里面的内容,没有编写expect函数会出警告
println!("You guessed: {}", guess); // 这里面的花括号是一个占位符,打印几个值就用几个花括号{}
let x=50;
let mut y = 100;
println!("x= {}, y = {}",x,y);//多个输出
println!("game over!");
}


use std::io;

fn main(){

println!("猜数!");

println!("猜测一个数字:");

let mut guess=String::new();//创建一个空白字符串并绑定到变量guess

io::stdin().read_line(&mut guess).expect("无法读取行!");
//下面这行和上面那行是等价的没有使用 use 导入的话,就需要使用下面的方式
// std::io::stdin().read_line(&mut guess).expect("无法读取行!");
println!("你猜测的数字是:{}",guess);

}



# 使用rand包需要在cargo.toml文件中将rand包声明为依赖
rand="0.3.14"  
# 添加完成后使用 cargo build 重新构建这个项目

升级依赖包使用 cargo update 命令升级依赖包

// 猜数游戏示例
use std::io;
use rand::Rng;//导入随机数模块  trait
use std::cmp::Ordering;
fn main() {
println!("Guess the number!");

let numrand=rand::thread_rng().gen_range(1,101);//左闭右合区间

println!("Please input your guess:");

let mut guess =String::new();

io::stdin().read_line(&mut guess).expect("Failed to read line");
//rust 允许使用同名的新变量来隐藏旧变量的值
//从这行之后,这个guess就不是上面那个变量了,而第二个guess是原来上面的那个变量
//用户要输入过程中按的回车键,会导入我们的输入字符串额外多出一个换行符,所以使用 trim()函数来去除
//trim()就是去掉字符串前后的空格
//parse()方法会把字符串解析成数值类型
let guess:u32=guess.trim().parse().expect("plasce type a number!");

println!("You guessed: {}", guess);

println!("随机数字为:{}",numrand);
match guess.cmp(&numrand){//和谁匹配就执行谁
Ordering::Less => println!("too small!"),
Ordering::Greater => println!("to big!"),
Ordering::Equal => println!("you win!"),
}

println!("game over!");

}

上面的代码里面有一个概念叫做 隐藏(shadow):

rust 允许使用同名的新变量来隐藏旧变量的值

使用循环来实现多次猜测
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main() {
println!("Guess the number!");

let numrand=rand::thread_rng().gen_range(1,101);//左闭右合区间

loop{//循环起始位置
println!("Please input your guess:");
let mut guess =String::new();

io::stdin().read_line(&mut guess).expect("Failed to read line");

let guess:u32=match guess.trim().parse(){
Ok(numrand) => numrand,
Err(_) => continue,//不需要错误信息可以下划线忽略
};//这里把 expect方法换成了match表达式

println!("You guessed: {}", guess);

println!("随机数字为:{}",numrand);

match guess.cmp(&numrand){
Ordering::Less => println!("too small!"),
Ordering::Greater => println!("to big!"),
Ordering::Equal => {
    println!("you win!");
    break;//猜对了就退出
}
}
}
}

这里把 expect方法换成了match表达式

let guess:u32=match guess.trim().parse(){
Ok(numrand) => numrand,
Err(_) => continue,//不需要错误信息可以下划线忽略
};

continue用法和C++、go、java等语言一样!

Rust保留的关键字

关键字

描述

as

执行基础类型转换,消除包含条目的指定 trait 的歧义,在 use 与 extern crate 语句中对条目进行重命名

break

立即退出一个循环

const

定义常量或者不可变祼指针

continue

继续下一次循环迭代

crate

连接一个外部包或一个代表了当前包的宏变量

dyn

表示 trait 对象可以进行动态分发

else

if 和 if let 控制结构的回退分支

enum

定义一个枚举

extren

连接外部包、函数、变量

false

字面量布尔值假

fn

定义一个函数或者函数指针类型

for

在迭代元素上进行迭代,实现了一个 trait,指定一个高阶生命周期

if

基于条件表达式的分支

impl

实现类型自有的功能或者 trait 定义的功能

in

for循环语法的一部分

let

绑定一个变量

loop

无条件循环

match

用模式匹配一个值

mod

定义一个模块

move

让一个闭包获得全部捕获变量的所有权

mut

声明引用、祼指针或者模式绑定的可变性

pub

声明结构体字段、impl块或模块的公共性

ref

通过引用绑定

return

从函数中返回

Self

指代正在其上实现 trait 的类型别外 S是大写的=

self

指代方法本身或者当前模块 s是小写的

staticc

全局变量或者持续整个程序执行过程的生命周期

struct

定义一个结构体

super

当前模块的父模块

trait

定义一个 trait

true

字面量布尔真

type

定义一个类型别外或关联类型

unsafe

声明不安全的代码、函数、trait或实现

use

把符号引入作用域中

where

声明一个用于约束类型的 从句

while

基于一个表达式结果的条件循环

未来可能会使用的保留关键字:

abstract

async

become

box

do

final

macro

override

priv

try

typeof

unsized

virtual

yield

通用编程概念

fn main(){
println!("hello world!");
//默认不可变
let mut x = 5;

println!("x= {}",x);

x=10;

println!("x= {}",x);

//常量:它不可以使用mut关键字,常量永远都是不可变的
//声明一个常量使用const关键字
//常量只可以绑定到常量表达式
//RUST中常量 一般使用大写字母
const MAX_POINTS:u32=1000;


//Shadowing
let x = 5;
println!("{}",&x);//5
let x =x+1;
println!("{}",&x);//6
let x =x+2;
println!("{}",&x);//8

let spaces_str="     ";
let spaces_num=spaces_str.len();
println!("{}",spaces_num);//5

使用 const来定义一个常量,不能使用let关键字来定义常量;

不能使用 mut 关键字修饰一个常量,常量总是不变的!

在 rust 中,变量名一般都有大写!

隐藏机制不同于将变量声明为 mut 的 !
重复使用 let 关键字会创建出新的变量,因此可以复用的时候改变它的类型!

rust数据类型

标量类型和复合类型

注意:Rust是一门静态类型语言,这意味着它在编译程序的过程中需要知道所有变量的具体类型。

标量类型:单个值类型的统称; 4种:整数、浮点数、布尔值、字符

/*标量类型:整数,浮点数(f32,f64默认的),布尔值,字符(char),字符串,元组,枚举
复合类型:数组,结构体,指针,元组,枚举
u32:无符号整数类型,占32位空间
u8,u16,u32,u64,u128 无符号
i8,i16,i32,i64,i128 有符号
无符号以U开头,有符号以I开头
整数默认类型是i32
*/
//isize 和 usize 这两种是由运算程序的计算机硬件决定的
let guess:u32 = "42".parse().expect("Not a number!");
println!("{}",guess);
//rust声明的变量没有使用会有警告
let x:f32 = 3.0;
let x:f64 = 4.0;
let b:bool = true;
let b:bool =false;
let x='z';
let y:char ='y';
let z='??';//也可以存放这种

整数类型有: (它们占的空间大小也就是后面对应的数字单位为bit)

  • 无符号:u8,u16,u32,u64,usize
  • 有符号:i8,i16,i32,i64,isize

usize和isize:取决于程序运行的目标平台;在64位架构上就是64bit,而32位架构上就是 32bit

rust默认的整数字面量是:i32

浮点数类型:

  • f32
  • f64 (默认)

复合类型:可以将多个不同类型的值组合为一个类型;2种:元组(tuple) 数组 (array)

//复合类型
//Tuple类型可以将多个类型的值放在一个类型里面,和C++中的元组类似
//tuple的长度的固定,一旦创建就不能改变
//如果不明确是什么类型,可以使用_来代替
let tup: (i32,char,bool)=(15,'a',true);
let tup:(_,_,bool)=(3.14,"boy",false);
let tup=(50,1.25,1);//也可以使用模式匹配
let ont=tup.0;
let two=tup.1;//也可以通过点来进行访问
let (x,y,z)=tup;
println!("{},{},{}",x,y,z);//获取tup的值 解构:将元组拆解为n个不同的部分
//数组
//数组和C++中的差不多
//长度也是固定的
// Vertor更加灵活,长度可以改变
//和数组类似,不确定使用哪个,就使用 Vector
let arr=[1,2,3,4,5,6,7,8,9];
let avec=vec![1,2,3,4,5,6,7,8,9];
println!("{}",avec[0]);
println!("{}",arr[5]);
//另外一声明数组的方法
let a=[3;5]; //创建数组并且初始化为5个3

元组和数组都拥有固定的长度!

元组用小括号();数组用中括号 []

有一种动态数组类型:vector

函数

rust使用蛇形命名法(只使用小写字母,使用下划线分隔单词)来规范函数和变量名称的风格!

// 函数调用 
add_function();
add(4,56);

let y=5+6;

let y={//表达式
    let x=3;
    x+1 //这个加上了分号就变成了语句;这个相当于返回值
};

let n1={
    let u=6+5;
    u
};


println!("{}",y);//4

=================================================
//函数和注释
// 声明函数使用  fn 关键字 : go语言使用 func 关键字
// 规范是函数名称使用小写,单词之间使用_分割
fn add_function(){
println!("hello function");
}

fn add(x:u32 , y: u32){//rust必须指定函数参数类型
println!("x={}",x);
println!("y={}",y);
}
// 函数的返回值
fn add1(x:u32 , y:u32) ->u32 {//在参数括号后面加上->类型 就是返回
let x=x+y;
x   //返回语句不能有分号,有了分号就变成了语句
}

参数和参数类型之间使用 : 分隔!

rust把语句和表达式区分为两个不同的概念:

  • **语句:**执行操作但不会返回值的指令
  • **表达式:**会进行计算并且产生一个值作为结果的 指令

记住:语句不会有返回值

表达式加上分号就会变成了语句。

rust函数的返回值使用 -> 返回值类型;如果是多个就是小括号括起来:-> (类型1, 类型2 ….)

fn five() -> i32 {
    5  //这样也是对的返回5
    //如果不使用这种方式返回,也可以使用 return,使用这个需要加分号 : return 5;
}
=================================================

fn five() -> i32 {
    5; //错的!!!不能加分号
}

rust注释

// 单行注释

/**/ 多行注释

控制流

if 和 else

示例:

// 控制表达式  if else
//这个和go,python语言差不多
let x=5;

if x<10 {
    println!("你的数字真小!");
}else if x>10&&x<90{
    println!("你的数字在10-90之间!");
}else{
    println!("你的数字真大!");
}
let y=true;//这里y的值不能为0或者1不然会报错!!!
if y {
    println!("{}",y);
}

rust不会自动尝试将非布尔类型的值转换为布尔类型!!!,所以上面代码中y的值只能为布尔值。

过多的else if 表达式应该用 match 替代!!!

let b=10;
//if是一个表达式,可以let语句右侧使用它来生成一个值
// 也可以这样判断实现像 ? : 相同的功能
let number=if b>5 {100} else { 900 }; //if和else里面的类型要一样,静态编译型语言

//else if 太多了,可以使用match来重构
let number=100;
match number{//这规则和case差不多,也可以使用下划线_
    1=>println!("one"),
    2=>println!("two"),
    3=>println!("three"),
    _=>println!("other"),
}

所有 if 分支里面可能返回的值都必须是一种类型的

let nu=if 4>5{
    54-12
}else{ -900+65};//像这样也是可以的,记住不要里面加分号
println!("nu={}",nu);
rust循环结构

rust提供了 3 种循环结构:loop 、while、for。

loop :反复执行一块代码,直到条件满足(break)或者我们强制退出!!

loop {
    ...
    if 条件 {
        
    }
}


// loop  不会像 do while必定会执行一次,其他和它一样,如果条件放在最前面,一开始就不成立,就不会执行
let mut count=0;
let res = loop{
   count+=1;
    println!("725");
    if count==10{
        break count;
    }
};//这里分号别忘记写了


let mut con1=0;
let res=loop{
    con1+=1;
    if con1 == 10 {
       break con1*2 //这里不加分号
    }
};
println!("con1={}",res);//20
let mut con2=0;
let res=loop{
    con2+=1;
    if con2 == 10 {
       break con2*2; // 这里加上分号
    }
};
println!("con2={}",res);//20
//上面两种方式是等价的

while 用法和其他语言一样

// while
let arr=[1,2,3,4,5,6,7,8,9];
let mut le=arr.len();//数组长度比数组下标大1
while le>0{
    le=le-1;
   println!("{}",arr[le]); 
}

for 循环:推荐使用简洁又高效;rust最为常用

// for
// 使用for循环又安全又高效
let arr=[1,2,3,4,5,6,7,8,9];
for a in arr.iter(){
println!("{}",a);
}
===============================================
语法: 
for 变量名 

Range:用来生成数字序列!

// Range 标准库提供
// 指定一个开始数字和一个结束数字,它可以生成它们之间的数字(左闭右开)
// rev方法可以反转 Range
for number in(1..10).rev(){//小括号数字中间是两个点
    println!("{}",number);
}

所有权

// 所有权是Rust最独特的特性核心特性
// 内存是通过所有权系统来管理的
//堆和栈是代码在运行时可以傅 的内存空间
// stack 栈  这上面的数据必须拥有固定的大小 
//  heap 堆  编译时大小未知或者大小可能发生变化的数据必须存放在 heap中
// 访问heap中的数据要比访问stack中的数据慢,多了次指针跳转

所有权是Rust最独特的特性核心特性

所有权规则:

  1. rust中的每一个值都有一个对应的变量作为它的所有者;
  2. 在同一时间内,值有且仅有一个所有者;
  3. 当所有者离开自己的作用域时, 它持有的值就会被释放掉;

作用域:一个对象在程序中有有效范围;

rust 中可以用大括号 {} 表示一个作用域,或者隔离一个作用域!!!

String类型

字符串字面量是不可变的;

let s="hello world!"; //不可变的 分配在栈上的

为了方便操作rust提供了第二种String类型:这个类型会在====上分配自己需要的存储空间:调用 from 函数来创建 String 实例

let s=String::from("hello");
//在堆上分配的,是可变的

区别:字符串字面量是分配在栈上的不可变,而String是分配堆上的是可变的!!!

内存布局:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XsJoFVJ1-1651467632358)(E:sysyPictures哔哩哔哩动画image-20220326202623950.png)]

注意图中 String 类型的分配方式;

内存与分配

两个关键概念:

  • rust 在变量离开作用域的时候,会调用一个叫作 drop的特殊函数
  • rust会在作用域结束的地方自动调用 drop 函数

在C++中这种对象生命周期结束时释放资源的模式也称为资源获取即初始化(RAII)

变量和数据交互的方式:移动 Move

// 变量和数据交的方式:移动(Move)
//多个变量可以与同一个数据使用独特的方式来交互
let s1=String::from("shenyang");
let s2=s1;//在这里这样,rust会废弃s1的所有权,s1的值被移动到s2中,s1的值被清空
//println!("{}",s1);//这里使用报错,因为s1已经被废弃了
/*let s1="shenyang";
let s2=s1;
像这样就可以,不会报错!!!
*/
println!("{}",s2);
// 一个String 由3部分组成:
// 一个指针,len(长度),cap(容量)分配在栈上,而字符串的内容被分配在堆上

内存布局:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JO54qY2f-1651467632359)(E:sysyPictures哔哩哔哩动画image-20220326203349038.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfZlmpuw-1651467632359)(E:sysyPictures哔哩哔哩动画image-20220327094827868.png)]

上面把 s1 的值赋给 s2 的时候只复制了它在存储在栈上的指针、长度及容量字段

需要注意的是它没有复制指针指向的堆上数据!

引出问题:s1 和 s2 离开作用的时候会尝试去重复释放相同的内存,导致二次释放

rust解决方案: rust在这种情况下会将 s1 废弃,不再视为一个有效的变量,s1 离开作用域后也不需要清理任何东西!!!

浅拷贝和深拷贝

C++中的深浅拷贝:

  • 深拷贝:在堆区重新申请空间进行拷贝操作、拷贝完整的内容
  • 浅拷贝:只拷贝地址,也就是编译器本身提供的拷贝构造函数做的浅拷贝操作

浅拷贝带来的问题:堆区的内存重复释放以及内存泄漏

有堆区开辟的属性,一定要提供拷贝构造函数防止浅拷贝带来的问题。

rust拷贝s1到s2的方式就可以视为浅拷贝。

术语: 移动(MOVE)

rust中应该是 s1 被移动到 s2 中。因为 s1 会被废弃了!!

一个设计原则:rust 永远不会自动地创建数据的深拷贝。所以在 rust中,任何自动的赋值操作都可以视为高效的。

需要用到深拷贝就是克隆(clone)

变量和数据交互的方式: 克隆 Clone

当要做深拷贝操作的时候,rust提供一个方法: Clone()

// clone 克隆 比较消耗资源
let a1=String::from("hello");
let a2=a1.clone();//克隆作了深度拷贝操作
println!("{},{}",a1,a2);//这里a1变量就没有被废弃,因为是直接把a1克隆给a2
// 和上面的作对比

Clone()方法复制栈上数据的同时,也复制了堆上的数据!!!

克隆有个缺点:就是比较消耗资源

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNe07bTL-1651467632360)(E:sysyPictures哔哩哔哩动画image-20220326203136545.png)]

重点:如果一个类型拥有了 Copy 这种 trait ,那么它的变量可以在赋值给其他变量之后仍然保持可用性。

如果一个类型本身或者这种类型的任意成员实现了 Drop 这种 trait ,那么rust 就不允许它实现 Copy 这种 trait了。

/*
stace上的数据:复制
Copy trait,可以用于完全存放在栈上的类型
如果一个类型实现Copy trait,那么旧的变量在赋值后仍然可以使用
一些拥有Copy trait的类型:
任何简单标量的组合类型都可以是Copy的;任何需要分配内存或者某种资源的都不是Copy的
拥有的:bool char 所有的浮点类型,所有的整数类型
tuple(元组)前提是其中所有的字段都是Copy的 eg:
(i32,i32)是
(i32,String)不是
*/

任何简单标量的组合类型都可以是Copy的;任何需要分配内存或者某种资源的都不是Copy的

所有权与函数

理解这里:要理解了上面的内容比如:复制操作、克隆操作、 Copy 、Drop

fn main(){
 // 所有权与函数
// 函数在返回值的过程中也会发生所有权的转移
let s1=gives_ownership();

let s2=String::from("hello");

let s3=takes_and_gives_back(s2);//s2的所有权被移动到函数里面,从这里开始 s2 不再有效

 let s4=100;//由于 i32 类型是 Copy 的,我们在这里之后还可以继续使用 s4
    
/*
一个变量离开作用域时会被Drop函数还回,除非它的所有权被转移另外一个变量上
*/
    
}

fn gives_ownership() -> String{
let some_string=String::from("hello");
some_string //这个的所有权移动到调用它的上面也就是上面的s1上
}

fn takes_and_gives_back(a_string:String)->String{//s2的所有权被移动到函数参数上面
    a_string //这个作为返回值的所有权移动到调用它的上面也就是上面s3上面
}

fn makes_copy(x:i32) {
    println!("x={}",x);//x在这里离开作用域并不会有什么特别的事发生就是正常的消亡
}

上面的函数中的返回值是移动操作不是返回所有权操作 !!!参数传递进去函数的时候,函数会获得所有权

返回值和作用域

??

遵循模式:将一个值赋值给另外一个变量时就会发生所有权转移,当一个持有堆数据的变量离开作用域时,它的数据就会被 Drop 清理回收,除非数据的所有权被移动到了另一个变量上 面。

函数在返回值的过程中也会发生所有权的转移!!!

问题:当希望调用函数的时候保留参数的所有权,就要将传入的值作为结果返回,但同时函数也可能会需要返回自己的结果。

??:采用元组解决:太过于繁琐

fn main(){
    
    let s1=String::from("hello");
    
    let (s2,len)=calculate_length(s1);
    //接收多个参数的时候,需要忽略某个参数可以下划线 
    println!("{},{}",s1,len);
}

fn calculate_length1(s:String) ->(String,usize) {
   let length= s.len();//取得所有权
   (s,length)//采用元组解决同时返回多个值
}

:采用元组可以让函数同时返回多个值!!!

引用与借用

?? 解决上面采用元组返回太过于繁琐的问题

问题:我们想要调用函数的时候,不转移值的所有权

??:& 代表引用的含义,可以在不获取所有权的情况下使用值。

* 代表解引用

& 参数类型 不可变引用(默认的)

& mut 参数类型 可变引用(调用时的参数也要是可变)

fn main(){
 let mut s1=String::from("hello");
    
//& 表示引用,允许使用值并且不取得所有权  对应解引用 * 
//把引用作为函数参数传递就叫引用
// 不可以修改借用的东西,引用也是默认不可变的,可以使用 mut来让引用可变  &mut 数据类型/参数
let len=calculate_length(&mut s1);//参数的变量也要是可以变的,否则会报错
    
println!("{},{}",s1,len);  
}

// 函数使用变量不获得所有权
fn calculate_length(s:&mut String) ->usize {//注意参数里面不是在变量前面加& ,而是在类型前面加&
    s.len()//不会取得所有权
}

fn first_world(s: &String ) -> usize {
let bytes=s.as_bytes();
for (i,&item) in bytes.iter().enumerate() {
   if item == b' '{
       return i;
   }
}
s.len()
}

??:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyvUSQ5g-1651467632361)(E:sysyPictures哔哩哔哩动画image-20220327105233388.png)]

??:通过引用传递参数给函数的方法就叫做借用!!!

???:可变引用

fn main(){
    
 let mut s=String::from("hello");
change(&mut s);  
    
}
// 可变引用
fn change(some_string: &mut String){
    some_string.push_str(", world");
    //相当拼接字符串的功能 append
}

:**限制点:在特定作用域中,对于某一块数据,只能有一个可变的引用(一次只能声明一个可变引用 )。**可以通过大括号来分隔作用域实现有多个可变引用

??:这里要多想想记住!!!!

// 可变引用有一个限制:要特定作用域内,对于某一块数据,只能有一个可变的引用 。
 let mut p=String::from("hello");
 let z1=&mut p;
 let z2=&mut p;//报错!!!违反了规则
 println!("{},{}",z1,z2);

上面的限制性规则可以帮助我们在编译时避免数据竞争。

??:数据竞争

以下三种行为会发生数据竞争:

  1. 两个或者多个指针同时访问同一个数据

  2. 至少有一个指针用于向空间中写入数据

  3. 没有使用任何机制来同步对数据的访问(没有同步访问)

// 可以通过创建新的作用域,来允许非同时的创建多个可变引用
//eg:
let mut k=String::from("hello");
{//可以大括号分隔作用域
    let s1=&mut k;
}//到这里s1就不再有效了,因为已经出了作用域了
let s2=&mut k;

??:可以通过花括号{ } ,来创建一个新的作用域范围,这样就可以创建多个可变引用 !!!

:限制:不可以同时拥有一个可变引用 和 一个不可变的引用;但同时有多个不可变的引用是可以的

let mut s=String::from("hello");
let r1=&s;
let r2=&s;
let s1=&mut s;//报错!!!!:因为不可以把s借用为可变的引用,因为它已经借给了不可变的引用 
println!("{},{},{}",r1,r2,s1);
悬垂引用

??**概念:**一个指针引用了内存中的某个地址,但是这块内存可能已经释放并且分配给其它变量使用了。

rust保证不会让引用进入悬垂状态!!!

??:这里我目前可以理解为:C++中的不要返回局部对象的引用,因为离开它自己的作用域也就被销毁了!!!

fn main(){
    // 悬空引用示例 
   let r=dangle();
}
fn dangle() -> &Stirng {//报错!!!
let s = String::from("hello");
&s  //s的引用返回给调用者,s在这里离开作用域并且被销毁,它指向的内存也就无效了
}
====================================================
//直接返回 String 就不会报错了
fn dangle() -> Stirng {
let s = String::from("hello");
s   //所有权被转移出函数并没有被销毁
}

??:引用的规则

  • 在任何一段给定的时间内,要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用
  • 引用总是有效的

切片(slicce)

切片(slicce):是 rust 中不持有所有权的数据类型。(允许我们引用集合中某一段连续的元素序列)

使用方式和go语言的切片差不多。

??:示例

fn main(){
    let mut s=String::from("hello");
let wordindex=first_world(&s);
println!("{}",wordindex);
// 字符串切片 和 
let s=String::from("hello world!");
let hello=&s[0..5];//左闭右开  中间数字之间也两个点
let hello=&s[..5];//等价于上面那个
let world=&s[6..11];
let world=&s[6..];//和上面那个一样
// 整个字符串
let u=[..];
}

方括号数字之间是两个点:

字符串切片的边界必须位于有效的 UTF-8 字符边界内。

[ start … end ] 是一个左闭右开区间

??:字符串切片的类型是: &str

因为 &str 是一个不可变的引用,所以字符串字面量自然也是不可变的

:字符串字面值实质上是一个切片

fn main(){
    let s1="hello";//字符串字面值实质是切片
 // 将字符串切片作为参数传递
// 使用 &str作为函数参数,这样就可以现时接String 类型和 & str 类型的参数,更加通用
// eg:
let my_string=String::from("hello world");
let wordindex=first_w(&my_string[..]);
let my_string_str="hello world";
let wordindex=first_w(&my_string_str[..]);//可以简化为下面这种形式,因字符串字面值本质是切片
let wordindex=first_w(my_string_str);
    
}


fn first_w(s: &String ) -> usize {
let bytes=s.as_bytes();
for (i,&item) in bytes.iter().enumerate() {
   if item == b' '{
       return i;
   }
}
s.len()
}



// 示例函数 参数为String引用建议改为这个
//因为这样改进后既可以处理 String类型又同时可以处理 &str 类型,更加通用
fn first_w(s:&str) -> &str{
let bytes=s.as_bytes();
for ( i,&item) in bytes.iter().enumerate(){
if item==b' '{
return &s[..i];
}
}
&s[..]
}

数组切片:

// 这个切片go语言中的切片用法差不多,go和rust中的切片数字都不能为负数
let a=[1,2,3,4,5];
let slice=&a[1..5];//数组切片;切片的第二个参数不可以像python那样写成负数

struct 结构体

//语法:
struct 结构体名  {
    字段名 : 类型 ,
    字段名 : 类型 ,
    ...          ,
    //最后一个字段也要有逗号
}


// 定义一个结构体在花括号里面为所有字段定义名称和类型
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool, //最后一个对象也要有逗号
}

?? 创建对象实例:不能只赋值其中几个字段,必须对所有字段赋值,顺序可以不一样

  //    创建一个对象实例
    // 不能只赋值其中几个字段,必须对所有字段赋值,顺序可以不一样
    let mut user1 = User {
        email: String::from("725482520"),
        username: String::from("shenyang"),
        active: true,
        sign_in_count: 1,
    };

使用 . 来说属性

    // 使用点 . 来访问属性
    println!("{}", user1.email);
    println!("{}", user1.username);
    println!("{}", user1.active);
    println!("{}", user1.sign_in_count);
   // 更改结构体的字段的值
user1.email = String::from("654321"); 
//前提要是创建对象是要可变的 加了 mut 关键字
//struct 的实例是可变的,那么实例中所有字段都是可变的

??**更新语法:**想用某个struct实例来创建一个新实例的时候可以使用更新语法

语法: … 对象实例名

   // struct更新语法:想用某个struct实例来创建一个新实例的时候可以使用更新语法
    let user2 = User {
        email: String::from("123456"),
        username: String::from("sy"),
        ..user1 
//在这使用了更新语法(也就是除了上面两个字段,其他的字段跟user1的一样)
    };

??:上面更新语法:user2除了自己定义的两个属性,其他的属性和 user1 相同。

struct可以作为函数的返回值

fn restr(e: String, u: String) -> User {
    User {
        email: e,
        username: u,
        active: true,
        sign_in_count: 1,
    }
}

字段初始化可以简写

//字段初始化可以简写,当字段名与字段值对应变量名相同时,就可以省略字段名
fn restr1(email: String, username: String) -> User {
    User {
        email, //可以使用简写方式
        username,
        active: true,
        sign_in_count: 1,
    }
}

struct 的实例是可变的,那么实例中所有字段都是可变的

结构体实例对象也分为可变和不可变的:

let mut 名称 = 结构体名 {
    对应字段赋值
}
====================================================
let  名称 = 结构体名 {
    对应字段赋值
}
Tuple struct
//语法:
struct 名称 ( 类型1 , 类型2 ,类型3 ... );
//最后一个类型不需要加逗号

struct Color(i32, i32, String, bool);

??:实例

// tuple struct 实例
let red = 
Color(255, 255,String::from("blacke"),true); 
//tuple struct 的实例
let mut black = 
Color(255, 255, String::from("blacke"), true); //tuple struct 的实例
black.0 = 246; //也可以使用点语法来访问元素
black.1 = 200; //想要改变必须创建时是可变的
black.2 = String::from("red");

使用点来访问属性,字段序号从 0 开始 !!!

struct 方法

方法第一个参数是self,相当于C++中的 this,方法可以有多个参数,但第一个必须是self***,在 impl 块里面定义方法**

每个 struct 允许拥有多个 impl 块

??:语法

impl  结构体名 {
    方法
}


// struct 方法
// 方法第一个参数是self,相当于C++中的 this,方法可以有多个参数,但第一个必须是self
//访问使用实例对象 加 . 访问
// 在 impl 块里面定义方法
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值