printf 与 scanf


学习 C 的过程中,通常是从控制台,也就是文本模式下开始,为了与程序互动,在控制台下输出程序执行结果,或是从控制台获取使用者的输入数据是基本需求,在 C 中标准输入输出是由 stdio.h 提供,这也就是为何要在程序的一开头总是加上:

#include <stdio.h>

将消息输出至控制台,称之为标准输出(Stand output),C 借由printf()将消息输出至控制台,至今已经看过几个printf()函数的应用了,基本上,printf()就是将指定的文本、数值等输出至屏幕上,并且执行过后会返回所输出的字符数,例如:

#include <stdio.h>

int main(void) {
    int count = printf("This is a test!\n");
    printf("%d\n", count);

    return 0;
}

"This is a test!\n"当中包括换行字符,共有 16 个字符,因此count的值会是 16,显示结果如下:

This is a test!
16

标准输出可以被重新导向至文件,可以在执行程序时使用>>将输出结果导向至指定文件,例如(假设编译后的可执行文件为 main):

main >> result.txt

如果程序的目的是显示"Hello! World!",则上面的执行会将结果导向至 result.txt,而不会在屏幕上显示"Hello! World!",result.txt中将会有输出结果 Hello! World!。

要重新导向标准输出是用>,标准输入则是<,而>>除了重导标准输出,还有附加的功能,也就是会把输出附加到被导向的目标文件后头,如果目标文件本来不存在,那么效果就和>一样。

如果在使用printf时要指定整数、浮点数、字符等进行显示,要配合格式指定字(format specifier),以下列出几个可用的格式指定码:

  • %c:以字符方式输出
  • %d:10 进制整数输出
  • %o:以 8 进制整数方式输出
  • %u:无符号整数输出
  • %x、%X:将整数以 16 进制方式输出
  • %f:浮点数输出
  • %e、%E:使用科学计数显示浮点数
  • %g、%G:浮点数输出,取 %f 或 %e(%f 或 %E),看哪个表示精简
  • %%:显示 %
  • %s:字符串输出
  • %lu:long unsigned 类型的整数
  • %p:指针类型

基本上,要显示的是什么数据类型,就必须搭配对应数据类型的格式指定字,但%d若用来输出某个字符,将显示其整数编码值,若%c用来显示某个整数,将显示该整数对应编码的字符,一个使用的范例如下所示:

#include <stdio.h>

int main(void) {
    printf("显示字符 %c\n", 'A');
    printf("显示字符编码 %d\n", 'A');
    printf("显示字符编码 %c\n", 65);    
    printf("显示十进制整数 %d\n", 15);
    printf("显示八进制整数 %o\n", 15);
    printf("显示十六进制整数 %X\n", 15);
    printf("显示十六进制整数 %x\n", 15);    
    printf("显示科学计数 %E\n", 0.001234);    
    printf("显示科学计数 %e\n", 0.001234);    

    return 0;
}

显示结果如下所示:

显示字符 A
显示字符编码 65
显示字符编码 A
显示十进制整数 15
显示八进制整数 17
显示十六进制整数 F
显示十六进制整数 f
显示科学计数 1.234000E-03
显示科学计数 1.234000e-03

可以在输出浮点数时指定精度,例如若为浮点数:

printf("example:%.2f\n", 19.234);

.2指定小数点后取两位,执行结果会输出:

example:19.23

也可以指定输出时,至少要预留的字符宽度,无论是数值或字符串,例如:

printf("example:%6.2f\n", 19.234);

整数 6 表示预留 6 个字符宽度,由于预留了 6 个字符宽度,不足的部份要由空白字符补上,执行结果会输出如下(19.23只占五个字符,所以补上一个空白在前端):

example: 19.23

若在%之后指定负号,例如%-6.2f,表示靠左对齐,没有指定则靠右对齐,例如:

#include <stdio.h>

int main(void) {
    printf("example:%6.2f\n", 19.234);
    printf("example:%-6.2f\n", 19.234);

    return 0;
}

显示结果如下:

example: 19.23
example:19.23

若事先无法决定字符宽度,则可以使用*,例如:

#include <stdio.h>

int main(void) {
    printf("%*d\n", 1, 1);
    printf("%*d\n", 2, 1);
    printf("%*d\n", 3, 1);

    return 0;
}

printf()*将被之后的第一个实参所取代,所以第一个printf()将预留一个字符宽度,第二个预留两个字符宽度,第三个预留三个,显示结果如下:

1
 1
  1

若是字符串的话,也可以使用%.*s,这表示要显示字符串中 0 到多个字符,实际的字符数可以在第二个参数指定,例如:

#include <stdio.h>

int main(void) {
    printf("%.*s\n", 3, "Justin");
    printf("%.*s\n", 5, "Justin");
    printf("%.*s\n", 7, "Justin");
}

执行结果如下:

Jus
Justi
Justin

如果打算获取使用者的输入,可以使用标准输入的scanf函数,并搭配格式指定字与&取址运算符指定给变量,例如:

#include <stdio.h>

int main(void) {
    int input;

    printf("请输入数字:");
    scanf("%d", &input);

    printf("你输入的数字:%d\n", input);

    return 0;
}

在程序中先定义了一个整数变量input,使用scanf()函数时,若输入的数值为整数,则使用格式指定字%d,若输入的是其他数据类型,则必须使用对应的格式指定字,如果是double,特别注意要使用%lf来指定。

你必须告知程序存储数据的变量地址,为此,必须使用&取址运算符,这会将变量的内存地址取出,则输入的数值就知道变量的内存地址并存储之(但字符数组名称本身就有地址信息,故不用&来取址,之后说明数组时会再看到)。

执行结果:

请输入数字:10
你输入的数字:10

scanf在接受输入时,可以接受多个值,也可以指定输入的格式,例如:

#include <stdio.h>

int main(void) {
    int number1, number2;

    printf("请输入两个数字,中间使用空白区隔):");
    scanf("%d %d", &number1, &number2);
    printf("你输入的数字:%d %d\n", number1, number2);

    printf("请再输入两个数字,中间使用-号区隔):");
    scanf("%d-%d", &number1, &number2);
    printf("你输入的数字:%d-%d\n", number1, number2);

    return 0;
}

在第一个scanf中,指定了使用空白来区隔两个输入,而第二个scanf中,指定了使用-来区隔两个输入,一个执行与输入的结果如下所示:

请输入两个数字,中间使用空白区隔):10 20
你输入的数字:10 20
请再输入两个数字,中间使用-号区隔):30-40
你输入的数字:30-40

scanf还可以指定可接受的字符集合,例如若只想接受 1 到 5 的字符,则可以如下:

#include <stdio.h>

int main(void) {
    char buf[50];

    printf("请输入 1 到 5 的字符:");
    scanf("%[1-5]", buf);
    printf("输入的字符为 %s\n", buf);

    fflush(stdin); // 清除输入缓冲区

    printf("请输入 XYZ 任一字符:");
    scanf("%[XYZ]", buf);
    printf("输入的字符为 %s\n", buf);

    return 0;
}

上面的str定义,为 C 语言中的字符数组与字符串scanf函数连续读入符合集合的字符并放到字符数组中,直到读到不符合的字符为止,剩下的字符仍会存在输入缓冲区中,可以直接使用fflush(stdin)清除输入缓冲区,以利进行下一次重新输入,scanf函数若成功,则返回成功填写的格式指定字数目,否则直接略过输入而返回 0,在学会流程控制语法之后,可以据以判断该作什么处理(像是使用if来针对不接受输入时的处理)。

执行结果如下:

请输入 1 到 5 的字符:146731
输入的字符为 14
请输入 XYZ 任一字符:XYZXDX
输入的字符为 XYZX

你可以使用%[0-9]指定获取 0 至 9 的字符,使用%[A-z]指定获取 ASCII 表中的 A 到 z 的字符,如果要排除的话,则使用^,例如%[^ABC]可获取ABC字符以外的所有字符。

在上面的例子中,buf的长度为 50,输入的字符最多只能是 49 个(在〈字符数组与字符串〉中会看到,字符串是字符数组,而且最后一个元素必须是空字符),scanf收到的字符若超出buf,称为缓冲区溢出(buffer overflow),会发生不可预期的结果,甚至成为安全弱点。

预防的方法之一是,限定scanf每次执行可以接受的最大字符数,例如:

#include <stdio.h>

int main(void) {
    char buf[10];

    printf("请输入字符串:");
    scanf("%9s", buf);
    printf("输入的字符串:%s\n", buf);

    return 0;
}

在上例中,若输入的字符超过 9,buf也只会收到 9 个字符加上一个空字符,超过的字符会留在输入缓冲区。

另一个预防输入超过buf长度的方式,是使用fgets,这可以参考〈putchar、getchar、puts、fgets〉的说明。


展开阅读全文