C语言学习(五)字符输入、输出和输入验证

参考书:《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. 练习

  1. 设计程序统计文件的字符数。

    #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;
}
  1. 统计大小写字母的个数

    #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
    
  2. 统计平均每个单词的字母数

    #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.
    
  3. 猜数字程序,使用二分查找策略。

    #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。
    按任意键关闭此窗口. . .
    
    1. 编写一个程序,显示提供加减乘除的菜单。

      #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。
      按任意键关闭此窗口. . .
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值