参考书:《C Primer Plus》第六版
1. 单字符I/O,缓冲区
首先可以看一下程序清单1:
#include<stdio.h>
int main(void){
char ch;
while((ch=getchar())!='#')
putchar(ch);
return 0;
}
运行,然后我们再控制台任意输出一些字符,中间穿插#
字符可以看一下运行结果
asdfasdfas#asdfasd
asdfasdfas
C:\Users\xhh\Source\Repos\cPrimerPlus_study\Debug\cPrimerPlus_study.exe (进程 51388)已退出,代码为 0。
按任意键关闭此窗口. . .
这是一个非常简单的程序,但其中有值得思考的一个问题。程序中有一个while
循环,我们直观的理解是:它每次循环读取一个输入的字符,如果这个字符不是#
字符就打印这个字符,否 则程序结束。但实际运行结果是我们可以在控制台任意输入一连串的字符,然后按Enter
键控制台会立即输出我们输入的字符串直到碰到#
字符,如果没有#
我们还可以接着输入字符。我们要理解这个结果首先要理解缓冲区的概念。
缓冲区:
大部分系统在用户按下Enter
键之前不会重复打印刚输入的字符,这种输入形式为缓冲输入。用户输入的字符被收集在一个称为缓冲区的临时存储区,按下Enter
后程序才可以使用用户输入的字符。当然也有无缓冲输入,这样的话程序可以理解使用刚输入的字符。
缓冲区的存在是必要的,试想一下,我们在输入一串字符时,如果输错了某个字符,缓冲区的存在使我们可以在按下Enter
前回过头修改前面输入的字符,如果没有缓冲区的话就没法修改。
当然,在有的时候我们也需要无缓冲输入,这样我们可以通过某个按键使程序立即执行相应的操作。
缓冲分为:完全缓冲I/O和行缓冲I/O。完全缓冲指当缓冲区被填满时才刷新缓冲区,通常出现在文件输入中。行缓冲指的是出现换行符时刷新缓冲区。键盘输入一般是行缓冲。
2.结束键盘输入
对于我们前面的那个程序,不断读取输入直到碰到#
字符,如果我们需要用到#
字符时需要考虑怎么输入这个字符同时又不让程序结束。我们先了解一下C处理文件的方式。
从概念上看,C程序处理的是流而不是直接处理文件。打开文件的过程就是把流和文件相关联。而且读写都通过流来完成。
C把输入和输出设备视为存储设备上的普通文件,把键盘和显示设备视为每个C程序自动打开的文件。stdin
流表示键盘输入,stdout
流表示屏幕输出。getchar()
、putchar()
、printf()
、scanf()
函数都是标准I/O包的成员,处理这两个流。
一个计算机系统必定要以某种方式来判断文件的开始和结束。检测文件结束的一种方法是在文件末尾放一个特殊的 字符作为标识,这样我们访问文件时碰到这个字符就知道文件结束了。CP/M、IBM-DOS和MS-DOS的文本文件曾采用这种方法,现在这些操作系统可以使用内嵌的Ctrl+Z
字符来表示文件结尾。
另一种方法是存储文件大小的信息,如文件有3000字节,那么程序在读到300字节时就达到文件的末尾。MS-DOS及其相关系统使用这种方法处理二进制文件,用这种方法可以在文件种存储所有的字符。新版DOS也用这种方法,UNIX用这种方法处理所有的文件。
不管采用何种方式,C语言中,用getchar()读取文件检测到文件结尾时会返回一个特殊的值:EOF
(end of file)。scanf()函数同样。EOF
定义在stdio.h
中。#define EOF (-1)
之所以是-1
是由于-1不对应任何字符。
某些系统可能把EOF
定义为其它的值,只要不和输入字符冲突就没有任何的问题。就是作为一个标志。
下面这个程序清单2就是一个简单示例:
#include<stdio.h>
int main(void){
char ch;
while((ch=getchar())!=EOF)//判断是否到达文件结尾
putchar(ch);
return 0;
}
输出
asdfjljadfksjjlj
asdfjljadfksjjlj
asdf^D
asdf
afdslfj^S^D^Z
afdslfjas
as
asdflkjl^Z
asdflkjl
^Z
C:\Users\xhh\Source\Repos\cPrimerPlus_study\Debug\cPrimerPlus_study.exe (进程 54108)已退出,代码为 0。
按任意键关闭此窗口. . .
在大多数UNIX和Linux系统中,在一行开始处按下Ctrl+D
会传输文件结尾信号,而PC中是Ctrl+Z
表示文件结束。
3. 重定向和文件
重定向输入
了解了这些后,我们可能会想我们用getchar()
函数时,输入设备是键盘,输入数据流是由字符组成,这些都没问题,如果我们让程序改变输入流的源头,就是我们如果将某个文件作为输入流会怎么样?我们要怎么做到?
默认情况下,C程序使用标准I/O包查找标准输入作为输入流,即stdin
流。如今计算机这么灵活,自然可以让一个程序从各种地方如文件查找输入,可以不是从键盘。
程序一般可以通过两种方式使用文件:1)显示使用特定的函数打开文件、关闭文件、读取文件、写入文件等,这个方法我们暂且不谈。2)设计能与键盘和屏幕互动的程序,通过不同的渠道重定向输入至文件和从文件输入。也就是把stdin
流重新赋给文件,这样可以用getchar()
函数从输入流获取数据。这种方法存在一些限制,但它用起来比较简单,且有必要了解其中的文件处理技术。
重定向的一个问题是它与操作系统有关。
这里要实践的话在Linux环境下应该是最好的,Linux的终端下可以用Vim写入C语言源文件,然后用make
指令生成,然后直接执行。 要在Window下用Linux终端也非常简单,直接在微软的应用商店下搜索ubuntu
然后下载安装,启动就是一个类似于CMD的窗口。
(关于Linux终端的一些指令我就不多作介绍,本身我也在一点一点学,由于平时需要用到Linux的情况很少,自己也没什么探索精神,所以也不急着学Linux,感觉Linux还是很好玩的。)
此时,需要安装一些包,对于ubuntu来说,可以用下面命令:
$ sudo apt-get install build-essential
简单指令如ls
是列出当前位置的文件列表,这个不难记住。
然后需要记住一些指令如:mkdir
创建文件夹、cp
拷贝文件、rm
删除文件、mv
移动文件。
在linux终端中输入
vim echoEof.h
echoEof.h
为我们要用到的C语言源文件,代码见程序清单2。
在编辑模式下输入源代码,然后按Esc
键输入:wq
然后按Enter
键,回到指令模式,输入
make echoEof
这时输入
./echoEof
就会直接执行我们这个程序。但我们这里重点不是怎么去执行程序,我们这里重定向输入,我们可以用这个程序打印同文件夹下的某个文本文件,如
xhh@DESKTOP-202JI6K:~$ ./echoEof < hello.c
#include "stdio.h"
int main(void){
char c;
while((c=getchar())!=EOF)
putchar(c);
}
//
输入的指令是
./echoEof < hello.c
<
符号为UNIX和DOS/Windows的重定向运算符。它使目标文件和stdin
流相关联,把文件中的内容导入echoEof
程序。这个有点意思。Windows
下也不是不行,但要先用Visual studio 编译C语言源代码然后在文件管理器中找到exe文件的路径,在CMD中用cd
指令定位到exe文件路径下,然后简单的用指令
cPrimerPlus_study.exe < test.txt
和前面的Linux命令同样的道理,CMD中输入可执行文件的文件名是直接执行程序。
重定向输出
既然可以重定向输入,也就可以重定向输出,将键盘输入的内容发送到特定文件中。Linux终端的指令:
xhh@DESKTOP-202JI6K:~$ ./echoEof >hello.c
are you ok?
xhh@DESKTOP-202JI6K:~$
xhh@DESKTOP-202JI6K:~$ ./echoEof <hello.c
are you ok?
>
符号是第二个重定向运算符。它把程序的输出重定向到目标文件。如果目标文件为已有文件,这样做会擦除源文件的内容,然后替换新的内容(我们通过键盘输入的内容)。你可以一直输入字符串最后一行的句首输入Ctrl+D
结束程序。
组合重定向
同时重定向输入和输入的效果是创建一个输入文件的副本(拷贝)。
xhh@DESKTOP-202JI6K:~$ ./echoEof<hello.c >newCopy.txt
xhh@DESKTOP-202JI6K:~$ ./echoEof >newCopy <hello.c
这时需要遵守以下原则:
- 重定向运算符只能用来连接一个可执行程序和一个数据文件,而不能用来连接一个数据文件和另一个数据文件,或者一个程序和另一个程序。
- 重定向运算符不能读取多个输入,也不能把输出顶向到多个文件。
- 通常运算符和文件名间的空格不是必须的。
4. 创建更友好的用户界面
我们有时需要混合数值和字符的输入,即同时使用getchar()
和scanf()
来处理字符和数值的输入。这时稍不注意就会犯一些错误如:
#include<stdio.h>
void display(char ch,int lines,int width){
int row,col;
for(row=1;row<=lines;row++){
for(col=1;col<=width;col++){
putchar(ch);
}
putchar('\n');
}
}
int main(void){
int ch;
int row,col;
printf("Enter a character and two integers:\n");
while((ch=getchar())!='\n'){
scanf_s("%d,%d",&row,&col);
display(ch,row,col);
printf("Enter another character and two integers;\n");
printf("Enter a newline to quit.\n");
}
printf("Bye.\n");
return 0;
}
输出
Enter a character and two integers:
c 2 3
Enter another character and two integers;
Enter a newline to quit.
Enter another character and two integers;
Enter a newline to quit.
Bye.
C:\Users\xhh\Source\Repos\cPrimerPlus_study\Debug\cPrimerPlus_study.exe (进程 57316)已退出,代码为 0。
按任意键关闭此窗口. . .
修改:
#include<stdio.h>
void display(char cr,int lines,int width){
int row,col;
for(row=1;row<=lines;row++){
for(col=1;col<=width;col++){
putchar(cr);
}
putchar('\n');
}
}
int main(void){
int ch,cr='!';
int row,col;
printf("Enter a character and two integers:\n");
while((ch=getchar())!='\n'){
if(scanf_s("%d %d",&row,&col)!=2)//scanf成功读取两个数值则返回2
break;
display(ch,row,col);
while (getchar()!='\n')
continue;
printf("Enter another character and two integers;\n");
printf("Enter a newline to quit.\n");
}
printf("Bye.\n");
return 0;
}
其中我们通过scanf()
函数的返回值来判断是否成功读取两个数值。
输出
Enter a character and two integers:
c 4 4
cccc
cccc
cccc
cccc
Enter another character and two integers;
Enter a newline to quit.
@ 3 4
@@@@
@@@@
@@@@
Enter another character and two integers;
Enter a newline to quit.
Bye.
C:\Users\xhh\Source\Repos\cPrimerPlus_study\Debug\cPrimerPlus_study.exe (进程 41200)已退出,代码为 0。
按任意键关闭此窗口. . .
5. 练习
-
设计程序统计文件的字符数。
#include<stdio.h> int main(void){ char c; int num=0; while((c=getchar())!=EOF) num++; printf("总共有%d个字符。\n",num); return 0; }
在Linux终端中输入
xhh@DESKTOP-202JI6K:~$ make echoEof cc echoEof.c -o echoEof xhh@DESKTOP-202JI6K:~$ ./echoEof <filetxt 总共有878个字符。
#include<stdio.h>
#include<ctype.h>
int main(void){
char ch;
while((ch=getchar())!=EOF){
if(ch=='\n')
printf("\\n:%d",ch);
if(ch=='\t')
printf("\\t:%d",ch);
if(isprint(ch))
printf("%c:%d",ch,ch);
else
printf("^%c:%d",ch+63,ch);
}
return 0;
}
-
统计大小写字母的个数
#include<stdio.h> #include<ctype.h> int main(void){ char ch; int upnum,lownum; while((ch=getchar())!=EOF){ if(isupper(ch)) upnum++; if(islower(ch)) lownum++; } printf("大写字母的个数为:%d",upnum); printf("\n小写字母的个数为:%d\n",lownum); return 0; }
xhh@DESKTOP-202JI6K:~$ make demo3 cc demo3.c -o demo3 xhh@DESKTOP-202JI6K:~$ ./demo3 <filetxt 大写字母的个数为:25 小写字母的个数为:669
-
统计平均每个单词的字母数
#include<stdio.h> #include<ctype.h> int main(void){ char ch; int numOfWord=0,numOfChar=0; _Bool isWord=0; while((ch=getchar())!=EOF){ if(isalpha(ch)){ numOfChar++; isWord=1; }else if(isWord){ numOfWord++; isWord=0; } } double len=(double)numOfChar/(double)numOfWord; printf("字母数:%d,单词数:%d,平均每个单词的长度:%lf",numOfChar,numOfWord,len); return 0; }
xhh@DESKTOP-202JI6K:~$ make demo4 cc demo4.c -o demo4 xhh@DESKTOP-202JI6K:~$ ./demo4 <filetxt 字母数:694,单词数:151,平均每个单词的长度:4.596026.
-
猜数字程序,使用二分查找策略。
#include<stdio.h> #include<ctype.h> int main(void){ //猜数字程序 int guess=50,up=100,down=0; printf("Pick an integer from 1 to 100. I will try to guess it.\n"); printf("Respond with a l if my guess is larger and with a s if my guess is smaller"); printf("and with a y if my guess is right.\n"); printf("Ok,I guess your number is %d\n",guess); char ch; while((ch=getchar())!='y'){ if(ch=='l'){ down=guess; guess=(guess+up)/2; printf("Well,then,is it %d\n",guess); }else if(ch=='s'){ up=guess; guess=(guess+down)/2; printf("Well,then,is it %d\n",guess); }else if(ch!='\n'){ printf("Invalid Enter\n"); } } printf("I knew I could do it!\n"); return 0; }
输出
Pick an integer from 1 to 100. I will try to guess it. Respond with a l if my guess is larger and with a s if my guess is smallerand with a y if my guess is right. Ok,I guess your number is 50 l Well,then,is it 75 l Well,then,is it 87 s Well,then,is it 81 s Well,then,is it 78 s Well,then,is it 76 l Well,then,is it 77 y I knew I could do it! C:\Users\xhh\Source\Repos\cPrimerPlus_study\Debug\cPrimerPlus_study.exe (进程 1120)已退出,代码为 0。 按任意键关闭此窗口. . .
-
编写一个程序,显示提供加减乘除的菜单。
#include<stdio.h> #include<ctype.h> void printTable(){ printf("Enter the operation of your choice:\n"); printf("a. add s. subtract\n"); printf("m. multiply d. divide\n"); printf("quit\n"); } float getf(){ float f; char ch; while(scanf_s("%f",&f)!=1){ if((ch=getchar())!='\n') printf("Please enter an number,such as 2.5,-1.78E8,or 3: "); } return f; } int main(void){ //猜数字程序 char ch; printTable(); float fir,sec; while((ch=getchar())!='q'){ switch (ch) { case 'a': printf("Enter first number: "); fir=getf(); printf("Enter second number: "); sec=getf(); printf("%f + %f = %f\n",fir,sec,fir+sec); break; case 's': printf("Enter first number: "); fir=getf(); printf("Enter second number: "); sec=getf(); printf("%f - %f = %f\n",fir,sec,fir-sec); break; case 'm': printf("Enter first number: "); fir=getf(); printf("Enter second number: "); sec=getf(); printf("%f * %f = %f\n",fir,sec,fir*sec); break; case 'd': printf("Enter first number: "); fir=getf(); printf("Enter second number: "); sec=getf(); while(sec==0){ printf("Enter a number other than 0: "); sec=getf(); } printf("%f / %f = %f\n",fir,sec,fir/sec); break; default: break; } if(ch!='\n') printTable(); } printf("Bye.\n"); return 0; }
输出
Enter the operation of your choice: a. add s. subtract m. multiply d. divide quit a Enter first number: 22 Enter second number: 33 22.000000 + 33.000000 = 55.000000 Enter the operation of your choice: a. add s. subtract m. multiply d. divide quit s Enter first number: 12 Enter second number: 32 12.000000 - 32.000000 = -20.000000 Enter the operation of your choice: a. add s. subtract m. multiply d. divide quit m Enter first number: 22.2 Enter second number: 33.3 22.200001 * 33.299999 = 739.260010 Enter the operation of your choice: a. add s. subtract m. multiply d. divide quit d Enter first number: 23 Enter second number: 0 Enter a number other than 0: 12 23.000000 / 12.000000 = 1.916667 Enter the operation of your choice: a. add s. subtract m. multiply d. divide quit q Bye. C:\Users\xhh\Source\Repos\cPrimerPlus_study\Debug\cPrimerPlus_study.exe (进程 1016)已退出,代码为 0。 按任意键关闭此窗口. . .
-