第 6章 C语言的基本程序设计
6.1 函数的定义与调用
6.2 函数的嵌套调用和递归调用
6.3 变量的作用域
6.4 变量的存储类别
6.5 内部函数与外部函数
6.6 指针与函数
6.7 用指针作函数参数
6.8 数组作为函数参数
6.9 返回指针的函数
6.10 main函数的命令行参数
6.1 函数的定义与调用
6.1.1 函数定义的一般形式从形式上看,函数分为有参函数和无参函数两种形式。
1.有参函数定义的一般形式类型名 函数名 (形式参数列表 )
{
局部变量说明;
语句序列部分;
}
一个函数由两大部分组成:函数的说明部分和函数体部分。
其中类型名、函数名和函数参数列表为函数的说明部分,称为函数头部。花括号,{ }”中的部分称为函数体,包括局部变量说明和语句序列部分。
类型名指明了本函数的类型。函数的类型实际上是函数返回值的类型,说明函数将给调用者提供什么类型的返回值。
函数名是由用户定义的标识符。对自定义函数,
其命名遵循 C语言标识符的命名规则。
形式参数表在函数名后的括号“()”内,由一个或多个类型标识符及变量标识符组成。在形参表中给出的参数称为形式参数,简称形参,各参数之间用逗号间隔。形式参数可以是各种类型的变量,必须在形参表中给出形参的类型说明。在进行函数调用时,主调函数将赋予这些形式参数实际的值。
在函数体中局部变量说明部分,是对函数体内部所用到的变量的类型说明。语句序列部分是实现函数功能的核心部分,它由C语言的基本语句组成。
2.无参函数定义的一般形式类型名 函数名 ()
{
局部变量说明;
语句序列部分;
}
无参函数从定义形式上看与有参函数的主要区别是函数名后的括号“()”没有形式参数,但函数名后的括号不可缺少。
由于无参函数没有参数,因此在调用无参函数时,主调用函数将不给被调用函数传递数据。并且大多数情况下,都不要求无参函数有返回值,对没有返回值的函数,其“类型名”可写为 void,表示该函数不返回任何类型的值。
6.1.2 函数的调用定义函数的目的是实现程序的某些子功能,
供其它函数调用。
1.函数调用的一般形式
C语言中,函数调用的一般形式为:
函数名 (实际参数表 )
实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式。各实参之间用逗号分隔,实参的顺序、类型必须与被调用函数的形参保持一致。无参函数调用时无实际参数表。
2.函数调用的方式
C语言中调用函数通常有函数表达式、函数语句和函数实参 3种方式,下面分别举例介绍。
(1)函数表达式方式:函数调用出现在表达式中,这种表达式称为函数表达式。这种方式要求函数一定要有返回值,以参加表达式的运算。否则会出现表达式错误。
(2)函数语句方式:函数调用的一般形式加上分号即构成函数语句。这种方式不需要函数有返回值。
3.函数的声明函数声明的一般形式为:
类型名 函数名 (类型 形参,类型 形参 …) ;
括号内给出了形参的类型和形参名,这便于编译系统进行检错,以防止可能出现的错误。
注意,函数声明后面的分号,;”不能少。
C语言规定,对以下几种情况可以省去主调用函数中对被调用函数的函数说明(声明)。
(1)当被调用函数的返回值是整型或字符型时,
可以不在主调用函数中说明而直接调用。这时系统将自动对被调用函数返回值按整型处理。
2)当被调用函数的函数定义出现在主调用函数之前时,主调用函数中不用再对被调用函数说明,可以直接调用。
3)如在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调用函数中,
不用再对被调用函数作说明。
4)对库函数的调用不需要再作说明,但必须把该函数的头文件用 include命令包含在源文件前部。
6.1.3 函数的参数和函数的值
1,函数的参数在调用函数时,主调用函数把实参的值传送给被调用函数的形参,从而实现了主调用函数向被调用函数的数据传递。
参数传递具有以下特点:
( 1)实参与形参各自占用独立的存储单元,函数调用时才将实参的值传递给形参单元。
( 2)形参变量只有在发生函数调用时才临时分配内存单元,在调用结束时,即释放所分配的内存单元。因此形参只在函数内部有效,函数调用结束返回主调用函数后,形参变量不再存在,不要企图在主调用函数中去使用形参变量。
( 3)实参可以是常量、变量、表达式、函数等,
无论实参是何种类型的量,在进行函数调用时,
它们都必须具有确定的值,以便把这些值传递给形参。
( 4)实参和形参在数量上、类型上、顺序上应严格一致,否则会发生“类型不匹配”错误。
( 5)函数调用中发生的数据传递是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变不会影响实参的值。
2,函数的值函数的值是指函数被调用结束之后,返回给主调用函数的值。
有两种情况可以终止被调用函数的执行并返回到调用它的函数中:一是执行到函数的最后一条语句后自然返回到调用它的函数中的调用处,这种情况一般不要求函数返回值;一是在函数执行过程中遇到返回语句时,将终止后续语句的执行,直接返回到调用它的函数中的调用处,这种情况一般都要求被调用函数返回一个值; C语言中的返回语句为 return语句。
return 语句的一般形式为:
return 表达式;

return (表达式 );
该语句的功能是先计算表达式的值,再将计算结果返回给主调用函数。
说明:
(1)在函数中允许有多个 return语句,但每次调用只能有一个 return 语句被执行,因此一次函数调用最多只能返回一个函数值。
(2)return语句中允许不带表达式。如果 return语句没有表达式,则仅表示在此处返回到它的调用处,而不返回任何值。
(3)函数返回值的类型应和函数定义中函数的类型保持一致。如果两者不一致,则以函数类型为准,自动进行类型转换。
(4)不返回函数值的函数,可以明确定义为“空类型”,类型说明符为,void”。为了使程序有良好的可读性并减少出错,凡不要求返回值的函数都应定义为空类型。 [Return]
6.2 函数的嵌套调用和递归调用
6.2.1 函数的嵌套调用
C语言中函数的定义是独立的,各函数之间是平行的关系,不存在上一级函数和下一级函数的问题,
因此,不允许作嵌套的函数定义。但是C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了函数的嵌套调用。
【 例 6.6】 编程计算 s=1!+2!+3! +…… +10!
程序代码如下:
#include <stdio.h>
longf2(int n) /*定义求 n的阶乘函数 */
{ long t=1;
int i;
for(i=1;i<=n;i++) t=t*i;
return t;
}
longf1(int p) /*定义求 n个数的和函数 */
{int i;
long sum=0;
for(i=1;i<=p;i++)
sum+=f2(i); /*调用求阶乘函数累加到和 */
return sum;
}
main( )
{ long s;
s=f1(10); /*调用求和函数 */
printf("s=%ld\n",s);
}
6.2.2 函数的递归调用
C语言中允许函数在执行过程中直接或间接的调用它自身,我们把函数通过直接或间接的方式对自己的调用称为函数的递归调用 。
把这种函数称为递归函数 。 如果函数在函数体内直接调用它本身,称直接递归 。
【 例 6.7】 编制程序,用递归法计算 n!。
n!的递归定义形式如下:
n!=1 (n=0,1)
n*(n-1)! (n>1)
程序代码如下:
#include <stdio.h>
long fac(int n) /*定义求 n的阶乘的递归函数 */
{ long f;
if(n<0) printf("n<0,input error");
else if(n==0) f=1;
else f=n*fac(n-1);
return(f);
}
main( )
{
int n;
long y;
printf("\ninput a inteager number:\n");
scanf("%d",&n);
y=fac(n);
printf("%d!=%ld",n,y);
} [Return]
6.3 变量的作用域函数的形参只在函数被调用时才分配内存单元,调用结束立即释放 。
这一点表明形参变量只有在函数体内有效,离开该函数就不能再使用了 。这种变量有效性的范围称变量的作用域 。
变量的作用域规则是:变量只能在它的作用范围内使用 。
6.3.1局部变量局部变量是在函数体内部 ( 或复合语句内部 ) 定义说明的变量,因此局部变量也称为内部变量 。 其作用域仅限于函数体内,在作用域以外再使用是非法的 。 例如:
void f1(int a) /*函数 f1*/
{ int b,c;
……
}
int f2(int x) /*函数 f2*/
{ int y,z;
……
}
main()
{ int m,n;
……
}
关于局部变量的作用域说明如下:
(1)主函数中定义的变量也是局部变量,只在主函数体内有效 。 同时主函数也不能使用其它函数体内定义的变量;
(2)形参变量属于被调用函数的局部变量,实参变量属于主调用函数的局部变量;
(3)允许在不同的函数中使用相同的变量名,它们占用不同的单元,不会发生混淆 。 如在上例中,形参和实参的变量名都为 n,是完全允许的;
(4)在复合语句中也可以定义变量,其作用域仅限于复合语句范围内;
6.3.2全局变量全局变量是指在函数外部定义的变量,又称为外部变量。它不属于哪一个函数,可以被整个个源程序文件中的所有函数所共享。
其作用域是从定义处开始,到整个源程序文件结束。因此在程序文件所有函数之前定义的全局变量可以供本程序文件中的所有函数使用。

int p,q; /*定义全局变量 */
float f1(int a) /*定义函数 f1*/
{ int b,c;

}
char r,s; /*定义全局变量 */
char f2(int x,int y) /*定义函数 f2*/
{ int i,j;

}
void main(void) /*主函数 */
{ int m,n;
全局变量的几点说明:
(1)全局变量在整个程序的执行过程中都占用存储空间,
一个函数对全局变量的修改会影响到其它调用这个变量的函数 。 全局变量使函数的通用性和程序的可读性降低 。 因此,使用全局变量时一定要细心,除非性能的特别要求,
要尽量避免使用全局变量 。
(2)在同一源程序文件中,允许全局变量和局部变量同名 。 如果局部变量和全局变量同名,则在局部变量的作用域内,全局变量被屏蔽,局部变量起作用 。 见例 6.11。
(3)为了与局部变量有明显的区分,建议全局变量的首字符采用大写字母表示 。
(4)在一个函数之前定义的全局变量,在该函数内可不再加说明直接使用。但要使用在函数之后定义的全局变量,
应作全局变量说明。全局变量的说明符为 extern。见例
6.12。 [Return]
6.4 变量的存储类别
C程序运行时占用的存储空间通常分为 3个部分:
程序区,静态存储区和动态存储区 。 程序区存放的是可执行程序的机器指令;静态存储区中存放的是需要占用固定存储单元的变量,如全局变量等;动态存储区存放的是不需要占用固定存储单元的变量,如函数形式参数以及函数调用时的现场保护和返回地址等 。
在 C语言中变量的定义包括 3个方面的内容:
l 变量的数据类型 。 如 int,char,float等;
l 变量的作用域,即变量的作用范围 。 如上节介绍的局部变量,全局变量的作用范围;
l 变量的存储类别 。 这是变量的另一个重要属性,是本节主要介绍的内容;
变量的存储类别分为两类:动态存储类别和静态存储类别 。
动态存储类别的变量指的是这样一种类型的变量:
预先不分配存储单元,当程序运行期间进入定义它的函数或复合语句时,才分配存储空间,离开时所占存储空间被释放 。 典型的例子是函数的形式参数,在函数定义时并没有给形参分配存储单元,只在函数被调用时才分配存储单元,函数执行完毕后,立即释放 。
静态存储类别的变量指的是:在变量定义时就分配存储空间,并一致保持不放,直到整个程序运行结束。 如前面介绍的全局变量都是此种存储类别的变量。
1.auto(自动 )变量函数中的局部变量,如不特别声明都被分配在内存的动态存储区中,如函数中的形参和在函数中定义的变量 ( 包括在复合语句中定义的变量 ),都属此类,
在调用该函数时系统给它们分配存储空间,在函数调用结束时就自动释放这些存储空间 。 把这类局部变量称为自动变量 。
自动变量用关键字 auto作存储类别的声明 。
自动变量是 C语言中使用最广泛的一种类型 。 C语言规定,变量定义时凡是未加存储类型说明的,均视为 auto类型 。 因此,如下两种定义方式的效果是等价的:
int a;
auto int a;
2.静态变量显然,函数定义结束后,函数体内定义的自动变量的值不能再被使用 。 如果希望在函数调用结束后,仍然保持函数中定义的局部变量的值,则可以将该局部变量定义为
,静态局部变量,,方法是用关键字 static对变量进行声明 。
静态局部变量所占用的存储单元位于静态存储区中 。
函数调用结束后,其值不消失 。
静态局部变量在编译时赋初值,并且只赋初值一次 。
程序运行时,初值已经确定,以后调用函数时不再在函数过程内赋初值,并且能保持上一次函数调用时的结果 。 如果没有给静态局部变量赋初值,则系统默认的初值为 0。
【 例 6.15】 求 sum = 1! +2! +3! +4! +5! 的值 。
auto变量和静态局部变量的区别总结如下:
(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元 。 在程序整个运行期间都不释放 。 而 auto变量属于动态存储类别,在动态存储区内分配存储单元,函数调用结束后即释放 。
(2)静态局部变量在编译时赋初值,即只赋初值一次;而对 auto变量赋初值是在函数调用时进行,每调用一次函数重新赋一次初值 。
(3)如果在定义局部变量时不赋初值的话,则对静态局部变量来说,
编译时自动赋初值 0( 对数值型变量 ) 或空字符 ( 对字符变量 ) 。 而对
auto变量如果不赋初值的话,则它是一个不确定的值 。
为了提高效率,C语言还允许程序员使用 cpu中的寄存器存放数据,
即可以通过变量访问寄存器 。 把这种放在寄存器中的变量叫,寄存器变量,。 由于它放在寄存器中,使用直接从寄存器中读写,从而提高了效率 。
寄存器变量用关键字 register作声明 。 例如:
register int i;
register char c;
[Return]
6.5 内部函数与外部函数
1.内部函数内部函数又称静态函数,有点类似于静态变量,但此处指的不是存储方式,而是指函数的作用域仅限本源程序文件 。
定义一个内部函数,只需在函数类型前再加一个关键字
,static”即可 。
内部函数定义形式如下:
static 函数类型名 函数名 ( 函数参数表 )
关键字,static”的含义是静态的,所以内部函数又称静态函数,但此处指的不是存储方式,而是指函数的作用域仅限本源程序文件 。
定义内部函数的好处是,不同源程序文件中的函数可以重名。这有利于多人合作编写程序,每位程序员都可以根据自己的需要给函数命名,而不必担心与其他程序员编写的函数同名。同时也便于管理,防止其他人错误地调用本函数。
2,外部函数
[extern] 函数类型名 函数名 ( 函数参数表 )
其中 extern为可以省略,前面讲的各函数都属于这种情况 。
如果在一个源程序文件中要调用另一个源程序文件中的函数,调用前需要用 extern声明要调用的函数为外部函数 。 例如:
extern 函数类型名 函数名(函数参数表);
需要说明的是,函数定义和函数声明是两个不同的概念。
定义一个函数包含两部分:函数头部和函数体,函数体又包括声明部分和语句部分两部分;而函数声明指的是在本函数中要调用其它源程序文件中的函数。函数定义只能有一次,
而函数声明可以有多次,函数声明可以出现在调用它的所有主调函数中,也可以出现在函数定义之前。函数声明语句后的分号,;,不可少。
[Return]
6.6 指针与函数
C语言中规定,一个函数在内存中占用一段连续的存储区,而函数名就是该函数所占内存区的首地址。
我们可以把函数的这个首地址 (或称入口地址 )赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。 我们把这种指向函数的指针变量称为,函数指针变量,。
函数指针变量定义的一般形式为:
类型名 (*指针变量名 )( );
其中,类型名,表示被指函数的返回值的类型 。
,(* 指针变量名 )”表示,*” 后面的变量是指针变量 。
最后的括号,( )”表示指针变量所指的是一个函数 。
例如,int (*pf)();
表示 pf是一个指向函数首地址的指针变量,该函数的返回值 (函数值 )是整型 。
利用函数指针变量调用函数的基本方法是:
(1)先定义函数指针变量;
(2)将要调用函数的首地址 (函数名 )赋给函数指针变量;
(3)将函数指针变量代替函数名使用,实现函数的调用;
用函数指针变量调用函数的一般形式为:
(*指针变量名 ) (实参表 )
使用函数指针变量应注意以下两点:
(1)对函数指针变量不能进行算术加减运算,这和数组指针变量不同 。 数组指针变量加减运算是指向下一个或上一个数组元素,而函数指针的加减运算没有意义 。
(2)函数调用中,(*指针变量名 )”的两边的括号不可少,其中的 *不应该理解为求值运算,在此处它只是一种表示符号。 [Return]
6.7 用指针作函数参数还可以用指针类型作为函数的参数 ( 形参或实参 ) 。 当用指针作为函数的参数时,此时传递的数据实际上是地址,即将一个变量的地址传送到函数中 。
在前面的例 6.5中,我们使用函数 swap()实现了形参 a,b的交换 。 由于函数的单向传递性,从程序运行结果得知,在 swap函数体内对形参 a,b的交换不能影响主调用函数中的实参 。 因此在主调用函数中无法使用交换后的结果 。 又因为对一个函数的一次调用最多只能返回一个值,因此,用局部变量的,值传递,的函数调用方法无法实现对多个实参值的改变 。 要解决这个问题,可以用指针作函数参数,对函数进行调用 。
当用指针作函数参数时,传递给形参的实际上是实参的地址,此时形参与实参指针指向的是同一个变量的内存单元 。 因此,通过指针对形参内存单元内容改变实际上也是对实参内存单元的改变 。
【 6.19】 用指针作参数实现两个数据的交换 。[Return]
6.8数组作为函数参数
6.8.1数组元素作函数实参数组元素作为函数的实参与普通变量作函数实参一样,是将数组元素的值传送给形参,参数的传递方式是单向的值传送,形参的变化不会影响实参数组元素 。
【 例 6.21】 假定一个数组中存放的是学生 5
门课的成绩,编写程序,判断数组中各元素的值,若大于等于 60 则输出,pass”,否则输出
,bad”。
6.8.2数组名作为函数参数我们知道,数组名就是数组的首地址,因此当把数组名作函数参数时,此时传递的是数组的首地址 。
而不是把实参数组的每一个元素的值都赋予形参数组的各个元素,实际上形参数组并不存在,编译系统不为形参数组分配内存 。 形参取得该首地址之后,形参与实参指向的是同一数组的内存空间 。
用数组名作函数参数时,要求形参和相对应的实参都必须是类型相同,维数相同的数组,都必须有明确的数组说明 。
用数组作函数参数时,实参和形参都可以是数组名;也可以一个用实参,另一个用指针或地址;还可以实参和形参都用指针或地址;
[Return]
6.9 返回指针的函数指针型函数说明的一般形式为:
类型名 *函数名 (形参表 )
函数名之前的,*” 号表明这是一个指针型函数,即返回值是一个指针 。 类型名表示返回的指针所指向的数据类型 。
int *f(int n) /* 说明指针型函数,返回指向整型变量的指针 */
{ int *p; /* p为指向整型的指针变量 */
......
return(p); /*返回指针 p,p与 f的类型一致 */
}
要注意,函数指针变量,和,指针型函数,这两者在写法和意义上的区别 。 如 int(*p)()和 int *p()是两个完全不同的量 。
int(*p)()是一个变量说明,说明 p 是一个指向函数入口的指针变量,
该函数的返回值是整型量,(*p)的两边的括号不能少 。
int *p() 则不是变量说明而是函数说明,说明 p是一个指针型函数,其返回值是一个指向整型量的指针,*p两边没有括号 。 [Return]
6.10 main函数的命令行参数
C语言规定,main函数可以带参数 。 带参数的 main函数的函数头部的一般格式如下:
main (int argc,char *argv[])
其中,argc是整型变量,用来存放命令行中命令与参数的总个数 。
argv是指向字符串的指针数组,用来存放这些参数的首地址,每一个参数都看成是一个字符串 。
由于 main函数不能被其它函数调用,因此不可能在程序内部取得实际值 。 那么,从何处把实参值赋予 main函数的形参呢? 实际上,main函数的参数值是从操作系统命令行上获得的 。 当运行一个可执行文件时,在 DOS
提示符下键入文件名,再输入实际参数即可把这些实参传送到 main的形参中去 。
需要特别注意的是,main 的两个形参和命令行中的参数在位置上不是一一对应的 。 因为,main的形参只有二个,而命令行中的参数个数原则上未加限制 。 argc的值是在输入命令行时由系统按实际参数的个数自动赋予的 (注意文件名本身也作为参数被计算在内 )。
例如有命令行为:
C:\>testexe Turboc foxpro C++[Return]