C语言程序设计中的函数分解与函数定义

2015-05-30 10:48周百顺
计算机教育 2015年4期
关键词:C语言模块化

周百顺

摘要:分而治之的模块化编程思想是C语言程序设计的指导思想,对复杂程序的函数分解则是对这一思想的实践。文章分析函数分解的时机和意义,讨论通过函数定义对分解后的子模块进行封装的一般原则,旨在指导C语言程序设计实践。

关键词:C语言:函数分解;函数定义;模块化

引 言

C语言是一种面向过程的结构化程序设计语言,是UNIX操作系统的主要编写语言,也是很多高级语言发展的基石。C语言与计算机底层结合紧密、执行效率高,使得其在追求性能和效率的嵌入式编程、系统级软件开发、数据通信等领域有着突出的优势。

结构化程序设计( structured progra-ming)的思想是由荷兰学者E.W.Dijikstra在20世纪60年代后期提出的,以过程为中心,强调功能分解和模块化设计。核心理念是采用“自顶向下逐步求精、分而治之”的方法进行大型程序设计。基本思想是:从待解决的初始问题出发,运用科学抽象的方法,把它分解成若干相对独立的小问题,依次细化,直至各个小问题获得解决为止,最后通过这些小问题的解逆向构造初始问题的解。C语言中的函数机制是实现结构化程序设计的重要保障,提供了将程序巾的代码片段抽取出来作为整体来使用和处理的手段,满足了复杂计算的分解和重组需要 。编写大型程序时应特别注意程序的功能分解,以及分解后的模块封装,对C语言程序设计而言主要是程序的函数分解和函数定义。

1 函数分解的时机和意义

程序开发过程中,随着需要处理的问题变得复杂,程序也会变得越来越长。长的程序牵扯情况复杂,编程人员更难把握,同时长程序的阅读和理解也更困难,这又反过来影响程序的开发和维护。处理复杂问题的基本方法就是化繁为简,分而治之。依据结构化程序设计思想,借助C语言中函数的抽象机制,对复杂程序进行函数划分的过程就是程序的函数分解。

针对复杂问题求解所采用的模块划分通常是从功能的角度进行,划分后的模块要具备“相对独立,功能单一”的特征。也就是说,一个好的模块必须具有高度的独立性和较强的功能。实际应用中通常用“耦合度”和“内聚度”两个指标从不同角度对模块的划分情况加以度量。耦合度是对模块之间相互依赖性大小的度量,耦合度越小,模块的相对独立性越大。高耦合的系统中,一个模块的修改往往会影响其他模块,低耦合系统中这种影响相对较小。内聚度是对模块内各元素之间相互依赖性大小的度量,内聚度越大,模块内各元素之间联系越紧密,其功能越强;反之,低内聚模块内各元素的关系较为松散。因此,模块划分时应当尽可能降低不同模块间的关联,提升单一模块自身的功能性,做到“耦合度尽量小,内聚度尽量大”。

此外,随着程序变大,程序中会出现许多不同位置需要做相同或类似工作的情况,分别写出这样的代码片段既使程序变长,又增加了不同部分间的相互关联。对于这些重复出现的相同或相似的片段,可从中抽取出共用的部分定义为子函数,原有程序中相关部分通过简单的函数调用语句代替。这样做不但可能缩短程序的代码,也将大大提高程序的可读性和易修改性,使得整个程序里同样的计算片段仅描述一次,如果需要修改这部分计算,只需要修改其对应的函数定义部分即可。

适当的函数分解使得复杂问题更容易分析和把握,提高了代码重用率,也使得程序设计中的分工合作成为可能。大型应用程序在开发过程中通常都会依据上述原则进行模块划分,由于模块间具有较高的独立性,在设计好模块间接口的情况下可将各个模块分配给不同的编程人员实现。对于程序中常用的基础功能模块,也可由专门的编程人员将其封装为子函数,集成到自定义的函数库中,供程序开发人员共用,既避免了重复开发,也有利于提升代码质量。这种分工合作使得软件开发效率有了显著的提高,同时也促进了代码编写的标准化。

2 函数定义与模块的封装

经过不断细化和函数分解,整个程序已经划分为若干个功能相对独立的子模块,接下来需要通过定义子函数来实现对单个模块的封装。函数的定义包括函数头部和函数体两部分。函数头部给出了函数的名字和类型特征,函数体则是函数功能的代码实现部分。定义一个函数就是根据模块的功能描述,对其进行函数封装,设计出函数头部信息并编写函数体相关代码,函数定义的一般格式如下 :

函数本身是一个相对独立的功能实体,是对完成一定功能的程序代码的封装,能够对一定的输人数据进行加工,产生预期的输出结果。因此,编写函数定义的过程和完成一个程序是类似的,都需要根据问题的描述,设计出解决方案,并编写相关的实现代码。两者一个很重要的区别在于其与外界交互的方式不同:程序是可以独立运行的,通过输入输出和使用该程序的终端用户通信;函数不能独立运行,只能被主程序调用执行,通过参数和返回值与它的调用程序之间进行信息交互,接收调用程序传递的输人数据,并将运行结果返回给调用程序。

可见,在针对具体问题编写函数定义时,同样需要首先分析出问题的输入和输出,然后通过自顶向下逐步求精的方法来设计解决问题的主要步骤,最后使用函数对其进行封装。一般情况下,问题的输入固定对应于函数的形式参数,而问题的输出则有两种方法返回给调用程序。。

第一种方法是通过返回值将输出结果返回给调用程序。函数体内通过return语句显式结束函数的执行,并返回一个特定类型的值作为函数调用的结果。此时,函数调用结果与该类型的变量相当,可以作为表达式的一部分参与运算。

例如,数学函数库中的sqrt函数就是通过返回值来传递计算结果,返回值类型为double型。当发生函数调用时,会将调用程序传人的实际参数的平方根作为结果返回。该函数调用的作用与任意double型变量一样,可以参与到相关运算中。

如:double result=5.3+sqrt(3);这样的语句是合法的,先执行函数调用,计算常数3的平方根并将其作为结果返回,然后在函数的调用点处用返回值代替函数调用参与加法运算,相加后的结果赋值给result变量。

第二种方法是通过指针形式的参数将输出结果返回给调用程序。在定义函数时,除了与输入相对应的形式参数外,额外增加一个或多个指针型参数,用于传递函数的输出结果。发生函数调川时,凋用程序在传人实际输入数据给函数的同时,还要在指针型参数的位置传人本地变量的地址作为实际参数,从而在函数调用结束时将结果通过该变量带回到调用程序中。

例如使用scanfo函数从键盘上读人数据时,输入会存储到作为参数提供的地址中。

据此,从参数功能的角度可以将函数定义中的参数进一步细分为输入型参数和输出型参数。前者用于接收调用程序传递给函数的输入数据,后者则用于将函数的计算结果返回给调用程序。输出型参数一般表现为指针类型的变量。

实际应用中,输出结果返回方式的选择,主要取决于待封装问题的输入输出特征。从某种意义上讲,参数提供了函数的输入,返回值是它的输出,输出返回给调用程序。因此,一般情况下,优先考虑通过返回值传递函数调用结果,只有当返同值无法很好地满足需要时,才会考虑通过指针型参数来传递调用结果。

综上,通过对待封装问题输入输出特征的分析,结合实际应用中的函数设计经验,可以总结出如下函数定义的一般原则:

函数定义原则1:一般情况下,待封装问题功能描述巾的输入同定对应于函数定义中的形式参数。即问题描述中有多少输入,函数定义中就要设置相应数量的形式参数与之对应。如果问题描述中没有明确的输入数据,则函数定义中可以没有形式参数,但函数名后面的括号必须有。

示例1:定义一个函数,用于计算3个整数巾的最大值。

分析:问题描述中明确指出函数要能够对输入的3个整数进行处理,找出最大值。因此,对应函数定义的参数列表中至少应该有3个int类型的形式参数,用于在函数调用时接收调用程序传人的3个整数。

函数定义原则2:如果待封装问题的功能描述中,输出结果为单一数值,适合通过返问值将问题的输出返回给调用程序。

同样针对上面的问题,输出结果为3个整数中的最大值,属于单一数值,可用返回值将结果返回。根据题意,最大值是整数类型。故,函数定义中的返回值类型可定义为int。函数头部的完整定义为:int Maxln3(int num_l,int num_2,intnum_3)。

函数定义原则3:如果待封装问题的功能描述中,输出结果为多个数值,适合通过输…型参数将问题的输出返回给调用程序。

示例2:编写函数定义,实现对实数进行分解,找出实数的符号位,整数部分和小数部分。

分析:问题的输人为1个实数,输出包含3部分:符号位、整数部分和小数部分。依据上述原则,函数定义中需要设置1个输入型参数对应于输入的实数,设置3个输个型参数用于传递实数分解后需要返回的三部分结果。返同值无实际意义,定义为void。函数头部的完整定义为:void Separate(double num, char*p_sign, int*p_int,double*p_frac)。

各参数的功能说明如下:

double number,接收输入实数的输入型形参

char*p_sign,返回实数的符号位

int*p_int,返回实数的整数部分

double*p_frac,返回实数的小数部分

对于函数返回结果为多个数值的情况,还可以通过全局变量和结构体变量进行结果传递。

全局变量能够为程序中的多个函数所共享,是函数间相互通信的一种手段,可以通过全局变量将子函数的计算结果带回给主程序 ,但是子函数中对全局变量的使用会显著放大函数的耦合度,破坏函数定义的独立性,不建议使用。

函数的返回值可以是C语言中允许的任意合法数据类型,包括结构体类型。为了能够通过一个返回值返回多个结果,可以将函数的返同值类型定义为包含多个成员变量的结构体类型,函数体内将多个计算结果分别保存给结构体变量的不同成员,并最终返回结构体变量。调用程序里可以通过对返回的结构体变量成员的访问,来使用返回结果。这种传递方式增加了调用程序对子函数输出结果使用的复杂度,一般也不建议使用。

函数定义原则4:如果待封装问题的功能描述中,要求对输入的数据进行修改并返回的,适合通过指针型参数来进行数据传递。此时,问题的输入和输出是重叠的,由相同的参数变量承载,指针型参数既能正确的将需要加工的输人数据传递给子函数,又能够在子函数内通过指针变量间接访问的特点实现子函数和调用程序间的数据共享,从而达到在子函数内修改主程序中数据的目的一

示例3:编写函数定义,实现两个整型变量的数值交换。

分析:这个问题中的两个整型变量既是输入义是输出,需要在子函数中交换两个输入参数变量的值,并在函数调用结束后将交换的结果返回调用程序。适合在函数定义中设置两个指针型参数,首先用于接收主程序中待交换的两个整型变量的地址,函数体内通过间接访问的方式实现对主程序中对应整型变量的使用,并完成数值的交换。返回值设置为空。函数头部可描述为:voidSwap(int*p_numl, int *p_num2)。

函数定义原则5:如果待封装问题的输入或输出为数组结构的数据,通常采用数组作为形式参数,调用时传人待处理数组的名字作为实参。数组名字代表了实参数组的首地址,从而使得用作实际参数的数组的存储空间被形式参数所共享,改变形参数组中某一元素的值也会改变实参数组中对应元素的值。

示例4:编写函数定义,实现对一个无序整型数组进行有序排列。

分析:问题的输入和输出为同一数组,输入时数组中的数据是无序的,输出时变为有序。可设置一个数组型形参来表示待处理的数组,返回值无实际意义,设置为空。函数头部可描述如为:void Sort(inta[])。

3 标准库函数与函数分解

标准库函数由编译系统提供,能够独立完成一定功能,也是程序函数分解思想的一种体现。库函数带来的这种分解不是针对某一具体应用,而是对程序设计中常用功能的一种抽取和封装,是由编译系统完成的系统级函数分解。程序设计中对标准库函数的使用,既降低了程序设计的复杂度和难度,也提升了编程的效率 。

4 结语

计算机程序设计语言是辅助人们实现想法,指挥计算机工作的工具。在程序设计课程学习过程中,要重视编程思想的理解和分析问题方法的培养,以及算法程序化能力的养成,这样才能做到学以致用。C语言程序设计的核心是分而治之的模块化编程思想,如何针对复杂问题进行函数分解,以及如何对分解后的子模块进行封装和使用是使用C语言编写程序的关键所在。

猜你喜欢
C语言模块化
模块化自主水下机器人开发与应用
基于模块化控制系统在一体化教学中的应用
基于Visual Studio Code的C语言程序设计实践教学探索
模块化住宅
基于C语言的计算机软件编程
C语言程序设计课程教学与学科专业相结合的探索
ACP100模块化小型堆研发进展
从模块化中得到的二氧化碳
模块化VS大型工厂
高职高专院校C语言程序设计教学改革探索