摘要:统计单词个数的程序是一个经典的C语言程序,传统的写法存在结构复杂、可读性差的不足。通过对相邻的两个字符同时进行判断,即可简化程序结构,改善程序的可读性。
关键词:单词个数;标记变量;结构性;可读性
中图分类号:TP312 文献标识码:A
文章编号:1009-3044(2020)36-0071-02
培养学生良好的编程能力是程序设计课程的核心目标[1],而编程能力的培养离不开一个个具体的案例程序。
在教学中选用优质的案例程序,对于培养学生的程序设计能力具有重要的示范引导作用[2-3],因此应该选择更加合理有效的程序实现方法。不过,在当前的C语言程序设计教材中,仍有个别案例程序的写法存在改进优化的空间。
1 统计单词个数的传统程序
“统计一个字符串中单词的个数(假定单词之间以空格分隔)”是C语言程序设计中的一个经典程序,是字符串处理部分的一个重要案例[4-5]。
下面的程序是在许多C语言教材中采用的一种传统写法。
源程序1:
#include
#define IN 1 /*表示在一个单词内部*/
#define OUT 0 /*表示在一个单词外部*/
int main(void)
{
char c;
int num,state;
state=OUT; /*初始状态位于单词外部*/
num=0;
while((c=getchar())!='\n')
{
if(c==' ')
state=OUT;
else if(state==OUT)
{
state=IN;
num++;
}
}
printf("单词个数=%d\n",num);
return 0;
}
这是一个经典的程序,同时也是一个比较晦涩难懂的程序。由于第一个单词之前可能有空格,同时两个单词之间也可能有多个空格,因此不能简单地通过统计空格的个数以得到单词的个数。该程序的编程思路是依据相邻的两个字符进行判断,当相邻的两个字符中,前一个是空格符(即处于单词外部),而后一个是非空格字符(即处于单词内部)时,说明找到一个新的单词的开头,从而将单词的个数加一。
由于在每次循环中只对当前读入的一个字符进行判断,因此在读入下一个字符之前,必须将当前字符对应的状态记录到标记变量state中。若当前字符是空格符(即处于单词外部),则state取值为OUT;若当前字符是非空格字符(即处于单词内部),则state取值为IN。当然,在读入任何字符之前,state也应该取值为OUT,即处于单词外部。
从而在读入新字符时,若state的值为OUT,则说明前一个字符是空格符;若state的值为IN,则说明前一个字符是非空格字符。因此,当读入的新字符是非空格字符,而state的值为OUT(即前一个字符是空格符)时,说明找到一个新的单词的开头。
这种写法之所以结构复杂、可读性差,不容易为初学者理解和掌握,其主要原因就在于每次循环只对当前一个字符进行判断,而不是对相邻的两个字符同时进行判断。
2 改进的程序
如何改进这个程序呢?其实,只需要利用两个字符变量存储相邻的两个字符,并在循环体中同时对相邻的两个字符的值进行判断,就可以取消标志变量state,从而降低程序的复杂度。
为了便于编写程序,可以设想在整个字符串之前添加一个空格,这并不影响单词个数的统计结果。
下面是改进之后的C语言源程序。
源程序2:
#include
int main(void)
{char c0,c;
int num;
num=0;
c0=' '; /*设想在整个字符串之前添加一个空格*/
while((c=getchar())!='\n') /*输入一个字符并存入变量c中*/
{
if(c0==' ' && c!=' ') /*相邻的两个字符中,前一个是空格符,后一个是非空格字符*/
num++; /*說明找到一个新的单词的开头,将单词的个数加一*/
c0=c; /*将变量c的值转存到变量c0中,为输入下一个字符做好准备*/
}
printf("单词个数=%d\n",num);
return 0;
}
程序运行结果:
在该程序中,利用两个字符变量c0和c存储相邻的两个字符,其中c0存储前一个字符,c存储后一个字符。这样,只需要同时对c0和c的值进行判断,即可确定是否找到一个新的单词。也就是当c0的值是空格符,而c的值是非空格字符时,说明找到一个新的单词的开头,从而将单词的个数加一。
可以发现,经过改进之后,程序在结构性和可读性方面均有较大程度的优化,从而降低了程序的复杂度,提高了学习者的接受度。
3 利用字符数组实现的程序
在前面的程序中,利用getchar函数逐个输入字符,利用两个字符变量的值不断交替,存储相邻的两个字符。
还有一种处理方式,就是利用一个字符型数组存储字符串。由于在第一个单词之前有可能没有空格符,因此若仍然采用查找一个单词的开头的方式,实现起来将会不甚方便。不过,可以变换一下思路,改为查找一个单词的末尾。
可以发现,在除了最后一个单词之外的每个单词之后至少有一个空格符,而在最后一个单词之后可能跟一个空格符,也可能直接跟一个空字符'\0',因此可以将判断规则修改为“当相邻的两个字符中,前一个是非空格字符,而后一个是空格符或空字符'\0'时,说明找到一个新的单词”[6]。
下面是利用字符数组实现的C语言源程序。
源程序3:
#include
#include
int main(void)
{char a[200];
int i,n,c=0;
printf("请输入一行以空格分隔的单词:\n");
gets(a);
n=strlen(a);
for(i=0;i<=n-1;i++)
{if(a[i]!=' '&&(a[i+1]==' '||a[i+1]=='\0'))
c++;
/*若第i个字符不是空格符,第i+1个字符是空格符或'\0',则表示找到一个单词的末尾*/
}
printf("单词个数=%d\n",c);
return 0;
}
可以发现,相对于前面的程序,利用字符型数组统计单词个数的程序在结构性和可读性方面均有进一步的改进。
4 进一步改进的程序
在前面的程序中均假定单词之间是以空格符分隔的,而在现实中单词之间也可以用标点符号分隔,如何应对这个问题呢?其实,只需要对源程序3稍加修改,就可以适应这种新的要求。
由于单词之间是以空格符或标点符号分隔的,而标点符号是不便于一一判断区分出来的,因此可以通过判断一个字符是不是字母或数字来确定是否处于单词内部。也就是当相邻的两个字符中,前一个是字母或数字(即处于单词内部)而后一个不是字母或数字(即处于单词外部)时,说明找到一个新的单词的末尾[6]。要判断一个字符是不是字母或数字,可以直接调用C语言中的isalnum库函数。
从而可以写出如下进一步改进的源程序。
源程序4:
#include
#include
#include
int main(void)
{char a[200];
int i,n,c=0;
printf("请输入一行以空格或标点分隔的单词:\n");
gets(a);
n=strlen(a);
for(i=0;i<=n-1;i++)
{if(isalnum(a[i])&&!isalnum(a[i+1]))
c++;
/*若第i个字符是字母或数字,第i+1个字符不是字母或数字,则表示一个单词结束*/
}
printf("单词个数=%d\n",c);
return 0;
}
程序运行结果:
5 结论
通过以上程序的改进过程可以发现,只要不迷信于教材中的经典案例,勇于改进,勇于创新,就能够向学生传授更加科学合理的知识和技能。而这个案例的改进过程本身,也是对学生进行创新教育的一个很好的样板。
参考文献:
[1] 陈涛. 面向编程能力培养的C语言教学模式研究[J].計算机教育,2020(1):100-103.
[2] 薛小锋.案例教学在非计算机专业“C语言程序设计”教学中的应用[J].江苏技术师范学院学报,2010(4):80-82,88.
[3] 丁海燕.高级语言程序设计案例教学模式的探讨[J].计算机教育,2011(8):65-68.
[4] 谭浩强.C程序设计[M].5版.北京:清华大学出版社,2017.
[5] 田淑清.全国计算机等级考试二级教程——C语言程序设计(2016年版)[M].北京:高等教育出版社,2015.
[6] 巨同升.C语言程序设计新思路[M].北京:科学出版社,2020.
【通联编辑:王力】