printf - 格式化输出
printf 的全称是 "Print Formatted",即“格式化打印”,它的作用是将数据按照你指定的格式,输出到标准输出设备(通常是你的屏幕)。

(图片来源网络,侵删)
基本语法
int printf(const char *format, ...);
- 返回值:它返回成功打印的字符个数(不包括结尾的
\0),如果发生错误,则返回一个负数。 format:这是一个格式化字符串,它包含了两种类型的字符:- 普通字符:会原样输出。
- 格式说明符:以 开头,用来指定后面要输出的变量的类型和格式。
- (省略号):表示这是一个可变参数列表,你需要提供与格式说明符一一对应的变量。
常用格式说明符
| 格式说明符 | 对应数据类型 | 描述 |
|---|---|---|
%d 或 %i |
int |
以十进制形式输出一个整数。 |
%c |
char |
输出一个字符。 |
%s |
char * (字符串) |
输出一个字符串。 |
%f |
float |
以小数形式输出一个浮点数。 |
%lf |
double |
注意:printf 中 %lf 和 %f 对于 double 类型通常可以混用,但 %f 是标准写法。 |
%p |
void * |
输出一个指针地址。 |
%x 或 %X |
int (无符号) |
以十六进制形式输出整数,%x 用小写字母,%X 用大写字母。 |
%o |
int (无符号) |
以八进制形式输出整数。 |
| - | 输出一个百分号 本身。 |
示例代码
#include <stdio.h>
int main() {
int age = 25;
char initial = 'J';
float height = 1.75f;
double pi = 3.141592653589793;
char name[] = "Alice";
// 普通字符和格式说明符混合
printf("My name is %s.\n", name); // 输出: My name is Alice.
printf("My initial is %c and my age is %d.\n", initial, age); // 输出: My initial is J and my age is 25.
// 输出浮点数,可以控制小数位数
printf("My height is %.2f meters.\n", height); // 输出: My height is 1.75 meters. (.2f 表示保留2位小数)
// 输出 double 类型
printf("The value of pi is %.10f.\n", pi); // 输出: The value of pi is 3.1415926536.
// 输出变量的地址
int num = 10;
printf("The address of num is %p.\n", &num); // 输出: The address of num is 0x7ffc... (具体地址值)
// 输出百分号
printf("This is a percent sign: %%\n"); // 输出: This is a percent sign: %
// 打印 printf 的返回值
int chars_printed = printf("This line has some text.\n");
printf("The last printf printed %d characters.\n", chars_printed); // 输出: The last printf printed 21 characters.
return 0;
}
scanf - 格式化输入
scanf 的全称是 "Scan Formatted",即“格式化扫描”,它的作用是从标准输入设备(通常是你的键盘)读取数据,并按照指定的格式存入变量中。
基本语法
int scanf(const char *format, ...);
- 返回值:它返回成功匹配并赋值的参数个数,如果到达文件末尾(EOF),则返回
EOF,如果发生匹配错误,则返回一个小于预期参数个数的值。 format:格式化字符串,包含普通字符和格式说明符。- (省略号):可变参数列表,这里必须是变量的地址,而不是变量本身,因为
scanf需要知道把读到的数据存到哪里去。
关键点:变量地址
scanf 需要变量的地址,在 C 语言中,使用取地址运算符 & 来获取变量的地址。
&age获取int变量age的地址。&initial获取char变量initial的地址。- 对于字符串数组(如
char name[50]),name本身就代表数组的首地址,所以不需要&符号。
常用格式说明符
| 格式说明符 | 对应数据类型 | 描述 |
|---|---|---|
%d |
int * |
从输入读取一个十进制整数。 |
%c |
char * |
读取一个字符。注意:它会读取包括空格、换行在内的任何字符。 |
%s |
char * |
读取一个字符串,直到遇到空白字符(空格、Tab、换行)。 |
%f |
float * |
读取一个浮点数。 |
%lf |
double * |
注意:scanf 中读取 double 类型必须使用 %lf,使用 %f 是错误的,会导致数据读取不正确。 |
%x |
int * |
以十六进制形式读取一个整数。 |
示例代码
#include <stdio.h>
int main() {
int number;
char grade;
char full_name[50];
double gpa;
printf("Please enter an integer: ");
int ret1 = scanf("%d", &number);
if (ret1 != 1) {
printf("Failed to read an integer.\n");
return 1; // 非正常退出
}
printf("You entered: %d\n", number);
printf("Please enter a single character: ");
// 注意:在 %c 前加一个空格,可以跳过之前输入留下的换行符
scanf(" %c", &grade);
printf("You entered: %c\n", grade);
printf("Please enter your full name: ");
// scanf 读取字符串时,遇到空格就停止,所以不能读取带空格的名字。
scanf("%s", full_name);
printf("Hello, %s\n", full_name);
printf("Please enter your GPA: ");
int ret2 = scanf("%lf", &gpa); // 必须使用 %lf 来读取 double
if (ret2 != 1) {
printf("Failed to read a double.\n");
return 1;
}
printf("Your GPA is: %.2f\n", gpa);
return 0;
}
scanf 的常见问题与解决方案
问题1:缓冲区残留的换行符
当你使用 scanf("%d", &num); 读取一个整数后,按下的回车键(\n)会留在输入缓冲区中,如果下一个 scanf 是 "%c",它会直接读取这个残留的换行符,而不是等待你输入新字符。

(图片来源网络,侵删)
解决方案:
在 "%c" 前面加一个空格 " %c",这个空格会告诉 scanf 跳过所有的空白字符(包括换行符),直到遇到一个非空白字符。
问题2:scanf 的返回值检查
如果用户输入了非预期的数据(比如你期望输入数字,但用户输入了字母),scanf 会失败,错误的输入会留在缓冲区中,导致后续的 scanf 也立即失败,程序可能进入死循环。
解决方案:
始终检查 scanf 的返回值,如果返回值不等于你期望读取的变量个数,就说明出错了,这时,你需要清空输入缓冲区,防止后续读取失败。

(图片来源网络,侵删)
示例:健壮的输入处理
#include <stdio.h>
#include <ctype.h> // 用于 isprint 函数
void clear_input_buffer() {
int c;
// 读取并丢弃缓冲区中所有剩余的字符,直到遇到换行符或文件结尾
while ((c = getchar()) != '\n' && c != EOF);
}
int main() {
int age;
printf("Enter your age: ");
// 循环直到成功读取一个整数
while (1) {
int result = scanf("%d", &age);
if (result == 1) {
// 成功读取,跳出循环
break;
} else if (result == EOF) {
printf("Input ended unexpectedly.\n");
return 1;
} else {
// 输入无效,清空缓冲区并提示用户重新输入
printf("Invalid input. Please enter an integer: ");
clear_input_buffer();
}
}
printf("You are %d years old.\n", age);
return 0;
}
scanf 和 printf 的总结与对比
| 特性 | printf |
scanf |
|---|---|---|
| 功能 | 输出数据到屏幕 | 输入数据从键盘 |
| 参数 | 变量值 | 变量地址 (用 & 获取) |
| 返回值 | 成功打印的字符数 | 成功赋值的参数个数 |
double类型 |
使用 %f 或 %lf |
必须使用 %lf |
| 健壮性 | 相对简单,不易出错 | 需要检查返回值并处理缓冲区,容易出错 |
| 核心 | 将内存中的数据转换为字符流显示 | 将键盘输入的字符流转换为内存中的数据 |
最佳实践:
- 使用
printf时,格式化字符串要清晰,适当使用\n控制换行。 - 使用
scanf时:- 务必检查返回值。
- 对于
char类型,考虑在格式说明符前加空格" %c"来跳过空白。 - 如果输入可能失败,准备好清空输入缓冲区的函数。
- 对于复杂的输入输出场景,尤其是读取整行文本(包括空格),
scanf往往不是最佳选择,可以考虑使用fgets+sscanf的组合,或者直接使用fgets然后自己解析字符串。
