- 输入/输出的格式化:使用
printf和scanf系列函数,按照指定的格式字符串来读写数据。 - 字符串的格式化:使用
sprintf、snprintf等函数,将格式化后的数据写入字符串缓冲区。
我会从基础到进阶,并结合代码示例为你详细解释。

(图片来源网络,侵删)
格式化输出 (printf 系列)
printf 是 C 语言中最常用的格式化输出函数,它位于 stdio.h 头文件中。
1 基本语法
int printf(const char *format, ...);
format:格式化字符串,它包含了两种类型的字符:- 普通字符:直接原样输出。
- 格式说明符:以 开头,用于指定后面参数的类型和输出格式。
- 可变参数列表,数量和类型必须与格式字符串中的格式说明符一一对应。
2 常用格式说明符
| 说明符 | 类型 | 示例 |
|---|---|---|
%d 或 %i |
有符号十进制整数 | printf("Number: %d", 42); |
%u |
无符号十进制整数 | printf("Unsigned: %u", 42); |
%f |
单精度浮点数 | printf("Float: %f", 3.14); |
%lf |
双精度浮点数 (用于 scanf 输入时是 %lf,printf 输出时用 %f 也可以,但推荐 %lf 保持一致性) |
printf("Double: %lf", 3.14159); |
%c |
单个字符 | printf("Char: %c", 'A'); |
%s |
字符串 (以 \0 结尾的字符数组) |
printf("String: %s", "Hello"); |
%p |
指针地址 | printf("Address: %p", &my_var); |
%x 或 %X |
十六进制整数 (小写/大写) | printf("Hex: %x", 255); // 输出 ff |
%o |
八进制整数 | printf("Octal: %o", 8); // 输出 10 |
| 输出一个百分号 | printf("100%%"); // 输出 100% |
3 修饰符
在 和类型字母之间可以添加修饰符,来控制输出格式。
- 宽度:指定输出的最小字符数。
printf("%5d", 42); // 输出 " 42" (右对齐,总宽度为5) printf("%-5d", 42); // 输出 "42 " (左对齐,总宽度为5) - 精度:
- 对于浮点数 (
%f,%lf),指定小数点后的位数。printf("%.2f", 3.14159); // 输出 "3.14" - 对于字符串 (
%s),指定最大字符数。printf("%.5s", "HelloWorld"); // 输出 "Hello" - 对于整数 (
%d),指定数字的最小位数(不足补前导0)。printf("%05d", 42); // 输出 "00042"
- 对于浮点数 (
- 标志:
- 总是显示符号(正数显示 ,负数显示 )。
printf("%+d", 42); // 输出 "+42" printf("%+d", -42); // 输出 "-42" ` (空格):正数前加空格,负数前加-`。printf("% d", 42); // 输出 " 42" printf("% d", -42); // 输出 "-42"- 对于八进制和十六进制,添加前缀。
printf("%#o", 10); // 输出 "012" printf("%#x", 255); // 输出 "0xff"
- 总是显示符号(正数显示 ,负数显示 )。
4 printf 示例
#include <stdio.h>
int main() {
int age = 30;
float height = 1.75f;
double pi = 3.1415926535;
char initial = 'J';
char name[] = "Linux C";
// 基本输出
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Height: %.2f meters\n", height); // 保留两位小数
// 使用修饰符
printf("Pi (with 4 decimal places): %.4lf\n", pi);
printf("Padded Age (5 digits): %05d\n", age);
printf("Left-aligned Age (5 digits): %-5d\n", age);
printf("Signed Age: %+d\n", age);
// 指针地址
int num = 100;
printf("Address of 'num': %p\n", (void*)&num); // 强制转换为void*是良好实践
return 0;
}
编译和运行:
gcc -o format_example format_example.c ./format_example
格式化输入 (scanf 系列)
scanf 用于从标准输入(通常是键盘)读取数据,并根据格式字符串进行解析,它也位于 stdio.h 中。

(图片来源网络,侵删)
1 基本语法
int scanf(const char *format, ...);
format:格式化字符串,包含普通字符和格式说明符。- 可变参数列表,必须是指针,用于存储读取到的数据。
2 关键点:必须使用指针
这是初学者最容易犯错的地方。scanf 需要知道要把读到的数据存到哪里去,所以你必须传递变量的地址。
int number;
scanf("%d", &number); // 正确,传递 number 的地址
// scanf("%d", number); // 错误!传递的是值,无法接收数据
3 常用格式说明符
与 printf 类似,但注意浮点数在 scanf 中必须使用 %lf 来读取 double 类型。
| 说明符 | 对应的变量类型 |
|---|---|
%d |
int * |
%f |
float * |
%lf |
double * |
%c |
char * |
%s |
char * (会读取直到遇到空白字符) |
%x |
int * (读取十六进制) |
4 scanf 示例
#include <stdio.h>
int main() {
int id;
char name[50];
double salary;
printf("Enter your ID: ");
scanf("%d", &id);
// 清除输入缓冲区中的换行符,防止它被下一个 %s 读取
while (getchar() != '\n');
printf("Enter your name: ");
// 注意:scanf("%s", name) 不安全,不能处理带空格的字符串
// 使用 fgets 更安全,但这里为了演示 scanf
scanf("%49s", name); // 读取最多49个字符,防止溢出
printf("Enter your salary: ");
scanf("%lf", &salary); // 必须用 %lf 读取 double
printf("\n--- Employee Info ---\n");
printf("ID: %d\n", id);
printf("Name: %s\n", name);
printf("Salary: %.2lf\n", salary);
return 0;
}
编译和运行:
gcc -o scanf_example scanf_example.c ./scanf_example
交互式输入:

(图片来源网络,侵删)
Enter your ID: 101
Enter your name: Linus Torvalds
Enter your salary: 1000000.50
--- Employee Info ---
ID: 101
Name: Linus
Salary: 1000000.50
你会发现 "Torvalds" 没有被读取,这就是 scanf("%s", ...) 的局限性。
字符串格式化
我们不想直接输出到屏幕,而是想把格式化后的数据存到一个字符串变量里。
1 sprintf (String Print Formatted)
sprintf 的行为和 printf 一样,只是它把结果写入一个字符数组(字符串),而不是输出到屏幕。
#include <stdio.h>
int main() {
int x = 10, y = 20;
char result_str[100]; // 确保缓冲区足够大
// 将格式化后的字符串写入 result_str
sprintf(result_str, "The sum of %d and %d is %d.", x, y, x + y);
printf("Generated string: %s\n", result_str);
return 0;
}
输出:
Generated string: The sum of 10 and 20 is 30.
危险:sprintf 不会检查目标缓冲区的大小,如果格式化后的字符串太长,会导致缓冲区溢出,这是一个严重的安全漏洞。
2 snprintf (Safe String Print Formatted)
snprintf 是 sprintf 的安全版本,它增加了一个参数 size,用于指定目标缓冲区的大小。
- 如果生成的字符串长度(包括结尾的
\0)小于size,则完整写入。 - 如果生成的字符串长度大于等于
size,则只写入size - 1个字符,并在最后添加\0,确保不会溢出。
强烈推荐在所有新代码中使用 snprintf 替代 sprintf。
#include <stdio.h>
int main() {
int x = 123456789;
char small_buffer[5]; // 故意设置一个很小的缓冲区
// 使用 snprintf 是安全的
// 它会写入 "1234",然后添加 '\0',总共 5 个字符
snprintf(small_buffer, sizeof(small_buffer), "%d", x);
printf("Small buffer content: %s\n", small_buffer); // 输出 "1234"
printf("Buffer size is safe: %zu\n", sizeof(small_buffer)); // 输出 5
return 0;
}
高级格式化
1 动态宽度与精度
你可以使用 作为占位符,在参数列表中动态指定宽度和精度。
#include <stdio.h>
int main() {
int width = 10;
int precision = 3;
double value = 123.456789;
// width 和 precision 的值由变量提供
printf("Dynamic width and precision: %*.*f\n", width, precision, value);
// 等价于 printf("Dynamic width and precision: %10.3f\n", value);
return 0;
}
输出:
Dynamic width and precision: 123.457
2 fprintf 和 fscanf
这两个函数可以将格式化输出/输入重定向到任何文件流,而不仅仅是标准输入/输出。
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w"); // 以写入模式打开文件
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
// 将格式化数据写入文件
fprintf(fp, "ID: %d, Name: %s, Score: %.2f\n", 1, "Alice", 95.5);
fclose(fp); // 关闭文件
// 现在以读取模式打开文件
fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
int id;
char name[50];
float score;
// 从文件中读取格式化数据
fscanf(fp, "ID: %d, Name: %s, Score: %f\n", &id, name, &score);
printf("Read from file: ID=%d, Name=%s, Score=%.2f\n", id, name, score);
fclose(fp);
return 0;
}
总结与最佳实践
- 优先使用
snprintf:永远不要使用sprintf,因为它不安全。snprintf是现代 C 语言的标准做法。 - 浮点数输入/输出注意:
printf输出double时,用%f或%lf都可以,但%lf更清晰。scanf输入double时,必须使用%lf。
- 处理字符串输入:
scanf("%s", ...)无法处理带空格的字符串,对于用户输入的整行文本,fgets()是更安全、更合适的选择。 - 检查返回值:
printf和scanf系列函数都返回成功处理的参数个数。scanf的返回值可以用来判断是否成功读取了所有期望的数据。int num, count; printf("Enter an integer: "); count = scanf("%d", &num); if (count != 1) { printf("Invalid input!\n"); } - 清除输入缓冲区:在
scanf读取数字后,换行符会留在缓冲区,可能会干扰后续的scanf("%s", ...)或fgets(),使用while (getchar() != '\n');可以清除缓冲区直到遇到换行符。
希望这份详细的指南能帮助你完全掌握 Linux C 语言中的格式化!
