它们都定义在标准库头文件 <stdio.h> 中,所以使用前必须包含它:

(图片来源网络,侵删)
#include <stdio.h>
printf - 格式化输出函数
printf 的作用是将格式化的数据输出到标准输出设备(通常是你的屏幕)。
基本语法
int printf(const char *format, ...);
- 返回值:它返回一个整数,表示成功打印的字符个数(不包括结尾的空字符
\0),如果发生错误,则返回一个负数。 format(格式控制字符串):这是一个双引号括起来的字符串,它定义了输出的格式,它包含两种类型的字符:- 普通字符:原样输出。
- 格式说明符:以 开头,用来指定后面变量的输出格式。
- (可变参数列表):这是一个“可变参数列表”,意味着你可以传入任意数量的参数,每个参数对应
format字符串中的一个格式说明符。
常用格式说明符
| 格式说明符 | 类型 | 示例 |
|---|---|---|
%d 或 %i |
有符号十进制整数 | printf("%d", 123); |
%c |
单个字符 | printf("%c", 'A'); |
%s |
字符串 (字符数组) | printf("%s", "Hello"); |
%f |
单精度浮点数 (默认保留6位小数) | printf("%f", 3.14); |
%lf |
双精度浮点数 (用于 scanf 读入,printf 中用 %f 也可) |
printf("%lf", 3.1415926); |
%e 或 %E |
科学计数法表示的浮点数 | printf("%e", 1000.0); // 输出 000000e+03 |
%g 或 %G |
根据数值大小自动选择 %f 或 %e |
printf("%g", 1000.0); // 输出 1000 |
%x 或 %X |
无符号十六进制整数 (小写/大写) | printf("%x", 255); // 输出 ff |
%o |
无符号八进制整数 | printf("%o", 255); // 输出 377 |
%u |
无符号十进制整数 | printf("%u", 255); |
| 输出一个百分号 | printf("%%"); // 输出 |
进阶用法:修饰符
在 和字母之间可以添加修饰符来更精确地控制输出。
- 宽度:用数字指定最小输出宽度,如果不足,则用空格填充。
printf("%5d", 99); // 输出 " 99" (99前有3个空格) printf("%5s", "Hi"); // 输出 " Hi" (Hi前有3个空格) - 对齐:默认是右对齐,在宽度前加 表示左对齐。
printf("%-5d", 99); // 输出 "99 " (99后有3个空格) - 精度:
- 对于浮点数 (
%f,%e,%g),.数字表示小数点后保留的位数。printf("%.2f", 3.14159); // 输出 "3.14" - 对于字符串 (
%s),.数字表示输出的最大字符数。printf("%.5s", "HelloWorld"); // 输出 "Hello" - 对于整数 (
%d),.数字表示输出的最小数字位数,不足时用前导零填充。printf("%05d", 99); // 输出 "00099"
- 对于浮点数 (
示例代码
#include <stdio.h>
int main() {
int age = 25;
float height = 175.5f;
char initial = 'J';
char name[] = "John Doe";
printf("--- Personal Info ---\n");
printf("Name: %s\n", name);
printf("Initial: %c\n", initial);
printf("Age: %d years old\n", age);
printf("Height: %.1f cm\n", height); // 保留一位小数
printf("----------------------\n");
// 使用宽度和对齐
printf("|%-10s|%-10s|\n", "Column1", "Column2");
printf("|%-10d|%-10.2f|\n", 100, 3.14159);
return 0;
}
输出结果:
--- Personal Info ---
Name: John Doe
Initial: J
Age: 25 years old
Height: 175.5 cm
----------------------
|Column1 |Column2 |
|100 |3.14 |
scanf - 格式化输入函数
scanf 的作用是从标准输入设备(通常是你的键盘)读取数据,并根据格式说明符将其存储到指定的变量中。

(图片来源网络,侵删)
基本语法
int scanf(const char *format, ...);
- 返回值:它返回一个整数,表示成功匹配并赋值的参数个数,如果读取失败(例如输入不匹配类型),则返回
0或EOF(文件结束符,通常是-1)。 format(格式控制字符串):和printf类似,包含普通字符和格式说明符。- (可变参数列表):必须是变量的地址,而不是变量本身,因为
scanf需要知道把读到的数据存放到哪里,获取地址需要使用取地址运算符&。
关键点:变量地址
这是 scanf 最容易出错的地方,你必须传递变量的地址。
int a;->&a(获取变量a的内存地址)float b;->&bchar c;->&cchar str[100];->str(数组名str会“退化”为其首元素的地址,所以不需要&)
常用格式说明符
scanf 的格式说明符和 printf 基本对应,但注意浮点数用的是 %lf。
| 格式说明符 | 对应的变量类型 |
|---|---|
%d |
int |
%c |
char |
%s |
char[] (字符数组/字符串) |
%f |
float |
%lf |
double |
%x |
int (以十六进制读取) |
%u |
unsigned int |
输入时的空白字符处理
scanf 在读取数据时,会自动跳过输入中的空白字符,包括空格、制表符 \t 和换行符 \n。
%d,%f,%lf,%s等都会自动跳过前导空白。%c不会跳过任何字符,它会读取输入缓冲区中的下一个字符,无论它是什么(包括空格和换行符)。
示例代码
#include <stdio.h>
int main() {
int id;
char name[50];
double score;
printf("Please enter your student ID: ");
scanf("%d", &id); // 读取一个整数到 id 的地址中
printf("Please enter your name: ");
scanf("%s", name); // 读取一个字符串,name本身就是地址
printf("Please enter your score: ");
scanf("%lf", &score); // 注意:读取 double 必须用 %lf
printf("\n--- Student Record ---\n");
printf("ID: %d\n", id);
printf("Name: %s\n", name);
printf("Score: %.2f\n", score);
return 0;
}
运行示例:

(图片来源网络,侵删)
Please enter your student ID: 1001
Please enter your name: Alice
Please enter your score: 95.5
--- Student Record ---
ID: 1001
Name: Alice
Score: 95.50
scanf 的常见陷阱与解决方案
陷阱1:缓冲区残留的换行符
假设你想先读一个整数,再读一个字符:
#include <stdio.h>
int main() {
int num;
char ch;
printf("Enter a number: ");
scanf("%d", &num); // 用户输入 "10" 并按回车
printf("Enter a character: ");
scanf("%c", &ch); // 问题在这里!ch 会接收到用户按下的回车符 '\n'
printf("You entered number: %d and char: %c\n", num, ch);
return 0;
}
运行示例:
Enter a number: 10
Enter a character: You entered number: 10 and char:
你会发现 ch 被赋值为换行符,而不是你期望输入的字符。
解决方案:
在读取 %c 之前,主动清空输入缓冲区,一个简单的方法是读取并丢弃缓冲区中的所有字符,直到遇到换行符或文件结尾。
// 在第二个 scanf 之前加上这行代码 while (getchar() != '\n'); // 读取并丢弃所有字符,直到遇到换行符
陷阱2:%s 的不安全性
scanf("%s", name); 非常危险,因为它不知道 name 数组有多大,如果用户输入的字符串超过了数组的容量(name 只有50个字符,但用户输入了100个),就会发生缓冲区溢出,可能覆盖掉其他重要数据,导致程序崩溃或安全漏洞。
解决方案:
使用 scanf 的宽度限制功能,指定最大读取的字符数(要为字符串末尾的 \0 预留一个位置)。
scanf("%49s", name); // 最多读取49个字符,留1位给 '\0'
更安全的替代方案:
使用 fgets 函数来读取整行输入,因为它可以指定最大读取长度,并且会读取换行符(你可以手动去掉它)。
fgets(name, sizeof(name), stdin); // 去掉末尾可能的换行符 name[strcspn(name, "\n")] = 0;
总结与对比
| 特性 | printf |
scanf |
|---|---|---|
| 功能 | 输出数据到屏幕 | 输入数据从键盘 |
| 参数 | 格式字符串 + 变量值 | 格式字符串 + 变量地址 (&var) |
| 返回值 | 成功打印的字符数 | 成功赋值的变量个数 |
| 浮点数 | 用 %f |
读 float 用 %f,读 double 用 %lf |
| 安全性 | 相对安全 | 需要格外小心缓冲区溢出和换行符问题 |
| 常见用法 | printf("Hello, %s!", name); |
scanf("%d", &age); |
掌握 printf 和 scanf 是学习C语言的第一步,也是进行交互式程序设计的基础,理解它们的格式说明符、参数传递方式以及常见的陷阱,对于编写健壮的C程序至关重要。
