Files

283 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### **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` 。