从函数指针数组的运用来看程序结构化设计

一、基础研究

我们之前从底层研究了c语言,也学习了c语言的组成部分和使用方法,但是c语言是一门编程语言,它是要用来编写程序的。要编写一个好的程序,首先要弄清这个程序要干什么,也就是需求分析,之后要思考及确定实现这个目标的方法,怎么样让程序简短、高效、易懂、可移植、方便维护和修改,这涉及到要怎么实现算法、怎样放置数据和代码、怎么写函数、怎么调用函数等等,这就是程序设计。程序设计是一个程序员综合实力的体现,要想设计出好的程序,要有程序设计思想。

我们来看程序1,程序的功能是:可以接收用户依次输入,一个字符串a、一个字符ch、一个字符串b,这些内容分别描述一个整型数据、一个运算符、一个整型数据,然后根据字符ch所描述的运算符如:“+”、“-”,对字符串ab的描述数据进行运算,将运算结果显示出来。

 

程序的执行结果如下:

 

这个程序的功能是用户先输入一个数,按回车后再输入加号或减号,再输入一个数,然后输出结果。这个功能有有几个重点:(1)输入方式,用什么函数输入(2)输入的字符串怎么转换成一个数输出(3)怎么进行输入检验。我们来看看上面的程序是怎么实现的:

(1)对于字符串使用gets()函数输出,对于符号使用getch()函数输出,因为c语言里没有字符串类型,所以这里定义的是两个字符数组,在这里定义的数组长度为20,也就是说用户最多能输入长度为20个字节的数据。Gets()函数可以无限读取,不会判断上限,以回车结束读取。Getch()函数可以从控制台读取一个字符,但是不显示在屏幕上,但是我们希望能把输入的符号也显示出来,所以程序在这里使用了一个printf()函数输出getch()函数得到的字符,其实这里也可以使用getche()函数,它是可以自动将输入的字符显示在屏幕上的。

(2)程序里是用atoi()函数处理输入的字符串数组的,它的功能是把字符串转换成整型数,它的参数是const char *,如果第一个非空格字符存在,是数字或者正负号则开始做类型转换,之后检测到非数字(包括结束符 \0) 字符时停止转换,返回整型数。否则,返回零。因为数组名可以看成一个指针,所以这里的类型是对应的。但是如果用户输入的包含不是数字的字符,那么转换的结果是0,但是这里没有对这种错误的检验和报错。

(3)程序对第二个输入的变量进行了判断,如果输入的字符不是加号和减号,那么会输出错误提示“error!”然后返回程序。

 

整个程序的逻辑就是先定义数组和变量,再输入,再判断输入,再输出。所以这里运算符的地位都是平等的,要将程序扩充为识别“*”、“/”,要在判断和输出里加上“*”、“/”,再在输出语句里实现相关算法。修改的程序如下:

 

如果要写错误判断,可以用循环判断数组ab里的每个字符,如果为字母,则输出错误提示。

 

再来看第二个程序:它功能与程序1的相同:

 

程序二是定义了两个运算函数addsub,分别实现将两个数相加和将两个数相减的功能。又定义了一个函数指针数组存放这两个函数的地址。这样可以通过数组来选择并调用函数。另外我们定义了一个字符指针code,并初始化为“+-”,然后在main函数里用for语句判断输入的符号是加号还是减号,如果是加号,则for语句结束后n的值为0,如果是减号,则for语句结束后n的值为1。但是这里有个问题:如果我们输入的符号不是加号或减号,那么for语句怎么跳出呢?我发现把for语句里的第一个code[n]去掉之后,程序就无法进行下面if语句的判断了,是不是code[n]指的是循环中n的值最大只能达到数组的长度呢?我们写一个程序来验证一下:

 

输出结果为:

 

 

果然,这样写可以限制循环的次数在数组的长度以内,那么这是为什么呢?因为之前我觉得有问题是p[i]指向的是内存的值,所以如果字符串之后的内存如果存储了别的数据判断就会出错。但是我忽略了字符串后面会加一个转义字符“\0,即数字0,所以这时code[i]0,即为false,所以循环会停止,而!Code[n]1,则if语句里判断能通过,所以会输出错误信息“error!”。

如果输入是加号或者减号,则不会执行if语句里的语句。调用函数指针数组func[c](atoi(a),atoi(b));根据n的值可以调用实现相加或相减的函数,然后输出函数返回的值即运算的结果。

那么我们根据程序的实现思路,如果要增加操作符,需要写一个实现该操作的函数,并将它加入到函数指针数组中,这样我们就可以通过函数指针数组来调用函数实现功能。修改后的程序如下:

 

执行的结果如下:

 

对比程序1和程序2,可以发现程序1的语句都是在main函数里,它使用的方法比较简单,但是要增加操作符比较麻烦,要修改程序需要再增加输出语句并修改判断方法,而程序2是用子函数来实现一个单独的功能,并使用函数指针来进行函数的调用,使用函数指针数组来管理函数,这样所有的输入、输出、判断语句都在main函数中,关于算法的语句都在子函数里,如果我们要增加新的操作符,只需要写一个新的子函数并在函数指针数组里将函数名写入,在字符指针里加入要添加的操作符,不需要修改main函数。

这样可以看出两个函数的设计思想是不同的,程序1比较符合人的思维,即定义、输入、判断、输出,这样集成度高,思路清晰,易于实现,但是如果对于比较复杂的程序会造成难以修改和维护的情况,程序2采用模块化的程序设计思想,用到了比较高级的方法,将功能模块化,而且用数组进行管理更加有序,便于修改和维护,有更强大的扩展性和通用性。

与程序1相比,程序2中的共性实现在main函数里,即输入、判断、输出的功能,个性实现在子函数,即addsub函数里。它的模块化的设计思想对于大型程序的开发、管理和维护有十分重要的作用,对于程序设计和优化有很强的指导意义。

二、扩展研究

有人将程序设计总结成了一些原则,我们来看看这些原则是什么:

(1)“开-闭”原则:我们设计的程序应当在不被修改的情况下进行扩展,即要做到当增加新功能时不修改原来的程序,只增加新的函数或子程序。上面的程序2就符合这个原则。

(2)迪米特法则:又叫做最少知识原则,即一个子程序最好与其他子程序有最少的沟通和了解,同样的,一个函数最好尽可能少的调用其他函数或者使用全局变量。

(3)单一职责原则:一个函数应该尽可能地完成单一的工作。

 

还有一些原则是针对高级语言总结的,这里就不做探讨了。

三、研究总结

我们现在写的程序都是一些小程序,调试和修改都比较容易,所以比较少涉及到程序设计思想。当我们开发大型程序时,程序设计思想就比较重要了。所以我们平时一定要注意培养好的程序设计习惯和正确的程序设计思想。好的设计思路可以减少我们犯错的频率和调试的难度。

posted @ 2015-03-24 20:56  stormpeach  阅读(823)  评论(0编辑  收藏  举报