C 输入输出
在 C++ 中,我们用 cin
和 cout
来控制输入输出;然而在 C 中,我们需要用两个函数
int scanf(const char*, ...);
int printf(const char*, ...);
来做这件事情。比如
int a{42};
cin >> a;
cout << a;
需要替换成
int a = 42;
scanf("%d", &a);
printf("%d", a);
printf
输出:首先,我们来看一下 printf
输出的用法。printf
这个函数它的第一个参数是一个字符串(实际上是指针)。在默认情况下,程序会输出这个字符串的内容,比如:
printf("Hello, world!");
就会输出 Hello, world!
。如果你要输出一个数,你就需要在输出数的位置上加上占位符(Placeholder)。比如,我想输出 Hello 42
,就需要这样做:
printf("Hello %d", 42);
其中,%d
表示这里应该输出一个整数。输出哪个整数呢?这个值应当在后面的第二参数提供。类似地,你可以输出两个、三个整数:
printf("The sum of %d and %d is %d", a, b, a + b);
这里面提供了三个 %d
占位符,它们分别对应 printf
后面的第二、第三、第四个参数。这时,如果 a
为 1
,b
为 2
,就会输出 The sum of 1 and 2 is 3
。
C 语言没有判断参数类型的能力,所以你需要通过占位符来指定输出内容的类型。比如,刚刚 %d
表示这里应当输出 int
类型的值。类似地,%f
表示输出 double
类型的值。
常用类型的占位符分别是:
类型 | 占位符 |
---|---|
int | %d |
short | %hd |
long | %ld |
long long ※ | %lld |
unsigned int | %u |
unsigned short | %hu |
unsigned long | %lu |
unsigned long long ※ | %llu |
double | %f |
long double | %Lf |
字符类型 | %c |
指针类型(void* ) | %p |
字符串(char* ) | %s |
※ C99 起
除此之外,还有这些占位符:
占位符 | 含义 |
---|---|
%o | 以八进制输出 unsigned int ,类似地有 %ho %lo %llo (含义同上) |
%x | 以十六进制输出 unsigned int ,类似地有 %hx %lx %llx |
%X | 同上;但字母大写 |
%e | 以科学记数法输出 double ,类似地有 %Le |
%E | 同上;但字母大写 |
%a ※ | 以十六进制输出 double ,类似地有 %La |
%A ※ | 同上;但字母大写 |
%g | 取 %f 和 %e 中较短者输出 |
%G | 取 %f 和 %E 中较短者输出 |
%% | 输出 % |
※ C99 起
除此之外,在 %
后面可以可选地依次加上 修饰、宽度 和 精度 的限制,即。
%修饰 宽度 精度 h/l/ll/L 上述占位符
其中:
- 修饰 是如下字符之一:
-
:指明输出左对齐(见下)+
:强制输出正号:强制正数多输出一个空格(从而与负号对齐)
#
:调整输出格式:- 对于整型,输出
0
0x
0X
前缀; - 对于浮点类型,强制输出小数点
- 对于整型,输出
0
:用0
而非作为补齐字符
- 宽度 是如下之一:
- 整数:指定输出的最小宽度;默认左侧补齐空格(此行为可通过 修饰 改变)
*
字符:宽度 由该占位符对应参数前的一个额外int
参数提供
- 精度 是如下之一:
.整数
:- 对于整型,指定最小位数为
整数
; - 对于浮点类型,指定小数点后位数为
整数
; - 对于
%g
或%G
等,指定最大有效数字个数为整数
; - 对于字符串,指定最大输出字符个数
- 对于整型,指定最小位数为
.*
:精度 由该占位符对应参数前的一个额外int
参数提供
例:
#include <stdio.h>
int main(void) {
printf("Strings:\n");
const char* s = "Hello";
printf(".%10s.\n.%-10s.\n.%*s.\n", s, s, 10, s);
printf("Characters: %c %%\n", 65); /* 65: 'A' */
printf("Integers\n");
printf("Decimal: %i %d %.6i %i %.0i %+i %u\n", 1, 2, 3, 0, 0, 4, -1);
printf("Hexadecimal: %x %x %X %#x\n", 5, 10, 10, 6);
printf("Octal: %o %#o %#o\n", 10, 10, 4);
printf("Floating point\n");
printf("Rounding: %f %.0f %.32f\n", 1.5, 1.5, 1.3);
printf("Padding: %05.2f %.2f %5.2f\n", 1.5, 1.5, 1.5);
printf("Scientific: %E %e\n", 1.5, 1.5);
printf("Hexadecimal: %a %A\n", 1.5, 1.5);
return 0;
}
输出:
Strings:
. Hello.
.Hello .
. Hello.
Characters: A %
Integers
Decimal: 1 2 000003 0 +4 4294967295
Hexadecimal: 5 a A 0x6
Octal: 12 012 04
Floating point
Rounding: 1.500000 2 1.30000000000000004440892098500626
Padding: 01.50 1.50 1.50
Scientific: 1.500000E+00 1.500000e+00
Hexadecimal: 0x1.8p+0 0X1.8P+0
scanf
输入:在日常的使用中,输入函数 scanf
相对比较容易。它同样使用占位符的语法,比如:
int a;
scanf("%d", &a);
中,%d
指明这里期望输入一个 int
类型的值。需要注意的是,scanf
后面的参数需要是地址,所以我们将变量 a
的地址 &a
放了进去。当执行 scanf
时,程序将读取到的值放在这个地址所指定的位置上。
需要取地址是因为 C 语言没有引用导致的。
类似地,如果要输入两个数,则
int a, b;
scanf("%d%d", &a, &b); /* 即 C++ 中的 cin>>a>>b; */
这样写。这时,它们处理输入的规则与 cin
是类似的:遇到空白字符停止此次读入并忽略它们,直至下一次读取。
和 printf
类似,scanf
接受以下占位符:
占位符 | 对应参数类型 |
---|---|
%d | int* |
%hd | short* |
%ld | long* |
%lld ※ | long long* |
%u | unsigned int* |
%hu | unsigned short* |
%lu | unsigned long* |
%llu ※ | unsigned long long* |
%f | float* |
%lf | double* |
%Lf | long double* |
%c | char* |
%s | 字符串(char* ,见下文) |
※ C99 起
其中,%s
较为特殊。它常见的用法是:
char a[30] = {};
scanf("%s", a);
注意这里无需取 a
的地址:因为数组 a
将转换为指向首元素 a[0]
的 char*
类型指针,这正是 %s
占位符所期望的。当使用 %s
时,程序会将读取到的字符挨个放在以参数地址为首的连续内存中,直至空白字符为止,并在最后追加 '\0'
。
类似地,scanf
还接受以下占位符:
占位符 | 含义 |
---|---|
%o | 期望八进制输入到 unsigned int* ,类似地有 %ho %lo %llo |
%x / %X | 期望十六进制输入到 unsigned int* ,类似地有 %hx %lx %llx |
%i | 通过前缀推测进制,并输入到 int* ,类似地有 %hi %li %lli |
scanf
还接受匹配语法。比如
scanf("%d,%d", &a, &b);
期望形如“逗号分隔的两个整数”这样的输入;若未能得到这样的输入,则导致输入失败(见下文)。
scanf
还提供了类正则的语法,提供形如%[0-9]
的占位符(实现定义)。scanf
也允许提供 宽度,若存在 宽度 则限定读入的最多字符数。若字符*
出现在scanf
占位符的%
后,则此占位符并不对应到任何参数。使用%%
占位符来匹配%
字符。
输入失败(选读)
若 scanf
未能按照占位符的规定读取到期望的值,亦或者没有匹配到期望的模式,抑或出现了 EOF,则导致输入失败。你可以通过 scanf
的返回值来判断输入失败:
scanf
在正常情况下返回成功写入的变量个数:比如scanf("%d%d", &a, &b)
在正常情况下返回2
;- 若某个模式匹配发生失败(比如期望整数但读取到非数字的字符),则后续的匹配输入不再进行,只返回当前成功匹配并写入的变量个数。
- 若发生了 EOF 错误,则返回
EOF
。EOF
是一个定义于头文件stdio.h
的宏,你可以把它理解成一个int
类型的值。
所以你可以这样判断输入是否出现问题:
int a;
// scanf("%d", &a) 在正常时返回 1,匹配失败时返回 0,EOF 时返回 EOF
if (scanf("%d", &a) != 1) {
printf("scanf failed");
}
/* 类似 C++ 中 if (cin >> a) ... */
解决的办法和 cin
类似,首先需清除失败状态,然后清空缓冲区。清除失败状态可通过调用 clearerr
,而清空缓冲区的一般方法是调用 getchar
耗尽:
int a;
if (scanf("%d", &a) != 1) {
printf("scanf failed");
clearerr(stdin); /* 清除失败状态(有时可省去) */
/* 以下是清空一行缓冲区的代码 */
int ch;
while((ch = getchar()) != '\n' && ch != EOF);
}
不过确实比较麻烦。
在 C 中,字符类型在更多场合下用
int
存储而非char
,原因就是需要足够大的空间提供给EOF
这个特殊字符。比如getchar
函数(作用类似 C++ 中cin.get()
)就返回int
类型。
有资料说
fflush(stdin);
可用于清理缓冲区,但这实际上是未定义行为。