【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!!!
