scanf 与 printf 学习笔记
在 C 语言中,scanf 和 printf 是最常见的一组输入输出函数:
scanf:按照格式从标准输入读取数据printf:按照格式向标准输出写入数据
它们都位于 <stdio.h> 中。C++ 也可以继续使用这两个函数,同时还提供了面向流的 cin 和 cout。这篇文章先学习 scanf、printf 的基本用法,再把它们与 cin、cout 放在一起比较。
一、最简单的例子
#include <stdio.h>
int main(void) {
int age;
printf("请输入年龄:");
scanf("%d", &age);
printf("你的年龄是 %d\n", age);
return 0;
}
运行时,程序会等待用户输入一个整数。这里有两个关键点:
printf的格式字符串中,%d表示一个int类型的整数。scanf需要把数据写入变量,所以要传入变量的地址&age。
可以把 &age 理解为“告诉 scanf 应该把读到的结果放在哪里”。
二、printf:格式化输出
1. 常用格式说明符
| 格式 | 对应类型 | 示例 |
|---|---|---|
%d |
int |
printf("%d", 42); |
%u |
unsigned int |
printf("%u", 42u); |
%ld |
long |
printf("%ld", number); |
%f |
double 输出 |
printf("%f", 3.14); |
%c |
char |
printf("%c", 'A'); |
%s |
字符串 | printf("%s", "hello"); |
%p |
指针地址 | printf("%p", (void *)ptr); |
示例:
#include <stdio.h>
int main(void) {
int count = 12;
double price = 3.5;
char level = 'A';
char name[] = "Rain";
printf("数量:%d\n", count);
printf("价格:%.2f\n", price);
printf("等级:%c\n", level);
printf("名字:%s\n", name);
return 0;
}
%.2f 表示浮点数保留两位小数。格式字符串中的普通文字会原样输出,格式说明符则会被后面的参数替换。
2. 宽度、对齐和填充
#include <stdio.h>
int main(void) {
printf("|%10d|\n", 123);
printf("|%-10d|\n", 123);
printf("|%05d|\n", 123);
printf("|%.2f|\n", 3.14159);
return 0;
}
输出效果大致如下:
| 123|
|123 |
|00123|
|3.14|
%10d:宽度至少为 10,默认右对齐%-10d:宽度至少为 10,左对齐%05d:宽度至少为 5,不足宽度时用0补齐%.2f:浮点数保留两位小数
3. printf 的返回值
printf 成功时返回实际输出的字符数;发生输出错误时返回负数。
#include <stdio.h>
int main(void) {
int written = printf("hello\n");
printf("上一条输出写入了 %d 个字符\n", written);
return 0;
}
平时可以直接忽略这个返回值,但在需要检查输出是否成功时,它很有用。
三、scanf:格式化输入
1. 读取不同类型
#include <stdio.h>
int main(void) {
int age;
double height;
char grade;
scanf("%d", &age);
scanf("%lf", &height);
scanf(" %c", &grade);
printf("年龄:%d,身高:%.2f,等级:%c\n",
age, height, grade);
return 0;
}
这里需要特别注意:
- 读取
int使用%d - 读取
double使用%lf - 读取
char使用%c %c前面的空格用于跳过输入缓冲区中残留的空白字符
scanf 中 %f 对应 float *,而 %lf 对应 double *。这和 printf 的浮点格式有差异:
float a;
double b;
scanf("%f", &a);
scanf("%lf", &b);
2. 为什么通常需要加 &
普通变量需要传地址:
int number;
scanf("%d", &number);
字符数组名本身会转换为指向首元素的指针,因此读取字符串时通常不写 &:
char name[20];
scanf("%19s", name);
这里的 19 很重要。它限制了最多读取的字符数量,并为字符串末尾的 \0 留出空间。%s 默认会持续读取非空白字符,所以不适合读取带空格的一整行。
3. 读取一整行
如果需要读取包含空格的文字,可以使用 fgets:
#include <stdio.h>
int main(void) {
char sentence[100];
fgets(sentence, sizeof(sentence), stdin);
printf("你输入的是:%s", sentence);
return 0;
}
scanf("%s", ...) 更适合读取一个单词,fgets 更适合读取一整行。两者的使用场景不同,不应该强行混用。
4. scanf 的返回值
scanf 返回成功匹配并赋值的项目数量。如果输入结束或发生读取错误,可能返回 EOF。
#include <stdio.h>
int main(void) {
int a;
double b;
int matched = scanf("%d %lf", &a, &b);
if (matched == 2) {
printf("读取成功:%d %.2f\n", a, b);
} else {
printf("输入格式不正确\n");
}
return 0;
}
检查返回值可以避免输入失败后继续使用未初始化变量。
四、scanf 最容易遇到的问题
1. %c 读到了换行符
下面的代码可能读不到用户真正输入的字符:
int number;
char ch;
scanf("%d", &number);
scanf("%c", &ch);
输入整数后按下回车,换行符可能还留在输入流中,第二个 scanf("%c", ...) 就会把它读走。
常见写法是在 %c 前加空格:
scanf(" %c", &ch);
格式字符串中的空白字符会让 scanf 跳过连续的空白,包括空格、制表符和换行。
2. 输入类型不匹配
int number;
scanf("%lf", &number);
格式说明符与变量类型不匹配,会导致未定义行为。应当让格式、变量类型和地址类型保持一致。
3. 忘记检查返回值
如果用户输入 abc,下面的 number 不会成功得到一个整数:
int number;
scanf("%d", &number);
printf("%d\n", number);
更稳妥的做法是检查返回值,并在复杂程序中设计好错误输入的处理方式。
五、cin 与 cout 的基本用法
cin 和 cout 属于 C++ 的输入输出流,定义在 <iostream> 中:
#include <iostream>
int main() {
int age;
std::cout << "请输入年龄:";
std::cin >> age;
std::cout << "你的年龄是 " << age << '\n';
return 0;
}
它们使用运算符完成输入输出:
std::cin >> value:从输入流读取数据std::cout << value:向输出流写入数据
和 printf 相比,cout 不需要为每个变量手动写 %d、%f 等格式说明符。类型信息由 C++ 编译器和流对象共同处理。
六、scanf、printf 与 cin、cout 的比较
| 对比项目 | scanf / printf |
cin / cout |
|---|---|---|
| 所属语言 | C 标准库,也可在 C++ 中使用 | C++ 标准库 |
| 头文件 | <stdio.h> 或 <cstdio> |
<iostream> |
| 输入方式 | 格式字符串 | >> 运算符 |
| 输出方式 | 格式字符串 | << 运算符 |
| 类型安全 | 依赖格式字符串,写错时风险较高 | 类型检查更自然 |
| 可读性 | 格式短,C 风格明显 | C++ 代码通常更直观 |
| 自定义类型 | 需要手动转换或格式化 | 可以重载 >> 和 << |
| 输入状态 | 通过返回值判断 | 通过 fail()、eof() 等状态判断 |
| 性能 | 通常较快 | 默认配置下可能较慢,可调整同步设置 |
1. 同一个功能的两种写法
使用 scanf 和 printf:
#include <cstdio>
int main() {
int a;
int b;
std::scanf("%d%d", &a, &b);
std::printf("sum = %d\n", a + b);
return 0;
}
使用 cin 和 cout:
#include <iostream>
int main() {
int a;
int b;
std::cin >> a >> b;
std::cout << "sum = " << a + b << '\n';
return 0;
}
两段代码的功能相同,但表达方式不同。前者依赖格式字符串,后者依赖流运算符。
2. 性能设置
在 C++ 竞赛或大量输入输出场景中,经常会看到:
#include <iostream>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return 0;
}
sync_with_stdio(false):解除 C++ 流与 C 标准流之间的同步cin.tie(nullptr):解除cin与cout的绑定,减少不必要的刷新
使用这两个设置后,不要在同一个程序中随意交替混用 scanf/printf 和 cin/cout,否则容易产生难以理解的缓冲行为。
3. 输入错误的处理
scanf 常通过返回值判断成功读取了几个项目:
int value;
if (scanf("%d", &value) != 1) {
printf("请输入整数\n");
}
cin 则可以检查流状态:
int value;
if (!(std::cin >> value)) {
std::cout << "请输入整数\n";
}
在复杂的 C++ 项目中,流状态还可以通过 std::cin.fail()、std::cin.eof() 等函数进一步判断。
七、应该怎么选择
可以按照下面的原则选择:
使用 scanf 和 printf 的情况
- 正在学习 C 语言
- 维护已有的 C 代码
- 需要简洁的格式化输出
- 已经明确掌握格式说明符和返回值检查
使用 cin 和 cout 的情况
- 编写现代 C++ 代码
- 需要处理自定义类型
- 更看重类型安全和代码可读性
- 希望使用运算符重载连接复杂对象
在 C++ 竞赛中,两种方案都很常见。scanf 和 printf 语法短、性能稳定;cin 和 cout 表达自然,配合关闭同步后通常也能满足大量数据输入。
八、一页速记版
printf
- 头文件:
<stdio.h>或 C++ 中的<cstdio> %d输出int%f输出double%c输出字符%s输出字符串%.2f表示保留两位小数
scanf
- 普通变量通常传地址:
scanf("%d", &number); float使用%fdouble使用%lf%c前加空格可以跳过残留空白%s要限制宽度,例如%19s- 始终检查返回值
cin / cout
- 输入:
std::cin >> value - 输出:
std::cout << value - 多个数据可以连续写:
std::cin >> a >> b - 大量输入时可使用
std::ios::sync_with_stdio(false); - 不要在关闭同步后随意混用 C 和 C++ 的输入输出
九、结语
scanf 和 printf 的核心是“格式字符串 + 参数地址”,而 cin 和 cout 的核心是“流 + 运算符”。前者更接近 C 语言底层接口,后者更符合 C++ 的对象和类型系统。
学习时可以先掌握下面这个最小闭环:
int number;
scanf("%d", &number);
printf("%d\n", number);
再逐步理解字符串、浮点数、字符读取、返回值检查和输入缓冲区问题。等这些基础内容熟悉以后,再比较 cin、cout 的流状态和性能设置,就能更清楚地知道不同输入输出工具适合什么场景。
