当前位置: 首页 > news >正文

Rust程序语言设计(1-4)

一、Hello world

fn main() {println!("Hello, world!!!");
}

二、猜数游戏 - 目标

  • 生成一个 1 到 100 间的随机数
  • 提示玩家输入一个猜测
  • 猜完之后,程序会提示猜测是太大还是太小了
  • 如果猜测正确,那么打印出一个庆祝信息,程序退出
(1)一次猜测
fn main() {println!("猜数!");println!("猜测一个数");// let mut foo = 1;// let bar = foo;    //immurable// foo = 2let mut guess = String::new();//String::new() 空字符串实例io::stdin().read_line(&mut guess).expect("无法读取行");//io::Result Ok, Errprintln!("你猜测的数是:{}",guess);
}
(2)生成随机数字
use std::io;    //prelude
use rand::Rng;      //traitfn main() {println!("猜数!");let secret_number = rand::thread_rng().gen_range(1..101);println!("随机数字是{}",secret_number);println!("猜测一个数");// let mut foo = 1;// let bar = foo;    //immurable// foo = 2let mut guess = String::new();//String::new() 空字符串实例io::stdin().read_line(&mut guess).expect("无法读取行");//io::Result Ok, Errprintln!("你猜测的数是:{}",guess);
}
(3)比较猜测数字和随机数字
use std::io;    //prelude
use std::cmp::Ordering;
use rand::Rng;      //traitfn main() {println!("猜数!");let secret_number = rand::thread_rng().gen_range(1..101); // i32 u32 i64println!("随机数字是{}",secret_number);println!("猜测一个数");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("无法读取行");//shadow 隐藏同名旧变量,从下一行之后,引用的是u32类型guess变量let guess:u32 = guess.trim().parse().expect("Please type a number");// trim() 去掉两端空白 parse() 把字符串转成整数类型println!("你猜测的数是:{}",guess);match guess.cmp(&secret_number) {Ordering::Less => println!("Too small!"),  //arm//如果前面成立,执行后面程序Ordering::Greater => println!("Too big!"),Ordering::Equal => println!("You win!"),}
}
(4)多次猜测
use std::io;    //prelude
use std::cmp::Ordering;
use rand::Rng;      //traitfn main() {println!("猜数!");let secret_number = rand::thread_rng().gen_range(1..101); // i32 u32 i64loop {//loop 循环println!("猜测一个数");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("无法读取行");// shadowlet guess:u32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,//错误处理,再次输入};println!("你猜测的数是:{}",guess);match guess.cmp(&secret_number) {Ordering::Less => println!("Too small!"),  //armOrdering::Greater => println!("Too big!"),Ordering::Equal => {println!("You win!");break;}}}
}
(5)运行结果

![[Pasted image 20251003140638.png]]

三、通用编程概念

(1)变量与可变性
1.变量

使用 let 来进行声明

fn main() {let x = 5;println!("The value of x is {}",x);x = 6;println!("The value of x is {}",x);
}

不加 mut 无法更改变量
![[Pasted image 20251003141505.png]]

添加 mut

fn main() {let mut x = 5;println!("The value of x is {}",x);x = 6;println!("The value of x is {}",x);
}

![[Pasted image 20251003142459.png]]

2.常量(constant)

概念:常量在绑定值以后不可变

  • 与不可变变量间的区别
区别
1.不可以使用mut,常量永远不可变
2.声明常量使用const关键字,类型必须被标注
3.常量可以在任何作用域内声明,包括全局作用域
4.常量只可以绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值
  • 在程序运行期间,常量在其声明的作用域内一直有效
  • 命名规范:Rust 里常量使用全大写字母,每个单词之间只能用下划线分开,如:
    • MAX_POINTS
      例如:const MAX_POINTS:u32 = 100_000;
3.Shadowing(隐藏)

概念:可以使用相同的名字声明新的变量,新的变量就会 shadow (隐藏/覆盖) 之前声明的同名变量
例:

fn main() {let x = 5;println!("{}",x);let x = x + 5;//可以重复命名println!("{}",x);
}

![[Pasted image 20251003144602.png]]

(2)数据类型

Rust 是静态编译语言,在编译时必须知道所有变量的类型

  • 基于使用的值,编译器通常能够判断出它的具体类型
  • 如果可能的类型比较多(如,字符串转整数),就必须添加类型的标注,否则编译会报错
fn main() {let guess:u32 = "42".parse().expect("Not a number");println!("{}",guess);
}
1.标量类型
  • 一个标量类型代表一个单一的值
  • Rust 有四个主要的标量类型:
    • 整数类型
    • 浮点类型
    • 布尔类型
    • 字符类型
整数类型
  • 整数类型没有小数部分
  • 例如u32就是无符号的整数类型,占据32位的空间
  • 无符号整数类型以u开头
  • 有符号整数类型以i开头
  • Rust 的整数类型列表如图:
LengthSignedUnsigned
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
  • 有符号范围:
    −(2nn−1)=>(2n−1−1) -(2nⁿ-1)=>(2ⁿ⁻¹-1) (2nn1)=>(2n11)
  • 无符号范围:
    0=> 2n-1 0 => 2ⁿ-1 0=> n-1
    整数溢出:
    例如:u8的范围是 0-255,如果将 u8 的值设为 256,那么:
  • 调试模式下编译:Rust 会检查整数溢出,如果发生溢出,程序运行时就会 panic
  • 发布模式下 (–release) 编译:Rust 不会检查可能导致 panic 的整数溢出
    • 如果溢出发生:Rust 会执行 “环绕” 操作:
      -256变成0,257变成1
    • 程序不会 panic
浮点类型
  • Rust 有两种基础的浮点类型,也就是含有小数部分的类型
    • f32,32位,单精度
    • f64,64位,双精度
  • Rust 的浮点类型使用了 IEEE-754 标准来表述
  • f64 是默认类型
布尔类型
  • Rust 布尔类型有两个值:true 和 false
  • 一个字节大小
  • 符号是 bool
字符类型
  • char 类型被用来描述语言中最基础的单个字符
  • 字符类型的字面值使用单引号
  • 4字节大小
  • 是 Unicode 标量值:除ASCII外,拼音、中日韩文、零长度空白字符、emoji表情
    • U+0000 到 U+D7FF
    • U+E000 到 U+10FFFF
  • Ubicode 没有“字符”的概念
2.复合类型
Tuple(元组)
  • Tuple 可以将多个类型的多个值放在一个类型中
  • Tuple 的长度是固定的:一旦声明就无法改变
创建Tuple
  • 在小括号里,将值用逗号分开
  • Tuple 中每个位置都对应一个类型, Tuple 中各个元素的类型不必相同
fn main() {let tup:(i32,f64,u8) = (500,6.4,1);println!("{},{},{}",tup.0,tup.1,tup.2);
}

![[Pasted image 20251003160329.png]]

获取Tuple的元素值
  • 可以使用模式匹配来解构(destructure
fn main() {let tup:(i32,f64,u8) = (500,6.4,1);let (x,y,z) = tup;println!("{},{},{}",x,y,z);
}
访问Tuple的元素

println!("{},{},{}",tup.0,tup.1,tup.2);

数组
  • 数组可以将多个值放在一个类型里
  • 数组中每个元素类型必须相同
  • 数组的长度是固定的
声明数组
  • 中括号内,逗号隔开
    let a = [1,2,3,4,5];
数组的用处
  • 将数据存放在stack上而不是heap上,保证有固定数量的元素
  • 数组没有Vector灵活
    • Vector 和数组类似,由标准库提供
    • Vector 的长度可以改变
    • 如不确定使用哪个时,用Vector
数组的类型
  • [类型,长度]
  • 例如:let a:[i32,5] = [1,2,3,4,5]
另一种声明数组的方法
  • 如果元素值都相等
    • 在中括号里指定初始值
    • 然后是一个“;”
    • 最后是数组长度
  • 例:let a = [3;5] = let a = [3,3,3,3,3]
访问数组的元素
  • 数组是 Stack 上分配的单个块的内存
  • 可以使用索引来访问数组的元素
fn main() {let list = [1,2,3,4,5,6,7,8,9,10];let first = list[0];let second = list[1];println!("第一个元素为{},第二个元素为{}",first,second);
}

![[Pasted image 20251003163017.png]]

  • 如果访问超出数组范围限制
    • 编译会通过
    • 运行会报错
(3)函数与注释
函数的声明

针对函数和变量名,Rust使用snakecase命名规范:

  • 所有字母都是小写,单词间使用下划线分开
fn main() {println!("First function");another_function();
}fn another_function() {println!("another function");
}

形参\实参

fn main() {another_function(5, 6);   // argument
}
fn another_function(x:i32, y:i32) {  //parameterprintln!("The value of x is : {}",x);println!("The value of y is : {}",y);
}

函数返回值

  • 在 -> 符号后边声明函数返回值的类型,但是不可以为返回值命名
  • 在 Rust里面,返回值就是函数体里面的一个表达式的值
  • 如果想提前返回,需使用 return 关键字,并指定一个值
fn five() -> i32 {5
}fn main() {let x = five();println!("The value of x is:{}",x);
}

传入参数

fn num(x:i32) -> i32 {x + 5
}fn main() {let x = num(6);println!("The value of x is:{}",x);
}
(4)控制流
if表达式
  • if 表达式允许根据条件执行不同的代码分支
    • 条件必须是 bool 类型
  • if 表达式中,与条件关联的代码块,叫做分支(arm)
  • 可以添加 else 表达式
fn main() {let num = 3;if num < 5 {println!("Condition was true");} else {println!("Condition was false");}
}

在 let 语句中使用 if

  • if 表达式可以放在 let 语句的等号右边
fn main() {let condition = true;let number = if condition {5} else {6};println!("The value of number is : {}" , number);
}
循环
  • Rust 提供了3种循环
  • loop、while、for
loop 循环
  • 反复执行一块代码,直到喊停
  • 可以使用 break 关键字,告知程序何时停止
fn main() {let mut counter = 0;let result = loop {counter += 1;if counter == 10{break counter * 2;}};println!("The result is : {}", result);
}
while 循环
fn main() {let mut num = 3;while num != 0 {println!("{}!",num);num = num - 1;}println!("LIFTOFF!!!");
}
For 循环
fn main() {let a = [10,20,30,40,50];for element in a.iter() {println!("The value is : {}",element);}
}
Range

rev 代表逆向遍历

fn main() {for number in (1..4).rev() {println!("{}!",number);}println!("LIFTOFF!");
}

四、认识所有权

(1)什么是所有权
1.所有权
  • Rust 的hexintexing
  • 所有程序运行时必须管理自己使用计算机内存方式
    • 垃圾处理机制,不断寻找不再使用的内存
    • 其他语言,程序员必须显式地分配和释放内存
  • Rust 的方式
    • 内存通过所有权系统管理,其中包含一组编译器在编译时检查的规则
    • 当程序运行时,所有权不会减慢程序的运行速度
Stack & Heap (栈内存和堆内存)

Stack 按值的接收顺序来存储,按相反的顺序移除(先进后出)

  • 添加数据叫做入栈
  • 移除数据叫做出栈
    所有内存在 Stack 上的数据必须拥有已知的固定的大小
  • 编译时大小未知或可能发生变化的数据必须存储在 heap
    Heap 内存组织性差:
  • 数据存入 heap 时,会请求一定数量的空间
  • 操作系统在 heap 中找到足够的空间,进行标记,并返回指针
  • 该过程称为分配
    指针是已知固定大小,可以存放在 stack
  • 如想要实际数据,必须使用指针定位
    将数据压到 stack 要比分配至 heap
访问数据

访问 heap 中的数据速度慢于访问 stack
Stack 中的数据相当于紧密连接着
heap 中的数据相当于分散在各个角落

所有权存在原因

所有权解决的问题

  • 跟踪代码的哪些部分正在使用 heap 的哪些数据
  • 最小化 heap 上的重复数据
  • 清理 heap 上未使用的数据
    :管理 heap 数据,是所有权存在的原因
2.所有权规则、内存与分配
所有权规则
  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除
变量作用域
fn main() {// s 变量不可用let s = "xxxxx";   //自该行起,到本代码块结束可用
}
//超出代码块范围,作用域结束,变量 s 不可用
String 类型
  • 字符串字面值:程序里手写的字符串值,不可变
  • Rust 还有第二种字符串类型:String
    String 类型值的创建
  • 使用 from 函数从字符串字面值创建 String 类型
let s = String::from("xxxxx");  
  • “::” 表示 fromString 类型下的函数
fn main() {let mut s = String::from("Hello");// 定义String类型s.push_str(", world!");// push_str :追加内容println!("{}",s);
}
内存和分配

字符串字面值是被硬编码到可执行文件中

  • 速度快、高效都是因为他的不可变性
    String 类型为了支持可变性,需要在 heap 上分配内存
  • 操作系统必须在运行时来请求内存
  • 通过调用 String::from 来实现
  • 使用完后,需要将内存返还给系统
  • 没有GC,就需要自己去识别哪些内存不再使用,并调用代码进行返还
  • 未返还,就是浪费内存
  • 提前返还,变量会变成非法
  • 不能多次进行,一次分配对应一次释放
    Rust采用的方法:当某个值的变量走出作用范围时,内存会自动交还个操作系统
变量与数据交互的方式:Move
let x = 5;
let y = x;

因为是固定值,所以这两个 5 就被压到了 stack中
针对String类型
其他语言的拷贝的方式可能会导致 二次释放(double free)

fn main() {let s1 = String::from("Hello");let s2 = s1;println!("{}",s1);   //无法调用已经移动的
}

Rust:则采用了废弃的方式来处理,当String类型的 s1 赋值给 s2 后,s1 在该作用域后续的代码中将无法调用

  • 浅拷贝(shallow copy)
  • 深拷贝(deep copy)
  • 由于 Rust 使 前置变量失效了,所以叫做:移动(Move)
变量与数据交互的方式:克隆Clone
fn main() {let s1 = String::from("Hello");let s2 = s1.clone();println!("{},{}",s1,s2);
}

当使用 Clone(克隆) 方法后,两个变量都是有效的

Stack上的数据:复制
  • 任何简单标量的组合类型都可以是 Copy 的
  • 任何需要分配内存或某种资源的都不是 Copy 的
3.所有权与函数

将值传递给函数和把值赋给变量是类似的

  • 将值传递给函数将发生移动或复制
fn main() {let s = String::from("Hello world");take_ownership(s);let x = 5makes_copy(x);println!("x:{}",x);
}fn take_ownership(some_string:String) {println!("{}", some_string);
}fn makes_copy(some_number:i32) {println!("{}",some_number);
}
返回值与作用域
  • 函数在返回值的过程中同样也会发生所有权转移
fn main() {let s1 = gives_ownership();let s2 = String::from("hello");let s3 = takes_and_gives_back(s2);
}fn gives_ownership() -> String {let some_string = String::from("hello");some_string
}fn takes_and_gives_back(a_string:String) -> String {a_string
}

变量的所有权遵循同样的模式

  • 把值赋给其他变量时会发生移动
  • 包含 heap 的数据的变量离开作用域时,它的值会被 drop 清理掉,除非数据的所有权已经转移
如何让函数使用某个值,但不获得所有权
fn main() {let s1 = String::from("hello");let (s2, len) = calculate_length(s1);println!("The length of '{}' is {}.",s2, len);
}fn calculate_length(s:String) -> (String, usize) {let length = s.len();(s, length)
}
(2)引用与借用

参数的类型是 &String 而不是 String

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()
}

& 符号表示引用:允许引用某些值而不取得其所有权
![[Pasted image 20251004173042.png]]

借用
  • 把引用作为函数参数这个行为叫做借用
  • 借用的内容无法更改,和变量一样,引用默认不可变
可变引用
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()
}

可变引用的限制:

  • 在特定作用域内,对某一块数据,只能有一个可变的引用
    可变引用的好处:
  • 编译时,防止数据竞争
    数据竞争的产生条件:
  • 两个或多个指针同时访问同一个数据
  • 至少有一个指针用于写入数据
  • 没有使用任何机制来同步对数据的访问
    可以通过创建新的作用域,来允许非同时的创建多个可变引用
    另一个限制
  • 不可以同时拥有一个可变引用和一个不可变引用
  • 多个不变的引用是可以的
    例:
fn main() {let mut s = String::from("Hello");let r1 = &s;let r2 = &s;let s1 = &mut s;println!("{} {} {}",r1,r2,s1)
}

结果:
![[Pasted image 20251004180825.png]]

悬空指针 Dangling References

概念:一个指针引用了内存中的某个地址,但是这块内存可能已经释放并分配给其他人使用了
Rust中,编译器可以保证引用永远都不是悬空引用

  • 如果引用了某些数据,编译器将保证在离开作用域前,数据不会离开作用域
fn main() {let r = dangle();
}fn dangle() -> &String {    //悬空指针let s = String::from("hello");&s
}

结果:
![[Pasted image 20251004181818.png]]

(3)切片

Rust 的另外一种不持有所有权的数据类型:切片(slice
例题,编写一个函数:

  • 它接收字符串作为参数
  • 返回它在这个字符串里找到的第一个单词
  • 如果函数没找到任何空格,那么整个字符串就被返回
    例:
fn main() {let mut s = String::from("Hello world");let wordIndex = first_word(&s);println!("{}", wordIndex);
}fn first_word(s:&String) -> usize {let bytes = s.as_bytes();//将String类型转换成数组for (i,&item) in bytes.iter().enumerate() {// .iter:返回集合中的每个元素// .enumerate():将 iter 返回的结果进行包装,并将结果作为元组的一部分进行返回if item == b' ' {return i;}}s.len()
}
字符串切片

字符串切片是指向字符串中一部分内容的引用

fn main() {let s = String::from("Hello world");//输出 hellolet hello = &s[0..5];//let hello = &s[..5];//输出 worldlet world = &s[0..11];//let world = &s[0..s.len()];//let world = &s[0..];//输出全部let all = &s[0..11]//let all = &s[0..s.len()]//let all = &s[..]
}

形式:[开始索引…结束索引] (左闭右开)

  • 开始索引就是切片的起始位置的索引
  • 结束索引是切片终止位置的下一个索引值
    注:
  • 字符串切片的范围索引必须发生在有效的 UTF-8 字符边界内
  • 如果尝试从一个多字节的字符中创建字符串切片,程序会报错退出
通过切片重写例题
fn main() {let mut s = String::from("Hello world");let wordIndex = first_word(&s);println!("{}", wordIndex);
}fn first_word(s:&String) -> &str {// &str : 字符串切片的类型let bytes = s.as_bytes();//将String类型转换成数组for (i,&item) in bytes.iter().enumerate() {// .iter:返回集合中的每个元素// .enumerate():将 iter 返回的结果进行包装,并将结果作为元组的一部分进行返回if item == b' ' {return &s[..i];}}&s[..]
}
字符串字面值是切片
fn main() {let s = "Hello world";println!("{}", s);
}

字符串字面值被直接存储在二进制程序中

let s = "Hello,World!";

变量 s 的类型是 &str ,它是一个指向二进制程序特定位置的切片

  • &str 是不可变引用,所以字符串字面值是不可变的
字符串切片作为参数传递
fn first_word(s:&String) -> &str{}

也可以将 &str 作为参数类型,这样可以同时接收 String&str 类型的参数

fn first_word(s:&str) -> &str{}
  • 使用字符串切片,直接调用该函数
  • 使用String,可以创建一个完整的 String 切片来调用该函数
    定义函数时使用字符串切片代替字符串引用会使API更加通用,且不损失任何功能
fn main() {let my_string = String::from("Hello world");let wordIndex = first_world(&my_string[..]);let my_string_literal = "hello world";let wordIndex = first_world(my_string_literal);
}fn first_world(s:&str) -> &str {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return &s[..i];}}&s[..]
}
列表切片
fn main() {let a = [1, 2, 3, 4, 5];let slice = &a[1..3]// &[i32] 类型切片
}
http://www.dtcms.com/a/443268.html

相关文章:

  • 做北京电梯招标的网站合肥seo外包平台
  • CodeForces 20251003 - ?
  • node服务端通过socket.io保持长连接通信示例
  • Super-Resolution Delay-Doppler Estimation for OFDM Passive Radar
  • 九江开发区建设环保局网站贵州住房建设厅官网查询
  • 郑州诺耀科技 - 郑州高端网站建设营销推广网站路径怎么做
  • 招聘网站源码下载岳阳市交通建设投资公司门户网站
  • 单细胞空间--免疫细胞与肾脏细胞的时空相互作用调控自身免疫性肾病中肾小球新月体形成
  • 巨野做网站的网站策划与运营考试题
  • 正品海外购网站有哪些中国十大建筑设计院排名
  • Docker Compose 多容器编排实战
  • 初创公司 建网站网站设计和营销
  • 救援启动!
  • Android 事件分发机制 图解
  • 做网站价钱电脑版qq在线登录网页入口
  • 140、【OS】【Nuttx】【周边】效果呈现方案解析:strace 日志解析(六)
  • 免费网站建设软件大全wordpress禁止中国ip
  • 有界区域上具有常数右端项的泊松方程解的上界估计
  • 做区位分析的地图网站wordpress主题分类目录主题
  • 网站建设实习每天内容手机网站开发 html
  • 手机网站制作案例教育网站建设备案
  • 网站开发时的闭包写法进一步加强区门户网站建设管理办法
  • 计算机网站设计怎么做扬中信息网
  • 专业网站名词解释资讯网站 怎样 增强用户粘度
  • 现代C++——并发编程
  • Dotnet通过OpenAI方式接入DeepSeek
  • 第八课(零基础友好版)|第一次训练模型(Teachable Machine,超详细)
  • 定制制作网站公司建筑模型网站有哪些
  • 【多线程】无锁数据结构(Lock-Free Data Structures)是什么?
  • YOLO入门教程(番外):卷积神经网络—卷积神经网络(LeNet)