LV01-03-C语言-输入与输出

本文主要是C语言基础——输入与输出相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
--- ---

注意:下边测试输入的使用实例中的 ↙ 代表换行。

一、输出函数

1. putchar

1.1 函数说明

在 linux 下可以使用 man 3 putchar 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int putchar(int c);

【函数说明】 C 标准库 <stdio.h> 中的函数,把参数 c 指定的字符写入到标准输出 stdout 中。

【函数参数】

  • c : int 类型,这是要输出的字符,该字符以其对应的 int 值进行传递。

【返回值】 int 类型,该函数以无符号 char 强制转换为 int 的形式返回写入的字符,如果发生错误则返回 EOF(-1) 。

【使用格式】 none

【注意事项】 putchar 函数只是输出一个字符,并不会自动换行。

1.2 使用实例

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(int argc, char *argv[])
{
char a = 0x23; /* ascii 中 0x23 = # */
putchar(a); /* 输出字符 */
putchar('\n'); /* 输出换行符 */
return 0;
}

2. puts

2.1 函数说明

在 linux 下可以使用 man 3 puts 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int puts(const char *s);

【函数说明】 C 标准库 <stdio.h> 中的函数,把一个字符串写入到标准输出 stdout ,直到空字符,但不包括空字符。换行符会被追加到输出中。

【函数参数】

  • str : char * 类型,这是要被写入的字符串。

【返回值】 int 类型,如果成功,该函数返回一个非负值为字符串长度(包括末尾的 \0 ),如果发生错误则返回 EOF(-1) 。

【使用格式】 none

【注意事项】 puts 函数输出时会自动换行

2.2 使用实例

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main(int argc, char *argv[])
{
int i = 0;
char str[16] = "abcdefg";/* 最后还有一个 \0 表示字符串的结束 */
i = puts(str); /* 输出字符串 */
printf("i=%d\n",i);
return 0;
}

3. printf

该函数应该是应用最多的函数,用法也极其丰富。

3.1 函数说明

在 linux 下可以使用 man 3 printf 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int printf(const char *format, ...);

【函数说明】 C 标准库 <stdio.h> 中的函数,发送格式化输出标准输出 stdout 。

【函数参数】

  • format : char 类型,格式化字符串( format ),就是格式化需要输出的字符串, format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
点击查看 format格式
  • format 标签属性
1
2
3
%[flags][width][.precision][length]specifier
// 即
%[标志][最小宽度][.精度][长度]类型。

后边还会有更详细的说明

  • … :输出表(附加参数),根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。

【使用格式】

1
printf("<格式化字符串>", <输出表>);

【返回值】 int 类型,如果成功,则返回写入的字符总数,否则返回一个负数。

【注意事项】 none

3.2 格式化字符串详解

3.2.1 格式字符( specifier )

格式字符 形式 意义
i, d %i, %d 以十进制形式输出带符号整数(正数不输出符号)
u %u 以十进制形式输出无符号整数
o %o 以八进制形式输出无符号整数(不输出前缀0)
x, X %x, %X 以十六进制形式输出无符号整数(不输出前缀0x)
f %f 以小数形式输出单、双精度实数
e, E %e, %E 以指数形式输出单、双精度实数
g, G %g, %G 以 %f 或 %e 中较短的输出宽度输出单、双精度实数
c %c 输出单个字符
s %s 输出字符串
**【注意事项】**想要输出 % 本身的话可以使用 %% 。

3.2.2 标识( flag )

标识 描述
- 在给定的字段宽度内左对齐,默认是右对齐
+ 强制在结果之前显示加号或减号(+ 或 -),即正数前面会显示 + 号。默认情况下,只有负数前面会显示一个 - 号。
space
(空格)
有符号值若为正,则在值前显示前导空格(但是不显示符号);若为负,则在值前显示-。
# 与 o、x 或 X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X。
与 e、E 和 f 一起使用时,会强制输出包含一个小数点,即使后边没有数字时也会显示小数点。默认情况下,如果后边没有数字时候,不会显示显示小数点。
与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除。
0 输出数值时指定左面不使用的空位置自动填0。

3.2.3 最小输出宽度( width )

宽度 描述
number 数值(十进制整数),数据长度 小于number,则左补空格;否则按实际输出。
* 星号,精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
如:printf("%0*d",6,1000); 将输出:001000

3.2.4 精度( .precision )

精度 描述
.number 对于整数说明符(d、i、o、u、x、X):precision 指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为 0 意味着不写入任何字符。
对于 e、E 和 f 说明符:要在小数点后输出的小数位数。
对于 g 和 G 说明符:要输出的最大有效位数。
对于 s : 要输出的最大字符数,不足number则正常输出,超过则截断。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。
对于 c 类型:没有任何影响。
当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。
.* 星号,精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
但是在尝试的时候似乎有一些数据类型不匹配的情况,不过也用的不多。

【注意事项】

1.精度格式符以 . 开头,后跟十进制整数。

2.对数值型数据的来说,未指定精度( .precision )时,隐含的精度为 6 位。

3.2.5 长度( length )

详情可查看该网站(含有 C99 标准):File input/output。

3.3 转义字符

转义字符 含义 ASCII码(十进制)
\a 响铃(BEL) 007
\b 退格(BS),将当前位置移到前一列 008
\f 换页(FF),将当前位置移到下页开头 012
\n 换行(LF),将当前位置移到下一行开头 010
\r 回车(CR),将当前位置移到本行开头 013
\t 水平制表(HT),跳到下一个TAB位置 009
\v 垂直制表(VT) 011
\\ 反斜线字符 \ 092
\' 单引号字符 ' 039
\" 双引号字符 " 034
\? 问号 ? 063
\0 空字符(NULL) 000
\ooo 1-3位八进制数所代表的任意字符 三位八进制
\xhh 1-2位十六进制数所代表的任意字符 二位十六进制
### 3.4 使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char c, str[20];
int a = 12345;
float pi = 3.141592653589;
double b = 0.123456789123456789;
strcpy(str, "strings"); /* 将字符串复制到数组 */
c = '\x41'; /* 两位十六进制转移字符 代表A */

printf("a = %d\n", a); /* 按照十进制整数格式输出,显示 a = 12345 */
printf("a = %d%%\n", a); /* 输出%号 结果 a = 12345% */
printf("a = %7d\n", a); /* 输出7位十进制整数 左边补空格,显示 a = 12345 */
printf("a = %07d\n", a); /* 输出7位十进制整数 左边补0,显示 a = 0012345 */
printf("a = %2d\n", a); /* a超过2位,按实际输出 a = 12345 */
printf("a = %-7d\n", a); /* 输出7位十进制整数 右边补空格,显示 a = 12345 */
printf("pi = %f\n", pi); /* 浮点数默认精度6位小数,结果 f = 13.141593 */
printf("pi = %6.4f\n", pi); /* 输出6列,小数点后4位,结果 f = 3.1416 */
printf("b = %lf\n", b); /* 输出长浮点数 b = 0.123457 */
printf("b = %18.17lf\n", b); /* 输出18列,小数点后16位,b = 0.12345678912345678 */
printf("c = %c\n", c); /* 输出字符 c = A */
printf("c = %x\n", c); /* 以十六进制输出字符的ASCII码 c = 41 */
printf("str[] = %s\n", str); /* 输出数组字符串str[] = strings */
printf("str[] = %6.3s\n", str);/* 输出最多3个字符的字符串 str[] = str */
return 0;
}

打印结果如下:

image-20220105210806637

4. fprintf

4.1 函数说明

在 linux 下可以使用 man 3 fprintf 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int fprintf(FILE *stream, const char *format, ...);

【函数说明】该函数可以传送格式化输出到一个文件中,后边学习标准IO的时候会再学习,这里简单提一下。

【函数参数】

  • stream : FILE * 类型指针变量,为文件指针变量。
  • format : char * 类型,为格式化字符串,与 printf 一致。

【返回值】 int 类型,成功时返回输出的字符个数;出错时返回 EOF 。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
FILE * fp;
size_t ret;
fp = fopen("file_name", "w");
ret = fprintf(fp, "%d-%d-%d", 2022, 5, 4);

【注意事项】如果将 stream 设置为 stdout ,那么 fprintf() 函数将会向显示器输出内容,与 printf 的作用相同。

4.2 使用实例

后边学习标准IO的时候会再详细学习。

5. sprintf

5.1 函数说明

在 linux 下可以使用 man 3 sprintf 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int sprintf(char *str, const char *format, ...);

【函数说明】该函数用于向指定的字符串中格式化写入数据,与 printf 类似,只是该函数将格式化的字符串写入到字符串中。

【函数参数】

  • str : char * 类型,为指向一个字符数组的指针,后边的格式化字符串将会被写入到该函数中。

  • format : char * 类型,为格式化字符串,与 printf 一致。

【使用格式】

1
2
char buff[100]= { 0 };
sprintf(buff,"%d-%d-%d", 2022, 5, 4);

【返回值】 int 类型,如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符,如果失败,则返回 EOF 。

【注意事项】 none

5.2 使用实例

后边学习标准IO再详细学习。

二、输入函数

1. getchar

1.1 函数说明 

在 linux 下可以使用 man 3 getchar 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int getchar(void);

【函数说明】 C 标准库 <stdio.h> 中的函数,从标准输入 stdin 获取一个字符,比如我们从键盘输入字符 A , B , C ,并按下回车后,我们的输入被放入了输入缓冲区,这个时候 getchar() 会从缓冲区中读取我们刚才的输入,一次只读取一个字符。

【函数参数】 none

【使用格式】 none

【返回值】 int 类型,该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果读取结束或发生读错误,则返回 EOF(-1) (输入 Ctrl+d ,就会返回 EOF )。

【注意事项】

(1)通过该函数输入的都是字符,例如输入数字 1 ,实际输入的是字符 1 。

(2)以 Enter 结束输入(空格不会导致输入结束结束),他可以接受空格字符, Enter 结束输入时,接受空格,会舍弃最后的回车符。

1.2 使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char a;
char b;
printf("请输入字符:");
a = getchar();
b = sizeof(a);
printf("输入的字符:");
putchar(a);
printf("\n");
printf("a = %d, a = %x, b = %d\n",a, a, b);
return 0;
}

运行结果如下:

image-20220106100551934

2. gets

2.1 函数说明

在 linux 下可以使用 man 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
char *gets(char *s);

【函数说明】 C 标准库 <stdio.h> 中的函数,从标准输入 stdin 读取一行(以 结束),并把它存储在 str 所指向的字符串中。当读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

【函数参数】

  • str : char * 类型,这是指向一个字符数组的指针,该数组存储了 C 字符串。

【使用格式】 none

【返回值】 char * 类型,如果成功,该函数返回 str 。如果发生错误或者到缓冲区末尾时还未读取任何字符,则返回 NULL 。

【注意事项】

(1)使用该函数的时候会产生警告,这是因为在后来的标准中已经移除该函数,取而代之的是 fgets 。

(2) gets 从键盘输入一以回车结束的字符串放入字符数组中,并自动加 \0 ,需要注意的是输入字符串长度应小于字符数组维数,遇到回车时结束读取。

(2)与 scanf 函数不同, gets 函数并不以空格作为字符串输入结束的标志,它可以读取空格,遇到空格不会结束读取。

(3)可以读取 Tab 键,但是我测试的时候貌似把 tab 当做一个空格进行处理了。

2.2 使用实例

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main(int argc, char *argv[])
{
char str[50];
printf("请输入字符串:");
gets(str);
printf("输入的字符串:");
puts(str);
printf("str[50] = %s,\n", str);
return 0;
}

运行结果如下:

image-20220106101538008

3. scanf

3.1 函数说明 

在 linux 下可以使用 man 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int scanf(const char *format, ...);

【函数说明】 C 标准库 <stdio.h> 中的函数,从标准输入 stdin 读取格式化输入,它不会读取空格、制表符、回车符,这些符号将会留在缓冲区中。

【函数参数】

  • format : char * 类型,格式化字符串( format ),这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符
点击查看 format 格式
  • format 说明符形式为: %[*][width][modifiers]type
参数 描述
* 这是一个可选的星号,表示数据是从流 stream 中读取的,但是可以被忽视,即它不存储在对应的参数中。
width 这指定了在当前读取操作中读取的最大字符数。
modifiers 为对应的附加参数所指向的数据指定一个不同于整型(针对 d、i 和 n)、无符号整型(针对 o、u 和 x)或浮点型(针对 e、f 和 g)的大小。
type 一个字符,指定了要被读取的数据类型以及数据读取方式。具体参见下一个表格。
  • … :附加参数(地址表),根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。

【使用格式】

1
scanf("<格式化字符串>", <地址表>);

【返回值】 int 类型,如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF(-1) 。

【注意事项】

(1) scanf 读取结束的标志:

  • <space> 、 Tab 、或 Enter 。
  • 遇非法输入。
  • 遇宽度结束(就是输入的时候有类似 %4d ,这种的话,要是我们输入 212345 ,那么它只会读取 4 个字符)。

(2)连续输入字符型变量(即使用 %c )的时候,空格和转义字符会作为有效字符输入(后边会有详细的实例说明)。

1
2
3
scanf(“%c%c%c”,&c1, &c2, &c3);
若输入a b c
则c1为a, c2为空格, c3为b

3.2 格式化字符串详解

3.2.1 *

基本没有用过,这里不再说明,以后用到了再补充。

3.2.2 格式字符( type )

格式字符 形式 意义
i, d %i, %d 输入或读取十进制整数,数字前面的 + 或 - 号是可选的。
u %u 输入或读取十进制无符号整数。
o %o 输入或读取八进制无符号整数。
x, X %x, %X 输入或读取十六进制无符号整数。
f, F %f, %F 输入或读取小数形式(指数形式浮点小数 )浮点数。
e, E %e, %E
g, G %g, %G
c %c 输入或读取单个字符。空格和转义字符作为有效字符输入。
s %s 输入或读取字符串,这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。

【注意事项】

(1)输入 double 类型数据必须用 lf ,不能用 f 。

(2)输入 % ,可以用 %% 。

3.2.3 数据宽度( width )

参数 描述
number 指定输入数据宽度,遇空格或不可转换字符结束。

3.2.4 修饰符( modifiers )

参数 描述
h 用于d, o, x前,指定输入为short型整数。
l 用于d, o, x前,指定输入为long型整数。
用于e, f前,指定输入为double型实数。
* 抑制符,指定输入项读入后不赋给变量。

3.3 地址表详解

scanf 函数读取时,后边的地址表代表的是地址,也就是说,必须是地址的形式。

类型 说明
变量 对于变量,应该加上 & 符号,如:&a, &b等。
数组 对于数组,数组名就代表了该数组的起始地址,所以不需要加 & 。如:a[12]→a即可。
指针 对于指针,指针名就代表了该指针指向的地址,所以不需要加 & 。如: *p → p 即可。

3.4 使用实例

3.4.1 一般使用实例

  • 格式字符串必须严格按书写格式输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main(int argc, char *argv[])
{
int a, a1, a2;
int b, b1, b2;
int c, c1, c2;
printf("请输入数据:");
scanf("%d%d%d",&a, &b, &c);
printf("输入的数据为:a = %d, b = %d, c = %d\n", a, b, c);

printf("请输入数据:");
scanf("%d,%d,%d",&a1, &b1, &c1);
printf("输入的数据为:a1 = %d, b1 = %d, c1 = %d\n", a1, b1, c1);

printf("请输入数据:");
scanf("%d %d %d",&a2, &b2, &c2);
printf("输入的数据为:a2 = %d, b2 = %d, c2 = %d\n", a2, b2, c2);

return 0;
}

运行效果:

image-20220106115533901

【注意事项】

(1) scanf 中的格式字符串中除了 % 部分的格式字符,其他的自行添加的(如 , 、其他的字母等)在输入的时候要严格按照格式一起输入,否则输入的数据会有问题。经实验证明,格式字符串中的空格(并不是从终端输入的时候的字符串)似乎不会对输入结果产生影响。

(2)在同时进行多个数字变量的数据输入时,相邻数据之间可以用 <space> 、 <Tab> 或者 <Enter> 均可,但要注意自己是否添加了其他字符。

3.4.2 字符输入时的空格

3.4.2.1 测试源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main(int argc, char *argv[])
{
char a, b, c, d;
printf("请输入字符(a b c):");
scanf("%c%c%c",&a, &b, &c);
printf("输入的字符为:a = %c(%#x), b = %c(%#x), c = %c(%#x)\n", a, a, b, b, c, c);

printf("请输入字符(d):");
scanf("%c",&d);
printf("输入的字符为:d = %c(%#x)\n", d, d);

return 0;
}

【注意事项】

(1)在用 %c 进行单个字符输入时,空格和转义字符作为有效字符输入

(2)在测试过程中,若有连续两个 scanf 函数进行输入时,上一个 scanf 多余出来的数据(包括最后的 <Enter> )将会直接作为下一个 scanf 的输入。

3.4.2.2 连续输入测试

我们这样输入数据:

1
2
hk@ubuntu-22-04:~/1sharedfiles/3Linux/01-LV1/04输入输出$ ./a.out 
请输入字符(a b c):123

然后我们就会得到这样的输出数据:

image-20230614224539118

我们查阅一下ASCII码表就会发现,0xa其实就是表示 \n ,也就是换行。上边的现象就说明,换行符号也被当做一个字符,并且直接被当做下一个scanf的输入了。

3.4.2.3 中间带空格
1
2
hk@ubuntu-22-04:~/1sharedfiles/3Linux/01-LV1/04输入输出$ ./a.out 
请输入字符(a b c):1 23

我们会得到以下输出信息:

image-20230614224845558

可以看到,空格被赋给了b,最后的3则直接赋给了d。

3.4.2.4 中间带回车

我们输入完12后,直接输入一个回车,根据前边的现象,a会被赋值为1,b会被赋值为2,c会被赋值为 0xa,然后由于缓冲区数据了,所以当遇到第二个scanf的时候会提示我们输入字符:

1
2
hk@ubuntu-22-04:~/1sharedfiles/3Linux/01-LV1/04输入输出$ ./a.out 
请输入字符(a b c):12

我们会看到这样的输出:

image-20230614225324540

3.4.3 字符串输入时的空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main(int argc, char *argv[])
{
char str1[20];
char str2[20];
printf("请输入字符串:");
scanf("%s",str1);
printf("输入的字符串为:str[20] = %s\n", str1);

printf("请输入字符串:");
scanf("%s",str2);
printf("输入的字符串为:str[20] = %s\n", str2);

return 0;
}
image-20220106155842151

【注意事项】

(1) %s 进行字符串输入时,遇到 <space> 即认为该数据结束。

(2)若有连续两个 scanf 函数进行输入时,上一个 scanf 输入的数据若有 <space> ,则 <space> 后的数据,将会直接作为下一个 scanf 的输入。

3.4.4 缓冲区的多余字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int main(int argc, char *argv[])
{
int y,m,d;
int y1,m1,d1;
int a;
char b;
float c;

printf("请输入数据:");
scanf("%4d%2d%2d", &y, &m, &d);
printf("输入的数据为:y = %d, m = %d, d = %d\n", y, m, d);

printf("请输入数据:");
scanf("%4d%3d%2d", &y1, &m1, &d1);
printf("输入的数据为:y1 = %d, m1 = %d, d1 = %d\n", y1, m1, d1);

printf("请输入数据:");
scanf("%d%c%f",&a,&b,&c);
printf("输入的数据为:a = %d, b = %c, c = %f\n", a, b, c);
return 0;
}

我们这样输入数据,

1
2
3
4
5
6
7
8
hk@ubuntu-22-04:~/1sharedfiles/3Linux/01-LV1/04输入输出$ ./a.out 
请输入数据:20230615
输入的数据为:y = 2023, m = 6, d = 15
请输入数据:2023061512
输入的数据为:y1 = 2023, m1 = 61, d1 = 51
请输入数据:2021a12310.315
输入的数据为:a = 2, b =
, c = 2021.000000

实际效果如下图:

image-20230615194016936

这里我们直接分析一下:

第一次输入:20230615↙,这个时候我们需要按%4d%2d%2d的格式读取数据,这样会以此得到y=2023,m=06,d=15,这是没问题的。

第二次输入:2023061512↙,这次我们按照%4d%3d%2d的格式读取,会得到y1=2023,m1=061,d1=51,这也是没问题的,这个时候其实缓冲区还剩下2这个数字。

第三次输入:2021a12310.315↙,我们按照%d%c%f的格式来读取,理论上我们应该获取到什么数据?我们实际验证一下吧:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main(int argc, char *argv[])
{
int a;
char b;
float c;
printf("请输入数据:");
scanf("%d%c%f",&a,&b,&c);
printf("输入的数据为:a = %d, b = %c, c = %f\n", a, b, c);
return 0;
}

我们同样输入2021a12310.315↙,会得到以下输出:

1
2
3
hk@ubuntu-22-04:~/1sharedfiles/3Linux/01-LV1/04输入输出$ ./a.out 
请输入数据:2021a12310.315
输入的数据为:a = 2021, b = a, c = 12310.315430

可是上边我们实际得到的是:

image-20230615194621832

这是为什么?原因就在于我们第二次输入的时候,缓冲区还剩下一个2和 ↙(换行),它俩直接作为最后一个scanf的输入,所以最后依次输入的时候scanf只会读取一个%f格式的数据,由于2021后边是字符a,所以%f就读取到了2021,最后做了强制类型转换,就输出了上边的数据啦。

【注意事项】

(1)输入数据时,要格外注意遇以下情况认为该数据结束:遇 <space> 、 Tab 、或 Enter ;遇非法输入;遇宽度结束。

(2)一定要注意上一次的数据输入是否还有余留,可能会导致后续输入出现问题,具体处理方法看后文。

3.4.5 最后的换行的处理实例

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(int argc, char *argv[])
{
int x;
char ch;
scanf("%d", &x);
// getchar(); /* 加上此行,吸收 <Enter> 或者可以用 scanf("%*c%c", &x); */
ch = getchar();
printf("x = %d, ch=%d\n", x, ch); /* \n 的十进制为 10 */

return 0;
}
image-20220106163053444

3.5 scanf 缓冲区处理

很多时候,我们输入了一串数据后,又继续使用 scanf 输入,但此时缓冲区可能会有上次遗留下来的其他字符,包括换行符,这会对之后的输入造成影响的。我们就用前边《缓冲区的多余字符》一小节的笔记中的例子来做对比

3.5.1 getchar 处理

3.5.1.1 处理方式

我们可以使用 getchar() 来吸收缓冲区剩下的字符:

1
2
char ch;
while ((ch = getchar()) != EOF && ch != '\n') ; //清除缓冲区的内容

【说明】输入流中多余的字符均可通过此行代码处理。

3.5.1.2 使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
int main(int argc, char *argv[])
{
int y,m,d;
int y1,m1,d1;
int a;
char b;
float c;
char ch;

printf("请输入数据:");
scanf("%4d%2d%2d", &y, &m, &d);
printf("输入的数据为:y = %d, m = %d, d = %d\n", y, m, d);

while ((ch = getchar()) != EOF && ch != '\n');

printf("请输入数据:");
scanf("%4d%3d%2d", &y1, &m1, &d1);
printf("输入的数据为:y1 = %d, m1 = %d, d1 = %d\n", y1, m1, d1);

while ((ch = getchar()) != EOF && ch != '\n');

printf("请输入数据:");
scanf("%d%c%f",&a,&b,&c);
printf("输入的数据为:a = %d, b = %c, c = %f\n", a, b, c);
return 0;
}

然后我们按照相同的方式输入数据,会得到以下输出:

image-20230615195721260

可以发现输入都正常了,但是吧最后的浮点型数据多了个430,,目前还不是很清楚为什么,不过问题不大,感觉像是打印的时候没有保留小数位数,应该算是多打印了内存中的一些其他数据,其实可以自己通过格式化字符串的时候那个精度来截断一下就好了。

3.5.2 scanf 自行处理

3.5.2.1 处理方式

我们还可以用 scanf 自己来处理:

1
scanf("%*[^\n]%*c");/* 遇到'\n'结束读取输入的字符,并将其读到的数据抛弃,然后再抛弃一个字符(其实这个字符是'\n'),此时缓存中不存在任何字符 */
  • * :这里的星号 * 表示读入某类型的内容,但是这个内容不保存到变量里,所以后面不需要对应的参量;
  • [] :里边的内容是只读入限定读入的字符,如 [abcd] 指的是只读入 abcd 的字符;
  • %*[^\n] :表示读入除了回车之外的字符以及读入一个字符后不保存,只有这样,才不会把输入回车吸收,导致不能退出程序;
  • %*c :是读入最后一个没有读入的回车;
3.5.2.2 使用实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int main(int argc, char *argv[])
{
int y,m,d;
int y1,m1,d1;
int a;
char b;
float c;

printf("请输入数据:");
scanf("%4d%2d%2d%*[^\n]%*c", &y, &m, &d);
printf("输入的数据为:y = %d, m = %d, d = %d\n", y, m, d);

printf("请输入数据:");
scanf("%4d%3d%2d%*[^\n]%*c", &y1, &m1, &d1);
printf("输入的数据为:y1 = %d, m1 = %d, d1 = %d\n", y1, m1, d1);

printf("请输入数据:");
scanf("%d%c%f%*[^\n]%*c",&a,&b,&c);
printf("输入的数据为:a = %d, b = %c, c = %f\n", a, b, c);
return 0;
}

按照前边一样的输入方式,可以得到以下输出情况:

image-20230615200824722

可以发现获取到的数据也都正常了。

3.6 scanf 其他用法实例

3.6.1 读取带空格的字符串

我们使用 scanf 的话,也想读取带空格的字符串的话,可以使用这样的格式:

1
scanf("%[^\n]%*c",str); /* 遇到'\n'结束读取输入的字符,并存入str,然后再抛弃一个字符 */

例如,

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(int argc, char *argv[])
{
char str[30];
while (1)
{
printf("请输入字符串:");
scanf("%[^\n]%*c", str); //读到'\n'结束读取
printf("输入的字符串为:%s\n", str);
}
return 0;
}
  • %*c :是读入最后一个没有读入的回车

【注意事项】后边不加 %*c 的话,依旧可以读取带空格的字符串,但是若是在一个循环中的话,会陷入死循环,至少我测试的时候是这样的,比如说上边的例子,若没有 %*c 的话,就会一直执行这一句

1
printf("输入的字符串为:%s\n", str);

3.6.2 读取特定字符后的字符

1
2
char ch;  
scanf("%*[^a]%*c%c",&ch); /* 遇到字符a停止读取,并舍弃a, 将a后的一个字符存入变量ch */

例如,

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(int argc, char *argv[])
{
char ch;
printf("请输入字符:");

scanf("%*[^a]%*c%c", &ch); /* 遇到字符a停止读取,并舍弃a, 将a后的一个字符存入变量ch */
printf("输入的字符为:%c\n", ch);
return 0;
}

我们编译运行的话,会有以下情况

1
2
请输入字符:sdaddsa sa d
输入的字符为:d

4. fscanf

4.1 函数说明

在 linux 下可以使用 man 3 fscanf 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int fscanf(FILE *stream, const char *format, ...);

【函数说明】该函数从流 stream 读取格式化输入,即从文件格式化读取数据,后边学习标准IO的时候会再详细学习。

【函数参数】

  • stream : FILE * 类型,为文件指针变量。
  • format : char * 类型,为格式化字符串,与 scanf 一致。

【返回值】 int 类型,如果成功,该函数返回成功匹配和赋值的个数,如果到达文件末尾或发生读错误,则返回 EOF 。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
FILE * fp;
int year, month, day;
fp = fopen("file_name", "r");
fscanf(fp,"%d-%d-%d",&year, &month, &day);

【注意事项】如果将 fp 设置为 stdin ,那么 fscanf() 函数将会从键盘读取数据,与 scanf 的作用相同。

4.2 使用实例

后边学习标准IO会再详细学习。

5. sscanf

5.1 函数说明

在 linux 下可以使用 man 3 sscanf 命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <stdio.h>

/* 函数声明 */
int sscanf(const char *str, const char *format, ...);

【函数说明】该函数从字符串读取格式化输入,表示我们可以通过此函数从一个字符串中格式化的读取我们要的格式数据。

【函数参数】

  • str : char * 类型,是格式化输入数据的来源。
  • format : char * 类型,为格式化字符串,与 scanf 一致。

【返回值】 int 类型,如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF 。

【使用格式】一般情况下基本使用格式如下:

1
2
3
char buff[100] = "2022-5-4";
int syear, smonth, sday;
sscanf(buff,"%d-%d-%d",&syear, &smonth, &sday);

【注意事项】 none

5.2 使用实例

后边学习标准IO会再详细学习。

三、输出可以带颜色?

1. ANSI 控制码简介

ANSI 控制码用于在字符显示系统中控制光标移动和字符色彩等,常用于 BBS 系统中。 ANSI ESCAPE SEQUENCES 又称为 VT100 系列控制码,国内译为 ANSI 控制码。 ANSI 控制码依赖终端,不是依赖语言,所以在 shell , perl , C 里应用都是可以的。

ANSI 控制码开始的标志都为 ESC[ , ESC 对应 ASCII 码表的 033 (八进制)。

2. ANSI 控制码

这里只写了一部分,还有一些其他的控制码,这里就不列出了。

\033[0m 关闭所有属性
\033[1m 设置高亮度
\033[4m 下划线
\033[5m 闪烁
\033[7m 反显
\033[8m 消隐
\033[30m ~ \033[37m 设置前景色(字体色)
30:黑 31:红 32:绿 33:黄 34:蓝 35:紫 36:深绿 37:白
\033[40m ~ \033[47m 设置背景色
40:黑 41:红 42:绿 43:黄 44:蓝 45:紫 46:深绿 47:白
\033[nA 光标上移n行
\033[nB 光标下移n行
\033[nC 光标右移n行
\033[nD 光标左移n行
\033[y;xH 设置光标位置,将光标移动到y行x列的位置
\033[2J 清屏,光标移到左上角
\033[K 清除从光标到行尾的内容
\033[s 保存光标位置
\033[u 恢复光标位置
\033[?25l 隐藏光标
\033[?25h 显示光标
**【显示字符属性控制】**
1
[n1;n2;......m

设定显示字符的属性状态。若有两个以上设定则以分号将代码隔开。除非重新设定,否则原设定之属性一直被保留,若想后边的不受影响,可以在结束后加一个 \033[0m 。

3. ANSI 码应用格式

3.1 printf使用格式

1
printf("\033[颜色值m 文本\n")  /* 最后加上\033[0m可以使其不影响其他字体 */

3.2 使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

/* 前景色(字体颜色),printf 打印输出的颜色定义,主要是用于实现带颜色的输出 */
#define CLS "\033[0m" /* 清除所有颜色 */
#define BLACK "\033[1;30m" /* 黑色加粗字体 */
#define RED "\033[1;31m" /* 红色加粗字体 */
#define GREEN "\033[1;32m" /* 绿色加粗字体 */
#define YELLOW "\033[1;33m" /* 黄色加粗字体 */
#define BLUE "\033[1;34m" /* 蓝色加粗字体 */
#define PURPLE "\033[1;35m" /* 紫色加粗字体 */
#define CYAN "\033[1;36m" /* 青色加粗字体 */
#define WHITE "\033[1;37m" /* 白色加粗字体 */
#define BOLD "\033[1m" /* 加粗字体 */

int main(int argc, char *argv[])
{
printf(RED"qidaink\n"CLS);
printf(GREEN"qidaink\n"CLS);
printf(BLUE"qidaink\n"CLS);
printf(CYAN"qidaink\n"CLS);
printf("\033[1;41;36mqidaink\033[0m\n");
printf(WHITE"qidaink\n"CLS);
return 0;
}