C语言格式化输入/输出printf vs. scanf 格式控制
**核心思想:** % 格式控制符允许你精确控制数据的显示printf)或读取scanf)方式。尽管它们都使用 %,但其内部修饰符width, precision 等)的含义和行为有显著差异。
---
### **I. printf() 中的格式控制 %[flags][width][.precision]type)**
* **目的:** 控制数据在屏幕上的**格式化输出**。
#### **A. 基本结构**
```
% [flags] [width] [.precision] type
```
#### **B. type (转换类型)**
* 定义要输出的数据类型。
* **常见类型:**
* d, i: 有符号十进制整数
* u: 无符号十进制整数
* o: 八进制无符号整数
* x, X: 十六进制无符号整数 (小写/大写字母)
* f, F: 浮点数 (十进制表示,默认6位小数)
* e, E: 浮点数 (科学计数法)
* g, G: 浮点数 (根据值自动选择 f 或 e)
* s: 字符串
* c: 字符
* p: 指针地址
* %: 输出一个字面 % 字符
#### **C. flags (可选,控制对齐、前缀、填充)**
* *-:左对齐**,默认是右对齐。
```c
printf("|%-10s|\n", "hello"); // Output: "|hello |"
```
* *+:强制在正数前显示 + 号**。
```c
printf("%+d\n", 123); // Output: "+123"
printf("%+d\n", -123); // Output: "-123"
```
* * (空格):在正数前显示空格** (如果 + 未使用)。
```c
printf("|% d|\n", 123); // Output: "| 123|"
printf("|% d|\n", -123); // Output: "|-123|"
```
* *0:用 0 填充**而不是空格 (仅用于数字类型,如果没指定 - 标志)。
```c
printf("|%05d|\n", 123); // Output: "|00123|"
```
* *#:替代形式** (例如%#x 会加上 0x 前缀%#o 0前缀%#f 即使小数部分为0也显示小数点)。
```c
printf("%#x\n", 255); // Output: "0xff"
printf("%#.0f\n", 3.0); // Output: "3."
```
#### **D. width (可选,整数):最小字段宽度**
* **位置:** 在 flags 之后,在 . 之前。
* **作用:** 指定输出的**最小字符数**。
如果数据本身的字符数*少于 width**:
默认*右对齐**,前面用**空格**填充。
* 如果使用 0 标志,则使用 0 填充。
如果使用 - 标志,则*左对齐**,后面用空格填充。
如果数据本身的字符数*多于 width**:
width 会被*忽略**,数据会**完全输出**,**不会截断**。
* **示例:**
* %7s: 最小7字符宽,右对齐,不足补空格。
```c
printf("|%7s|\n", "hi"); // Output: "| hi|"
printf("|%7s|\n", "topsecret"); // Output: "|topsecret|" (不截断)
```
* %5d: 最小5位数字宽,右对齐,不足补空格。
```c
printf("|%5d|\n", 12); // Output: "| 12|"
printf("|%05d|\n", 12); // Output: "|00012|"
```
#### **E. .precision (可选,以 . 开头后接整数):精度**
* **位置:** 在 width 之后。
* **作用:** 含义**取决于 type**。
* **具体类型说明:**
* **对于整数类型 d, i, u, o, x, X):**
表示输出的*最小数字位数**。
* 如果实际数字位数少于精度,则在前面补 0 填充。
* **不会截断**。
* **示例 %.30d:** 打印一个整数,使其至少有30位字符,不足则前面补 0。
```c
printf("|%.5d|\n", 123); // Output: "|00123|"
printf("|%.30d|\n", 456); // Output: "|000000000000000000000000000456|"
printf("|%.2d|\n", 7); // Output: "|07|"
printf("|%.2d|\n", 12345); // Output: "|12345|" (多于精度不截断)
```
* **对于浮点类型 f, e, g):**
表示小数点后显示的*位数**。默认是6位。
* **会进行四舍五入。**
* **示例 %.2f:** 打印浮点数,保留两位小数。
```c
printf("|%.2f|\n", 3.14159); // Output: "|3.14|"
printf("|%.0f|\n", 6.789); // Output: "|7|"
```
* **对于字符串类型 s):**
表示输出的*最大字符数**。
如果字符串长度超过精度,则会*截断**。
* **示例 %7.3s:** 最小7字符宽,字符串最多取前3个字符,右对齐。
```c
printf("|%.3s|\n", "hello"); // Output: "|hel|"
printf("|%7.3s|\n", "hello"); // Output: "| hel|"
printf("|%7.7s|\n", "hi"); // Output: "| hi|" (少于精度不填充,只限制最大长度)
```
---
### **II. scanf() 中的格式控制 %[*][width][modifiers]type)**
* **目的:** 控制从输入流中**读取**数据的方式。
#### **A. 基本结构**
```
% [*] [width] [modifiers] type
```
#### **B. type (转换类型)**
* 定义要读取的数据类型。与 printf 类似,但也有一些 scanf 独有的:
* %d, %i, %u, %o, %x
* %f (读取 float), %lf (读取 double), %Lf (读取 long double)
* %s: 字符串 (读取非空白字符,遇到空白符停止)
* %c: 字符 (读取任何字符,包括空白符)
* %[chars]: 匹配指定字符集中的字符。
* %[^chars]: 匹配不在指定字符集中的字符。
#### **C. * (可选):抑制赋值**
* **位置:** 紧随 % 之后。
* **作用:** scanf 会读取匹配的输入项,但**不将其存储到对应的变量中**,而是直接丢弃。
* **示例:** scanf("%*d %s", str); 会读取并丢弃一个整数,然后读取一个字符串到 str。
#### **D. width (可选,整数):最大字段宽度**
* **位置:** 在 % 或 * 之后。
* **作用:** 指定从输入中读取的**最大字符数**。
* scanf 会读取最多 width 个字符,或者直到遇到不匹配的字符(对于数字)或空白字符(对于 %s),两者取其先。
如果输入数据超过 width,则*会截断**,只读取前 width 个字符。
* **示例:**
* %7s: 从输入中读取最多7个非空白字符到字符串变量。
```c
char buffer[20];
printf("Enter string: "); // Input: "HelloWorldExample"
scanf("%7s", buffer); // buffer will contain "HelloWo"
printf("Read: %s\n", buffer); // Output: "Read: HelloWo"
```
* %3d: 从输入中读取最多3位数字到整数变量。
```c
int num;
printf("Enter number: "); // Input: "123456"
scanf("%3d", &num); // num will be 123
printf("Read: %d\n", num); // Output: "Read: 123"
```
* %2c: 读取2个字符,包括空白字符。
```c
char c1, c2;
printf("Enter two chars: "); // Input: "a B" (a, space, B)
scanf("%2c", &c1); // c1 will be 'a', c2 will be ' '
scanf("%c", &c2);
printf("%c%c\n", c1, c2); // Output: "a " (读取了"a ",第二个scanf再读取"B")
```
* **注意scanf 中的 . (精度) 是无效的。** scanf 格式字符串中没有 .precision 这一概念。如果你尝试使用,它通常会被忽略或导致编译器警告。
#### **E. modifiers (可选,修饰符)**
* *h:** short int 或 unsigned short int。 (如 %hd, %hu)
* *l:** long int 或 unsigned long int %ld, %lu)double %lf).
* *ll:** long long int 或 unsigned long long int %lld, %llu).
* *L:** long double %Lf).
---
### **III. 总结对比 printf vs. scanf)**
| 特性 | printf (输出) | scanf (输入) |
| :------------- | :-------------------------------------------------- | :-------------------------------------------------------- |
| *width** | **最小**字符数:不足补空/零,**超出不截断**。 | **最大**读取字符数:达到宽度或遇分隔符即停止,**超出截断**。 |
| *.precision** | **有意义!** 含义根据类型而变:<br>- s: **最大**字符数 (截断)<br>- f: 小数点后位数 (四舍五入)<br>- d, i: 最少数字位数 (补零) | **无意义!** 在 scanf 格式字符串中被忽略或导致警告。 |
| *flags** | 如 - (左对齐), 0 (补零), + (显示正号), # 等。 | **无**此概念,但有 * 标志表示抑制赋值。 |
| **目标** | 控制数据**如何显示**。 | 控制数据**如何读取**。 |
---
**关键记忆点:**
* *printf 的 width 是“至少”precision 有时是“至多”s)或“至少”d)或“小数位”f)。**
* *scanf 的 width 总是“至多”scanf 没有 precision。**
* *printf (out)** 偏向**美观和完整性**,通常不会轻易截断原始数据(除了字符串的 .precision)。
* *scanf (in)** 偏向**限制和解析**,会根据 width 截断输入数据。
理解这些差异对于正确编写 C 语言的输入输出代码至关重要。