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 上述占位符
其中:
- 修饰 是如下字符之一: - -:指明输出左对齐(见下)
- +:强制输出正号
- :强制正数多输出一个空格(从而与负号对齐)
- #:调整输出格式:- 对于整型,输出 00x0X前缀;
- 对于浮点类型,强制输出小数点
 
- 对于整型,输出 
- 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);可用于清理缓冲区,但这实际上是未定义行为。