283 lines
11 KiB
Markdown
283 lines
11 KiB
Markdown
### **C++ 格式化输入与输出 讲义**
|
||
|
||
#### **一、 引言**
|
||
|
||
##### **问题引入:数字反转**
|
||
|
||
【书本P21页 例2-7】我们来看一个问题:
|
||
> 输入一个三位浮点数,例如 `123.4`,要求把这个数字彻底翻转过来,变成 `4.321` 并输出。
|
||
|
||
乍一看,这个问题似乎很难下手。我们熟悉的 `cin` 只能整个地读取这个数字,`cout` 也只能整个地输出。我们如何能把——`1`、`2`、`3`、`.`、`4`——拆开,再按新的顺序重新组合呢?
|
||
|
||
---
|
||
|
||
#### **二、 解法一:数学运算**
|
||
|
||
在揭晓新工具之前,我们先用已有的知识尝试解决。
|
||
|
||
##### **思路铺垫**
|
||
|
||
对于 `123.4`,最碍事的就是那个小数点。如果我们能先把它变成整数 `1234`,问题就简化成了“整数拆分”。
|
||
|
||
##### **核心工具:提取器与削减器**
|
||
|
||
我们回顾一下拆分整数的两个工具:
|
||
|
||
1. **个位的提取器 (`% 10`)**
|
||
* **作用**:获取一个整数的**最右边一位**。
|
||
* **原理**:任何整数除以10的**余数**,正好就是它的个位数。
|
||
* **示例**:`1234 % 10` 结果是 `4`。
|
||
|
||
2. **数字的削减器 (`/ 10`)**
|
||
* **作用**:**砍掉**一个整数的**最右边一位**。
|
||
* **原理**:在C++中,`整数 / 整数` 的结果会**直接舍弃小数部分**。
|
||
* **示例**:`1234 / 10` 结果是 `123`。
|
||
|
||
##### **步骤拆解**
|
||
|
||
1. **第一步(提取)**:使用 **提取器 (`% 10`)** 获得当前数字的个位数。
|
||
2. **第二步(削减)**:使用 **削减器 (`/ 10`)** 砍掉个位数,得到一个更小的数,为下一次操作做准备。
|
||
|
||
---
|
||
|
||
**具体案例**
|
||
|
||
让我们来手动拆解整数 `479`。
|
||
|
||
**分步演示**:
|
||
|
||
* **初始状态**: 我们有一个整数 `n = 479`。
|
||
|
||
* **第一轮操作 (处理 `479`)**:
|
||
|
||
1. **提取**: `479 % 10` -> 我们得到了数字 **`9`**。
|
||
2. **削减**: `479 / 10` -> `n` 现在变成了 **`47`**。
|
||
|
||
* *我们成功地把 `9` 从 `479` 中分离了出来,并且得到了一个更简单的问题:如何拆解 `47`。*
|
||
|
||
* **第二轮操作 (处理 `47`)**:
|
||
|
||
1. **提取**: `47 % 10` -> 我们得到了数字 **`7`**。
|
||
2. **削减**: `47 / 10` -> `n` 现在变成了 **`4`**。
|
||
|
||
* *小结:我们又分离出了 `7`,问题变得更简单了:如何拆解 `4`。*
|
||
|
||
* **第三轮操作 (处理 `4`)**:
|
||
|
||
1. **提取**: `4 % 10` -> 我们得到了数字 **`4`**。
|
||
2. **削减**: `4 / 10` -> `n` 现在变成了 **`0`**。
|
||
|
||
* *小结:我们分离出了最后的 `4`,`n` 变成了 `0`,说明拆解工作已全部完成!*
|
||
|
||
而对于我们的题目:**输入一个三位浮点数,例如 `123.4`,要求把这个数字彻底翻转过来,变成 `4.321` 并输出。**
|
||
|
||
我们就也能通过整数拆分的办法来解决了。
|
||
|
||
1. **预处理**:`double num = 123.4; int n = num * 10;` -> 得到整数 `1234`。
|
||
2. **逐位拆分**:
|
||
* `int d = n % 10;` // d = 4
|
||
* `int c = (n / 10) % 10;` // c = 3
|
||
* `int b = (n / 100) % 10;` // b = 2
|
||
* `int a = n / 1000;` // a = 1
|
||
3. **重新组合输出**:
|
||
* `cout << d << "." << c << b << a;`
|
||
|
||
**结论**:通过整数拆分的方法可以解决本问题,且一定要掌握通过数学运算拆分整数的办法。
|
||
|
||
---
|
||
|
||
#### **常见问题**
|
||
|
||
1. **问:对一个个位数(比如 `8`)使用这两个工具会怎样?**
|
||
* **答**:完全符合规则,不会出问题!
|
||
* **提取器**:`8 % 10` 的结果是 `8`。(8除以10,商0余8,成功提取出了最后一位数字 8)
|
||
* **削减器**:`8 / 10` 的结果是 `0`。(8除以10,整数商是0,成功削减了最后一位数字 8)
|
||
2. **问:如果数字里有 `0` 呢?比如 `205`?**
|
||
* **答**:规则同样适用!
|
||
* **第一轮**: `205 % 10` -> `5`;`205 / 10` -> `20`。
|
||
* **第二轮**: `20 % 10` -> `0`;`20 / 10` -> `2`。
|
||
* **第三轮**: `2 % 10` -> `2`;`2 / 10` -> `0`。
|
||
|
||
---
|
||
|
||
#### **要点回顾**
|
||
|
||
1. **个位的提取器 (`% 10`)**:它的作用就是帮你**看到并取走**一个整数最右边的那一位。
|
||
2. **数字的削减器 (`/ 10`)**:它的作用就是**砍掉**一个整数最右边的那一位,让数字变小。
|
||
3. 而且“提取”后必须“削减”,这样才能为处理下一位数字做准备。它们总是成对出现。
|
||
|
||
---
|
||
|
||
#### **三、 `printf` **
|
||
|
||
##### **核心概念与语法**
|
||
|
||
`printf` 的全称是 **Print Formatted(格式化打印)**。
|
||
它的作用是:**先写好一段带有“空位”的模板句子,然后再把变量按顺序填入这些空位中。**
|
||
|
||
**语法格式:**
|
||
|
||
> `printf("包含格式说明符(占位符)的字符串", 变量1, 变量2, ...);`
|
||
|
||
##### **`printf` 的使用方法**
|
||
|
||
1. **打印纯文本**
|
||
|
||
```cpp
|
||
printf("Hello, World!\n"); // \n 代表换行
|
||
```
|
||
|
||
2. **打印单个变量**
|
||
`printf` 在于用**格式说明符**(以`%`开头的占位符)去指定“空位”的类型,也就是先挖个坑占个座,之后我来告诉你什么数据会放在这里你进行输出就可以了。
|
||
|
||
```cpp
|
||
int age = 30;
|
||
// %d 是专门用来放整数的占位符
|
||
printf("我的年龄是 %d 岁。\n", age);
|
||
```
|
||
**逻辑解释:**
|
||
|
||
1. `"我的年龄是%d岁。\n"` 是输出的模板字符串。
|
||
2. `%d` 是一个占位符。
|
||
3. `printf` 会在输出时,自动按照顺序用后面 `age` 变量的值(30)去替换 `%d` 的位置。
|
||
|
||
3. **混合打印多个变量**
|
||
|
||
`printf` 也可以极其方便地在一句话中同时输出多个、多种类型的变量,而不需要像 `cin` 或 `cout` 那样反复使用 `<<` 拼接。
|
||
|
||
```cpp
|
||
int age = 15;
|
||
double height = 168.5;
|
||
char rank = 'S';
|
||
|
||
// 依次使用 %d, %lf, %c 作为占位符
|
||
printf("我今年%d岁,身高是%lf厘米,综合评级:%c\n", age, height, rank);
|
||
```
|
||
**核心规则**:
|
||
|
||
* **一一对应:** 字符串里有 3 个坑(`%d`, `%lf`, `%c`),后面就必须跟着 3 个变量(`age`, `height`, `rank`)。
|
||
* **按顺序入座:** `printf` 会严格按照从左到右的顺序,把第一个变量填入第一个占位符,第二个变量填入第二个占位符,以此类推。
|
||
* **类型必须匹配:** 第一个是整数类型 `age`,所以必须用 `%d`;第二个是小数类型 `height`,必须用 `%lf/%f`;第三个是字符类型 `rank`,必须用 `%c`。
|
||
|
||
##### **常用格式说明符(书本P24页,重要!)**
|
||
|
||
占位符的顺序必须与后面变量的顺序一一对应,且数据类型必须兼容!
|
||
|
||
| 占位符 | 对应数据类型 | 说明 |
|
||
| ---------- | -------------- | ------------------------------------------------------------ |
|
||
| **`%d`** | `int` | 打印整数 |
|
||
| **`%f`** | `float`、`double` | 打印浮点数 (默认保留6位小数) |
|
||
| **`%lf`** | `float`、`double` | 输出时与 `%f` 相同;小数位数由精度指定 |
|
||
| **`%c`** | `char` | 打印单个字符 |
|
||
| **`%.mf`** | `float/double` | **技巧:** 打印浮点数,并**精确保留m位小数**。例如 `%.2f` 就是保留两位。 |
|
||
|
||
### `cout` 与 `printf` 写法对比
|
||
|
||
| 操作目标 | `cout` 写法 | `printf` 写法 |
|
||
| ---------------- | ------------------------------- | ------------------------------- |
|
||
| 打印纯文本 | `cout << "输出的内容";` | `printf("输出的内容");` |
|
||
| 打印单个变量 | `cout << num1;` | `printf("%d", num1);` |
|
||
| 混合打印多个变量 | `cout << num1 << "和" << num2;` | `printf("%d和%d", num1, num2);` |
|
||
|
||
---
|
||
|
||
#### **四、 `scanf` **
|
||
|
||
##### **概念与语法**
|
||
|
||
`scanf` 的全称是 **Scan Formatted(格式化扫描/输入)**。
|
||
它的逻辑和 `printf` 基本一致,只不过从输出数据 变成了 输入数据:它会根据你提供的格式字符串模板,去键盘的输入中**抓取**对应的数据。
|
||
|
||
**语法格式:**
|
||
|
||
> `scanf("格式控制字符串", &变量1, &变量2, ...);`
|
||
|
||
##### **要注意 `scanf` 必须加 `&`(取地址符)**
|
||
|
||
**原理:**
|
||
|
||
* `cin` 很智能,你告诉它抽屉的名字(如 `age`),它就能自己找到。
|
||
* `scanf` 是一位老员工,你必须把这个抽屉在内存中的**具体物理地址**交给它,它才能把数据放进去。
|
||
|
||
那么在变量名前加上 `&`,就相当于提取了这个变量的地址,所以这个符号也被称为取地址符!
|
||
|
||
> **警告:** 如果忘记写 `&`,程序在运行到输入阶段时会直接崩溃!
|
||
|
||
**基础使用示例:**
|
||
|
||
```cpp
|
||
int age;
|
||
printf("请输入您的年龄:");
|
||
scanf("%d", &age); // 从键盘输入获取一个整数,存入 age 所在的内存地址
|
||
// 现在 age 中就储存了输入的数据
|
||
```
|
||
|
||
##### **`scanf` 的使用技巧**
|
||
|
||
1. **一次性读取多个数据**
|
||
```cpp
|
||
int a;
|
||
double b;
|
||
scanf("%d%lf", &a, &b); // 从键盘输入中读取一个整数和一个double类型的数据
|
||
```
|
||
*用户输入时用空格、Tab或回车 (空白字符) 隔开即可,`scanf` 会自动跳过它们。*
|
||
|
||
2. **易错点:不要在格式字符串里乱加标点,除非题目格式明确要求了**
|
||
|
||
* **错误写法**:`scanf("%d,%d", &a, &b);`
|
||
* **后果**:那么用户输入时**必须严格**按照格式输入:**[一个整数 逗号 一个整数]** 的格式,例如`5, 10` 。如果不带逗号输入`[5 10]`的话就会读取失败。
|
||
* **正确、安全的写法**:`scanf("%d%d", &a, &b);` 保持格式字符串的干净,只保留占位符。
|
||
|
||
---
|
||
|
||
#### **五、 实战演练**
|
||
|
||
##### **实战1:数字反转**
|
||
|
||
**解题思路**:我们不把 `123.4` 看作数字,而是看作“3个字符 + 1个小数点 + 1个字符”。利用 `scanf` 的格式匹配能力,将它们分离到不同的`char`变量中!
|
||
|
||
```cpp
|
||
#include <cstdio>
|
||
|
||
int main() {
|
||
char a, b, c, d;
|
||
// 输入格式为 %c%c%c.%c,匹配 [字符 字符 字符 小数点 字符] 的格式将4个字符分离出来
|
||
scanf("%c%c%c.%c", &a, &b, &c, &d);
|
||
|
||
// 按照 d.cba 的倒序格式输出
|
||
printf("%c.%c%c%c\n", d, c, b, a);
|
||
return 0;
|
||
}
|
||
```
|
||
*用 `scanf` 和 `printf`问题就会变得非常简单!*
|
||
|
||
##### **实战2:再分肥宅水(书本P23页)**
|
||
|
||
**题目要求**:$t$ 毫升水均分给 $n$ 名同学。输出每人获得多少毫升(严格保留3位小数)以及一共需要多少个杯子(每人2个)。
|
||
|
||
```cpp
|
||
#include <cstdio>
|
||
|
||
int main() {
|
||
double t;
|
||
int n;
|
||
|
||
// 用 %lf 读 double, 用 %d 读 int
|
||
scanf("%lf%d", &t, &n);
|
||
|
||
// t/n 对应 %.3f (保留三位小数), n*2 对应 %d,中间用 \n 换行
|
||
printf("%.3f\n%d\n", t / n, n * 2);
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **六、 总结**
|
||
|
||
* `cin/cout`:语法简单直观,能自动识别类型,是我们日常编程的常用选择。
|
||
* `scanf/printf`:
|
||
* **优点**:执行效率更高;拥有强大的**格式控制能力**,能实现精准的输入匹配和带精度的输出。
|
||
* **缺点**:语法稍显复杂,需要手动匹配占位符和变量类型,并且 `scanf` 必须带 `&`。
|
||
* **如何选择?** 在需要精确控制输入输出格式(如本讲的例题),就非常推荐使用 `scanf/printf` 。 |