C 语言printf()和scanf()详解
一、printf()函数概述
printf()
是 C 语言标准库中用于格式化输出的函数,它在<stdio.h>
头文件中声明。这个函数功能强大,允许我们以特定格式将数据输出到标准输出设备(通常是控制台)。其基本语法如下:
int printf(const char *format, ...);
这里,format
是一个字符串,它包含了普通字符以及格式说明符。...
表示可变参数列表,用于传递需要按照格式说明符输出的数据。printf()
函数返回成功输出的字符数,如果发生错误则返回一个负数。
二、格式说明符
格式说明符是 printf()
函数的核心部分,它们决定了如何格式化和输出数据。格式说明符以 %
字符开头,后面跟着一个或多个修饰符,最后是一个转换说明符。以下是一些常见的转换说明符:
1. 整数类型
%d
和%i
:用于输出有符号十进制整数。%d
和%i
在功能上基本相同,但%i
在解析输入时,如果前缀是0x
或0X
,会将其按十六进制解析,而%d
始终按十进制解析。
#include <stdio.h>
int main() {
int num = 10;
printf("使用 %d 输出: %d\n", num);
printf("使用 %i 输出: %i\n", num);
return 0;
}
%u
:用于输出无符号十进制整数。
#include <stdio.h>
int main() {
unsigned int num = 10;
printf("无符号整数输出: %u\n", num);
return 0;
}
%o
:用于输出无符号八进制整数。
#include <stdio.h>
int main() {
unsigned int num = 10;
printf("八进制输出: %o\n", num);
return 0;
}
%x
和%X
:用于输出无符号十六进制整数。%x
使用小写字母(a - f)表示十六进制数字,%X
使用大写字母(A - F)。
#include <stdio.h>
int main() {
unsigned int num = 10;
printf("小写十六进制输出: %x\n", num);
printf("大写十六进制输出: %X\n", num);
return 0;
}
2. 浮点类型
%f
:用于输出单精度或双精度浮点数,默认保留 6 位小数。
#include <stdio.h>
int main() {
double num = 3.1415926;
printf("浮点数输出: %f\n", num);
return 0;
}
%e
和%E
:以指数形式输出浮点数。%e
使用小写的e
表示指数部分,%E
使用大写的E
。
#include <stdio.h>
int main() {
double num = 3.1415926;
printf("指数形式输出(小写 e): %e\n", num);
printf("指数形式输出(大写 E): %E\n", num);
return 0;
}
%g
和%G
:根据数值的大小自动选择%f
或%e
格式,以较短的形式输出浮点数。%g
使用小写的e
(如果使用指数形式),%G
使用大写的E
。
#include <stdio.h>
int main() {
double num1 = 3.1415926;
double num2 = 12345678901234567890.0;
printf("自动选择格式(%g): %g\n", num1);
printf("自动选择格式(%g): %g\n", num2);
printf("自动选择格式(%G): %G\n", num1);
printf("自动选择格式(%G): %G\n", num2);
return 0;
}
3. 字符和字符串类型
%c
:用于输出单个字符。
#include <stdio.h>
int main() {
char ch = 'A';
printf("字符输出: %c\n", ch);
return 0;
}
%s
:用于输出字符串。字符串必须以空字符'\0'
结尾。
#include <stdio.h>
int main() {
char str[] = "Hello, World!";
printf("字符串输出: %s\n", str);
return 0;
}
三、修饰符
在 %
和转换说明符之间,可以插入一些修饰符,用于进一步控制输出的格式。
1. 字段宽度
字段宽度是指输出数据所占的最小字符数。如果数据的实际宽度小于字段宽度,会在数据前面(默认左对齐时)或后面(右对齐时)填充空格。字段宽度可以是一个正整数常量,也可以是 *
,当使用 *
时,实际的字段宽度由可变参数列表中的下一个整数指定。
#include <stdio.h>
int main() {
int num = 10;
printf("字段宽度为 5: |%5d|\n", num);
printf("字段宽度由变量指定: |%*d|", 5, num);
return 0;
}
2. 精度
精度用于控制浮点数的小数位数或字符串的最大输出长度。对于浮点数,精度表示小数点后的数字个数;对于字符串,精度表示最多输出的字符数(不包括空字符)。精度以 .
开头,后面跟着一个正整数常量,或者是 *
,当使用 *
时,实际的精度由可变参数列表中的下一个整数指定。
#include <stdio.h>
int main() {
double num = 3.1415926;
printf("保留 2 位小数: %.2f\n", num);
char str[] = "Hello, World!";
printf("最多输出 5 个字符: %.5s\n", str);
printf("精度由变量指定: %.2f", num);
return 0;
}
3. 对齐方式
默认情况下,输出是右对齐的。可以使用 -
修饰符来指定左对齐。
#include <stdio.h>
int main() {
int num = 10;
printf("右对齐(字段宽度 5): |%5d|\n", num);
printf("左对齐(字段宽度 5): |%-5d|\n", num);
return 0;
}
4. 填充字符
除了使用空格填充外,还可以使用 0
修饰符指定用 0
填充。这在输出整数时,特别是需要固定宽度且前导零的情况下很有用。
#include <stdio.h>
int main() {
int num = 10;
printf("用 0 填充(字段宽度 5): |%05d|\n", num);
return 0;
}
四、转义序列
在 printf()
的格式字符串中,还可以使用转义序列来表示一些特殊字符。常见的转义序列有:
\n
:换行符,将光标移动到下一行的开头。
#include <stdio.h>
int main() {
printf("第一行\n第二行");
return 0;
}
\t
:水平制表符,用于在输出中插入一个制表符的空格,使输出对齐。
#include <stdio.h>
int main() {
printf("Name\tAge\nJohn\t25\nJane\t30");
return 0;
}
\\
:表示反斜杠字符\
本身。这在需要输出反斜杠时很有用,因为反斜杠在 C 语言中有特殊含义,需要用\\
来表示一个实际的反斜杠。
#include <stdio.h>
int main() {
printf("路径: C:\\Program Files\n");
return 0;
}
\'
和\"
:分别用于输出单引号和双引号字符。因为单引号和双引号在 C 语言中用于界定字符常量和字符串常量,所以需要用转义序列来输出它们。
#include <stdio.h>
int main() {
printf("他说: \"你好!\"");
printf('这是一个字符: \'A\'');
return 0;
}
五、printf()函数的返回值
printf()
函数返回成功输出的字符数,如果发生错误则返回一个负数。这个返回值在一些情况下很有用,例如可以用来检查输出是否成功,或者根据输出的字符数进行一些后续处理。
#include <stdio.h>
int main() {
int count = printf("Hello, World!");
printf("\n输出的字符数: %d\n", count);
return 0;
}
六、scanf()函数概述
scanf()
是 C 语言标准库中用于格式化输入的函数,同样在<stdio.h>
头文件中声明。它用于从标准输入设备(通常是键盘)读取数据,并按照指定的格式将数据存储到变量中。其基本语法如下:
int scanf(const char *format, ...);
format
字符串包含了格式说明符,用于指定输入数据的格式。可变参数列表 ...
包含了接收输入数据的变量的地址。scanf()
函数返回成功匹配和赋值的输入项数,如果在匹配或读取输入时发生错误,或者在到达文件末尾时没有读取任何项,则返回 EOF(通常定义为 -1)。
七、scanf()的格式说明符
scanf()
的格式说明符与 printf()
有一些相似之处,但也有一些重要的区别。
1. 整数类型
%d
和%i
:用于读取有符号十进制整数。与printf()
类似,%d
始终按十进制解析输入,而%i
如果输入前缀是0x
或0X
,会按十六进制解析。
#include <stdio.h>
int main() {
int num;
printf("请输入一个整数: ");
if (scanf("%d", &num) == 1) {
printf("你输入的整数是: %d\n", num);
} else {
printf("输入错误\n");
}
return 0;
}
%u
:用于读取无符号十进制整数。
#include <stdio.h>
int main() {
unsigned int num;
printf("请输入一个无符号整数: ");
if (scanf("%u", &num) == 1) {
printf("你输入的无符号整数是: %u\n", num);
} else {
printf("输入错误\n");
}
return 0;
}
%o
:用于读取无符号八进制整数。输入的八进制数可以以0
开头,但不是必须的。
#include <stdio.h>
int main() {
unsigned int num;
printf("请输入一个八进制整数: ");
if (scanf("%o", &num) == 1) {
printf("转换为十进制是: %u\n", num);
} else {
printf("输入错误\n");
}
return 0;
}
%x
和%X
:用于读取无符号十六进制整数。输入的十六进制数可以以0x
或0X
开头,但不是必须的。%x
接受小写的十六进制数字(a - f),%X
接受大写的十六进制数字(A - F)。
#include <stdio.h>
int main() {
unsigned int num;
printf("请输入一个十六进制整数: ");
if (scanf("%x", &num) == 1) {
printf("转换为十进制是: %u\n", num);
} else {
printf("输入错误\n");
}
return 0;
}
2. 浮点类型
%f
:用于读取单精度浮点数。
#include <stdio.h>
int main() {
float num;
printf("请输入一个浮点数: ");
if (scanf("%f", &num) == 1) {
printf("你输入的浮点数是: %f\n", num);
} else {
printf("输入错误\n");
}
return 0;
}
%lf
:用于读取双精度浮点数。在 C 语言中,scanf()
读取双精度浮点数必须使用%lf
,而printf()
中%f
即可用于输出单精度和双精度浮点数。
#include <stdio.h>
int main() {
double num;
printf("请输入一个双精度浮点数: ");
if (scanf("%lf", &num) == 1) {
printf("你输入的双精度浮点数是: %lf\n", num);
} else {
printf("输入错误\n");
}
return 0;
}
3. 字符和字符串类型
%c
:用于读取单个字符。需要注意的是,%c
会读取输入缓冲区中的任何字符,包括空格、制表符和换行符。
#include <stdio.h>
int main() {
char ch;
printf("请输入一个字符: ");
if (scanf("%c", &ch) == 1) {
printf("你输入的字符是: %c\n", ch);
} else {
printf("输入错误\n");
}
return 0;
}
%s
:用于读取字符串。它会在遇到空格、制表符或换行符时停止读取,并自动在字符串末尾添加空字符'\0'
。
#include <stdio.h>
int main() {
char str[50];
printf("请输入一个字符串: ");
if (scanf("%s", str) == 1) {
printf("你输入的字符串是: %s\n", str);
} else {
printf("输入错误\n");
}
return 0;
}
八、scanf()的修饰符
scanf()
也支持一些修饰符,不过其修饰符的种类和用法与 printf()
有所不同。
1. 抑制符 *
在格式说明符的 %
之后加上 *
,可以抑制对输入数据的赋值。也就是说,scanf()
会读取相应的数据,但不会将其存储到任何变量中。
#include <stdio.h>
int main() {
int num1, num2;
printf("请输入三个整数,用空格隔开: ");
if (scanf("%d %*d %d", &num1, &num2) == 2) {
printf("第一个和第三个整数分别是: %d, %d\n", num1, num2);
} else {
printf("输入错误\n");
}
return 0;
}
2. 最大字符数
在格式说明符的 %
之后,可以指定一个整数,表示读取的最大字符数。这在读取字符串时非常有用,可以防止缓冲区溢出。
#include <stdio.h>
int main() {
char str[10];
printf("请输入一个字符串(最多 9 个字符): ");
if (scanf("%9s", str) == 1) {
printf("你输入的字符串是: %s\n", str);
} else {
printf("输入错误\n");
}
return 0;
}
九、处理输入缓冲区
在使用 scanf()
时,需要特别注意输入缓冲区的问题。当从键盘输入数据时,数据首先被存储在输入缓冲区中,scanf()
从缓冲区中读取数据。如果输入缓冲区中还有未处理的数据,可能会影响后续的 scanf()
操作。
1. 清除输入缓冲区
在连续使用 scanf()
读取不同类型的数据,特别是在读取字符或字符串之前读取了整数或浮点数时,可能需要清除输入缓冲区中的剩余字符(如换行符)。可以使用以下方法清除输入缓冲区:
#include <stdio.h>
#include <stdlib.h>
int main() {
int num;
char ch;
printf("请输入一个整数: ");
scanf("%d", &num);
// 清除输入缓冲区中的换行符
while ((getchar()) != '\n');
printf("请输入一个字符: ");
scanf("%c", &ch);
printf("你输入的整数是: %d,字符是: %c\n", num, ch);
return 0;
}
2. 避免缓冲区溢出
在读取字符串时,使用最大字符数修饰符(如 %ns
,n
为整数)可以有效避免缓冲区溢出。另外,也可以使用 fgets()
函数代替 scanf("%s")
来读取字符串,fgets()
会读取整行输入,包括换行符,并将其存储到指定的缓冲区中,从而减少缓冲区溢出的风险。
#include <stdio.h>
int main() {
char str[50];
printf("请输入一个字符串: ");
fgets(str, sizeof(str), stdin);
// fgets() 会读取换行符,可进行处理
str[strcspn(str, "\n")] = '\0';
printf("你输入的字符串是: %s\n", str);
return 0;
}
十、scanf()的返回值
scanf()
函数返回成功匹配和赋值的输入项数。这个返回值可以用于检查输入是否成功。如果返回值不等于预期的输入项数,可能表示输入有误。例如,当期望读取两个整数,但 scanf()
返回 1 时,说明只有一个整数被成功读取,可能输入格式不正确。
#include <stdio.h>
int main() {
int num1, num2;
printf("请输入两个整数,用空格隔开: ");
int result = scanf("%d %d", &num1, &num2);
if (result == 2) {
printf("你输入的两个整数是: %d, %d\n", num1, num2);
} else {
printf("输入错误,只读取了 %d 个整数\n", result);
}
return 0;
}
十一、printf()和scanf()的常见错误及注意事项
- 格式说明符不匹配:在
printf()
或scanf()
中使用格式说明符时,务必确保其与实际的数据类型匹配。例如,用%d
输出浮点数或用%f
读取整数都会导致未定义行为。
#include <stdio.h>
int main() {
double num = 3.14;
// 错误:用 %d 输出浮点数
printf("错误输出: %d\n", num);
return 0;
}
- 忘记取地址符:在
scanf()
中,传递给函数的变量必须是地址。忘记取地址符&
会导致未定义行为,因为scanf()
需要知道将数据存储在哪里。
#include <stdio.h>
int main() {
int num;
// 错误:忘记取地址符
scanf("%d", num);
return 0;
}
- 缓冲区溢出:在使用
scanf("%s")
读取字符串时,如果输入的字符串长度超过了目标数组的大小,就会发生缓冲区溢出,这是一个严重的安全漏洞。使用%ns
格式说明符或fgets()
函数可以避免这个问题。 - 输入缓冲区残留数据:如前文所述,连续使用
scanf()
读取不同类型的数据时,可能会因为输入缓冲区中残留的换行符等字符而导致问题。需要及时清除输入缓冲区,以确保程序的正确性。 - 格式字符串中的普通字符:在
printf()
的格式字符串中,普通字符会被直接输出。在scanf()
的格式字符串中,普通字符必须与输入完全匹配(除了空白字符,空白字符可以匹配任意数量的空白字符)。例如,scanf("a%d", &num)
要求输入以a
开头,然后是一个整数。
通过深入理解 printf()
和 scanf()
函数的原理、格式说明符、修饰符以及常见问题,我们能够在 C 语言编程中更加准确和高效地进行输入输出操作,编写出健壮的程序。