1. 输入输出的概念

  1. 所谓输入输出是以计算机为主体而言的。标准的输出指屏幕终端,标准的输入指键盘,其它的输入输出可以是文件、打印机、扫描仪等设备。
  2. 在 C 语言中,所有的数据输入输出都是由库函数完成的,因此都是函数语句。在使用 C 语言库函数时,要用预处理指令#include将有关“头文件”包括到源文件中
  3. 使用标准输入输出库函数时要用到stdio.h文件,因此源文件开头应有以下预处理指令#include <stdio.h>,stdio是standard input & outupt的意思

2、单字符:getchar/putchar

getchar是头文件“stdio.h”内处理键盘输入函数,从键盘上输入一个字符,通常再将其赋予一个字符变量,char 字符变量= getchar(); :::info int getchar(); ::: 注意:getchar函数只能接受单个字符,输入数字也按字符处理。输入多于一个字符时,只接收第一个字符。

putchar是头文件“stdio.h”内处理字符输出函数,在屏幕终端上输出单个字符。 :::info int putchar(字符变量); ::: 返回:输出的字符
注意:对控制字符则执行控制功能,不在屏幕上显示。

  1. #include <stdio.h>
  2. int main() {
  3. /* getchar函数 */
  4. printf("Please input a character: ");
  5. char ch = getchar(); // 从缓冲区内读取一个字符
  6. /* putchar函数 */
  7. putchar('\n'); // 先输出一个换行
  8. putchar(ch); // 输出用户输入内容
  9. putchar(getchar()); // 读取缓冲区内的\n
  10. return 0;
  11. }

编译运行: :::success b07@SB:~/c/chapter4$ gcc -Wall src/getchar_putchar.c -o bin/getchar_putchar
b07@SB:~/c/chapter4$ ./bin/getchar_putchar
Please input a character: 1

1
b07@SB:~/c/chapter4$ ::: 注意回车输入后缓冲区内是1\n,第一个getchar()函数取1,第二个getchar()函数取\n

3. 格式化输出printf函数

printf函数称为格式输出函数,其关键字最末一个字母f即为“格式”(format)之意。其功能是按用户指定的格式,把指定的数据显示到显示器屏幕上。

printf函数是一个标准库函数,其函数原型声明在头文件“stdio.h”中。使用语法 :::info printf(“格式控制字符串”, 输出列表); :::

  • 格式控制字符串用于指定输出格式,可由格式字符串非格式字符串两种组成。
  • 格式控制字符串是以%开头的字符串,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。
  • 非格式字符串在输出时原样照印,在显示中起提示作用。输出列表中给出了各个输出项(表达式),要求格式控制字符串和各输出项在数量和类型上应该一一对应。

printf函数格式控制字符串的一般形式 :::info %[标志][输出最小宽度][精度][长度]类型 ::: 类型字符用以表示输出数据的类型,其格式字符和意义如下表。

格式字符 意义
d 以十进制形式输出带符号整数(正数不输出符号)
c 输出单个字符
s 输出字符串
o/O 以八进制形式输出无符号整数(不输出前缀0)
x/X 以十六进制形式输出无符号整数(不输出前缀0x)
u 以十进制形式输出无符号整数
f 以小数形式输出单、双精度实数
e/E 以指数形式输出单、双精度实数
g,G 以%f或%e中较短的输出宽度输出单、双精度实数

标志:标志字符为-, +, #, 空格四种,其意义如下表。

格式字符 意义
- 结果左对齐,右边填空格
+ 输出符号(正号或负号)
空格 输出值为正时冠以空格,为负时冠以负号
# 对c,s,d,u类无影响;
对o/O类, 在输出时加前缀0;
对x/X类,在输出时加前缀0x;
对 e,g,f 类当结果有小数时才给出小数点
x/X 以十六进制形式输出无符号整数(不输出前缀0x)
u 以十进制形式输出无符号整数
f 以小数形式输出单、双精度实数
e/E 以指数形式输出单、双精度实数
g,G 以%f或%e中较短的输出宽度输出单、双精度实数

输出最小宽度:用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。
精度:精度格式符以“.”开头,后跟十进制整数。如果输出数字,则表示小数的位数;如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。
长度:格式符为h,l两种,h表示按短整型量输出,l按长整型量输出。

  1. #include <stdio.h>
  2. int main() {
  3. /* printf函数 */
  4. int x = 10;
  5. float y = 20.555f;
  6. double z = 33.343;
  7. long double w = 44.322l;
  8. char ch = 'a';
  9. char str[] = "hello world";
  10. printf("%05d %u %o %#o %#X\n",
  11. 6553, x, x, x, x);
  12. printf("%.2f %e %E\n", y, y, y);
  13. printf("%c\n", ch);
  14. printf("%30s\n", str); // 长度大于str串,前面默认补空格
  15. printf("%d%%\n", x); // 输出百分号
  16. printf("%d\n", 20, 30); // 输出项过多
  17. printf("z:%f\n", z); // 使用%f或%lf都可以,但是scanf必须%lf
  18. printf("w:%Lf\n", w); // 输出长双精度
  19. return 0;
  20. }

编译运行: :::warning b07@SB:~/c/chapter4$ gcc -Wall src/printf.c -o bin/printf
src/printf.c: In function ‘main’:
src/printf.c:17:9: warning: too many arguments for format [-Wformat-extra-args]
printf(“%d\n”, 20, 30); // 输出项过多
^~
b07@SB:~/c/chapter4$ ./bin/printf
06553 10 12 012 0XA
20.56 2.055500e+01 2.055500E+01
a
hello world
10%
20
z:33.343000
w:44.322000 :::

4. 格式化输入scanf函数

scanf函数是一个标准库函数,其函数原型声明在头文件“stdio.h”中。scanf函数称为格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中。
scanf函数的使用语法 :::info scanf(“格式控制字符串”,地址列表); :::

  • 格式控制字符串的作用与printf函数相同,但不能显示非格式字符串,也就是不能显示提示字符串。
  • 地址列表中给出各变量的地址,地址是由地址运算符“&”后跟变量名组成的。

scanf函数格式字符串的一般形式 :::info %[输入数据宽度][长度]类型 ::: 类型:表示输入数据的类型,其格式符和意义如下表。

格式字符 字符意义
d 输入十进制整数,碰到空白符可跳过,遇到非数值则终止
o 输入八进制整数,碰到空白符可跳过,遇到非数值则终止
x 输入十六进制整数,碰到空白符可跳过,遇到非数值则终止
u 输入无符号十进制整数,碰到空白符可跳过,遇到非数值则终止
f或e 输入实型数(用小数形式或指数形式)
c 输入单个字符
s 输入字符串(遇到空白符则停止,不能用于输入含有空白符的串

输入数据宽度:用十进制整数指定输入的宽度,若实际输入宽度超过指定宽度,则多余部分会被截掉。
长度:长度格式符为lh,表示输入长整型数据(如%ld)和双精度浮点数(如%lf)。h表示输入短整型数据。 :::danger scanf函数非常多的坑。

  1. 格式控制字符和格式字符必须对应!不对应则函数直接终止,可能造成输入数据不全,但是它不报错!
  2. 缓冲区问题:在进行scanf("%d", &x);后进行scanf("%c", &c)c变量一定是'\n'。因为scanf函数只从缓存区内拿走符合要求的字符,遇到不符合字符就终止函数,并且这个非法字符依然还留在缓冲区内。因此连续scanf函数需要注意缓存区问题!
  3. 空白格式字符可以跳过多余的空白符,但是至少要输入一个空白符。 ::: ```c

    include

int main() { / scanf函数%d坑 / int x, y, cnt; printf(“Please input x and y(eg: 2 3): “); cnt = scanf(“%d%d”, &x, &y); // %d会自动跳过空格 printf(“You inputed %d number: x = %d, y = %d\n”, cnt, x, y);

  1. printf("Please input x and y(eg: 2,3): ");
  2. cnt = scanf("%d,%d", &x, &y); // 两数字直接必须输入','
  3. printf("You inputed %d number: x = %d, y = %d\n", cnt, x, y);
  4. return 0;

}

  1. 编译:
  2. :::success
  3. b07@SB:~/c/chapter4$ gcc -Wall src/scanf_digit.c -o bin/scanf_digit
  4. :::
  5. **缓冲区问题**:
  6. :::success
  7. b07@SB:~/c/chapter4$ ./bin/scanf_digit<br />Please input x and y(eg: 2 3): i3 5<br />You inputed 0 number: x = 32668, y = -503466928<br />Please input x and y(eg: 2,3): You inputed 0 number: x = 32668, y = -503466928
  8. :::
  9. 第一次并按下回车后,缓冲区内数据为`^I3\n`,由于第一个`scanf("%d%d", &x, &y)`函数执行失败,缓冲区依旧没变,第二个`scanf("%d,%d", &x, &y)`依旧失败。
  10. **格式字符不对应问题**:
  11. :::success
  12. b07@SB:~/c/chapter4$ ./bin/scanf_digit<br />Please input x and y(eg: 2 3): 3<br />6<br />You inputed 2 number: x = 3, y = 6<br />Please input x and y(eg: 2,3): 66 8<br />You inputed 1 number: x = 66, y = 6
  13. :::
  14. 第一次输入有前置空白和中间穿插换行符`\n`以及`6`的最后还有一个`\n`字符一并输入缓冲区内,第一个`scanf("%d%d", &x, &y)`函数将当前缓冲区内除了最后一个`\n`字符外没提取出来后,其余都提取出来了,因为接收到两个整数。
  15. 第二次输入前缓冲区内仍有`'\n'`字符,因此第二次输入后缓冲区内变为`'\n','66','\012','\012','\012','\012','8','\n'`,第二个`scanf("%d,%d", &x, &y)`只能读入`66`后,发现`'\012'`空格和格式字符`,`不对应,因此终止函数,缓冲区内只剩`'\012','\012','\012','\012','8','\n'`
  16. **字符串问题**:
  17. 1. 若实际输入字符数大于字符数组长度,则会发生溢出!
  18. 1. 若输入字符串含有空白符,`scanf`停止读取!
  19. ```c
  20. #include <stdio.h>
  21. int main() {
  22. /* scanf函数%s坑 */
  23. int cnt;
  24. char s[8]; // 若想当作字符串,则最多输入7字符,最后一个为'\0'
  25. printf("Please input a string: ");
  26. cnt = scanf("%s", s); // 数组名就是地址
  27. // %s只是如何解释,遇到'\0'才停止
  28. printf("You inputed %d string: %s\n", cnt, s);
  29. return 0;
  30. }

编译: :::success b07@SB:~/c/chapter4$ gcc -Wall src/scanf_string.c -o bin/scanf_string ::: 输入字符过多: :::danger b07@SB:~/c/chapter4$ ./bin/scanf_string
Please input a string: 123456789
You inputed 1 string: 123456789
stack smashing detected : terminated
Aborted (core dumped) ::: 因scanf函数读取输入字符123456789\n,直到最后一个\n才结束,导致向原数组中非法地址进行输入。输出时%s无法在s数组合法地址中找到'\0'结束输出,因此造成非法访问。
输入含有空白字符: :::success b07@SB:~/c/chapter4$ ./bin/scanf_string
Please input a string: 12 34
You inputed 1 string: 12 ::: 两个数字之间是\t分割,导致只读取到12字符。

可以使用fget(char *src, size(字节), stdin)解决实际输入大于存储空间和接受含空白字符的输入问题

  1. #include <stdio.h>
  2. int main() {
  3. /* fgets字符串输入函数 */
  4. char s[8]; // 若想当作字符串,则最多输入7字符,最后一个为'\0'
  5. printf("Please input a string: ");
  6. // 从标准输入中读取 sizeof(s) - 1 个字符,因为最后fgets帮追加'\0'字符
  7. fgets(s, sizeof(s), stdin);
  8. printf("You inputed string: %s\n", s);
  9. return 0;
  10. }

编译运行: :::success b07@SB:~/c/chapter4$ gcc -Wall src/fgets.c -o bin/fgets
b07@SB:~/c/chapter4$ ./bin/fgets
Please input a string: 123456789
You inputed string: 1234567
b07@SB:~/c/chapter4$ ./bin/fgets
Please input a string: 12 45 78
You inputed string: 12 45 7 :::

scanf函数的其它使用注意点

  1. scanf函数中没有精度控制,如:scanf("%5.2f", &a)是非法的。不能企图用此语句输入小数为2位的实数。
  2. scanf中要求给出变量地址,如给出变量名则会出错。如scanf("%d", a);是非法的,应改为scnaf("%d", &a);才是合法的。
  3. 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔则可用空格,TAB或回车作间隔。C编译在碰到非法数据(如对“%d”输入“12A”时,A即为非法数据)时即认为该数据结束。
  4. 在输入字符数据时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。
  5. 如果格式控制串中有非格式字符则输入时也要输入该非格式字符
  6. 如输入的数据与输出的类型不一致时,虽然编译能够通过,但结果将不正确。(格式字符只是告知编译器以什么形式输出变量,不关乎变量具体类型)

5. 格式化控制字符*的特殊用法

在printf函数中可以用来控制输入浮点数的有效位数。在scanf函数中可以用来跳过前面格式控制字符。

  1. #include <stdio.h>
  2. int main() {
  3. /* *特殊用法 */
  4. char sex = 'F'; // 默认女
  5. double salary;
  6. char name[8]; // 最多读取7个字符
  7. printf("Please input name, sex and salary: ");
  8. int cnt = scanf("%7s %*c %lf", name, &salary);
  9. printf("Accept %d variable\n", cnt);
  10. // 宽度,小数点(四舍五入),变量
  11. printf("%s,%c,%*.*f\n", name, sex, 10, 2, salary);
  12. return 0;
  13. }

编译运行: :::success b07@SB:~/c/chapter4$ ./bin/asterisk
Please input name, sex and salary: 1234567 M 1999.5653
Accept 2 variable
1234567,F, 1999.57 ::: 虽然字符数组name大小为8,但%7s实际只要7个字符,并额外追加'\0'进字符数组中。其中格式字符串中空格字符可以跳过多余空格,其中%*c当占位符M字符

name实际输入不能太多,过多造成缓冲区内前面有多余字符,导致salary无法准确读入。 :::success b07@SB:~/c/chapter4$ ./bin/asterisk
Please input name, sex and salary: 12345678 M 1999.5653
Accept 1 variable
1234567,F, 0.00 ::: 当金星%lf时,因为缓存区内为'\012','\012','M', '1', '9', '9', '9', '3', '.', '5', '6', '5', '3',虽然前面两个空格都被格式空格字符给消耗掉,但是M%lf不匹配,因函数停止,只读取一个值。