【Rust】基本概念
目录
- 第一个 Rust 程序:猜数字
- 基本概念
- 变量和可变性
- 可变性
- 常量
- 变量隐藏
- 数据类型
- 标量类型
- 整型
- 浮点型
- 数值运算
- 布尔型
- 字符类型
- 复合类型
- 元组
- 数组
- 函数
- 参数
- 语句与表达式
- 函数返回值
- 控制流
- 使用 if 表达式控制条件
- if 表达式
- 使用 else if 处理多重条件
- 在 let 语句中使用 if
- 使用循环重复执行
- 使用 loop 重复执行代码
- 从循环返回值
- 循环标签
- while 条件循环
- 使用 for 遍历集合
第一个 Rust 程序:猜数字
正式开始学习 Rust 前,先来看一下下面的代码,其中有注释,用于大致了解 Rust 中的基本概念和用法,看不懂也没关系,后面会慢慢讲到。
猜数字小游戏:
use std::io; //引入标准库std中的io库
use rand::Rng; //引入第三方库rand中的Rng trait,类似于Java中的接口或者抽象类
//也可以用use rand::prelude::*;
use std::cmp::Ordering; //引入标准库std中的cmp模块中的Ordering类型fn main() {//println!是宏(不是函数),用于打印到标准输出(默认带换行)println!("Guess the number!");//生成1-100的随机数/**可以理解成rand中的thread_rng()返回了一个ThreadRng类型的随机数生成器*并且ThreadRng实现了Rng trait的gen_range()方法*所以该生成器调用了rand库中Rng trait中的gen_range()方法来生成指定范围内的随机数*//**rand::thread_rng() 返回一个 ThreadRng 类型的随机数生成器*该类型实现了 Rng trait,而 gen_range() 就是定义在 Rng trait 中的方法*因此,我们可以通过 ThreadRng 调用 gen_range() 来生成指定范围内的随机数*/let secret_number = rand::thread_rng().gen_range(1..=100); //1-100的随机数// "secret_number: {}"是输出模板,{} 是占位符,表示要插入一个变量的值println!("secret_number: {}",secret_number);loop{println!("Please input your guess.");//调用 String 类型的关联函数 new(),创建一个空的 String 实例。let mut guess = String::new();//读取用户输入数据//表示从标准输入中获取一个输入句柄,等价于 std::io::stdin()//返回一个 Stdin 类型的对象,用于读取用户输入io::stdin()//read_line 方法会等待用户输入一行内容并按下 Enter//它会把输入的内容追加到 guess 字符串里(包括换行符 \n)//参数是 &mut guess,所以 guess 必须是一个可变的 String//返回一个 Result<usize>,表示读取了多少个字节,或者发生了错误.read_line(&mut guess)//expect 是 Result 类型的方法。//如果 read_line 成功,程序继续执行。//如果失败(例如输入设备出错),就会打印 "Failed to read line",然后程序崩溃退出(panic).expect("Failed to read line");//也能用match来替代expect/*match io::stdin().read_line(&mut guess) {Ok(_) => {},Err(_) => {println!("Failed to read line");return;}};*/println!("You guessed: {}",guess);//match匹配 parse() 返回的结果,是 Result 类型//.trim() 去除前后空白字符,返回 &strlet guess: u32 = match guess.trim().parse(){Ok(num) => num,//如果成功解析,num 就是那个转换后的 u32 数字,把它赋值给前面的 let guess: u32Err(_) => continue,//如果解析失败,比如你输入了字母,程序就跳过当前循环,continue 重新来过};//这是 cmp() 方法,来自 PartialOrd/Ord trait(u32 实现了它)//返回值是一个 Ordering 枚举类型match guess.cmp(&secret_number) {Ordering::Less => println!("Too small!"),Ordering::Equal => {println!("You win!");break;},Ordering::Greater => println!("Too big!"),}}
}
大致了解完了吗,下面将开启 Rust 之旅!
基本概念
变量和可变性
可变性
在 Rust 中变量默认是不可改变的(immutable)。
当变量不可变时,一旦变量被赋予了某个值,这个值就不能被改变。代码如下:
fn main() {let x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");
}
运行上述代码将得到一条与不可变性有关的错误信息:
error[E0384]: cannot assign twice to immutable variable `x`--> src/main.rs:4:5|
2 | let x = 5;| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;| ^^^^^ cannot assign twice to immutable variable|
help: consider making this binding mutable|
2 | let mut x = 5;| +++
这是由于尝试修改了变量 x 的值导致的编译时错误。尽管变量默认是不可变的,仍然可以通过在变量名前添加关键字 mut 来使其可变。代码如下:
fn main() {let mut x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");
}
结果如下:
The value of x is: 5
The value of x is: 6
常量
与不可变变量类似,常量(constants)是绑定到一个名称的不允许被改变的值。
常量与变量的区别:
- 常量不允许用
mut修饰 - 常量不仅默认不可变,且总是不可变
- 常量声明使用关键字
const而不是let,且必须注明值的类型 - 常量可以在任何作用域中声明,包括全局作用域
- 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值
以下是一个声明常量 THREE_HOURS_IN_SECONDS 的代码示例:
fn main() {const THREE_HOURS_IN_SECONDS: u32= 60 * 60 * 3;println!("Three hours in seconds is {THREE_HOURS_IN_SECONDS}s");
}
结果如下:
Three hours in seconds is 10800s
变量隐藏
在 Rust 中变量的值不能修改,但是允许定义一个与之前变量同名的新变量。
当定义了一个新的同名变量,在使用变量名时,使用的是新的变量而不是旧的变量,这种情况称为第一个变量被第二个变量隐藏。
第二个变量“遮蔽”了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被隐藏或第二个变量的作用域(一对 {} 就是变量的作用域)结束。
可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏,如下所示:
fn main() {let x = 5;let x = x + 1;{let x = x * 2;println!("The value of x in the inner scope is: {x}");}println!("The value of x is: {x}");
}
结果如下:
The value of x in the inner scope is: 12
The value of x is: 6
隐藏与将变量标记为 mut 是有区别的:
| 特性 | mut | 隐藏(shadowing) |
|---|---|---|
| 允许修改值 | ✅ 是 | ✅ 是(通过新变量) |
| 允许改变类型 | ❌ 否 | ✅ 是 |
| 是否是同一个变量 | ✅ 是(在内存中相同) | ❌ 否(创建了新绑定) |
| 作用域 | 相同作用域 | 每次新 let 是新作用域 |
| 是否会影响原变量 | ✅ 是 | ❌ 不会(是新变量) |
| 是否可以变不可变/反之 | ❌ 否(必须在一开始就决定) | ✅ 是(新绑定可以改变 mut 状态) |
数据类型
在 Rust 中,每一个值都属于某一个数据类型(data type),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。
Rust 的数据类型分为两类数据类型子集:标量和复合。
Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出想要用的类型。当多种类型均有可能时,必须增加类型注解。
标量类型
标量类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。
整型
整数是一个没有小数部分的数字。
Rust 中的整型分为两大类:
| 类别 | 说明 |
|---|---|
| 有符号整型 | 可以表示正数和负数(有符号位),i 开头 |
| 无符号整型 | 只能表示正数和零,u 开头 |
整型类型及其大小范围:
| 类型 | 大小 | 范围(十进制) |
|---|---|---|
i8 | 8位 | -128 到 127 |
i16 | 16位 | -32,768 到 32,767 |
i32 | 32位 | -2,147,483,648 到 2,147,483,647 |
i64 | 64位 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
i128 | 128位 | 非常大的范围(用于加密或大数运算) |
isize | 平台相关 | 32位系统上为 i32,64位系统上为 i64 |
u8 | 8位 | 0 到 255 |
u16 | 16位 | 0 到 65,535 |
u32 | 32位 | 0 到 4,294,967,295 |
u64 | 64位 | 0 到 18,446,744,073,709,551,615 |
u128 | 128位 | 同上 |
usize | 平台相关 | 类似于 isize |
整型字面值基本格式:
let a = 42; // 推断为 i32(默认)
let b = 42u8; // 明确指定为 u8
let c = 42_i64; // 下划线分隔的类型后缀形式(等价于 42i64)
为了增强可读性可以使用下划线 _
let big_number = 1_000_000; // = 1000000
let hex = 0xff_ff; // = 65535
Rust 支持多种进制的数字字面值:
| 写法 | 说明 | 示例 |
|---|---|---|
| 十进制 | 默认 | 42 |
十六进制 (0x) | 十六进制数值 | 0xff |
八进制 (0o) | 八进制数值 | 0o77 |
二进制 (0b) | 二进制数值 | 0b1010 |
单字节字符 (b'') | 仅限于u8 | b'A' |
整型溢出:
- Debug 模式:默认检查溢出,溢出会 panic
- Release 模式:不 panic,采用 wrap-around(回绕)行为
let x: u8 = 255;
let y = x + 1; // Debug 模式下会 panic;Release 下为 0
显式溢出方法:
| 方法名 | 行为 |
|---|---|
wrapping_add() | 溢出时回绕 |
checked_add() | 溢出时返回 None |
overflowing_add() | 返回值和是否溢出的布尔值 |
saturating_add() | 溢出时返回最大或最小值(饱和行为) |
//wrapping_* —— 二进制补码回绕
let x: u8 = 255;
let y = x.wrapping_add(1);
println!("{}", y); // 输出 0,因为 u8 最大值是 255//checked_* —— 安全检测,有无溢出
let x: u8 = 255;
let result = x.checked_add(1);
match result {Some(val) => println!("Result: {}", val),None => println!("Overflow occurred!"),
}//overflowing_* —— 返回值和溢出标志
let x: u8 = 255;
let (val, overflowed) = x.overflowing_add(1);
println!("val = {}, overflowed = {}", val, overflowed); // val = 0, overflowed = true//saturating_* —— 饱和计算(到边界)
let x: u8 = 250;
let y = x.saturating_add(10);
println!("{}", y); // 输出 255,不会变成 4,而是饱和到最大值
浮点型
Rust 也有两个原生的浮点数类型,它们是带小数点的数字。
Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64。
浮点数采用 IEEE-754 标准表示。f32 是单精度浮点数,f64 是双精度浮点数。
fn main() {let x = 2.0; // f64let y: f32 = 3.0; // f32
}
数值运算
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数。
代码示例:
fn main() {// additionlet sum = 5 + 10;println!("The sum of 5 and 10 is {}", sum);// subtractionlet difference = 95.5 - 4.3;println!("The difference of 95.5 and 4.3 is {}", difference);// multiplicationlet product = 4 * 30;println!("The product of 4 and 30 is {}", product);// divisionlet quotient = 56.7 / 32.2;println!("The quotient of 56.7 and 32.2 is {}", quotient);let truncated = -5 / 3; // 结果为 -1println!("The truncated result of -5 / 3 is {}", truncated);// remainderlet remainder = 43 % 5;println!("The remainder of 43 % 5 is {}", remainder);
}
结果如下:
The sum of 5 and 10 is 15
The difference of 95.5 and 4.3 is 91.2
The product of 4 and 30 is 120
The quotient of 56.7 and 32.2 is 1.7608695652173911
The truncated result of -5 / 3 is -1
The remainder of 43 % 5 is 3
布尔型
Rust 中的布尔类型有两个可能的值:true 和 false。Rust 中的布尔类型使用 bool 表示。例如:
fn main() {let t = true;let f: bool = false; //带有显式类型注释
}
使用布尔值的主要场景是条件表达式,例如 if 表达式。
字符类型
Rust 的 char 类型是语言中最原生的字母类型。下面是一些声明 char 值的例子:
fn main() {let c = 'z';let z: char = 'ℤ'; // with explicit type annotationlet heart_eyed_cat = '😻';
}
用单引号声明 char 字面量,而与之相反的是,使用双引号声明字符串字面量。Rust 的 char 类型的大小为四个字节,并代表了一个 Unicode 标量值,这意味着它可以比 ASCII 表示更多内容。
复合类型
复合类型可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
元组
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
fn main() {let tup: (i32, f64, u8) = (500, 6.4, 1);
}
输出元组不能用 {},要用 Debug trait 中的 {:?} 或 {:#?}
println!("The value of tup is: {:?}", tup);
为了从元组中获取单个值,可以使用模式匹配来解构元组值,像这样:
fn main() {let tup = (500, 6.4, 1);let (x, y, z) = tup;println!("The value of x is: {x}");println!("The value of y is: {y}");println!("The value of z is: {z}");
}
使用了 let 和一个模式将 tup 分成了三个不同的变量,x、y 和 z。这叫做 解构,因为它将一个元组拆成了三个部分。
也可以使用点号(.)后跟值的索引来直接访问它们。例如:
fn main() {let x: (i32, f64, u8) = (500, 6.4, 1);let five_hundred = x.0;println!("The value of five_hundred is: {}", five_hundred);let six_point_four = x.1;println!("The value of six_point_four is: {}", six_point_four);let one = x.2;println!("The value of one is: {}", one);
}
结果如下:
The value of five_hundred is: 500
The value of six_point_four is: 6.4
The value of one is: 1
不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。
数组
另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust 中的数组长度是固定的。
将数组的值写成在方括号内,用逗号分隔:
fn main() {let a = [1, 2, 3, 4, 5];
}
可以像这样编写数组的类型:在方括号中包含每个元素的类型,后跟分号,再后跟数组元素的数量:
let a: [i32; 5] = [1, 2, 3, 4, 5];
还可以通过在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组:
let a = [3; 5]; //let a = [3, 3, 3, 3, 3];
访问数组元素跟访问元组中的单个值类似,也是通过索引来访问:
fn main() {let a = [1, 2, 3, 4, 5];let first = a[0];println!("The first element is: {}", first);let second = a[1];println!("The second element is: {}", second);
}
结果如下:
The first element is: 1
The second element is: 2
跟其他编程语言一样,Rust 中也要预防索引越位。
函数
函数在 Rust 代码中非常普遍。已经见过语言中最重要的函数之一:main 函数,它是很多程序的入口点。你也见过 fn 关键字,它用来声明新函数。
Rust 代码中的函数和变量名使用 snake case 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。这是一个包含函数定义示例的程序:
fn main() {println!("Hello, world!");another_function();
}fn another_function() {println!("Another function.");
}
参数
跟其他编程语言一样,Rust 可以定义为拥有参数的函数,参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。
fn main() {another_function(5); //传入的5是实参
}fn another_function(x: i32) { //x是形参println!("The value of x is: {x}");
}
Rust 规定:要求在函数定义中提供类型注解。也就是说在函数签名中,必须声明每个参数的类型。
fn main() {print_labeled_measurement(5, 'h');
}fn print_labeled_measurement(value: i32, unit_label: char) {println!("The measurement is: {value}{unit_label}");
}
语句与表达式
Rust 是一门基于表达式的语言,函数体由一系列的语句和一个可选的结尾表达式构成:
- 语句(Statements)是执行一些操作但不返回值的指令
- 表达式(Expressions)计算并产生一个值
常见语句:
-
变量绑定语句
let x = 5; -
函数调用语句
println!("Hello, world!"); -
表达式后加分号也变成语句
3 + 4; // 这是表达式加上分号,变成了语句,不返回值
常见表达式:
-
算术表达式
let y = 3 + 4; // 表达式 3 + 4 的结果是 7 -
函数返回值
fn add(a: i32, b: i32) -> i32 {a + b // 没有分号,是表达式,返回值 } -
if 表达式
let max = if x > y { x } else { y }; // if 是一个表达式 -
代码块表达式
let z = {let a = 5;let b = 10;a + b // 最后一行没有分号,整个 block 是表达式 }; // z = 15
函数返回值
函数可以向调用它的代码返回值。虽然并不对返回值命名,但要在箭头(->)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。使用 return 关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。这是一个有返回值的函数的例子:
fn five() -> i32 {5
}fn main() {let x = five();println!("The value of x is: {x}");
}
输出结果如下:
The value of x is: 5
let x = five(); 这一行表明我们使用函数的返回值初始化一个变量。因为 five 函数返回 5。five 函数没有参数并定义了返回值类型,不过函数体只有单单一个 5 也没有分号,因为这是一个表达式,需要返回它的值。
看另一个例子:
fn main() {let x = plus_one(5);println!("The value of x is: {x}");
}fn plus_one(x: i32) -> i32 {x + 1;
}
运行代码产生错误:
error[E0308]: mismatched types--> src/main.rs:7:24|
7 | fn plus_one(x: i32) -> i32 {| -------- ^^^ expected `i32`, found `()`| || implicitly returns `()` as its body has no tail or `return` expression
8 | x + 1;| - help: remove this semicolon to return this value
主要的错误信息,“mismatched types”(类型不匹配),揭示了代码的核心问题。函数 plus_one 的定义说明它要返回一个 i32 类型的值,不过语句并不会返回值,使用单位类型 ()表示不返回值。因为不返回值与函数定义相矛盾,从而出现一个错误。
函数返回值不要以分号结尾!!!
控制流
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码的能力是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 if 表达式和循环。
使用 if 表达式控制条件
if 表达式
if 表达式允许根据条件执行不同的代码分支。提供一个条件并表示如果条件满足,运行这段代码;如果条件不满足,不运行这段代码。
fn main() {let number = 3;if number < 5 {println!("condition was true");} else {println!("condition was false");}
}
所有的 if 表达式都以 if 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 number 的值是否小于 5。在条件为 true 时希望执行的代码块位于紧跟条件之后的大括号中。
也可以包含一个可选的 else 表达式来提供一个在条件为 false 时应当执行的代码块。如果不提供 else 表达式并且条件为 false 时,程序会直接忽略 if 代码块并继续执行下面的代码。
输出结果如下:
condition was true
得注意的是代码中的条件 必须 是
bool值。
使用 else if 处理多重条件
可以将 else if 表达式与 if 和 else 组合来实现多重条件。例如:
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 if number % 2 == 0 {println!("number is divisible by 2");} else {println!("number is not divisible by 4, 3, or 2");}
}
当执行这个程序时,它按顺序检查每个 if 表达式并执行第一个条件为 true 的代码块。注意即使 6 可以被 2 整除,也不会输出 number is divisible by 2,更不会输出 else 块中的 number is not divisible by 4, 3, or 2。原因是 Rust 只会执行第一个条件为 true的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
在 let 语句中使用 if
因为 if 是一个表达式,我们可以在 let 语句的右侧使用它,例如:
fn main() {let condition = true;let number = if condition { 5 } else { 6 };println!("The value of number is: {number}");
}
number 变量将会绑定到表示 if 表达式结果的值上:
The value of number is: 5
代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。
if的每个分支的可能的返回值都必须是相同类型.
使用循环重复执行
多次执行同一段代码是很常用的,Rust 为此提供了多种循环。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。
使用 loop 重复执行代码
loop 关键字告诉 Rust 一遍又一遍地执行一段代码直到明确要求停止。
fn main() {loop {println!("again!");}
}
当运行这个程序时,我们会看到连续的反复打印 again!,直到手动停止程序。大部分终端都支持一个快捷键,ctrl+c,来终止一个陷入无限循环的程序。
除了用 ctrl+c 手动停止外,Rust 提供了一种从代码中跳出循环的方法。可以使用 break关键字来告诉程序何时停止循环。循环中的 continue 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。
从循环返回值
在循环的过程中,可能会需要将操作的结果返回给其它的代码。如果将返回值加入你用来停止循环的 break 表达式,它会被停止的循环返回:
fn main() {let mut counter = 0;let result = loop {counter += 1;if counter == 10 {break counter * 2;}};println!("The result is {result}");
}
输出结果如下:
The result is 20
跟传统的 C/C++、Java 不同,Rust 中的
break还能返回值。
循环标签
如果存在嵌套循环,break 和 continue 应用于此时最内层的循环。可以选择在一个循环上指定一个循环标签,然后将标签与 break 或 continue 一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。下面是一个包含两个嵌套循环的示例:
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}");
}
外层循环有一个标签 counting_up,它将从 0 数到 2。没有标签的内部循环从 10 向下数到 9。第一个没有指定标签的 break 将只退出内层循环。break 'counting_up; 语句将退出外层循环。这个代码打印:
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
while 条件循环
在程序中计算循环的条件也很常见。当条件为 true,执行循环。当条件不再为 true,调用 break 停止循环。Rust 为此内置了一个语言结构,它被称为 while 循环。
fn main() {let mut number = 3;//当条件为 true 就执行,否则退出循环while number != 0 {println!("{number}!");number -= 1;}println!("TIME OVER!!!");
}
输出结果如下:
3!
2!
1!
TIME OVER!!!
可以使用 while 结构来遍历集合中的元素,比如数组。例如:
fn main() {let a = [10, 20, 30, 40, 50];let mut index = 0;while index < 5 {println!("the value is: {}", a[index]);index += 1;}
}
这里,代码对数组中的元素进行计数。它从索引 0 开始,并接着循环直到遇到数组的最后一个索引(这时,index < 5 不再为真)。输出结果如下:
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
但是这种手动设置 index < 5 往往会因为粗心或者不注意而导致索引越位的现象,进而导致程序出错,因此将使用下面的方法来遍历数组。
使用 for 遍历集合
作为更简洁的替代方案,可以使用 for 循环来对一个集合的每个元素执行一些代码:
fn main() {let a = [10, 20, 30, 40, 50];for element in a {println!("the value is: {element}");}
}
当然,Rust 中的 for 循环也是有带索引的形式:
fn main() {for number in (1..4).rev() {println!("{number}!");}println!("TIME OVER!!!");
}
in (1...4) 表示的是 1 到 4 但不包括 4 的数字,即 1、2、3。输出结果如下:
3!
2!
1!
TIME OVER!!!
相关文章:
【Rust】基本概念
目录 第一个 Rust 程序:猜数字基本概念变量和可变性可变性常量变量隐藏 数据类型标量类型整型浮点型数值运算布尔型字符类型 复合类型元组数组 函数参数语句与表达式函数返回值 控制流使用 if 表达式控制条件if 表达式使用 else if 处理多重条件在 let 语句中使用 i…...
AI-Sphere-Butler之如何使用Llama factory LoRA微调Qwen2-1.5B/3B专属管家大模型
环境: AI-Sphere-Butler WSL2 英伟达4070ti 12G Win10 Ubuntu22.04 Qwen2.-1.5B/3B Llama factory llama.cpp 问题描述: AI-Sphere-Butler之如何使用Llama factory LoRA微调Qwen2-1.5B/3B管家大模型 解决方案: 一、准备数据集我这…...
C++学习之游戏服务器开发十四QT登录器实现
目录 1.界面搭建 2.登录客户端步骤分析 3.拼接登录请求实现 4.发送http请求 5.服务器登录请求处理 6.客户端处理服务器回复数据 7.注册页面启动 8.qt启动游戏程序 1.界面搭建 查询程序依赖的动态库 ldd 程序名 do 1 cdocker rm docker ps -aq 静态编译游戏服务程序&a…...
协同推荐算法实现的智能商品推荐系统 - [基于springboot +vue]
🛍️ 智能商品推荐系统 - 基于springboot vue 🚀 项目亮点 欢迎来到未来的购物体验!我们的智能商品推荐系统就像您的私人购物顾问,它能读懂您的心思,了解您的喜好,为您精心挑选最适合的商品。想象一下&am…...
【LLM】Ollama:容器化并加载本地 GGUF 模型
本教程将完整演示如何在支持多 GPU 的环境下,通过 Docker 实现 Ollama 的本地化部署,并深度整合本地 GGUF 模型。我们将构建一个具备生产可用性的容器化 LLM 服务,包含完整的存储映射、GPU 加速配置和模型管理方案。 前提与环境准备 操作系统…...
实践项目开发-hbmV4V20250407-Taro项目构建优化
Taro项目构建优化实践:大幅提升开发效率 项目背景 在开发基于ReactTaro的前端项目时,随着项目规模的增长,构建速度逐渐成为开发效率的瓶颈。通过一系列构建优化措施,成功将开发环境的构建速度提升了30%-50%,显著改善…...
MySQL中根据binlog日志进行恢复
MySQL中根据binlog日志进行恢复 排查 MySQL 的 binlog 日志问题及根据 binlog 日志进行恢复的方法一、引言二、排查 MySQL 的 binlog 日志问题(一)确认 binlog 是否开启(二)查找 binlog 文件位置和文件名模式(三&#…...
Jenkins的地位和作用
所处位置 Jenkins 是一款开源的自动化服务器,广泛应用于软件开发和测试流程中,主要用于实现持续集成(CI)和持续部署(CD)。它在开发和测试中的位置和作用可以从以下几个方面来理解: 1. 在开发和测…...
【集合】底层原理实现及各集合之间的区别
文章目录 集合2.1 介绍一下集合2.2 集合遍历的方法2.3 线程安全的集合2.4 数组和集合的区别2.5 ArrayList和LinkedList的区别2.6 ArrayList底层原理2.7 LinkedList底层原理2.8 CopyOnWriteArrayList底层原理2.9 HashSet底层原理2.10 HashMap底层原理2.11 HashTable底层原理2.12…...
软考高级-系统架构设计师 论文范文参考(二)
文章目录 论企业应用集成论软件三层结构的设计论软件设计模式的应用论软件维护及软件可维护性论信息系统安全性设计论信息系统的安全性设计(二)论信息系统的架构设计论信息系统架构设计(二) 论企业应用集成 摘要: 2016年9月,我国某省移动通信有限公司决定启动VerisB…...
srp batch
参考网址: Unity MaterialPropertyBlock 正确用法(解决无法合批等问题)_unity_define_instanced_prop的变量无法srp合批-CSDN博客 URP | 基础CG和HLSL区别 - 哔哩哔哩 (bilibili.com) 【直播回放】Unity 批处理/GPU Instancing/SRP Batche…...
【Linux运维涉及的基础命令与排查方法大全】
文章目录 前言1、计算机网络常用端口2、Kali Linux中常用的命令3、Kali Linux工具的介绍4、Ubuntu没有网络连接解决方法5、获取路由6、数据库端口 前言 以下介绍计算机常见的端口已经对应的网络协议,Linux中常用命令,以及平时运维中使用的排查网络故障的…...
【2025最新Java八股】redis中io多路复用怎么回事,和多线程的关系
io多路复用 IO 多路复用和多线程是两种不同的技术,他们都是用于改善程序在处理多个任务或多个数据流时的效率和性能的。 但是他俩要解决的问题不一样!IO多路复用主要是提升I/O操作的效率和利用率,所以适合 IO 密集型应用。多线程则是提升CP…...
Webview+Python:用HTML打造跨平台桌面应用的创新方案
目录 一、技术原理与优势分析 1.1 架构原理 1.2 核心优势 二、开发环境搭建 2.1 安装依赖 2.2 验证安装 三、核心功能开发 3.1 基础窗口管理 3.2 HTML↔Python通信 JavaScript调用Python Python调用JavaScript 四、高级功能实现 4.1 系统级集成 4.2 多窗口管理 五…...
Nginx HTTP 414 与“大面积”式洪水攻击联合防御实战
一、引言 在大规模分布式应用中,Nginx 常作为前端负载均衡和反向代理服务器。攻击者若结合超长 URI/头部攻击(触发 HTTP 414)与海量洪水攻击,可在网络层与应用层形成双重打击:一方面耗尽缓冲区和内存,另一…...
Oracle高级语法篇-集合操作
Oracle 集合操作详解 作为数据库领域的佼佼者,Oracle 提供了功能强大的集合操作符,它们能够合并多个查询的结果集,极大提升数据处理效率。接下来,本文将从基础知识点到实战案例,全方位剖析 Oracle 的集合操作。 一、…...
克服储能领域的数据处理瓶颈及AI拓展
对于储能研究人员来说,日常工作中经常围绕着一项核心但有时令人沮丧的任务:处理实验数据。从电池循环仪的嗡嗡声到包含电压和电流读数的大量电子表格,研究人员的大量时间都花在了提取有意义的见解上。长期以来,该领域一直受到对专…...
包含物体obj与相机camera的 代数几何代码解释
反余弦函数的值域在 [0, pi] 斜体样式 cam_pose self._cameras[hand_realsense].camera.get_model_matrix() # cam2world# 物体到相机的向量 obj_tcp_vec cam_pose[:3, 3] - self.obj_pose.p dist np.linalg.norm(obj_tcp_vec) # 物体位姿的旋转矩阵 obj_rot_mat self.ob…...
excel解析图片pdf附件不怕
背景 工作中肯定会有导入excel还附带图片附件的下面是我解析的excel,支持图片、pdf、压缩文件实现 依次去解析excel,看看也没有附件,返回的格式是Map,key是第几行,value是附件list附件格式都被解析成pdf格式Reader.jav…...
【Spring】依赖注入的方式:构造方法、setter注入、字段注入
在Spring框架中,除了构造器注入(Constructor Injection)和Setter注入(Setter Injection),还有一种依赖注入方式:字段注入(Field Injection)。字段注入通过在Bean的字段上…...
mybatis实现增删改查1
文章目录 19.MyBatis查询单行数据MapperScan 结果映射配置核心文件Results自定义映射到实体的关系 多行数据查询-完整过程插入数据配置mybatis 控制台日志 更新数据删除数据小结通过id复用结果映射模板xml处理结果映射 19.MyBatis 数据库访问 MyBatis,MyBatis-Plus…...
Git,本地上传项目到github
一、Git的安装和下载 https://git-scm.com/ 进入官网,选择合适的版本下载 二、Github仓库创建 点击右上角New新建一个即可 三、本地项目上传 1、进入 要上传的项目目录,右键,选择Git Bash Here,进入终端Git 2、初始化临时仓库…...
基于flask+vue框架的灯饰安装维修系统u49cf(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
系统程序文件列表 项目功能:用户,工单人员,服务项目,订单记录,服务记录,评价记录 开题报告内容 基于 FlaskVue 框架的灯饰安装维修系统开题报告 一、选题背景与意义 (一)选题背景 随着城市化进程的加速与居民生活品质的显著提升…...
【算法】BFS-解决FloodFill问题
目录 FloodFill问题 图像渲染 岛屿数量 岛屿的最大面积 被围绕的区域 FloodFill问题 FloodFill就是洪水灌溉的意思,假设有下面的一块田地,负数代表是凹地,正数代表是凸地,数字的大小表示凹或者凸的程度。现在下一场大雨&…...
GIS开发笔记(10)基于osgearth实现二三维地图的一键指北功能
一、实现效果 二、实现原理 获取视图及地图操作器,通过地图操作器来重新设置视点,以俯仰角 (0.0)和偏航角 (-90.0)来设置。 osgEarth::Util::Viewpoint(…) 这里创建了一个新的 Viewpoint 对象,表示一个特定的视角。构造函数的参数是: 第一个参数:是视角名称。 后面的 6 个…...
Spring Boot日志系统详解:Logback与SLF4J的默认集成
大家好呀!👋 今天我们来聊聊Spring Boot中一个超级重要但又经常被忽视的功能——日志系统! 一、日志系统的重要性 首先,咱们得明白为什么日志这么重要?🤷♂️ 想象一下,你正在玩一个超级复…...
【C++】Json-Rpc框架项目介绍(1)
项目介绍 RPC(Remote Procedure Call)即远程过程调用,是一种通过网络从远程计算机程序中请求服务而不需要了解底层网络实现细节的一种 协议 。 RPC(Remote Procedure Call)可以使用多种网络协议进行通信,如…...
Docker 部署 PostgreSQL 数据库
Docker 部署 PostgreSQL 数据库 基于 Docker 部署 PostgreSQL 数据库一、拉取 PostgreSQL 镜像二、运行 PostgreSQL 容器三、运行命令参数详解四、查看容器运行状态 基于 Docker 部署 PostgreSQL 数据库 一、拉取 PostgreSQL 镜像 首先,确保你的 Docker 环境已正确…...
用 Go 优雅地清理 HTML 并抵御 XSS——Bluemonday
1、背景与动机 只要你的服务接收并回显用户生成内容(UGC)——论坛帖子、评论、富文本邮件正文、Markdown 等——就必须考虑 XSS(Cross‑Site Scripting)攻击风险。浏览器在解析 HTML 时会执行脚本;如果不做清理&#…...
Python爬虫从入门到实战详细版教程
Python爬虫从入门到实战详细版教程 文章目录 Python爬虫从入门到实战详细版教程书籍大纲与内容概览第一部分:爬虫基础与核心技术1. 第1章:[爬虫概述](https://blog.csdn.net/qq_37360300/article/details/147431708?spm=1001.2014.3001.5501)2. 第2章:HTTP协议与Requests库…...
