C 输入输出

在 C++ 中,我们用 cincout 来控制输入输出;然而在 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 后面的第二、第三、第四个参数。这时,如果 a1b2 ,就会输出 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 接受以下占位符:

占位符对应参数类型
%dint*
%hdshort*
%ldlong*
%lld long long*
%uunsigned int*
%huunsigned short*
%luunsigned long*
%llu unsigned long long*
%ffloat*
%lfdouble*
%Lflong double*
%cchar*
%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 错误,则返回 EOFEOF 是一个定义于头文件 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); 可用于清理缓冲区,但这实际上是未定义行为。

最近更新:
代码未运行