C语言归纳二-数据的输入输出

目录

一、汇总前面学过的格式控制符

二、printf用法

2.1 长度控制

2.2 在屏幕的任何位置输出数据

三、输入用法

3.1 scanf()函数

3.2 输入单个字符

3.3 输入字符串

四、缓冲区

五、清空缓冲区

5.1 清空输出缓冲区

5.2 清空输入缓冲区

七、C语言模拟密码输入(显示星号)

八、C语言非阻塞式键盘监听


一、汇总前面学过的格式控制符

格式控制符说明
%c输出一个单一的字符
%hd、%d、%ld以十进制、有符号的形式输出 short、int、long 类型的整数
%hu、%u、%lu以十进制、无符号的形式输出 short、int、long 类型的整数
%ho、%o、%lo以八进制、不带前缀、无符号的形式输出 short、int、long 类型的整数
%#ho、%#o、%#lo以八进制、带前缀、无符号的形式输出 short、int、long 类型的整数
%hx、%x、%lx
%hX、%X、%lX
以十六进制、不带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字也小写;如果 X 大写,那么输出的十六进制数字也大写。
%#hx、%#x、%#lx
%#hX、%#X、%#lX
以十六进制、带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字和前缀都小写;如果 X 大写,那么输出的十六进制数字和前缀都大写。
%f、%lf以十进制的形式输出 float、double 类型的小数
%e、%le
%E、%lE
以指数的形式输出 float、double 类型的小数。如果 e 小写,那么输出结果中的 e 也小写;如果 E 大写,那么输出结果中的 E 也大写。
%g、%lg
%G、%lG
以十进制和指数中较短的形式输出 float、double 类型的小数,并且小数部分的最后不会添加多余的 0。如果 g 小写,那么当以指数形式输出时 e 也小写;如果 G 大写,那么当以指数形式输出时 E 也大写。
%s输出一个字符串

二、printf用法

2.1 长度控制

(1)width表示最小输出宽度,'-'表示左对齐,不加是右对齐。

#include <stdio.h>
int main()
{
    int a1=20, a2=345, a3=700, a4=22;
    int b1=56720, b2=9999, b3=20098, b4=2;
    int c1=233, c2=205, c3=1, c4=6666;
    int d1=34, d2=0, d3=23, d4=23006783;

    printf("%-9d %-9d %-9d %-9d\n", a1, a2, a3, a4);
    printf("%-9d %-9d %-9d %-9d\n", b1, b2, b3, b4);
    printf("%-9d %-9d %-9d %-9d\n", c1, c2, c3, c4);
    printf("%-9d %-9d %-9d %-9d\n", d1, d2, d3, d4);

    return 0;
}

m 是正数,以%+d输出时要带上正号;n 是负数,以+d输出时要带上负号。

m 是正数,以% d输出时要在前面加空格;n 是负数,以% d输出时要在前面加负号。

%.0f表示保留 0 位小数,也就是只输出整数部分,不输出小数部分。默认情况下,这种输出形式是不带小数点的,但是如果有了#标志,那么就要在整数的后面“硬加上”一个小数点,以和纯整数区分开。

#include <stdio.h>
int main(){
    int m = 192, n = -943;
    float f = 84.342;
    printf("m=%10d, m=%-10d\n", m, m);  //演示 - 的用法
    printf("m=%+d, n=%+d\n", m, n);  //演示 + 的用法
    printf("m=% d, n=% d\n", m, n);  //演示空格的用法
    printf("f=%.0f, f=%#.0f\n", f, f);  //演示#的用法

    return 0;
}

输出结果:

m=       192, m=192      
m=+192, n=-943
m= 192, n=-943
f=84, f=84.

(2)精度长度控制

.precision 表示输出精度,也就是小数的位数。

  • 当小数部分的位数大于 precision 时,会按照四舍五入的原则丢掉多余的数字;
  • 当小数部分的位数小于 precision 时,会在后面补 0。

另外,.precision 也可以用于整数和字符串,但是功能却是相反的:

  • 用于整数时,.precision 表示最小输出宽度。与 width 不同的是,整数的宽度不足时会在左边补 0,而不是补空格。
  • 用于字符串时,.precision 表示最大输出宽度,或者说截取字符串。当字符串的长度大于 precision 时,会截掉多余的字符;当字符串的长度小于 precision 时,.precision 就不再起作用。
#include <stdio.h>
int main(){
    int n = 123456;
    double f = 882.923672;
    char *str = "abcdefghi";
    printf("n: %.9d  %.4d\n", n, n);
    printf("f: %.2lf  %.4lf  %.10lf\n", f, f, f);
    printf("str: %.5s  %.15s\n", str, str);
    return 0;
}

/*
运行结果:
n: 000123456  123456
f: 882.92  882.9237  882.9236720000
str: abcde  abcdefghi
*/

关于printf()的输出,要注意一下缓冲区问题。

Linux和Mac OS下数据不直接输出,没有\n就先放到缓冲区,不显示,遇到\n显示,Windows下不是。

2.2 在屏幕的任何位置输出数据

光标定位需要使用 windows.h 头文件中的SetConsoleCursorPosition函数,它的使用方式为:

SetConsoleCursorPosition(HANDLE hConsoleOutput, COORD  dwCursorPosition);

hConsoleOutput表示控制台缓冲区句柄,可通过GetStdHandle(STD_OUTPUT_HANDLE)来获得;dwCursorPosition是光标位置,也就是第几行第几列,它是 COORD 类型的结构体。

例如,将光标定位到控制台的第3行第3列:

#include <stdio.h>
#include <windows.h>

int main(){
    //定义光标位置
    COORD coord;
    coord.X = 3;  //第3行
    coord.Y = 3;  //第3列
    //获取控制台缓冲区句柄
    HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    //设置光标位置
    SetConsoleCursorPosition(ConsoleHandle, coord);

    printf("123");

    return 0;
}

注意:窗口的左上角是第 0 行第 0 列,而不是我们通常所认为的第 1 行第 1 列。编程语言中的很多计数都是从 0 开始的,而不是从 1 开始。

搞定了光标重定位,你就可以将输出顺序打乱了,想在哪里输出就在哪里输出。例如:

#include <stdio.h>
#include <windows.h>

//设置光标位置
void setCursorPosition(int x, int y);
//设置文字颜色
void setColor(int color);

int main(){
    setColor(3);
    setCursorPosition(3, 3);
    puts("★");

    setColor(0XC);
    setCursorPosition(1, 1);
    puts("◆");

    setColor(6);
    setCursorPosition(6, 6);
    puts("■");

    return 0;
}

//自定义的光标定位函数
void setCursorPosition(int x, int y){
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
//自定义的文字颜色函数
void setColor(int color){
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color) ;
}

运行结果:

三、输入用法

在C语言中,有多个函数可以从键盘获得用户输入:

  • scanf():和 printf() 类似,scanf() 可以输入多种类型的数据。
  • getchar()、getche()、getch():这三个函数都用于输入单个字符。
  • gets():获取一行数据,并作为字符串处理。

scanf() 是最灵活、最复杂、最常用的输入函数,但它不能完全取代其他函数,大家都要有所了解。

3.1 scanf()函数

scanf格式化扫描输入。注意"%d %d"之间是有空格的,所以输入数据时也要有空格。scanf() 对输入数据之间的空格的处理比较宽松,并不要求空格数严格对应,多几个少几个无所谓,只要有空格就行。。对于 scanf(),输入数据的格式要和控制字符串的格式保持一致。

%p是一个新的格式控制符,它表示以十六进制的形式(带小写的前缀)输出数据的地址。如果写作%P,那么十六进制的前缀也将变成大写形式。

#include <stdio.h>
int main()
{
    int a='F';
    int b=12;
    int c=452;
    printf("&a=%p, &b=%p, &c=%p\n", &a, &b, &c);
   
    return 0;
}

注意一下这个代码及相对应的输入:

#include <stdio.h>
int main()
{
    int a, b, c;

    scanf("%d %d", &a, &b);
    printf("a+b=%d\n", a+b);
    scanf("%d   %d", &a, &b);
    printf("a+b=%d\n", a+b);
    scanf("%d, %d, %d", &a, &b, &c);
    printf("a+b+c=%d\n", a+b+c);
   
    //划重点下面这部分。
    scanf("%d is bigger than %d", &a, &b);
    printf("a-b=%d\n", a-b);

    return 0;
}

运行结果:

10    20↙
a+b=30
100 200↙
a+b=300
56,45,78↙
a+b+c=179
25 is bigger than 11↙
a-b=14

第四个 scanf() 要求整数之间以is bigger than分隔。

下面(1)到(3)只有(2)(3)值得看。

(1)用户每次按下回车键,程序就会认为完成了一次输入操作,scanf() 开始读取用户输入的内容,并根据格式控制字符串从中提取有效数据,只要用户输入的内容和格式控制字符串匹配,就能够正确提取。

本质上讲,用户输入的内容都是字符串,scanf() 完成的是从字符串中提取有效数据的过程。

按道理说scanf会在缓冲区中自动扫描符合格式的数据,但是也会出现一些问题,在格式复杂的时候,缓冲区会带来一些问题。

看代码,划重点。

#include <stdio.h>
int main()
{
    int a = 1, b = 2;
    scanf("a=%d", &a);
    scanf("b=%d", &b);
    printf("a=%d, b=%d\n", a, b);

    return 0;
}

输入示例:

a=99↙
a=99, b=2

输入a=99,按下回车键,程序竟然运行结束了,只有第一个 scanf() 成功读取了数据,第二个 scanf() 仿佛没有执行一样,根本没有给用户任何机会去输入数据。

如果我们换一种输入方式呢?

a=99b=200↙
a=99, b=200

这样 a 和 b 都能够正确读取了。注意,a=99b=200中间是没有任何空格的。

肯定有好奇的小伙伴又问了,如果a=99b=200两个数据之间有空格又会怎么样呢?我们不妨亲试一下:

a=99 b=200↙
a=99, b=2

你看,第二个 scanf() 又读取失败了!在前面的例子中,输入的两份数据之前都是有空格的呀,为什么这里不能带空格呢,真是匪夷所思。好吧,这个其实还是跟缓冲区有关系,将在(3)中解释。

 

(2)scanf() 的这些特性都是有章可循的,其根源就是行缓冲区。

当遇到 scanf() 函数时,程序会先检查输入缓冲区中是否有数据:

  • 如果没有,就等待用户输入。用户从键盘输入的每个字符都会暂时保存到缓冲区,直到按下回车键,产生换行符\n,输入结束,scanf() 再从缓冲区中读取数据,赋值给变量。
  • 如果有数据,那就看是否符合控制字符串的规则:
    • 如果能够匹配整个控制字符串,那最好了,直接从缓冲区中读取就可以了,就不用等待用户输入了。
    • 如果缓冲区中剩余的所有数据只能匹配前半部分控制字符串,那就等待用户输入剩下的数据。
    • 如果不符合,scanf() 还会尝试忽略一些空白符,例如空格、制表符、换行符等:
      • 如果这种尝试成功(可以忽略一些空白符),那么再重复以上的匹配过程。
      • 如果这种尝试失败(不能忽略空白符),那么只有一种结果,就是读取失败。
#include <stdio.h>
int main()
{
    int a, b=999;
    char str[30];
    printf("b=%d\n", b);
    scanf("%d", &a);
    scanf("%d", &b);
    scanf("%s", str);
    printf("a=%d, b=%d, str=%s\n", a, b, str);
    return 0;
}

运行结果:

b=999
100 http://c.biancheng.net↙
a=100, b=999, str=http://c.biancheng.net

程序执行到第一个 scanf() 时等待用户输入,从键盘输入100 http://c.biancheng.net,按下回车键,scanf() 匹配到 100,赋值给变量a,同时将内部的位置指针移动到 100 后面。

到了第二个 scanf(),缓冲区中有数据,会直接读取。此时缓冲区中的内容为http://c.biancheng.net↙,即使忽略开头的空格也不是 scanf() 想要的整数,所以匹配失败了,不会给变量 b 赋值,b 的值保持不变,这就是两次输出变量 b 的值相同的原因。

匹配失败意味着不会移动内部的位置指针,此时缓冲区中的内容仍然是http://c.biancheng.net↙。执行到底三个 scanf() 时,它想要一个字符串,这不是正好捡漏吗,把http://c.biancheng.net赋值给 str 就好了。

注意,scanf()、gets() 在读取字符串时会忽略换行符,不会把换行符作为字符串的内容。

scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。

(3)不能忽略空白符的情形:

#include <stdio.h>
int main()
{
    int a = 1, b = 2;
    scanf("a=%d", &a);
    scanf("b=%d", &b);
    printf("a=%d, b=%d\n", a, b);

    return 0;
}

输入示例:

a=99↙
a=99, b=2

输入a=99,按下回车键,程序竟然运行结束了,只有第一个 scanf() 成功读取了数据,第二个 scanf() 仿佛没有执行一样,根本没有给用户任何机会去输入数据。这是为什么呢?

第一个 scanf() 执行完后,将 99 赋值给了 a,缓冲区中只剩下一个换行符\n;到了第二个 scanf(),发现缓冲区中有内容,但是又不符合控制字符串的格式,于是尝试忽略这个空白符。注意,这个时候的空白符是不能忽略的,所以就没有办法了,只能读取失败了。

实测发现,空白符在大部分情况下都可以忽略,前面的两个例子就是这样。但是当控制字符串不是以格式控制符 %d、%c、%f 等开头时,空白符就不能忽略了,它会参与匹配过程,如果匹配失败,就意味着 scanf() 读取失败了。

本例中,第二个 scanf() 的开头并不是格式控制符,而是写死的b字符,所以不会忽略换行符,而换行符和b又不匹配,怎么办呢?没办法,只能读取失败了。

如果我们换一种输入方式呢?

a=99 b=200↙
a=99, b=2

你看,第二个 scanf() 也读取失败了。执行到第二个 scanf() 时,缓冲区中剩下 b=200↙,开头的空格依然不能忽略,然而又和控制字符串不匹配,所以只能读取失败了。

两种输入方式都不行,究竟该如何输入呢?很简单,不要让两份数据之间有空白符,只能像下面一样输入:

a=99b=200↙
a=99, b=200

这样 a 和 b 都能够正确读取了。注意,a=99b=200中间是没有任何空格的。

最后,我们再修改一下上面的代码,将第二个 scanf() 改成下面的样子:

scanf("%d", &b);

运行结果:

a=100↙
200↙
a=100, b=200

此时,第二个 scanf() 的控制字符串以%d开头,就可以忽略换行符了。忽略换行符以后,缓冲区中就没有内容了,所以会等待用户输入。输入 200 以后,第二个 scanf() 就匹配成功了,将 200 赋值给变量 b。

那么,为什么只有当控制字符串以格式控制符开头时,才会忽略换行符呢?我也觉得这个特性很奇怪,目前还未想明白,也没有资料可查,请读者先记住这个结论。

(4) 指定读取长度

还记得在 printf() 中可以指定最小输出宽度吗?就是在格式控制符的中间加上一个数字,例如,%10d表示输出的整数至少占用 10 个字符的位置:

  • 如果整数的宽度不足 10,那么在左边以空格补齐;
  • 如果整数的宽度超过了 10,那么以整数本身的宽度来输出,10 不再起作用。

其实,scanf() 也有类似的用法,也可以在格式控制符的中间加一个数字,用来表示读取数据的最大长度,例如:

  • %2d表示最多读取两位整数;
  • %10s表示读取的字符串的最大长度为 10,或者说,最多读取 10 个字符。

(5)匹配特定的字符

%s 读取到的字符串中不能包含空白符,有些情况会比较尴尬,例如,无法将多个单词存放到一个字符串中,因为单词之间就是以空格为分隔的,%s 遇到空格就读取结束了。

 scanf() 的存在字符匹配方式,就是%[xxx][ ]包围起来的是需要读取的字符集合。例如,%[abcd]表示只读取字符abcd,遇到其它的字符就读取结束;注意,这里并不强调字符的顺序,只要字符在 abcd 范围内都可以匹配成功,所以你可以输入 abcd、dcba、ccdc、bdcca 等。

#include <stdio.h>
int main(){
    char str[30];
    scanf("%[abcd]", str);
    printf("%s\n", str);
    return 0;
}
/*
输入输出示例 ②:
baccbaxyz↙
baccba
*/

为了简化字符集合的写法,scanf() 支持使用连字符-来表示一个范围内的字符,例如 %[a-z]、%[0-9] 等。

注意,连字符左边的 ASCII 码要小于右边的,如果反过来,那么它的行为是未定义的。

常用的连字符举例:

  • %[a-z]表示读取 abc...xyz 范围内的字符,也即小写字母;
  • %[a-zA-Z]表示读取大写字母和小写字母,也即所有英文字母;
  • %[a-z-A-Z0-9]表示读取所有的英文字母和十进制数字;
  • %[0-9a-f]表示读取十六进制数字。

(6)不匹配特定的字符

scanf() 允许我们在%[ ]中直接指定某些不能匹配的字符,具体方法就是在不匹配的字符前面加上^,例如:

  • %[^\n]表示匹配除换行符以外的所有字符,遇到换行符就停止读取;
  • %[^0-9]表示匹配除十进制数字以外的所有字符,遇到十进制数字就停止读取。
#include <stdio.h>
int main(){
    char str1[30], str2[30];
    scanf("%[^0-9]", str1);
    scanf("%*[^\n]"); scanf("%*c");  //清空缓冲区
    scanf("%[^\n]", str2);
    printf("str1=%s \nstr2=%s\n", str1, str2);
    return 0;
}

输入示例:

abcXYZ@#87edf↙
c c++ java python go javascript↙
str1=abcXYZ@#
str2=c c++ java python go javascript

请注意第 6 行代码,它的作用是读取一行字符串,和 gets() 的功能一模一样。你看,scanf() 也能读取带空格的字符串呀,谁说 scanf() 不能完全取代 gets(),这明显是错误的说法。

(7)丢弃读取到的字符

scanf() 允许把读取到的数据直接丢弃,不往变量中存放,具体方法就是在 % 后面加一个*,例如:

  • %*d表示读取一个整数并丢弃;
  • %*[a-z]表示读取小写字母并丢弃;
  • %*[^\n]表示将换行符以外的字符全部丢弃。
#include <stdio.h>
int main(){
    int n;
    char str[30];
    scanf("%*d %d", &n);
    scanf("%*[a-z]");
    scanf("%[^\n]", str);
    printf("n=%d, str=%s\n", n, str);
    return 0;
}

输入示例:

100 999abcxyzABCXYZ↙
n=999, str=ABCXYZ

对结果的分析:整数 100 被第一个 scanf() 中的%*d读取后丢弃了,整数 999 被第%d读取到,并赋值给 n。此时缓冲区中剩下 abcxyzABCXYZ,第二个 scanf() 将 abcxyz 读取并丢弃,剩下的 ABCXYZ 被最后一个 scanf() 读取到并赋值给 str。

scanf("%*[^\n]"); scanf("%*c");

scanf("%*[^\n]");将换行符前面的所有字符清空,scanf("%*c");将最后剩下的换行符清空。

3.2 输入单个字符

(1)getchar()

最容易理解的字符输入函数是 getchar(),它就是scanf("%c", c)的替代品,除了更加简洁,没有其它优势了;或者说,getchar() 就是 scanf() 的一个简化版本。
(2)_getche()

_getche() 就比较有意思了,它没有缓冲区,输入一个字符后会立即读取,不用等待用户按下回车键,这是它和 scanf()、getchar() 的最大区别。注意,_getche() 位于 conio.h 头文件中,而这个头文件是 Windows 特有的,Linux 和 Mac OS 下没有包含该头文件。请看下面的代码:

#include <stdio.h>
#include <conio.h>
int main()
{
    char c = _getche();
    printf("c: %c\n", c);
    return 0;
}

(3)_getch()

_getch() 也没有缓冲区,输入一个字符后会立即读取,不用按下回车键,这一点和 _getche() 相同。getch() 的特别之处是它没有回显,看不到输入的字符。所谓回显,就是在控制台上显示出用户输入的字符;没有回显,就不会显示用户输入的字符,就好像根本没有输入一样。

输入@后,_getch() 会立即读取完毕,接着继续执行 printf() 将字符输出。但是由于 _getch() 没有回显,看不到输入的@字符,所以控制台上最终显示的内容为c: @
注意,和 _getche() 一样,_getch() 也位于 conio.h 头文件中,也不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用。

#include <stdio.h>
#include <conio.h>
int main()
{
    char c = _getch();
    //getch()已经被取代了。
    printf("c: %c\n", c);

    return 0;
}

3.3 输入字符串

gets(),gets() 和 scanf() 的主要区别是:

  • scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
  • gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。

也就是说,gets() 能读取含有空格的字符串,而 scanf() 不能。

四、缓冲区

缓冲区是为了让低速的输入输出设备和高速的用户程序能够协调工作,让快速的 CPU 不必等待慢速的输入输出设备,并降低输入输出设备的读写次数。

用户程序的执行速度可以看做 CPU 的运行速度,如果没有各种硬件的阻碍,理论上它们是同步的。

根据数据刷新(也可以称为清空缓冲区,就是将缓冲区中的数据“倒出”)的时机,可以分为全缓冲、行缓冲、不带缓冲。这种分类才本节要重点讲解的内容。

(1)全缓冲

在这种情况下,当缓冲区被填满以后才进行真正的输入输出操作。缓冲区的大小都有限制的,比如 1KB、4MB 等,数据量达到最大值时就清空缓冲区。

全缓冲的典型代表是对硬盘文件的读写,我们将在《C语言文件操作》一章中深入讲解。

在实际开发中,将数据写入文件后,打开文件并不能立即看到内容,只有清空缓冲区,或者关闭文件,或者关闭程序后,才能在文件中看到内容。这种现象,就是缓冲区在作怪。

(2) 行缓冲

在这种情况下,当在输入或者输出的过程中遇到换行符时,才执行真正的输入输出操作。行缓冲的典型代表就是标准输入设备(也即键盘)和标准输出设备(也即显示器)。

① 在讲解 printf() 时,我们在Linux 或者 Mac OS 平台测试了如下的代码:

#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("C语言中文网");
    sleep(5);  //程序暂停5秒钟
    //Windows下是Sleep(5000);
    //#include<Windows.h>
    printf("http://c.biancheng.net\n");

    return 0;
}

运行程序后,会发现第一个 printf() 并没有立即输出,而是等待 5 秒以后,和第二个 printf() 一起输出了。

究其原因,就是 printf() 带有行缓冲区,"C语言中文网"这几个字符要先放入缓冲区中,而不是立即显示到屏幕上。放入缓冲区以后,程序又暂停了 5 秒,然后执行第二个 printf(),又将"http://c.biancheng.net\n"放入了缓冲区。注意最后的换行符\n,它会使得缓冲区刷新,将缓冲区中的所有内容都输出到显示器上,所以我们才看到两个 printf() 一起输出。

Windows平台下printf不带和缓冲区的。

② 对于 scanf(),不管用户输入多少内容,只要不按下回车键,就不进行真正的读取。这是因为 scanf() 是带有行缓冲的,用户输入的内容会先放入缓冲区,直到用户按下回车键,产生换行符\n,才会刷新缓冲区,进行真正的读取。

(3) 不带缓冲

不带缓冲区,数据就没有地方缓存,必须立即进行输入输出。

getche()、getch() 就不带缓冲区,输入一个字符后立即就执行了,根本不用按下回车键。

Windows下printf()、puts()、putchar()等输出函数不带缓冲区,直接输出。

补充特殊定义:

C语言标准规定,输入输出缓冲区要具有以下特征:

  • 当且仅当输入输出不涉及交互设备时,它们才可以是全缓冲的。
  • 错误显示设备不能带有缓冲区。
  • 行缓冲遇到换行符\n时会刷新;

上面提到的 perror() 其实就是向错误显示设备上输出信息,但是现代计算机已经把显示器作为了错误显示设备,所以 perror() 也是向显示器上输出内容。

五、清空缓冲区

5.1 清空输出缓冲区

fflush(stdout);Linux和Mac OS平台下测试,Windows平台不需要(因为输出函数没有缓冲区):

#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("C语言中文网");
    fflush(stdout);  //本次输出结束后立即清空缓冲区
    sleep(5);
    printf("http://c.biancheng.net\n");
   
    return 0;
}

5.2 清空输入缓冲区

(1) 使用 getchar() 清空缓冲区

getchar() 是带有缓冲区的,每次从缓冲区中读取一个字符,包括空格、制表符、换行符等空白符,只要我们让 getchar() 不停地读取,直到读完缓冲区中的所有字符,就能达到清空缓冲区的效果。请看下面的代码:

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

举例应用:

#include <stdio.h>
int main()
{
    int a = 1, b = 2;
    char c;
    scanf("a=%d", &a);
    while((c = getchar()) != '\n' && c != EOF); //在下次读取前清空缓冲区
    scanf("b=%d", &b);
    printf("a=%d, b=%d\n", a, b);
   
    return 0;
}

(2) 使用 scanf() 清空缓冲区

scanf() 还有一种高级用法,就是使用类似于正则表达式的通配符,这样它就可以读取所有的字符了,包括空格、换行符、制表符等空白符,不会再忽略它们了。并且,scanf() 还允许把读取到的数据直接丢弃,不用赋值给变量。

请看下面的语句:

scanf("%*[^\n]"); scanf("%*c");

(1)和(2)两种清空输入缓冲区的方案是通用的,任何平台下都适用。

七、C语言模拟密码输入(显示星号)

八、C语言非阻塞式键盘监听

就是在用户不输入的时候程序也可以显示东西,具体对比一下下面的两个代码:

#include <stdio.h>
#include <conio.h>
int main(){
    char ch;
    int i = 0;
    //循环监听,直到按Esc键退出
    while(ch = getch()){
        if(ch == 27){
            break;
        }else{
            printf("Number: %d\n", ++i);
        }
    }
    return 0;
}
#include <stdio.h>
#include <windows.h>
#include <conio.h>
int main(){
    char ch;
    int i = 0;
    //循环监听,直到按Esc键退出
    while(1){
        if(kbhit()){  //检测缓冲区中是否有数据
           /*
            kbhit() 函数会检测缓冲区中是否有数据,如果有的话就返回非 0 值,没有的话就返回 0 值。
            但是 kbhit() 不会读取数据,数据仍然留在缓冲区
            */
            ch = getch();  //将缓冲区中的数据以字符的形式读出
            if(ch == 27){
                break;
            }
        }
        printf("Number: %d\n", ++i);
        Sleep(1000);  //暂停1秒
    }
    return 0;
}


每次循环,kbhit() 会检测用户是否按下某个键(也就是检测缓冲区中是否有数据),没有的话继续执行后面的语句,有的话就通过 getch() 读取,并判断是否是 Esc,是的话就退出循环,否则继续循环。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值