第六章 变量、数据类型与基本运算

引子:计算机程序如何"思考"?

想象一下,你正在教一个完全不懂数学的小朋友做计算。你会怎么做?

你可能会说:"看,这是数字1,这是数字2,如果你把它们放在一起,就变成了3。"

计算机程序也是这样工作的。它就像一个非常听话但有点"笨"的学生,需要你明确地告诉它:

  • 这是什么?(数据类型)
  • 把它放在哪里?(变量)
  • 对它做什么?(运算)

数据的"生命":从输入到输出

让我们看一个简单的例子:计算圆的面积。

人类思维过程:

  1. 我知道圆的面积公式是 π × 半径²
  2. 如果半径是5,那么面积就是 3.14 × 5 × 5 = 78.5

计算机程序思维过程:

  1. 我需要一个地方存储半径值 → 创建变量 radius
  2. 我需要一个地方存储π值 → 创建常量 PI
  3. 我需要一个地方存储计算结果 → 创建变量 area
  4. 执行计算:area = PI * radius * radius
  5. 显示结果
fn main() {
    // 1. 存储数据
    let radius = 5.0;           // 半径
    const PI: f64 = 3.14159;    // 圆周率
    
    // 2. 操纵数据(计算)
    let area = PI * radius * radius;
    
    // 3. 输出结果
    println!("半径为 {} 的圆的面积是:{:.2}", radius, area);
}

程序 = 数据 + 操作

所有的计算机程序,本质上都是在做三件事:

  1. 存储数据 - 把信息放在"盒子"里
  2. 操纵数据 - 对数据进行各种操作(计算、比较、转换等)
  3. 输出结果 - 把处理后的信息展示出来

就像做菜一样:

  • 食材 = 数据(面粉、鸡蛋、糖)
  • 烹饪过程 = 操作(搅拌、加热、冷却)
  • 成品 = 结果(蛋糕)

为什么需要不同的数据类型?

想象你在整理房间:

  • 书籍 → 放在书架上(整整齐齐)
  • 衣服 → 放在衣柜里(叠好分类)
  • 食物 → 放在冰箱里(保持新鲜)
  • 工具 → 放在工具箱里(方便取用)

不同的东西需要不同的存放方式。同样,不同的数据也需要不同的"容器":

  • 数字(年龄、价格)→ 数字类型
  • 文字(姓名、地址)→ 字符串类型
  • 真假(开关状态)→ 布尔类型
  • 多个相关数据 → 数组或元组

简单的例子

这一章,我们将从最简单的数据操作开始:

  1. 变量、常量、静态变量 - 学会创建和使用"数据盒子",了解数据如何存储的
  2. 数据类型 - 了解不同数据的种类(占据的内存大小)
  3. 基本运算 - 了解如何操作不同类型的数据

下面是一个简单的计算器程序。这个程序需要:存储用户输入的数字,进行加减乘除运算,保存计算结果,最后显示最终答案。

这就是我们这一章要学习的内容:如何在Rust中存储和处理数据

让我们从一个简单的例子开始:

fn main() {
    // 存储用户输入的数字
    let number1 = 10;
    let number2 = 5;
    
    // 进行运算
    let sum = number1 + number2;
    let difference = number1 - number2;
    let product = number1 * number2;
    let quotient = number1 / number2;
    
    // 显示结果
    println!("{} + {} = {}", number1, number2, sum);
    println!("{} - {} = {}", number1, number2, difference);
    println!("{} × {} = {}", number1, number2, product);
    println!("{} ÷ {} = {}", number1, number2, quotient);
}

运行这个程序,我们会看到:

10 + 5 = 15
10 - 5 = 2
10 × 5 = 50
10 ÷ 5 = 2

在这个例子中,number1number2sumdifferenceproductquotient 都是变量,用来存储数据。 105整型数据+-*/运算符println! 用来显示结果。

6.1 变量声明与可变性

什么是变量?

想象变量就像一个盒子,可以存放东西。在编程中,变量用来存储数据,比如数字、文字等。

在Rust中声明变量

在Rust中,我们用let关键字来创建一个"盒子"(变量):

#![allow(unused)]
fn main() {
let age = 25;        // 创建一个名为age的盒子,里面放数字25
let name = "小明";    // 创建一个名为name的盒子,里面放文字"小明"
let is_student = true; // 创建一个名为is_student的盒子,里面放true
}

变量的可变性

Rust有一个重要的特点:默认情况下,变量一旦创建就不能改变内容。

#![allow(unused)]
fn main() {
let age = 25;
age = 26; // 错误!不能修改age的内容
}

这就像用胶水封住的盒子,一旦放好东西就不能再换了。

如果你需要一个可以改变内容的盒子,需要用mut关键字:

#![allow(unused)]
fn main() {
let mut age = 25;  // 创建一个可以修改的盒子
age = 26;          // 现在可以修改了!
age = 27;          // 还可以继续修改
}

为什么要这样设计?

这种设计有几个好处:

  1. 防止意外修改:避免程序中出现意外的数据变化
  2. 代码更清晰:一看就知道哪些数据会变,哪些不会变
  3. 编译器优化:Rust编译器可以更好地优化代码

与C语言的对比

如果你学过C++语言,会发现Rust的做法很不同:

// C语言:变量默认可以修改
int age = 25;
age = 26; // 正常
#![allow(unused)]
fn main() {
// Rust:变量默认不能修改
let age = 25;
age = 26; // 编译错误!
}

6.2 基本数据类型

什么是数据类型?

就像现实世界中有不同的东西(数字、文字、真假),编程中也有不同的数据类型。每种类型都有特定的用途和特点。

Rust的基本数据类型

Rust提供了多种数据类型,让我们一一了解:

整型(整数)

整型用来存储整数,比如年龄、数量等:

#![allow(unused)]
fn main() {
let age: i32 = 25;           // 32位有符号整数,可以存储正数和负数
let count: u32 = 100;        // 32位无符号整数,只能存储正数
let small_number: u8 = 255;  // 8位无符号整数,范围0-255
}

整型类型表:

类型范围用途
i8-128 到 127小整数
u80 到 255小正数
i32-2,147,483,648 到 2,147,483,647常用整数
u320 到 4,294,967,295常用正数
i64很大范围大整数
u64很大范围大正数

补充:为什么数据会有范围限制?

这要从计算机的存储原理说起。计算机用二进制(0和1)来存储数据,每个数字类型都有固定的位数。

以8位整数为例,计算机用8个二进制位来存储一个数字:

8位二进制:00000000 到 11111111
转换为十进制:0 到 255

这就是为什么u8的范围是0到255。对于有符号整数i8,最高位用来表示正负号:

正数:00000000 到 01111111 (0 到 127)
负数:10000000 到 11111111 (-128 到 -1)

32位整数用32个二进制位,所以范围更大:

i32: 32位有符号,范围约±21亿
u32: 32位无符号,范围0到约43亿

这就像用固定位数的数字来表示数字一样。如果你只有3位数字,最多只能表示0到999。计算机的位数限制决定了数据类型的范围,这是为了在存储效率和数值范围之间找到平衡。

浮点型(小数)

浮点型用来存储小数:

#![allow(unused)]
fn main() {
let pi: f64 = 3.14159;       // 64位浮点数,精度高
let price: f32 = 19.99;      // 32位浮点数,精度较低但节省内存
}

布尔型(真假)

布尔型只有两个值:true(真)和false(假):

#![allow(unused)]
fn main() {
let is_student = true;       // 是学生
let is_working = false;      // 不是在工作
}

字符型

字符型用来存储单个字符:

#![allow(unused)]
fn main() {
let grade = 'A';             // 字母
let emoji = '😊';            // 表情符号
let chinese = '中';          // 中文字符
}

注意: Rust的字符是Unicode字符,可以存储中文、表情符号等,而不仅仅是英文字母。

元组(组合数据)

元组可以同时存储多个不同类型的数据:

#![allow(unused)]
fn main() {
let person: (String, i32, bool) = ("小明".to_string(), 25, true);
// 包含:姓名、年龄、是否为学生

// 访问元组中的数据
let name = person.0;    // "小明"
let age = person.1;     // 25
let is_student = person.2; // true
}

数组(同类型数据列表)

数组用来存储多个相同类型的数据:

#![allow(unused)]
fn main() {
let scores: [i32; 5] = [85, 92, 78, 96, 88];  // 5个成绩
let first_score = scores[0];  // 85
let second_score = scores[1]; // 92
}

数组的特点:

  • 长度固定,创建后不能改变
  • 所有元素必须是相同类型
  • 用索引访问(从0开始)

类型推断

Rust很聪明,通常能自动判断数据类型:

#![allow(unused)]
fn main() {
let x = 42;        // Rust自动判断为i32
let y = 3.14;      // Rust自动判断为f64
let z = true;      // Rust自动判断为bool
}

但有时为了代码清晰,我们会显式标注类型:

#![allow(unused)]
fn main() {
let age: u8 = 25;  // 明确指定为u8类型
}

与C++的对比

Rust和C++都是系统级编程语言,但在数据类型设计上有一些重要区别:

基本类型对比:

C++Rust说明
inti3232位有符号整数
unsigned intu3232位无符号整数
charu88位无符号整数(字节)
wchar_tcharUnicode字符(Rust的char是32位)
floatf3232位浮点数
doublef6464位浮点数
boolbool布尔类型
std::array<T, N>[T; N]固定长度数组
std::tuple<...>(T1, T2, ...)元组

类型安全对比:

// C++:允许隐式类型转换,可能导致意外行为
int x = 42;
double y = x;  // 隐式转换:int -> double
char c = x;    // 隐式转换:int -> char(可能丢失数据)

// 更危险的例子
double price = 19.99;
int dollars = price;  // 隐式转换:19.99 -> 19(丢失小数部分)
#![allow(unused)]
fn main() {
// Rust:需要显式类型转换,防止意外数据丢失
let x: i32 = 42;
let y: f64 = x as f64;  // 显式转换:i32 -> f64
let c: u8 = x as u8;    // 显式转换:i32 -> u8

// 浮点数转整数需要显式转换
let price: f64 = 19.99;
let dollars: i32 = price as i32;  // 明确知道会丢失小数部分
}

字符类型对比:

// C++:char是1字节,主要用于ASCII字符
char c1 = 'A';           // ASCII字符
char c2 = 65;            // 数字表示
// char c3 = '中';       // 错误!中文字符需要多字节

// C++11引入的宽字符
wchar_t wc = L'中';      // 宽字符,支持Unicode
#![allow(unused)]
fn main() {
// Rust:char是32位Unicode字符,支持所有Unicode字符
let c1: char = 'A';      // ASCII字符
let c2: char = '中';     // 中文字符
let c3: char = '😊';     // 表情符号
let c4: char = 'α';      // 希腊字母

// 如果需要字节操作,使用u8
let byte: u8 = b'A';     // 字节字面量
}

数组对比:

// C++:数组长度在运行时可能不确定
int arr1[5] = {1, 2, 3, 4, 5};           // 固定长度
std::array<int, 5> arr2 = {1, 2, 3, 4, 5}; // C++11,固定长度
std::vector<int> vec = {1, 2, 3, 4, 5};   // 动态长度

// 数组越界检查(可选)
arr1[10] = 100;  // 未定义行为,可能崩溃
#![allow(unused)]
fn main() {
// Rust:数组长度在编译时确定,有边界检查
let arr: [i32; 5] = [1, 2, 3, 4, 5];  // 固定长度,编译时检查

// 运行时边界检查
// arr[10] = 100;  // 编译错误!索引超出范围

// 如果需要动态长度,使用Vec(后续章节学习)
let vec = vec![1, 2, 3, 4, 5];  // 动态数组
}

元组对比:

// C++17之前:没有内置元组
struct Person {
    std::string name;
    int age;
    bool is_student;
};

// C++17:引入std::tuple
#include <tuple>
auto person = std::make_tuple("小明", 25, true);
std::string name = std::get<0>(person);
int age = std::get<1>(person);
#![allow(unused)]
fn main() {
// Rust:内置元组支持
let person: (&str, i32, bool) = ("小明", 25, true);
let name = person.0;    // 通过索引访问
let age = person.1;
let is_student = person.2;

// 解构赋值
let (name, age, is_student) = person;
}

类型推断对比:

// C++11:auto关键字
auto x = 42;        // 推断为int
auto y = 3.14;      // 推断为double
auto z = "hello";   // 推断为const char*

// C++17:类模板参数推导
std::vector<int> vec = {1, 2, 3};  // 需要指定类型
auto vec2 = std::vector{1, 2, 3};  // C++17,自动推导
#![allow(unused)]
fn main() {
// Rust:强大的类型推断
let x = 42;         // 推断为i32
let y = 3.14;       // 推断为f64
let z = "hello";    // 推断为&str

// 集合类型推断
let vec = vec![1, 2, 3];  // 推断为Vec<i32>
let arr = [1, 2, 3];      // 推断为[i32; 3]
}

内存安全对比:

// C++:需要手动管理内存
int* ptr = new int(42);
// ... 使用ptr
delete ptr;  // 容易忘记,导致内存泄漏

// 智能指针(C++11)
#include <memory>
auto ptr = std::make_unique<int>(42);  // 自动管理内存
#![allow(unused)]
fn main() {
// Rust:自动内存管理,编译时检查
let x = 42;  // 自动分配
// 作用域结束时自动释放

// 所有权系统(后续章节详细学习)
let s1 = String::from("hello");
let s2 = s1;  // s1的所有权转移给s2
// println!("{}", s1);  // 编译错误!s1已经被移动
}

总结:

Rust的类型系统比C++更加严格和安全:

  1. 类型安全:Rust要求显式类型转换,防止意外数据丢失
  2. 内存安全:Rust通过所有权系统在编译时防止内存错误
  3. Unicode支持:Rust的char类型原生支持所有Unicode字符
  4. 边界检查:Rust在编译时和运行时都进行数组边界检查
  5. 类型推断:Rust的类型推断更强大,代码更简洁

这些设计使得Rust在保持高性能的同时,提供了更好的安全性和开发体验。

6.3 常量与静态变量

什么是常量?

常量就像永远不变的盒子,一旦设置就不能修改。在Rust中,我们用const来定义常量:

#![allow(unused)]
fn main() {
const PI: f64 = 3.14159;           // 圆周率
const MAX_STUDENTS: u32 = 100;     // 最大学生数
const GREETING: &str = "你好!";    // 问候语
}

常量的特点:

  • 必须明确指定类型
  • 值必须在编译时确定
  • 不能修改
  • 通常用大写字母命名

静态变量

静态变量在整个程序运行期间都存在,用static定义:

#![allow(unused)]
fn main() {
static LANGUAGE: &str = "Rust";
static mut COUNTER: u32 = 0;  // 可变的静态变量(需要unsafe)
}

什么时候使用常量?

当你有一些永远不会改变的值时,比如:

  • 数学常数(π、e等)
  • 配置参数(最大连接数、超时时间等)
  • 固定的字符串

常量、静态变量与不可变变量的区别:

在Rust中,我们经常听到"常量"、"静态变量"和"不可变变量"这些概念,它们看起来很相似,但实际上有很大的区别。理解这些区别对于写出正确的代码非常重要。

**不可变变量(let)**是最常见的变量声明方式。当你用let声明一个变量时,它默认是不可变的,这意味着一旦赋值就不能再改变。但不可变变量有一个重要的特点:它们的作用域是局部的,只在声明它们的代码块内有效。当你离开这个作用域时,变量就会被释放。不可变变量适合存储那些在函数或代码块内不会改变的值,比如循环计数器、临时计算结果等。

**常量(const)**是编译时常量,它们的值必须在编译时就能确定,并且在整个程序运行期间都不会改变。常量在编译时就被内联到使用它们的地方,这意味着不会占用额外的内存空间。常量通常用于定义那些在整个程序中都不会改变的固定值,比如数学常数、配置参数、错误码等。由于常量是全局的,你可以在程序的任何地方使用它们。

**静态变量(static)**是全局变量,它们在程序的整个运行期间都存在,并且只初始化一次。静态变量占用固定的内存空间,所有访问都指向同一个内存位置。静态变量适合存储那些需要在程序的多个部分共享的数据,比如程序的配置信息、全局状态等。需要注意的是,静态变量默认是不可变的,如果需要修改,必须使用unsafe块。

让我们通过一个具体的例子来理解它们的区别:

// 不可变变量:作用域局部,离开作用域就释放
fn calculate_area(radius: f64) -> f64 {
    let pi = 3.14159;  // 局部不可变变量
    pi * radius * radius
} // pi在这里被释放

// 常量:编译时确定,全局可用,编译时内联
const PI: f64 = 3.14159;
const MAX_RETRY_COUNT: u32 = 3;

fn calculate_circle_area(radius: f64) -> f64 {
    PI * radius * radius  // 使用全局常量
}

// 静态变量:全局存在,占用固定内存
static APP_NAME: &str = "我的计算器";
static mut COUNTER: u32 = 0;  // 可变的静态变量

fn increment_counter() {
    unsafe {
        COUNTER += 1;  // 需要unsafe块
    }
}

fn main() {
    // 使用不可变变量
    let result = calculate_area(5.0);
    
    // 使用常量
    let area = calculate_circle_area(5.0);
    
    // 使用静态变量
    println!("应用名称:{}", APP_NAME);
    increment_counter();
}

适用场景总结:

当你需要存储一个在函数内部不会改变的值时,使用不可变变量。比如计算过程中的中间结果、循环中的临时值等。不可变变量让代码更安全,防止意外修改,同时编译器可以进行更好的优化。

当你需要定义那些在整个程序中都不会改变的固定值时,使用常量。比如数学常数、物理常数、程序配置参数等。常量的优势是编译时内联,不占用额外内存,并且可以在程序的任何地方使用。

当你需要在程序的多个部分共享同一个数据时,使用静态变量。比如程序的名称、版本号、全局配置等。静态变量占用固定的内存空间,所有访问都指向同一个位置,适合存储全局状态。如果需要修改静态变量,必须使用unsafe块,这提醒开发者要小心处理全局状态。

与C语言的对比:

// C语言
#define PI 3.14159           // 宏定义,预处理时替换
const int MAX_SIZE = 100;    // 常量
static int counter = 0;      // 静态变量
#![allow(unused)]
fn main() {
// Rust
const PI: f64 = 3.14159;     // 编译时常量
const MAX_SIZE: u32 = 100;   // 编译时常量
static COUNTER: u32 = 0;     // 静态变量
}

Rust的设计更加严格和清晰,每种类型都有明确的用途和生命周期,这有助于写出更安全、更高效的代码。

不可变静态变量 vs 常量的更细致的对比:

你可能会问,既然静态变量可以是不可变的,那它和常量有什么区别呢?这是一个很好的问题,它们的区别主要体现在内存使用和访问方式上。

**常量(const)**在编译时就被内联到使用它们的地方。这意味着每次使用常量时,编译器都会直接替换为常量的值,不会占用额外的内存空间。常量更像是"宏",在编译时就确定了最终的值。

**不可变静态变量(static)**在内存中有固定的位置,所有对它的访问都指向同一个内存地址。静态变量占用实际的内存空间,并且在整个程序运行期间都存在。

让我们通过一个例子来理解这个区别:

const PI: f64 = 3.14159;
static GLOBAL_CONFIG: &str = "production";

fn calculate_area(radius: f64) -> f64 {
    PI * radius * radius  // PI在这里被内联,相当于直接写3.14159
}

fn get_config() -> &'static str {
    GLOBAL_CONFIG  // 返回指向静态变量内存地址的引用
}

fn main() {
    let area1 = calculate_area(5.0);
    let area2 = calculate_area(10.0);
    
    // 在汇编层面,PI被内联为两个独立的数值
    // 而GLOBAL_CONFIG在两个地方都指向同一个内存地址
    
    let config = get_config();
    println!("配置:{}", config);
}

什么时候使用常量,什么时候使用静态变量?

当你需要的是一个简单的数值或字符串,并且这个值在编译时就能确定时,使用常量。常量的优势是零内存开销,编译时优化更好。比如数学常数、错误码、固定的配置字符串等。

当你需要的是一个复杂的数据结构,或者需要获取数据的引用时,使用静态变量。静态变量适合存储较大的数据,比如配置结构体、全局状态对象等。虽然占用内存,但所有访问都指向同一个位置,节省了重复存储的空间。

实际应用场景:

// 使用常量:简单的数值和字符串
const MAX_RETRY_COUNT: u32 = 3;
const DEFAULT_TIMEOUT: u64 = 5000;
const ERROR_MESSAGE: &str = "操作失败";

// 使用静态变量:复杂的数据结构
static APP_CONFIG: AppConfig = AppConfig {
    name: "我的应用",
    version: "1.0.0",
    debug: false,
};

struct AppConfig {
    name: &'static str,
    version: &'static str,
    debug: bool,
}

fn main() {
    // 常量被内联,没有内存开销
    for _ in 0..MAX_RETRY_COUNT {
        // 重试逻辑
    }
    
    // 静态变量占用内存,但所有访问都指向同一个位置
    println!("应用:{} v{}", APP_CONFIG.name, APP_CONFIG.version);
}

6.4 基本运算符和表达式

什么是运算符?

运算符就是用来进行各种运算的符号,比如加减乘除。让我们通过例子来学习:

算术运算符

#![allow(unused)]
fn main() {
let a = 10;
let b = 3;

let sum = a + b;        // 加法:10 + 3 = 13
let diff = a - b;       // 减法:10 - 3 = 7
let product = a * b;    // 乘法:10 × 3 = 30
let quotient = a / b;   // 除法:10 ÷ 3 = 3(整数除法)
let remainder = a % b;  // 取余:10 % 3 = 1
}

整数除法 vs 浮点除法:

#![allow(unused)]
fn main() {
let result1 = 10 / 3;   // 整数除法,结果是3
let result2 = 10.0 / 3.0; // 浮点除法,结果是3.333...
}

比较运算符

比较运算符用来比较两个值,结果总是truefalse

#![allow(unused)]
fn main() {
let x = 5;
let y = 10;

let is_equal = x == y;      // 等于:false
let is_not_equal = x != y;  // 不等于:true
let is_less = x < y;        // 小于:true
let is_greater = x > y;     // 大于:false
let is_less_equal = x <= y; // 小于等于:true
let is_greater_equal = x >= y; // 大于等于:false
}

逻辑运算符

逻辑运算符用来组合多个条件:

#![allow(unused)]
fn main() {
let is_student = true;
let is_working = false;

let both = is_student && is_working;  // 与:false(两个都为真才为真)
let either = is_student || is_working; // 或:true(有一个为真就为真)
let not_student = !is_student;        // 非:false(取反)
}

类型转换

Rust不会自动转换类型,需要手动转换:

#![allow(unused)]
fn main() {
let integer = 5;
let float = 2.5;

// 错误:不能直接相加不同类型的数
// let result = integer + float;

// 正确:先转换类型
let result = integer as f64 + float;  // 5.0 + 2.5 = 7.5
}

常用的类型转换:

  • as f64:转换为64位浮点数
  • as i32:转换为32位整数
  • as u8:转换为8位无符号整数

运算符优先级

就像数学中的运算顺序,Rust也有运算符优先级:

#![allow(unused)]
fn main() {
let result = 2 + 3 * 4;  // 先乘后加:2 + 12 = 14
let result2 = (2 + 3) * 4; // 先括号:5 * 4 = 20
}

优先级从高到低:

  1. 括号 ()
  2. 乘除取余 * / %
  3. 加减 + -
  4. 比较 == != < > <= >=
  5. 逻辑 && ||

与C语言的对比

运算C语言Rust说明
整数除法5 / 2 = 25 / 2 = 2相同
浮点除法5.0 / 2.0 = 2.55.0 / 2.0 = 2.5相同
类型转换自动转换需要asRust更严格
逻辑运算&& || !&& || !相同

6.5 实战练习

现在让我们动手实践,巩固学到的知识。每个练习都包含详细的步骤指导。

练习1:变量操作

目标: 学习如何声明和使用可变变量

步骤:

  1. 创建一个新的Rust项目:cargo new practice1
  2. src/main.rs中编写以下代码:
fn main() {
    // 声明一个可变的整型变量,初始值为10
    let mut number = 10;
    println!("初始值:{}", number);
    
    // 将其加5
    number = number + 5;
    println!("加5后:{}", number);
    
    // 再乘以2
    number = number * 2;
    println!("乘以2后:{}", number);
}

运行结果应该是:

初始值:10
加5后:15
乘以2后:30

练习2:使用元组

目标: 学习如何创建和使用元组

步骤:

  1. 创建新项目:cargo new practice2
  2. 编写代码:
fn main() {
    // 定义一个包含三种不同类型的元组
    let student = ("小明", 18, true);  // 姓名、年龄、是否为学生
    
    // 分别打印每个元素
    println!("姓名:{}", student.0);
    println!("年龄:{}", student.1);
    println!("是否为学生:{}", student.2);
    
    // 使用解构来获取元组中的值
    let (name, age, is_student) = student;
    println!("解构后 - 姓名:{},年龄:{},是否为学生:{}", name, age, is_student);
}

练习3:数组操作

目标: 学习如何创建数组并计算总和

步骤:

  1. 创建新项目:cargo new practice3
  2. 编写代码:
fn main() {
    // 创建一个长度为5的整型数组
    let scores = [85, 92, 78, 96, 88];
    
    // 计算所有元素的和
    let mut sum = 0;
    for score in scores.iter() {
        sum = sum + score;
    }
    
    // 计算平均值
    let average = sum as f64 / scores.len() as f64;
    
    println!("成绩:{:?}", scores);
    println!("总分:{}", sum);
    println!("平均分:{:.2}", average);
}

练习4:类型转换

目标: 学习如何处理类型转换

步骤:

  1. 创建新项目:cargo new practice4
  2. 先尝试错误的代码(会被编译器阻止):
fn main() {
    let integer: i32 = 42;
    let float: f64 = 3.14;
    
    // 这行代码会编译错误,取消注释试试看
    // let result = integer + float;
    
    // 正确的做法:使用as进行类型转换
    let result = integer as f64 + float;
    println!("{} + {} = {}", integer, float, result);
    
    // 更多类型转换的例子
    let small_int: u8 = 255;
    let large_int: u32 = small_int as u32;
    println!("{} 转换为 u32 后:{}", small_int, large_int);
}

练习5:综合应用

目标: 综合运用本章学到的所有知识

步骤:

  1. 创建新项目:cargo new practice5
  2. 编写一个简单的学生成绩计算程序:
fn main() {
    // 定义常量
    const PASS_SCORE: f64 = 60.0;
    
    // 学生信息(姓名,数学成绩,英语成绩,是否缺考)
    let student1 = ("张三", 85.5, 92.0, false);
    let student2 = ("李四", 78.0, 88.5, false);
    let student3 = ("王五", 0.0, 0.0, true);  // 缺考
    
    // 计算每个学生的平均分
    let students = [student1, student2, student3];
    
    for student in students.iter() {
        let (name, math, english, absent) = student;
        
        if *absent {
            println!("{}:缺考", name);
        } else {
            let average = (math + english) / 2.0;
            let status = if average >= PASS_SCORE { "及格" } else { "不及格" };
            println!("{}:数学{},英语{},平均{:.1},{}", 
                     name, math, english, average, status);
        }
    }
}

运行结果应该是:

张三:数学85.5,英语92.0,平均88.8,及格
李四:数学78.0,英语88.5,平均83.3,及格
王五:缺考

练习提示

  • 如果遇到编译错误,仔细阅读错误信息,编译器会告诉你问题在哪里
  • 可以尝试修改代码中的值,看看结果会有什么变化
  • 不要害怕犯错,编程就是在不断试错中学习的

6.6 小结与思考

恭喜你!你已经完成了Rust基础语法的学习。让我们来回顾一下这一章学到了什么:

本章要点总结

1. 变量就像盒子

  • let创建变量(盒子)
  • 默认情况下盒子是封住的(不可变)
  • mut创建可以打开的盒子(可变)

2. 数据类型就像不同的东西

  • 整型:整数(年龄、数量)
  • 浮点型:小数(价格、分数)
  • 布尔型:真假(是否、开关)
  • 字符型:单个字符(字母、符号、中文)
  • 元组:组合数据(姓名+年龄+状态)
  • 数组:同类型列表(成绩单、购物清单)

3. 运算符就像数学符号

  • 算术:+ - * / %
  • 比较:== != < > <= >=
  • 逻辑:&& || !
  • 类型转换:as

4. 常量是永远不变的盒子

  • const定义
  • 必须明确类型
  • 通常用大写字母命名

与C语言的主要区别

特点C语言Rust好处
变量默认可变不可变更安全
类型转换自动手动更明确
字符类型1字节4字节Unicode支持中文
数组长度可变固定更安全

思考题

初级思考:

  1. 为什么Rust默认变量不可变?这样设计有什么好处?
  2. 你能举出生活中哪些东西是"不可变的"吗?(比如生日、性别)

中级思考: 3. Rust的char和C语言的char有什么不同?为什么这样设计? 4. 什么时候应该使用常量而不是普通变量?

高级思考: 5. 如果让你设计一个学生管理系统,你会用哪些数据类型来存储学生信息? 6. 为什么Rust不自动进行类型转换?这样设计有什么优缺点?