【摘要】在C程序设计中,必须坚决执行变量、函数先定义,后使用的原则。在定义时务必做到,首先根据估算的可能发生的最大或最小值、充分考虑将要发生的各种运算,正确选择变量函数的数据类型,以避免数据溢出及运算异常。其次根据变量的作用域和生命历程,选择合理的存储模式,以优化程序,提高程序运行效率。
【关键词】合理定义;数据类型;存储模式;正确编程
1.一个简单程序带来的深层思考
有位学生在学习Turbo C语言程序设计时,根据圆周率的级数展开式:
这一程序,编译、连接时连警告信息也没有顺利通过,然而在运行时区发生了意想不到的错误结果。计算后输出的结果并非事先人们已经知道的3.141593,而是显示:
pi=2.000000
这说明,程序中存在软错误,即逻辑错误、数据类型错误、运算错误等。这位学生反复检查,却并未发现有数学运算方面的问题,找不到错误出现的原因。无奈之下,只好求助于老师。
老师看后告诉他:主函数完全正确,但两个子函数fact()、multi()及各函数体内的局部变量p的数据类型不对。不能把它们定义为int型,而应定义为double型。
学生根据老师的提示进行了修改,果然得到了正确的结论。但尚不明白为什么这么小小的修改,就会产生完全不同的结果。老师启发他,认真看书,务必搞清楚定义变量、函数时,正确选择数据类型的重要意义。
2.正确定义变量的数据类型是避免错误的有效方法之一
在C语言程序设计中,始终必须坚持一个非常行重要的原则,即变量必须“先定义而后使用”。变量的定义格式是:
数据类型 变量名1[=值1][,变量名2[=值2]……];
C语言的数据类型分为基本型、构造型、指针型、空型。他们决定了变量在内存中的存储形式、值域及所允许的运算。
2.1 数值类数据的机内存储形式
在C语言中基本数据类型分为:数值型、字符型、枚举型三大类。其中数值型数据又分为:整型、实型两个子类。
(1)整型数据以补码形式存储
整型数据又可细分为:短整型short、整型int、长整型long三类。Turbo C中,它们分别按1、2、4个字节(8、16、32个二进制位)以补码形式在计算机中存放。
(2)实型数据以浮点型是存储
实型数据又可细分为:单精度型float、双精度型double两类。Turbo C中,它们分别按4、8个字节(32、64个二进制位)以浮点形式在计算机中存放。
Float型数据存放时,1位数符位(0为正1为负)、1位阶符位(0为正1为负)、7位阶码位、23位尾数位。
double型数据存放时,1位数符位(0为正1为负)、1位阶符位(0为正1为负)、11位阶码位、52位尾数位。
以下面是+987.654浮点数在计算机内存储的示意格式。
2.2 数值类数据的值域
值域指数据所允许取值的范围。在C语言中整型数与实型数,因其存储形式不同,所以值域大有差别。
(1)整型数据的值域
用整型数据表示的数值绝对精确,但表示的范围却很有限。如超限会产生数据溢出,而可怕的是这种溢出C语言并不给出错误信息,全凭程序设计者的经验与细心才能避免。设一个整型变量m占n个二进制位,则它的取值范围为:
例如int整型变量,按2字节补码方式二进制存储,因此它的取值范围为:-215到+215-1之间,即:-32768到+32767之间。
(2)实型数据的值域
用实型数据表示的数,尽管是近似值,但取值范围却很大。float、double型数据的有效数据分别是7位、15位,取值范围分别是±1038、±10308之间。
2.3 数值类数据所允许的操作
对于数值型数据,系统定义了它们允许的运算为数值运算,例如加减乘除、乘方开方、指数对数、三角函数等。但正因为它们有整型实型之分,因此各自运算的规则又尽不同。例如浮点数不能做取余运算,而做除法运算时,两个整型数相除时除法运算符(/)按整型算子的运算规则进行,否则按实型算子的规则进行除运算。例如:3./2结果为1.5,3/2结果为1,其原因前者进行的是浮点除,后者进行的是整型除。
3.程序错误的最主要原因是变量函数的数据类型选择不当
正因为上述程序中,将函数fact()、multi()、它们各自函数体内的局部变量p都定义成了int型,因此程序运行时,将会发生下面两个错误。
3.1 数据溢出
在int型变量的情况下,做阶乘、做数值连乘,都极易发生数据溢出。例如,在Turbo C中,如将变量定义为int型,那么做阶乘时会发现,7!正确,输出为:5040。但做8!时,不是人们预期的40320,而是美名其妙的-25216。因+40320的二进制为(1001110110000000),而在16位补码方式下,这个二进制正好代表-25216,可见此时已经发生数据溢出。所以两个函数体内的局部变量p必须定义为浮点数,而且定义为值域最大的double型最好。
3.2 主程序中发生运算错误
由于两个子函数都被定义成了整型函数,因而在主函数中for循环内的:
语句被反复调用时,两个子函数每次返回的值均为整型。此时除法运算符将进行两个整型数相除,按整型算子的运算规则进行运算。经过分析可以看出,由于该语句右边的表达式中,分子始终小于分母,除的结果为0,所以n个item,每一个都是0,故而执行后面的:
sum+=item;
结果始终等于sum的初值1,当循环结束后sum的值仍然为1。因此在执行:
pi=sum*2;
后只能输出2.000000。
同理,当将子函数的数据类型定义为double后,它们的返回值为双精度,两个双精度相除,除法运算符起浮点算子的作用,每个item项都不会是0,它们的和sum则应在初值1的基础上不断累加,最后输出即为pi的正确值。
4.合理定义变量的存储模式可优化程序提高效率
完整的变量定义语句格式为:
[存储模式]数据类型 变量名1[=值1][,变量名2[=值2]……];
存储模式决定了变量在计算机内存中存储的区域,它分为:auto自动型、reg-ister寄存器型、static静态型、extern外部型。其中:
auto型是变量存储类型的缺省模式。对于在函数内定义的变量或函数的形参,都是局部变量,它们被动态分配在内存的动态存储区。程序在运行中,当调用它所在的函数时,将对其动态分配存储空间,并动态进行初始化。函数调用结束,所占内存空间释放。下次调用重新分配,重新初始化。对于不在任何函数体内声明的全局变量,将存放在静态区,静态区的数据有存储单元始终占有的特点,因此其作用域从定义处开始一直到程序结束。上例中的所有局部变量和函数的形参,其存储模式都是auto型的。
register型变量将存放在CPU内,它受到计算机CPU的限制,不但个数有限,而且数据类型也受到严格限制,不得为long长整型、double双精度、float单精度型,因此使用极少。
static型变量,不管它是在函数内定义的局部static型,还是不在任何一个函数内定义的外部static型,都存储在静态区,从定义开始处即始终占有存储单元。对于局部static型变量,其最大的两个特点是:首先它们始终占有存储单元,但局部可见,具有继承性。其次程序运行时,只是在定义此类变量的函数第一次被调用时作一次初始化,以后再调用,则不再进行初始化。局部可见的含义是,尽管此类变量始终占有静态区为之分配的固定存储单元,因而全程有效,但只能在定义它的函数内被引用。具有继承性的含义则指函数每次调用结束时将保留本次调用中此类变量的更新的结果,供下次调用时继续使用。
extern型变量,也存放在静态区,但它却不是真正意义上的存储模式,而是对变量作用域的扩展。这种扩展,既可使在本C源程序文件内,将该变量的作用域扩展到定义它的前面其它函数中,又可将它作用域扩展到并未定义它的其它C源文件的函数。
仔细分析上面程序中的两个子函数,发现每次做阶乘都要循环从1连乘到i;每次做奇数的连乘,都是从3开始一直乘到(2*i+1)。这样会造成大量反复进行乘法运算,增加计算机的运算量,会使程序的执行时间较长。
但是我们知道:
如果能在子函数内p变量定义时,将其存储模式定义为静态类型static,就会将上一次运行的p的结果保存下来,在本次调用时,只要在上次p的结果上乘上i或(2*i+1)即可,使得每次只做一次乘运算,运算效率大幅度提高,运算时间会大幅度的压缩。下面给出改进后的源程序。
总之,在进行C程序设计时,必须根据变量的值域、要进行的运算,选择合适的数据类型;根据变量的作用域、生命历程选择合理的存储模式,方可编写出能够真正正确解决实际问题的源程序来。
参考文献
[1]赵森等.高职高专21世纪计算机规划教材——C程序设计[M].北京:冶金工业出版社,2005.
[2]李丽娟等.C语言程序设计教程[M].北京:人民邮电出版社,2006.
[3]谢膺白等.Visual FoxPro数据库教程[M].上海:复旦大学出版社,2011.
作者简介:王玲(1981—),女,陕西旬阳人,大学本科,助理讲师,现供职于渭南技师学院,研究方向:计算机软件方面的教学。