第六章 函数
第一节 函数的概念和模块化程序设计
一、函数的概念
函数是一个可以反复执行的程序段。在一个程序中,如果需要多次执行某项功能或操作,则可 以把完成该功能或操作的程序段从程序中独立出来定义为函数,而原来程序中需要执行该功能或操 作时可以通过函数调用来代替,以达到简化程序的目的。
1. C 语言函数的特点
C 语言的函数具有以下特点:
1)一个 C 程序由一个或多个函数组成,其中必须有且只能有一个 main 函数(称为主函数)。
2)C 程序的执行从主函数开始,如果在主函数中调用其他函数,调用后返回到主函数,并在主 函数中结束整个程序的运行。
3)主函数可以调用其他函数,但其他函数不能调用主函数。主函数由操作系统调用,其他函数 之间可以互相调用。
4)函数之间,没有从属关系,互相独立,不能嵌套定义。
2. C 语言函数的分类:
- 从函数定义的角度,分为库函数和用户函数。
- 从函数有无参数的角度,函数分为有参函数和无参函数。
- 从函数有无返回值的角度,函数分为有返回值函数和无返回值函数。
- C语言允许将一个源程序分放在不同的程序文件中,采用分块编译、链接 生成一个目标程序,其中每个程序文件称为一个”编译单元”。每个编译单 元可以包含若干个函数。从函数作用范围的角度,函数分为外部函数和内部 函数。外部函数是可以被任何编译单元调用的;内部函数只能在本编译单元 被调用。
二、函数的定义
- 函数定义的一般格式 任何函数(包括主函数)都是由函数首部和函数体两部分组成。函数定义的一般形式如下:
数据类型符 函数名(形式参数表)
{
数据定义语句部分;
执行语句部分;
}
通常,一对花括号括起来的部分称为“函数体”,函数体前面的部分称为“函数首部”。其中:
(1)函数首部
“数据类型符”规定本函数返回值的数据类型,可以是各种基本数据类型,也可以是指针类型 (只要在函数名前面加一个*)
。例如,函数返回值是整型,数据类型符应为 int。C99 标准不再支 持隐含式的 int 规则。需要注意的是,如果函数无返回值,数据类型符应使用 void
。
需要注意的是,函数首部末尾不能加分号。
“函数名”是一个标识符,在同一个编译单元中的函数不能重名。
“形式参数表”是使用逗号分隔的若干个形式参数及其数据类型的说明,具体格式如下:
数据类型符 形式参数 1[,数据类型符 形式参数 2,…]
例如:int abc(a1,b1,c1)
其中,每个形式参数都可以是一个变量名、一维数组名[长度]、二维数组名[行长度][列长度]、
指针变量名、指针数组名[长度]等。需要注意的是,当形式参数是数组时,一维数组的长度和二
维数 组的行长度可以省略。
若省略了形式参数表,则表示该函数没有参数,即定义的是无参函数。
(2)函数体
“数据定义语句部分”由若干个数据定义语句组成,用来定义本函数中使用到的变量)数组和 指针变量等。
“执行语句部分”由本函数中完成函数功能的程序段组成。如果是有返回值的函数,则其中至 少有一条返回语句“return(表达式);”
,表达式的值就是本函数的返回值。返回语句中的圆括号可 以省略。函数的返回值是通过函数中的返回语句获得的。返回语句的一般格式是 return(返回值表达 式);。如果是无返回值的函数,则返回语句应为“return;”,也可以省略返回语句。返回语句在函 数体的执行语句部分的位置是任意的。返回语句的功能是结束本函数的运行,返回到主调函数的函 数调用语句继续执行。
【说明】 C 语言中,所有函数(包括主函数)都是平行的。一个函数的定义,可以放在程序中的任意位 置,主函数之前或之后。但是在一个函数的函数体内不能再定义另一个函数,即不允许函数的嵌套定义。
2. 各类函数定义的格式
(1)无参函数的定义
如果一个函数在被调用时不需要主调函数向其传递数据,则该函数可以定义为无参函数,其定 义的一般格式如下:
数据类型符或 void 函数名(void)
{ 数据定义语句部分;
执行语句部分;
}
其中,void 表示函数没有参数,void 也可以省略,而只在函数名后面跟一对空括号,但这对空 括号不能省略。
(2)有参函数的定义
如果一个函数在被调用时需要主调函数向其传递数据,则该函数可以定义为有参函数,其定义 的一般格式如下:
数据类型符或 void 函数名(形式参数表)
{
数据定义语句部分;
执行语句部分;
}
定义时有参函数比无参函数多了一个参数表。调用有参函数时,主调函数将赋予这些形式参数 实际的值。为了与主调函数提供的实际参数区别,将函数定义中的参数表称为形式参数表,简称形 参表。
(3)无返回值函数的定义
无返回值函数定义的一般格式如下:
void 函数名(形式参数表或 void)
{
数据定义语句部分;
执行语句部分;
}
(4)有返回值函数的定义
有返回值函数定义的一般格式如下:
数据类型符 函数名(形式参数表或 void)
{
数据定义语句部分;
执行语句部分;
}
有返回值函数和无返回值函数的主要区别体现在两个方面。
一是数据类型符,有返回值函数的数据类型符可以选取任意的数据类型符,而无返回值函数的数据
类型符只能是 void;
二是有返回值函数的函数体中必须有语句 return(表达式);,而无返回值函数的函数体中的语句
return;可以有也可以省略。
3. 返回值类型
函数返回值的类型即函数类型,是指返回给主调函数的结果的类型,应根据具体函数的功能确 定。如果函数不返回任何值,则函数返回值类型定义为 void,称为“无类型”。
三、函数的调用
C 程序是通过对函数的调用来执行函数体的,被调函数应该“先定义后调用”。
1. 函数调用的一般格式
函数名([实际参数表])
其中,[ ]中的部分可以省略。
说明:
1)实参的个数、类型和顺序,应该与被调函数所要求的形参个数、类型和顺序一致,才能正确 地进行函数之间的数据传递。
2)当有多个实参时,实参之间用逗号分隔。
3)无参函数没有参数,则调用时函数名后面跟一对空的圆括号,但这对圆括号必须保留。 例如,无参函数 print 的调用语句可写为:print( ); 有参函数 add 的调用语句可写为:add(2.0,4.3);
2. 函数调用的方式
C 语言中,可以用以下几种方式实现函数调用。
(1)以函数表达式的方式调用函数
函数调用作为表达式的一项,出现在表达式中,以函数的返回值参与表达式的运算。这种方式 要求函数是有返回值的。例如 i=2*add(x,y);和 c=add(a,b);等。
(2)以函数语句的方式调用函数
C 语言中的函数可以只进行某些操作而不返回函数值,这时的函数调用可以作为一条独立的语 句。例如 printf(“hello\n”);和 funcl(x,y,z);等。
(3)以函数实参的方式调用函数
函数调用作为另一个函数调用的实参出现。这种方式是把该函数的返回值作为实参进行传递, 因此要求该函数必须有返回值。
3. 函数的形式参数和实际参数
形参是在函数定义时设置的
,用来接收从主调函数传来的对应的实参数据。实参是调用函数时 的实际参数
,实参可以是常量、变量或表达式,也可以是函数的返回值,无论哪种形式必须有确定 的值。实参在数量、类型和顺序上与形参必须一一对应和匹配。如果参数的数量不一致,则会出现 编译错误;如果参数的顺序不一致,则传递到被调函数中的数据不合逻辑;如果参数的类型不一致, 则按照形参类型对实参进行自动转换,如果是不能进行转换的类型,则出现编译错误。
4. 函数的返回
调用函数时,程序从主调函数跳转到被调函数,并从被调函数的第一条语句开始执行,执行完 最后一条语句后返回到主调函数的调用处。如果中间遇到返回语句,则函数立即返回主调函数。
返回语句有以下两种使用方式:
1)无返回值的函数 return;
2)有返回值的函数 return(返回值表达式);或 return 返回值表达式;
函数的返回值是通过被调函数中的返回语句获得的。若函数没有返回值,则应将函数返回值的 类型定义为 void,在函数体中可以没有返回语句,也可以使用无返回值的 return 语句,但是不能 使用有返回值的 return 语句;若函数有返回值,则应将函数定义为非 void 类型,在函数体中只能 使用有返回值的 return 语句,而且必须有该语句。函数的返回值只能有一个,但可以有多个 return 语句,一旦执行到其中一个 return 语句,则立即返回主调函数,被调函数中的其他语句不再执行。
例如,
int f(int x,int y)
{
return(x*y);
return(x+y);
}
执行第一个 return 语句时,程序返回到主调函数,第二个 return 语句不会被执行。
实例:
#include <stdio.h>
int f(int x,int y)
{
return(x+y);
return(x*y);
}
int main(void)
{
int a;
a=f(1,2);
printf("%d\n",a);
}
}
运行结果:
return 语句中表达式的数据类型应与函数首部定义的函数返回值的数据类型一致。若不一致, 以函数首部定义的数据类型为准,系统自动进行数据类型转换;如果不能进行转换,则出现编译错误,例如:
int add(float x,float y)
{
return x+y;
}
其中,x+y 的值是 float 型,函数定义的返回值类型为 int 型,二者类型不一致,系统会自动将 x+y 的值的数据类型转换为 int 型,并将该 int 型的值作为函数的返回值。这种函数返回值的数据类 型与 return 语句中表达式的数据类型不一致的情况使程序可读性差,应尽量避免出现,应使两者的 数据类型保持一致。
主函数由操作系统调用,主函数中的 return 语句将结束程序的运行。主函数的返回值用于向操 作系统返回程序的退出状态。若返回 0,表示程序正常退出;若返回其他值,则表示各种不同的出 错状态,系统据此判断程序的运行状态。
C99 标准规定主函数的形式如下:
int main(void)
{ ...
return 0;
}
5. 函数调用的过程
函数调用的过程如下:
1)暂停执行函数调用所在的语句,转向执行被调函数。
2)为函数的所有形参分配内存,再将所有实参的值计算出来,依次赋予对应的形参(注意, 若形参是数组则不给形参分配内存,参见本章第三节)。若是无参函数,则不执行这一操作。
3)进入函数体,先执行数据定义语句部分,为函数体中定义的变量、数组等分配内存。
4)执行函数体中的执行语句部分。如果是无返回值的函数,则执到返回语句;如果返回语句 被省略,则执行到函数体的右花括号;如果是有返回值的函数,则执行到返回语句时,计算表达式 的值作为函数的返回值。
4)执行函数体中的执行语句部分。如果是无返回值的函数,则执行到返回语句;如果返回语句 被省略,则执行到函数体的右花括号;如果是有返回值的函数,则执行到返回语句时,计算表达式 的值作为函数的返回值。
5)收回分配给函数体中定义的变量、数组、形参等的内存单元。
6)返回到主调函数继续执行。如果函数调用的形式是“语句”,则执行其后面的语句;如果 函数调用的形式是“表达式”,则继续执行表达式所在的语句。
四、形参和实参
形式参数和实际参数
形式参数:定义函数时函数名后面括号中的变量名
实际参数:调用函数时函数名后面括号中的表达式
四、模块化程序设计方法
C 语言中,一个大型程序由许多源程序文件(又称为程序模块,通常是一些相关函数的集合) 组成,而源程序文件由编译预处理命令和许多函数组成。
C 源程序文件(程序模块)的结构如下:
- 包含文件,例如,#include<stdio.h>。
- 宏定义,例如,#define PI 3.14159。
- 条件编译,例如,#if…#else…#endif。
- 函数声明,例如,定义函数的原型。
- 全局变量说明,例如,函数外定义的变量。
- 函数定义。
每个程序模块均具有确定的功能,以及待处理的输入数据和处理结果的输出数据。为了确保程序模块的正确性,每一程序模块都需要独立调试并验证其正确性。当大型程序的所有模块均调试并 验证后,将它们拼接在一起,通过互相调用的方式组成一个完整的大型程序。这种程序设计方法称为模块化程序设计方法。
本质上,模块化程序设计方法是将比较复杂的程序分割成若干个函数,通过主函数调用其他函 数,以及函数之间的互相调用,完成整个程序的功能。
第二节 函数声明
函数声明又称为函数原型,其一般格式:
[存储类型][数据类型符] 函数名(形参类型[形参名 1][,形参类型[形参名 2],…]);
其中,[ ]中的内容可以省略。
若被调函数为:
1)库函数,除了少数库函数(如 scanf( )、printf( ))外,都要求在本文件开头用文件包含命 令#include<头文件名.h>或#include“头文件名.h”包含被调函数的信息。
2)用户函数,若主调函数与被调函数不在同一个编译单元,则在定义函数的编译单元中必须将 该函数定义为外部函数,同时在主调函数的函数体或主调函数所在编译单元的开头将被调函数按照 如下格式进行声明:
extern 数据类型符 函数名(形参表);
需要注意的是,函数声明是一条语句,末尾加分号;而函数首部不是语句,末 尾不加分号。
说明:
1、函数声明告知编译程序函数返回的数据类型、函数所要接收的参数个数、类 型和顺序,编译程序利用函数声明校验函数调用是否正确。
2、函数声明中可以只说明形参类型和形参个数,而无须说明形参名。
3、函数声明可以在主调函数中,也可以在所有函数的外部。
4、函数声明、函数定义、函数调用要保持一致。
5、如果程序中没有包含函数声明,则编译程序会用第一次出现时的该函数来构 造函数声明。
【例 】对被调用的函数作声明
#include <stdio.h>
void main()
{
float add(float x,float y ); //对被调用函数的声明
float a,b,c;
scanf("%f,%f",&a,&b);
c=add(a,b);
printf("sum is %f",c);
}
float add(float x, float y)/*函数首部*/
{
float z; /*函数体 */
z=x+y;
return(z);
}
结果:
这里需要注意一个问题:
函数的入口在main(),程序从上至下执行的,如果你的函数在main()之后定义并且没有声明,执行的时候编译器会找不到定义而编译不过。声明就是要让编译器知道函数原型 (检测错误)
总之,如果main()函数在前,必须在main()中写函数声明;如果函数在main()前面,可以不在main()中写函数声明
1、main()函数在前不声明,报错
2、main()函数在后不声明,正常运行
第三节 函数的参数和数据传递方式
C语言规定在函数之间传递数据包括值传递、地址传递、返回值和全局变量传递四 种方式。
一、值传递方式
值传递方式是在形参和实参之间传递数据的一种方式,值传递方式传递的是参数值。判断是否 是值传递方式的唯一方法是看函数定义时的形参是不是变量形式。如果形参是变量,则是值传递方 式。调用该函数时的实参可以是常量、变量、表达式、数组元素等。无论实参是何种类型,在进行 函数调用时,它们都必须具有确定的值,以便把这些值传递给形参。
函数调用时,C 语言对值传递方式的形参变量分配内存单元,然后将实参的值保存到相应的内 存单元,完成值传递;函数调用结束时,即刻自动释放分配给形参变量的内存单元,其值将丢失。 因此,形参变量只在该函数内有效,即函数调用结束返回主调函数后,则不能再使用该形参变量。 下次再调用该函数时,将给形参变量重新分配内存。
值传递方式能够确保不管在被调函数中怎样操作或改变形参的内容,但主调函数中的实参并未 发生变化。实参对形参的数据传递是单向的,即只能从主调函数将实参值传递给被调函数的形参, 而不能把被调函数的形参值反向传递给主调函数的实参。实参和形参占用不同的内存单元,即使重 名也互不影响。
二、数组作为函数参数的数据传递方式
数组作为函数参数有两种形式,一种是把数组元素作为函数的实参使用;另一种是把数组名作 为函数的形参和实参使用。
- 数组元素作为函数实参
- 数组元素即下标变量,它与普通变量没有区别。数组元素只能用做函数实参,和变量作为函数 实参一样。在调用函数时,把数组元素的值传递给形参,实现单向的值传递方式。需要注意的是:
1)数组元素作为实参时,只要数组的类型和函数形参的类型一致即可,并不要求函数的形参也 是下标变量。换言之,对数组元素的处理是按普通变量对待的。
2)普通变量或下标变量作为函数参数时,形参变量和实参变量由编译程序分配不同的内存单元。 在函数调用时进行值传送,把实参变量的值赋予形参变量。
2. 数组名作为函数参数
数组名作为函数参数时,既可以作为形参,也可以作为实参。数组名作为函数参数时,要求形 参和相应的实参必须是同类型的数组(或指向数组的指针变量),都必须有明确的数组定义。
如果形参是数组名,则传递方式称为“地址传递方式”。调用函数时的实参是地址型表达式, 例如数组的首地址或已经赋值的指针变量、指针数组元素等。
调用函数时,将实参的地址赋予对应的形参数组作为其首地址,由于形参和实参的地址相同, 即它们占用相同的内存,这些内存中的数据在主调函数中通过实参可以使用,而在被调函数中通过 形参数组也可以使用,即这些内存中的数据是由主调函数和被调函数共享的。
使用这种“地址传递方式”时,系统将不再为形参数组分配另外的内存,从被调函数返回时, 也不会收回形参数组所占用的内存。因此,当形参数组发生变化时实参数组也随之变化。
当一维数组名作为形参时可以指定数组的长度,也可以不指定数组的长度,而只在数组名后面 跟一对空的方括号。有时为了灵活地处理不同长度的数组,可以专门定义一个形参,传递需要处理 的数组元素的个数。实参数组和形参数组的长度可以一致也可以不一致。若形参数组长度大于或等 于实参数组长度,则形参能得到实参全部元素值;若形参数组长度小于实参数组长度,则形参数组 只取实参数组的一部分,其余不起作用。
当多维数组名作为实参和形参时,形参数组可以省略第一维的长度说明,但第二维及以上的长 度说明不能省略。
数组名作为形参时,实参向形参传递的是实参数组的首地址,实参数组和形参数组的各元素按 照存储结构一 一对应共享存储空间。
三、利用返回值的数据传递方式
利用返回值的数据传递方式并不是在形参和实参之间进行数据传递,而是通过函数调用直接返 回一个值到主调函数。因此,这种方式通常用于从被调函数向主调函数回传值。利用返回值传递数 据,在定义函数时需要注意以下两点:
1)函数首部中需要有“数据类型符”,说明该函数返回值的数据类型。
2)函数体中需要有语句“return(表达式);”,其中的表达式即是函数的返回值。
第四节 变量的存储类型和作用域
C 程序由函数组成,每个函数都会用到变量。变量具有存储类型、存储方式、生存期和作用域 等多种属性。
一、变量的存储类型
存储类型决定变量在内存中的存储位置。在计算机的内存和 CPU 的寄存器都可以存储变量。变 量存储区域的不同决定了变量存储类型的不同。
C 语言中变量的存储类型分为自动型(auto)、寄存器型(register)、外部型(extern)和
静态型(static)四种。
变量的存储方式分为两大类,即静态存储方式和动态存储方式。静态存储方式是在编译时由系统分配固定的存储空间,直到程序运行结束后才释放所占用的存储空间;动态存储方式是在程序运 行时根据需要动态地分配和释放存储空间。 自动型变量和寄存器型变量属于动态存储方式;外部型变量和静态型变量属于静态存储方式。
1、自动型变量
【格式】[auto] 数据类型 变量表;
定义变量时若声明存储类型为 auto,则所定义的变量为自动型变量。
例如,auto int x,y;
其中,关键字 auto 可以省略。当省略 auto 时,C 语言默认为 auto 型,即定义变量时不特别 声明存储类型的都默认为自动型变量。程序中使用的变量大多是自动型变量。
【说明】
1)自动型变量存放在内存的动态存储区,属于动态存储方式。从分配到释放之间是自动型变量的生存期。
2)定义自动型变量时如果没有初始化,则其值是不确定的,是系统赋予的随机数;如果初始化,则赋初值操作是在进入所定义的函数或复合语句时进行的,且每次都要重新初始化。
3)由于自动型变量的作用域和生存期都局限于定义它的个体内,因此不同的个体中允许使用重 名的变量而不会混淆。
建议:系统不会混淆,并不意味着人也不会混淆,所以尽量少用重名的自动型变量!
2. 静态型变量
【格式】static 数据类型 变量表;
定义变量时若声明存储类型为 static,则所定义的变量为静态型变量。
例如,static int x,y;
【说明】
1)静态型变量存放在内存的静态存储区,属于静态存储方式。在程序执行过程中,即使所在 函数调用结束也不释放。换言之,在程序执行期间,静态型变量始终存在,始终占用固定的存储空间,直到程序运行结束后才释放存储空间。静态型变量的生存期与程序的运行期相同。因为函数中 的静态型变量在函数调用结束时不释放占用的存储空间,因此其值能保留下来,供下一次调用该函 数时使用。
2)定义静态型变量时如果没有初始化,则编译系统自动赋值 0(整型和实型)或’\0’(字符型)。
例如,
static int x,y[2];
printf("%d,%d,%d\n",x,y[0],y[1]);
则执行后输出结果均为 0。
【例】自动型变量与静态型变量的例子。
#include<stdio.h>
void auto_static(void) /*定义函数 auto_static*/
{
int var_auto=0; /*定义自动型变量,每次调用都重新初始化*/
static int var_static=0; /*定义静态型变量,只初始化一次*/
printf("var_auto=%d,var_static=%d\n",var_auto,var_static);
++var_auto;
++var_static;
}
int main(void) /*主函数*/
{
int i;
for(i=0;i<=2;i++)
auto_static( ); /*调用函数 auto_static*/
return 0;
}
运行结果:
3. 寄存器型变量
【格式】register 数据类型 变量表;
一般情况下,变量的值都存储在内存中。为了提高执行效率,C 语言允许将频繁使用的局部变量的值存储到 CPU 的通用寄存器,这种变量称为寄存器型变量。定义变量时若声明存储类型为 register,则所定义的变量为寄存器型变量。寄存器型变量一般是在函数中定义的,退出函数后则释 放所占用的 CPU 寄存器。寄存器型变量可以进行初始化。通常,寄存器变量使用较少,在此不作过多介绍。
【说明】
1)只有局部变量才能定义为寄存器型变量,全局变量不行。
2)对于占用字节数较多的变量,如 long、float 和 double 型的变量一般不能定义为寄存器型 变量。
3)对寄存器型变量的实际处理随系统而异。
4)由于 CPU 具有的通用寄存器数量有限,所以允许定义的寄存器型变量的数量也有限,不能 定义任意多个寄存器型变量,具体限制取决于运行环境和编译系统。
4. 外部型变量
【格式】extern 数据类型 变量表;
【说明】
外部型变量是专门用于在多个编译单元之间传递数据的。
【格式】extern 数据类型 变量表;
定义变量时若声明存储类型为 extern,则所定义的变量为外部型变量。
【说明】外部型变量是专门用于在多个编译单元之间传递数据的。当编译单元 A 需要使用在编译单元 B 中定义的变量,则编译单元 A 需要将该变量声明为外部型变量,以便 C 编译系统在编译单 元 A 之外的其他编译单元中寻找该变量的定义,而在编译单元 B 中需要定义该变量的存储类型和数据类型。
关键字 extern 与 auto、static、register 的用法不同,后三个关键字是在定义变量时加关键字, 而 extern 是对已经定义的全局变量进行声明。
二、变量的生存期和作用域
1、变量的生存期
由于必须为定义的变量分配内存单元(或寄存器),所以变量有存活时间的问题,从系统为变量分配内存单元(或寄存器)开始到系统收回内存单元(或寄存器)的期间称为“变量的生存期”。 在变量的生存期以外使用该变量会导致编译错误。
2、变量的作用域
变量有效的范围称为变量的作用域。C 语言中所有的变量都有其作用域。变量说明的位置不词, 其作用域也不同。 用户使用的存储空间分为程序区、静态存储区和动态存储区三部分,如图 6-2 所示。其中,程 序区存放源程序代码,静态存储区和动态存储区存放数据。
3、 全局变量和局部变量
根据变量生存期的不同,变量可分为局部变量和全局变量
生存期覆盖了定义点到整个程序结束的变量称为“全局变量”;生存期只覆盖了某个函数(或 复合语句)的变量称为“局部变量
全局变量存放在静态存储区,属于静态存储方式。在整个程序运行期间,全局变量始终占用固 定的存储单元,直到程序运行结束才释放。全局变量的生存期与程序运行期相同。定义全局变量时 如果没有初始化,则自动赋值 0(整型和实型)或’\0’(字符型)。用于说明全局变量的存储类型的 关键字是 extern 和 static。
1)用 extern 在一个文件内扩展全局变量的作用域。全局变量的作用域是从它的定义处开始到 本程序文件末尾,如果位于全局变量之前的函数要引用该全局变量,需要在函数内用关键字 extern 将其声明为外部变量,表示该变量是一个已经定义的全局变量,声明后其作用域便扩展到了声明处, 从声明处开始,就可以合法使用该变量。用 extern 声明变量时,变量的数据类型可以写也可以不写。
2)用 extern 将全局变量的作用域扩展到其他文件。当源程序由多个文件组成时,如果在 B 文件中想引用 A 文件中已定义的全局变量,则需要在 B 文件中用 extern 对该全局变量进行声明。
3)用 static 将全局变量的作用域限制在本文件。如果希望某些全局变量只限于被本文件引用而 不能被其他文件引用,可以在定义这些全局变量时加一个 static 声明。
对于局部变量而言,声明存储类型的作用是指定变量的存储位置(静态存储区或动态存储区) 和生存期;对于全局变量而言,声明存储类型的作用是扩展或限制变量的作用域。
4、内部变量和外部变量
内部变量:
在一个函数(或复合语句)内部定义的变量称为“内部变量”
关于内部变量的作用域,需要注意以下几点:
(1)主函数中定义的内部变量,只能在主函数中使用,其他函数不能使用。
(2)形参变量也是内部变量,仅限于函数内使用。
(3)允许在不同函数中使用重名的变量,它们代表不同的对象,分配不同的内存单元,互不干扰,也不 会发生混淆。
(4)复合语句中也可以定义变量,所定义的变量是内部变量,其作用域只在该复合语句范围内。
外部变量:
在函数外部定义的的变量称为“外部变量”。外部变量不属于任何一个函数,其作用域是从定义外部变量 的位置开始到本源程序文件结束。 对于外部变量,需要注意以下几点: (1)在同一源程序文件中,允许外部变量和内部变量重名。
(2)外部变量的作用域是从定义点到本文件结束
三、利用全局变量的数据传递方式
利用全局变量传递数据的方式并不是在形参和实参之间传递数据,而是利用在主调函数和被调 函数都有效的全局变量,在主调函数和被调函数之间共享数据。
如前所述,全局变量是生存期覆盖了定义点到整个源程序结束的变量。具体地,全局变量有两 种,一是在任何函数之外定义的全局变量,其作用域覆盖了定义点到整个源程序结束的所有函数, 这种全局变量叫作“外部变量”;二是在函数体内部声明为 static 型的变量,该变量从函数返回后, 仍保留所分配的内存(活着),但是不能使用,其作用域仍是该函数体内。这种全局变量叫作“内 部变量”。因此,在函数之间利用全局变量传递数据,只能使用“外部变量”。
第五节 函数的嵌套调用和递归调用
C 语言允许函数的嵌套调用和递归调用。
1、函数的嵌套调用
函数的嵌套调用是指在调用函数时,被调函数又调用了其他函数或其自身。当被调函数是函数 自身时,称为函数的递归调用,递归调用是嵌套调用的一种特例。函数嵌套调用的过程如图 6-3 。
图 6-3 所示是函数两层嵌套调用的情形,即主函数调用函数 x( ),函数 x( )又调用函数 y( )。其 中,①〜⑨表示程序的执行顺序。
第六节 常用库函数
一、常用的数学处理函数
下面是一些常用的数学处理的库函数,它们均包含在头文件 math.h 中。
1. 计算整型绝对值函数
【函数首部】int abs(int x)
【返回值】返回整数 x 的绝对值。
例如,abs(-5)结果是 5。
例:
//C语言
#include<stdio.h>
#include<stdlib.h>
int main(void) {
// your code goes here
int a = -10;
long b = -100;
long c = 1000;
printf("%d\n", abs(a));
//10
printf("%ld,,,%ld\n", abs(b),b);
//10
printf("%lld\n", abs(c));
//10
return 0;
}
2. 计算长整型绝对值函数
【函数首部】long labs(long x)
【返回值】返回长整型数 x 的绝对值。
例如,labs(-6L)结果是 6L
3. 计算实型绝对值函数
【函数首部】double fabs(double x)
【返回值】返回双精度实数 x 的绝对值。
例如,fabs(-6.3)结果是 6.3。
4. 计算小于或等于 x 的最大整数函数
【函数首部】double floor(double x)
【返回值】返回小于或等于 x 的最大整数对应的双精度实数。
例如,floor(-2.3)结果是-3.0。floor(5.9)结果是 5.0。
5. 计算大于或等于 x 的最小整数函数
【函数首部】double ceil(double x)
【返回值】返回大于或等于 x 的最小整数对应的双精度实数。
例如,ceil(-2.3)结果是-2.0。 ceil(5.9)结果是 6.0。
6. 计算平方根函数
【函数首部】double sqrt(double x)
【返回值】返回 x 的平方根。
【说明】X 的值≥0。
7. 计算常用对数函数
【函数首部】double log10(double x)
【返回值】返回常用对数 log10(x)的值。
【说明】x 的值>0。
8. 计算自然对数函数
【函数首部】double log(double x)
【返回值】返回自然对数 ln(x)的值。
【说明】x 的值>0
9. 计算指数函数
【函数首部】double exp(double x)
【返回值】返回 e 的 x 次幂的值。
10. 计算 10 的 x 次幂函数
【函数首部】double pow10(int x)
【返回值】返回 10x的值。
例如,pow10(-2)结果是 0.010000。
11. 计算 x 的 y 次方函数
【函数首部】double pow(double x,double y)
【返回值】返回 xy的值。
【说明】不能出现 x、y 均<0;或 x≤0,而 y 不是整数的情况。
例如,pow(2,-3)结果是 0.125000。 pow(0.2,3)结果是 0.008000。
12. 计算正弦函数
【函数首部】double sin(double x)
【返回值】返回正弦函数 sin(x)的值。
【说明】x 以弧度为单位。如果是角度,则用 x*3.1415926/180 转换为弧度。
13. 计算余弦函数
【函数首部】double cos(double x)
【返回值】返回正弦函数 cos(x)的值。
【说明】x 以弧度为单位。如果是角度,则用 x*3.1415926/180 转换为弧度。
14. 计算正切函数
【函数首部】double tan(double x)
【返回值】返回正弦函数 tan(x)的值。
【说明】x 以弧度为单位。如果是角度,则用 x*3.1415926/180 转换为弧度。
二、常用的类型转换函数
下面是一些常用的类型转换库函数,它们均包含在头文件 stdlib.h 中
1. 字符串转换成浮点数函数
【函数首部】double atof(char *x)
【返回值】返回 x 所指向的字符串转换成的实数。
【说明】x 所指向的字符串中存放的应当是一个实数形式。
例如,“32.1”、“0.321e2”,则转换结果分别是 32.1 和 32.1。
如果不是正确的实数形式, 则转换结果将取前面若干个能组成实数的字符。
例如,“2S32.1”和“0.3b21e2”,则转换结果分 别是 2.0 和 0.3。
2. 字符串转换成整数函数
【函数首部】int atoi(char *x)
【返回值】返回 x 所指向的字符串转换成的整型数。
【说明】x 所指向的字符串中存放的应当是一个整数形式。
例如,"32”、"321",则转换结果分别是 32 和 321。如果不是正确的整数形式,则转换结果
将取前面若干个能组成整数的字符对应的整数。
例如,"2S32”和”45b21e2”,则转换结果分别 是 2 和 45。
三、常用的字符处理函数
下面是一些常用的字符处理库函数,它们均包含在头文件 ctype.h 中
1. 判断是否字母函数
【函数首部】int isalpha(int x)
【返回值】若 x 中存放的字符是字母。则返回非 0(真);否则,返回 0(假)。
例如,
isalpha(50)结果是 0(50 对应的字符是'2')。
isalpha('w')结果是非 0。
2. 判断是否小写字母函数
【函数首部】int islower(int x)
【返回值】若 x 中存放的字符是小写字母,则返回非 0(真);否则,返回 0(假)。
例如,
islower(98)结果是非 0(98 对应的字符是'b')。
islower('G')结果是 0。
3. 判断是否大写字母函数
【函数首部】int isupper(int x)
【返回值】若 x 中存放的字符是大写字母。则返回非 0(真);否则,返回 0(假)。
例如,
isupper(98)结果是 0(98 对应的字符是'b')。
isupper('G')结果是非 0。
4. 判断是否数字字符函数
【函数首部】int isdigit(int x)
【返回值】若 x 中存放的字符是数字字符,则返回非 0(真);否则,返回 0(假)。
例如,
isdigit(50)结果是非 0(50 对应的字符是'2')。
isdigit('b')结果是 0
5. 将大写字母转换为小写字母函数
【函数首部】int tolower(int x)
【返回值】若 x 中存放的字符是大写字母,则返回值是对应的小写字母;若 x 中存放的字符不 是大写字母,则返回值等于 x 的原值。
例如,
tolower(50)结果是'2'(50 对应的字符是'2')。
tolower('B')结果是'b'。
6. 将小写字母转换为大写字母函数
【函数首部】int toupper(int x)
【返回值】若 x 中存放的字符是小写字母,则返回值是对应的大写字母;若 x 中存放的字符不 是小写字母,则返回值等于 x 的原值。
例如,
toupper(50)结果是'2'(50 对应的字符是'2')。
toupper('b')结果是'B'。
四、其他的常用函数
1. 随机数发生器初始化函数
【函数首部】void randomize( )
【功能】对随机数发生器进行初始化。
【返回值】无
2. 随机数发生函数
【函数首部】int random(int x)
【功能】产生一个 0〜x-1 的随机整数。
【返回值】返回一个 0〜x-1 的随机整数。
以上两个库函数所在的头文件均为 stdlib.h。