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()); // 读取缓冲区内的\n
return 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必须%lf
printf("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;
}
编译:
:::success
b07@SB:~/c/chapter4$ gcc -Wall src/scanf_digit.c -o bin/scanf_digit
:::
**缓冲区问题**:
:::success
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
:::
第一次并按下回车后,缓冲区内数据为`^I3\n`,由于第一个`scanf("%d%d", &x, &y)`函数执行失败,缓冲区依旧没变,第二个`scanf("%d,%d", &x, &y)`依旧失败。
**格式字符不对应问题**:
:::success
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
:::
第一次输入有前置空白和中间穿插换行符`\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
不匹配,因函数停止,只读取一个值。