第六章 变量、数据类型与基本运算
引子:计算机程序如何"思考"?
想象一下,你正在教一个完全不懂数学的小朋友做计算。你会怎么做?
你可能会说:"看,这是数字1,这是数字2,如果你把它们放在一起,就变成了3。"
计算机程序也是这样工作的。它就像一个非常听话但有点"笨"的学生,需要你明确地告诉它:
- 这是什么?(数据类型)
- 把它放在哪里?(变量)
- 对它做什么?(运算)
数据的"生命":从输入到输出
让我们看一个简单的例子:计算圆的面积。
人类思维过程:
- 我知道圆的面积公式是 π × 半径²
- 如果半径是5,那么面积就是 3.14 × 5 × 5 = 78.5
计算机程序思维过程:
- 我需要一个地方存储半径值 → 创建变量
radius
- 我需要一个地方存储π值 → 创建常量
PI
- 我需要一个地方存储计算结果 → 创建变量
area
- 执行计算:
area = PI * radius * radius
- 显示结果
fn main() { // 1. 存储数据 let radius = 5.0; // 半径 const PI: f64 = 3.14159; // 圆周率 // 2. 操纵数据(计算) let area = PI * radius * radius; // 3. 输出结果 println!("半径为 {} 的圆的面积是:{:.2}", radius, area); }
程序 = 数据 + 操作
所有的计算机程序,本质上都是在做三件事:
- 存储数据 - 把信息放在"盒子"里
- 操纵数据 - 对数据进行各种操作(计算、比较、转换等)
- 输出结果 - 把处理后的信息展示出来
就像做菜一样:
- 食材 = 数据(面粉、鸡蛋、糖)
- 烹饪过程 = 操作(搅拌、加热、冷却)
- 成品 = 结果(蛋糕)
为什么需要不同的数据类型?
想象你在整理房间:
- 书籍 → 放在书架上(整整齐齐)
- 衣服 → 放在衣柜里(叠好分类)
- 食物 → 放在冰箱里(保持新鲜)
- 工具 → 放在工具箱里(方便取用)
不同的东西需要不同的存放方式。同样,不同的数据也需要不同的"容器":
- 数字(年龄、价格)→ 数字类型
- 文字(姓名、地址)→ 字符串类型
- 真假(开关状态)→ 布尔类型
- 多个相关数据 → 数组或元组
从简单到复杂
这一章,我们将从最简单的数据操作开始:
- 变量 - 学会创建和使用"数据盒子"
- 数据类型 - 了解不同数据的"性格特点"
- 基本运算 - 掌握数据之间的"互动方式"
想象一下,你正在写一个简单的计算器程序。这个程序需要:
- 存储用户输入的数字
- 进行加减乘除运算
- 保存计算结果
- 显示最终答案
这就是我们这一章要学习的内容:如何在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
在这个例子中:
number1
、number2
、sum
、difference
、product
、quotient
都是变量,用来存储数据10
、5
是整型数据+
、-
、*
、/
是运算符println!
用来显示结果
这一章,我们就来详细学习:
- 如何声明和使用变量
- Rust支持哪些数据类型
- 如何进行基本运算
- 与C语言相比有什么不同
准备好了吗?让我们开始吧!
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; // 还可以继续修改 }
为什么要这样设计?
这种设计有几个好处:
- 防止意外修改:避免程序中出现意外的数据变化
- 代码更清晰:一看就知道哪些数据会变,哪些不会变
- 编译器优化:Rust编译器可以更好地优化代码
与C语言的对比
如果你学过C语言,会发现Rust的做法很不同:
// C语言:变量默认可以修改
int age = 25;
age = 26; // 正常
#![allow(unused)] fn main() { // Rust:变量默认不能修改 let age = 25; age = 26; // 编译错误! }
小结
- 变量就像盒子,用来存储数据
- 用
let
创建变量 - 默认情况下变量不能修改
- 用
mut
创建可以修改的变量
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 | 小整数 | | u8 | 0 到 255 | 小正数 | | i32 | -2,147,483,648 到 2,147,483,647 | 常用整数 | | u32 | 0 到 4,294,967,295 | 常用正数 | | i64 | 很大范围 | 大整数 | | u64 | 很大范围 | 大正数 |
浮点型(小数)
浮点型用来存储小数:
#![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语言的对比
C语言 | Rust | 说明 |
---|---|---|
int | i32 | 32位有符号整数 |
unsigned int | u32 | 32位无符号整数 |
char | u8/char | u8是字节,char是Unicode字符 |
float | f32 | 32位浮点数 |
double | f64 | 64位浮点数 |
数组 | [T; N] | 固定长度数组 |
结构体 | struct | 后续章节学习 |
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等)
- 配置参数(最大连接数、超时时间等)
- 固定的字符串
与普通变量的区别:
#![allow(unused)] fn main() { let normal_var = 42; // 普通变量,可以修改(如果声明为mut) const CONSTANT = 42; // 常量,永远不能修改 }
与C语言的对比:
// C语言
#define PI 3.14159
const int MAX_SIZE = 100;
#![allow(unused)] fn main() { // Rust const PI: f64 = 3.14159; const MAX_SIZE: u32 = 100; }
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... }
比较运算符
比较运算符用来比较两个值,结果总是true
或false
:
#![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 }
优先级从高到低:
- 括号
()
- 乘除取余
*
/
%
- 加减
+
-
- 比较
==
!=
<
>
<=
>=
- 逻辑
&&
||
与C语言的对比
运算 | C语言 | Rust | 说明 |
---|---|---|---|
整数除法 | 5 / 2 = 2 | 5 / 2 = 2 | 相同 |
浮点除法 | 5.0 / 2.0 = 2.5 | 5.0 / 2.0 = 2.5 | 相同 |
类型转换 | 自动转换 | 需要as | Rust更严格 |
逻辑运算 | && || ! | && || ! | 相同 |
6.5 实战练习
现在让我们动手实践,巩固学到的知识。每个练习都包含详细的步骤指导。
练习1:变量操作
目标: 学习如何声明和使用可变变量
步骤:
- 创建一个新的Rust项目:
cargo new practice1
- 在
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:使用元组
目标: 学习如何创建和使用元组
步骤:
- 创建新项目:
cargo new practice2
- 编写代码:
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:数组操作
目标: 学习如何创建数组并计算总和
步骤:
- 创建新项目:
cargo new practice3
- 编写代码:
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:类型转换
目标: 学习如何处理类型转换
步骤:
- 创建新项目:
cargo new practice4
- 先尝试错误的代码(会被编译器阻止):
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:综合应用
目标: 综合运用本章学到的所有知识
步骤:
- 创建新项目:
cargo new practice5
- 编写一个简单的学生成绩计算程序:
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 | 支持中文 |
数组长度 | 可变 | 固定 | 更安全 |
思考题
初级思考:
- 为什么Rust默认变量不可变?这样设计有什么好处?
- 你能举出生活中哪些东西是"不可变的"吗?(比如生日、性别)
中级思考:
3. Rust的char
和C语言的char
有什么不同?为什么这样设计?
4. 什么时候应该使用常量而不是普通变量?
高级思考: 5. 如果让你设计一个学生管理系统,你会用哪些数据类型来存储学生信息? 6. 为什么Rust不自动进行类型转换?这样设计有什么优缺点?