深入理解 C 语言中的 scanf、printf
欢迎来到我的:世界
希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 !
目录
- 前言
- 内容
- scanf-格式化输入函数
- printf-格式化输出函数
- 总结
前言
在 C 语言编程中,输入输出(I/O)操作是基础且重要的部分。
scanf
、printf
、fscanf
、fprintf
、sscanf
和sprintf
是 C 语言中用于输入输出的六个核心函数。它们之间既有联系也有区别,理解它们之间的关系对于编写高效的 C 程序至关重要。本文将先详细介绍scanf
和printf
函数及其相互关系。
内容
scanf-格式化输入函数
scanf 是一个标准库函数,用于从标准输入(通常是键盘)读取格式化输入。
scanf函数功能是从stdin
读取数据,并根据参数格式将其存储到附加参数所指向的位置。
它的原型如下:
int scanf ( const char * format, ... );
参数解释:
format
:这是一个字符串参数,它指定了输入数据的格式。format 字符串可以包含以下几种元素:
- 普通字符:在 scanf 函数执行时,输入中必须包含与这些普通字符相同的字符,否则匹配失败。例如,scanf(“%d,%d”, &a, &b); 要求用户输入的两个整数之间用逗号分隔。
- 空白字符:包括空格、制表符和换行符。scanf 函数会忽略输入中的一个或多个连续的空白字符。例如,scanf(“%d %d”, &a, &b); 可以接受用户输入的两个整数之间用一个或多个空格、制表符或换行符分隔。
- 格式说明符:用于指定输入数据的类型,常见的格式说明符如下:
%d
:用于读取十进制整数。
%f
:用于读取单精度浮点数。
%lf
:用于读取双精度浮点数。
%c
:用于读取单个字符。
%s
:用于读取字符串,遇到空白字符(空格、制表符、换行符)停止。
%x 或 %X
:用于读取十六进制整数。
%o
:用于读取八进制整数。
...
:这是可变参数列表,它是一系列指针,指向用于存储输入数据的变量。这些指针的类型必须与 format 字符串中的格式说明符相匹配。
返回值
:scanf 函数返回成功匹配并赋值的输入项的数量。如果发生读取错误或到达文件末尾,返回EOF
。
注意:
在 C 语言里,EOF
是一个预定义的常量,它代表“End Of File”
(文件结束),
EOF
在<stdio.h>
头文件中被定义,其值通常是-1
。在代码里可直接使用,而无需额外定义。
scanf的格式说明符原型为
%[*][width][length]specifier
,各部分用法如下:
specifier
(说明符):决定提取字符类型、解释方式及对应参数类型。
比如i
用于整数(可包含正负号,默认十进制,0前缀为八进制,0x前缀为十六进制);
d
或u
用于十进制整数(d有符号,u无符号);
o
用于八进制整数(无符号);
x
用于十六进制整数(无符号);
f、e、g
用于浮点数 ;
c
用于字符;
s
用于字符串(遇空格停止);
p
用于指针地址;
n
用于记录已读取字符数;
%
用于匹配单个%字符
注意:除n
外,每个说明符至少要消耗一个输入字符,否则匹配失败。
*
(可选):抑制赋值,读取数据但不存储。
width
(可选):指定读取的最大字符数。
length
(可选):指定数据大小,如h表示短整型,l表示长整型等。
举例说明:
int int_num;
printf("请输入一个整数(支持十进制、八进制0开头、十六进制0x开头):");
scanf("%i", &int_num);
printf("读取的整数是:%d\n", int_num);
输入:122 输出:122
输入:0122 输出:82
输入:0x122输出:290
// 读取十进制整数(d或u说明符示例)
int decimal_signed;
unsigned int decimal_unsigned;
printf("请输入一个有符号十进制整数和一个无符号十进制整数,用空格隔开:");
scanf("%d %u", &decimal_signed, &decimal_unsigned);
printf("有符号十进制整数:%d,无符号十进制整数:%u\n", decimal_signed, decimal_unsigned);
输入:-10 -10 输出:-10 4294967286
// 读取字符串(s说明符示例)
char str[50];
printf("请输入一个字符串:");
scanf("%s", str);
printf("读取的字符串是:%s\n", str);
输入:hello 输出:hello
输入:hello world 输出:hello
这时你会发现,怎么输出的结果和我预想的不一样?
使用·scanf("%s", str);
读取字符串时,%s
格式说明符会在遇到空白字符(如空格、制表符、换行符等)时停止读取。
当输入"hello world
"时,scanf
读到"hello
"后面的空格就停止了,只将"hello
"存储到了字符数组str
中。
后续使用printf
输出str
时,自然就只输出了"hello
"。
而剩下来的字符串“world”,就还会在缓冲区中,此时若再使用scanf读取一次,就可以将“world”读取出来了;如:
char str1[50];
char str2[50];
printf("请输入一个字符串:");
scanf("%s", str1);
scanf("%s", str2);
printf("读取的字符串是s1:%s\n", str1);
printf("读取的字符串是s2:%s\n", str2);
所以说我们在使用scanf函数时需要注意很多:
比如:
遇空白字符停止读取
%s格式说明符在读取字符串时,一旦碰到空白字符(像空格、制表符、换行符)就会停止。所以,scanf无法读取包含空格的完整句子。
缓冲区溢出风险
scanf函数使用%s格式说明符读取字符串时,不会对输入字符串的长度进行检查,要是输入的字符串长度超出了目标数组的容量,就会引发缓冲区溢出问题,这可能导致程序崩溃或者产生安全漏洞。
格式说明符与变量类型匹配
scanf 函数中的格式说明符必须与要存储输入数据的变量类型相匹配,否则会导致未定义行为或数据读取错误。
匹配失败和错误处理
scanf 函数在匹配失败时会停止读取输入,并返回成功匹配并赋值的输入项的数量。可以根据返回值来判断输入是否成功。
字符输入的空格处理
当使用 %c 格式说明符读取字符时,scanf 不会自动跳过空白字符(空格、制表符、换行符)。如果之前的输入操作留下了空白字符在输入缓冲区,这些空白字符可能会被 %c 读取。例如:
int main() {
int num;
char str[100];
printf("请输入一个整数: ");
scanf("%d", &num);
char ch = 0;
printf("请输入一个字符: ");
scanf("%c", &ch);
printf("你输入的整数是: %d\n", num);
printf("你输入的字符是: %c\n", ch);
return 0;
}
这段代码就是典型的不注意缓冲区,
当你使用scanf("%d", &num)
; 读取一个整数后,按下回车键,回车键产生的换行符\n
会留在输入缓冲区中。接着执行scanf("%c", &ch)
; 时,%c
格式说明符不会自动跳过空白字符(包括换行符),所以它会直接读取留在缓冲区中的换行符并赋值给ch
,而不会等待你输入新的字符。
解决方法:
1:使用 getchar() 消耗换行符
2:在 %c 前加空格
printf-格式化输出函数
函数原型:
int printf(const char *format, ...);
printf
函数功能将由format
指向的C字符串写入标准输出(stdout
)。如果format
包含格式说明符(以%
开头的子序列),则格式化format
之后的其他参数并将其插入到结果字符串中,以替换它们各自的说明符。
使用通俗易懂的话就是说:它的作用是按照指定格式将数据输出到标准输出设备(通常是显示器)
注意:stdout
会将数据输出到终端(如命令行窗口
),不过,在 Unix/Linux
系统中,你可以借助重定向操作符(如>
)把 stdout
的输出重定向到文件或者其他程序。
参数解释:
format
:这是一个字符串,它规定了输出的格式,并且可以包含普通字符和格式说明符。
普通字符:这些字符会原样输出。格式说明符
:以 % 开头,后面跟着一个或多个字符,用于指定要输出的数据的类型和格式。...
:这是可变参数列表,它包含了要按照 format 字符串中的格式说明符进行输出的数据。
格式说明符的原型为%[flags][width].[precision][length]specifier
,其中最后的specifier
字符最为关键,决定了对应参数的类型和解释方式。
格式说明符具体类型
整数类:
d / i | 输出有符号十进制整数 |
---|---|
u | 输出无符号十进制整数 |
o | 输出无符号八进制数 |
x / X | 分别以小写、大写形式输出无符号十六进制整数 |
浮点数类:
f / F | 分别以小写、大写形式输出十进制浮点数 |
---|---|
e / E | 分别以小写、大写的科学计数法(尾数 / 指数)形式输出 |
g / G | 根据数值情况,选择最短表示形式(%e或%f,%E或%F)输出 |
a / A | 分别以小写、大写形式输出十六进制浮点数 |
其他类型:
c | 输出单个字符 |
---|---|
s | 输出字符字符串 |
p | 输出指针地址 |
n | 不实际打印,需传入指向有符号整数的指针,将已写入的字符数存储到该指针指向的位置 |
% | %%会向输出流写入一个%字符 |
子说明符(修饰符)用法
flags(标志):
- | 在指定字段宽度内左对齐,默认是右对齐 |
---|---|
+ | 强制在结果前加上正负号,正数也加 |
(space) 空格 | 正数前加空格,负数前加负号 |
# | 与某些格式说明符(如a、A、e、E、f、F、g、G、o、x、X )一起使用时,强制输出包含小数点等特定格式 |
0 | 在填充时用 0 而不是空格 |
width(宽度):
表示最少打印的字符数。若要打印的值短于该数,结果用空格填充;若长于该数,结果不截断。
可以用*
指定,此时需要在格式化字符串前额外提供一个整数参数来指定宽度。
.precision(精度):
对于整数格式说明符(d、i、o、u、x、X
),指定最少打印的数字位数,不足则用前导 0 填充,值不会截断。
对于a、A、e、E、f、F
说明符,指定小数点后的位数,默认是 6 位。
对于g、G
说明符,指定最多打印的有效数字位数。
对于s
说明符,指定最多打印的字符数,默认打印到字符串末尾。
同样可以用*
指定,需在格式化字符串前额外提供一个整数参数来指定精度。
格式说明符示例举例说明:
int main() {
int int_num = 123;
unsigned int uint_num = 456;
float float_num = 3.1415926;
char char_num = 'A';
char str[] = "Hello";
int* ptr = &int_num;
// 整数输出
printf("有符号十进制整数: %d\n", int_num);
printf("无符号十进制整数: %u\n", uint_num);
printf("无符号八进制数: %o\n", uint_num);
printf("小写十六进制整数: %x\n", uint_num);
printf("大写十六进制整数: %X\n", uint_num);
// 浮点数输出
printf("小写十进制浮点数: %f\n", float_num);
printf("大写十进制浮点数: %F\n", float_num);
printf("小写科学计数法: %e\n", float_num);
printf("大写科学计数法: %E\n", float_num);
printf("最短表示形式(小写): %g\n", float_num);
printf("最短表示形式(大写): %G\n", float_num);
printf("小写十六进制浮点数: %a\n", float_num);
printf("大写十六进制浮点数: %A\n", float_num);
// 其他类型输出
printf("单个字符: %c\n", char_num);
printf("字符串: %s\n", str);
printf("指针地址: %p\n", (void*)ptr);
return 0;
}
对于flags示例:
#include <stdio.h>
int main() {
int num = 123;
float f_num = 3.14;
// 左对齐
printf("左对齐示例: |%-10d|\n", num);
// 强制显示正负号
printf("强制显示符号示例: |%+d|\n", num);
// 正数前加空格
printf("正数加空格示例: |% d|\n", num);
// 特定格式输出(以十六进制为例)
printf("特定格式示例: |%#x|\n", num);
// 用0填充
printf("0填充示例: |%05d|\n", num);
// 浮点数相关
printf("浮点数0填充示例: |%08.2f|\n", f_num);
return 0;
}
对width(宽度)实例:
int main() {
int num = 123;
char str[] = "abc";
// 指定宽度
printf("指定宽度示例(整数): |%5d|\n", num);
printf("指定宽度示例(字符串): |%10s|\n", str);
// 使用*指定宽度
int width = 8;
printf("使用*指定宽度示例: |%*d|\n", width, num);
return 0;
}
.[precision](精度实例):
int main() {
int int_num = 12;
float float_num = 3.1415926;
char str[] = "HelloWorld";
// 整数精度
printf("整数精度示例: |%05d|\n", int_num);
// 浮点数精度
printf("浮点数精度示例: |%.2f|\n", float_num);
// 字符串精度
printf("字符串精度示例: |%.5s|\n", str);
// 使用*指定精度
int precision = 3;
printf("使用*指定精度示例: |%.*s|\n", precision, str);
return 0;
}
在使用printf函数时,还需注意:
格式说明符与参数的匹配
类型匹配:printf函数的格式说明符必须与对应的参数类型相匹配。如果不匹配,可能会导致未定义行为,输出结果可能是错误的,甚至可能引发程序崩溃;
缓冲区问题
缓冲区刷新:printf 函数通常会使用缓冲区来提高性能。在某些情况下,输出可能不会立即显示在屏幕上,而是先存储在缓冲区中。可以使用 fflush(stdout); 函数来强制刷新标准输出缓冲区,确保输出立即显示。例如:
性能问题
频繁调用:频繁调用 printf 函数会影响程序的性能,因为每次调用都涉及到系统调用和缓冲区管理。如果需要输出大量数据,建议先将数据存储在一个字符串中,然后一次性输出。
总结
到了最后:感谢支持
------------对过程全力以赴,对结果淡然处之