1. 输入输出的概念
- 所谓输入输出是以计算机为主体而言的。标准的输出指屏幕终端,标准的输入指键盘,其它的输入输出可以是文件、打印机、扫描仪等设备。
- 在 C 语言中,所有的数据输入输出都是由库函数完成的,因此都是函数语句。在使用 C 语言库函数时,要用预处理指令
#include将有关“头文件”包括到源文件中 - 使用标准输入输出库函数时要用到
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(字符变量);
:::
返回:输出的字符
注意:对控制字符则执行控制功能,不在屏幕上显示。
#include <stdio.h>int main() {/* getchar函数 */printf("Please input a character: ");char ch = getchar(); // 从缓冲区内读取一个字符/* putchar函数 */putchar('\n'); // 先输出一个换行putchar(ch); // 输出用户输入内容putchar(getchar()); // 读取缓冲区内的\nreturn 0;}
编译运行:
:::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按长整型量输出。
#include <stdio.h>int main() {/* printf函数 */int x = 10;float y = 20.555f;double z = 33.343;long double w = 44.322l;char ch = 'a';char str[] = "hello world";printf("%05d %u %o %#o %#X\n",6553, x, x, x, x);printf("%.2f %e %E\n", y, y, y);printf("%c\n", ch);printf("%30s\n", str); // 长度大于str串,前面默认补空格printf("%d%%\n", x); // 输出百分号printf("%d\n", 20, 30); // 输出项过多printf("z:%f\n", z); // 使用%f或%lf都可以,但是scanf必须%lfprintf("w:%Lf\n", w); // 输出长双精度return 0;}
编译运行:
:::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 | 输入字符串(遇到空白符则停止,不能用于输入含有空白符的串) |
输入数据宽度:用十进制整数指定输入的宽度,若实际输入宽度超过指定宽度,则多余部分会被截掉。
长度:长度格式符为l和h,表示输入长整型数据(如%ld)和双精度浮点数(如%lf)。h表示输入短整型数据。
:::danger
scanf函数非常多的坑。
- 格式控制字符和格式字符必须对应!不对应则函数直接终止,可能造成输入数据不全,但是它不报错!
- 缓冲区问题:在进行
scanf("%d", &x);后进行scanf("%c", &c)时c变量一定是'\n'。因为scanf函数只从缓存区内拿走符合要求的字符,遇到不符合字符就终止函数,并且这个非法字符依然还留在缓冲区内。因此连续scanf函数需要注意缓存区问题! - 空白格式字符可以跳过多余的空白符,但是至少要输入一个空白符。
:::
```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);
printf("Please input x and y(eg: 2,3): ");cnt = scanf("%d,%d", &x, &y); // 两数字直接必须输入','printf("You inputed %d number: x = %d, y = %d\n", cnt, x, y);return 0;
}
编译::::successb07@SB:~/c/chapter4$ gcc -Wall src/scanf_digit.c -o bin/scanf_digit:::**缓冲区问题**::::successb07@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:::第一次并按下回车后,缓冲区内数据为`^I3\n`,由于第一个`scanf("%d%d", &x, &y)`函数执行失败,缓冲区依旧没变,第二个`scanf("%d,%d", &x, &y)`依旧失败。**格式字符不对应问题**::::successb07@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:::第一次输入有前置空白和中间穿插换行符`\n`以及`6`的最后还有一个`\n`字符一并输入缓冲区内,第一个`scanf("%d%d", &x, &y)`函数将当前缓冲区内除了最后一个`\n`字符外没提取出来后,其余都提取出来了,因为接收到两个整数。第二次输入前缓冲区内仍有`'\n'`字符,因此第二次输入后缓冲区内变为`'\n','66','\012','\012','\012','\012','8','\n'`,第二个`scanf("%d,%d", &x, &y)`只能读入`66`后,发现`'\012'`空格和格式字符`,`不对应,因此终止函数,缓冲区内只剩`'\012','\012','\012','\012','8','\n'`。**字符串问题**:1. 若实际输入字符数大于字符数组长度,则会发生溢出!1. 若输入字符串含有空白符,`scanf`停止读取!```c#include <stdio.h>int main() {/* scanf函数%s坑 */int cnt;char s[8]; // 若想当作字符串,则最多输入7字符,最后一个为'\0'printf("Please input a string: ");cnt = scanf("%s", s); // 数组名就是地址// %s只是如何解释,遇到'\0'才停止printf("You inputed %d string: %s\n", cnt, s);return 0;}
编译:
:::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 :
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)解决实际输入大于存储空间和接受含空白字符的输入问题
#include <stdio.h>int main() {/* fgets字符串输入函数 */char s[8]; // 若想当作字符串,则最多输入7字符,最后一个为'\0'printf("Please input a string: ");// 从标准输入中读取 sizeof(s) - 1 个字符,因为最后fgets帮追加'\0'字符fgets(s, sizeof(s), stdin);printf("You inputed string: %s\n", s);return 0;}
编译运行:
:::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函数的其它使用注意点
- scanf函数中没有精度控制,如:
scanf("%5.2f", &a)是非法的。不能企图用此语句输入小数为2位的实数。 - scanf中要求给出变量地址,如给出变量名则会出错。如
scanf("%d", a);是非法的,应改为scnaf("%d", &a);才是合法的。 - 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔则可用空格,TAB或回车作间隔。C编译在碰到非法数据(如对“%d”输入“12A”时,A即为非法数据)时即认为该数据结束。
- 在输入字符数据时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。
- 如果格式控制串中有非格式字符则输入时也要输入该非格式字符
- 如输入的数据与输出的类型不一致时,虽然编译能够通过,但结果将不正确。(格式字符只是告知编译器以什么形式输出变量,不关乎变量具体类型)
5. 格式化控制字符*的特殊用法
在printf函数中可以用来控制输入浮点数的有效位数。在scanf函数中可以用来跳过前面格式控制字符。
#include <stdio.h>int main() {/* *特殊用法 */char sex = 'F'; // 默认女double salary;char name[8]; // 最多读取7个字符printf("Please input name, sex and salary: ");int cnt = scanf("%7s %*c %lf", name, &salary);printf("Accept %d variable\n", cnt);// 宽度,小数点(四舍五入),变量printf("%s,%c,%*.*f\n", name, sex, 10, 2, salary);return 0;}
编译运行:
:::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不匹配,因函数停止,只读取一个值。
