第七章:用函数实现模块化程序设计

第七章:用函数实现模块化程序设计

7.1 为什么要用函数

函数就是功能。每一个函数用来实现一个特定的功能。函数的名字反映其代表的功能。
在设计一个较大的程序时,往往把它分为若干个程序模块,每一个模块包括一个或多个函数,每个函数实现一个特定的功能。一个C程序可以由一个主函数和若干个其他函数构成。由主函数调用其他函数其他函数也可以相互调用。同一个函数可以被一个或多个函数调用任意多次。
除了可以使用库函数外,有的部门还编写一批本领域或本单位常用到的专用函数,供本领域或本单位的人员使用。
在程序设计中要善于利用函数,以减少重复编写程序段的工作量,也更便于实现模块化的程序设计。
注意:
(1)一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对于较大的程序,一般不希望把所有内容全放在一个文件中,而是将他们分别放在若干个源文件中,由若干个源程序文件组成一个C程序。这样便于编写和编译,提高调试效率,一个源程序文件可以为多个C程序共用。
(2)一个源程序文件由一个或多个函数以及其他有关内容(如指令、数据声明与定义等)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
(3)C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。
(4)所有函数都是平行的,即在定义函数时是分别进行的,是相互独立的。一个函数不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main函数。main函数是被操作系统调用的。
(5)从用户使用的角度来看,函数由两种:

在这里插入图片描述

7.2 怎样定义函数

7.2.1为什么要定义函数

C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。定义函数应包含以下几个内容;
(1)指定函数的名字,以便以后按名调用。
(2)指定函数的类型,即函数返回值的类型。
(3)指定函数的参数的名字和类型,以便在调用函数时向它们传递数据,对无参函数不需要这项。
(4)指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能。这是最重要的,是在函数体中解决的

7.2.2 定义函数的方法

1.定义无参函数

定义无参函数的一般形式为:
在这里插入图片描述
函数体包括声明部分语句部分

2.定义有参函数

一般形式:
在这里插入图片描述

3.定义空函数

形式:
在这里插入图片描述
在编写程序的开始阶段,可以在将来准备扩充功能的地方写上一个空函数,只是这些函数暂时还未编写好,先用空函数占一个位置,等以后扩充程序功能时用一个编号的函数代替它。这样做,程序的结构清楚,可读性好,以后扩充新功能方便,对程序结构影响不大。

7.3 调用函数

7.3.1 函数的调用形式

一般形式为:
函数名(实参表列)

如果是无参函数,则“实参表列”可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开。
按函数调用在程序中出现的形式和位置来分,可以有以下3种函数调用方式。

在这里插入图片描述

7.3.2函数调用时的数据传递

1.形式参数和实际参数

在调用有参函数时,主调函数和被调用函数之间有数据传递关系。从前面已知:在定义函数时函数名后面括号中的变量名称为 “形式参数” (简称形参)或 “虚拟参数”。在主调函数中调用一个函数时,函数名后面括号中的参数称为 “实际参数”(简称实参)。实参可以是常量、变量或表达式。

2.实参和形参间的数据传递

在调用函数过程中,系统会把实参的值传递给被调用函数的形参。或者说,形参从实参得到一个值。该值在函数调用期间有效,可以参加该函数中的运算。
在调用函数过程中发生的实参与形参间的数据传递称为 “虚实结合”
注意:
在这里插入图片描述

7.3.3 函数调用的过程

(1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。在发生函数调用时,函数max的形参才被临时分配内存单元。
(2)将实参的值传递给对应形参。如图7.3所示,实参的值为⒉,把2传递给相应的形参x,这时形参x就得到值2,同理,形参y得到值3。
(3)在执行max函数期间,由于形参已经有值,就可以利用形参进行有关的运算(例如把x和y比较,把x或y的值赋给z等)。
(4)通过return语句将函数值带回到主调函数。例7.2中在return语句中指定的返回值是z,这个z就是函数max的值(又称返回值)。执行return语句就把这个函数返回值带回主调函数main。应当注意返回值的类型与函数类型一致。现在, max函数为int型,返回值是变量z,也是int型。二者一致。
如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。(5)调用结束,形参单元被释放。注意:实参单元仍保留并维持原值,没有改变。如果在执行一个被调用函数时,形参的值发生改变,不会改变主调函数的实参的值。例如,若在执行max函数过程中x和y的值变为10和15,但 a和 b仍为2和3,见图7.4。这是因为实参与形参是两个不同的存储单元。
注意:实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。

7.3.4 函数的返回值

通常,希望通过函数调用使主调函数能得到一个确定的值,这就是函数值(函数的返回值)
下面对函数值作一些说明。
(1)函数的返回值是通过函数中的return语句获得的
(2)在定义函数时应当指定函数值的类型
(3)在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。即函数类型决定返回值的类型
(4)对于不带回值的函数,应当用定义函数为“void类型”(或称“空类型”)。

7.4 对被调用函数的声明和函数模型

在一个函数中调用另一个函数(即被调用函数)需要具备如下条件:
(1)首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)。但仅有这一条件还不够。
(2)如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息“包含”到本文件中来。
(3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明(declaration)。声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在遇到函数调用时,编译系统能正确识别函数并检查调用是否合法。

写函数声明时,可以简单地照写已定义的函数的首行,再加一个分号,就成了函数的 “声明”。函数的首行(即函数首部)称为函数原型(function prototype)
为什么要用函数的首部来作为函数声明呢?这是为了便于对函数调用的合法性进行检查。因为在函数的首部包含了检查调用函数是否合法的基本信息(它包括了函数名,函数值类型、参数个数、参数类型和参数顺序),在检查函数调用时要求函数名、函数类型、参数个数和参数顺序必须与函数声明一致,实参类型必须与函数声明中的形参类型相同(或赋值兼容,如实型数据可以传递给整型形参,按赋值规则进行类型转换)。否则就按出错处理。这样就能保证函数的正确调用。
根据以上的介绍,函数声明的形式一般有两种,分别为
(1)函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2,…,参数类型n 参数名n);
(2)函数类型 函数名(参数类型1 ,参数类型2,…,参数类型n);
注意:对函数的“定义”和“声明”不是同一回事。函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的,独立的函数单位。而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包含函数体。
如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明。
由于在文件的开头(在函数的外部)已对要调用的函数进行了声明(这些称为“外部的声明”),因此在程序编时,编译系统已从外部声明中知道了函数的有关信息﹐所以不必在主调函数中再重复进行声明。写在所有函数前面的外部声明在整个文件范围中有效。

7.5 函数的嵌套调用

C语言的函数定义是互相平行、独立的,也就是说,在定义函数时,一个函数内不能再定义另一个函数,即不能嵌套定义,但可以嵌套调用函数,即在调用一个函数的过程中﹐又调用另一个函数,见
图7.5。
图7.5表示的是两层嵌套(连main函数共3层函数),其执行过程是:
①执行main函数的开头部分;
②遇函数调用语句,调用函数a,流程转去a函数;
③执行a函数的开头部分;
④遇函数调用语句,调用函数b,流程转去函数b;
⑤执行b函数,如果再无其他嵌套的函数,则完成b函数的全部操作;
⑥返回到a函数中调用b函数的位置;
⑦继续执行a函数中尚未执行的部分,直到a函数结束;⑧返回main函数中调用a函数的位置;
⑨继续执行main函数的剩余部分直到结束。
在这里插入图片描述
递推方法求最大值:

int max2(int a, int b){
	return(a>=b?a:b);}

int max4(int a,int b,int c,int d)
	{int max2(int a,int b);   //对max2的函数声明
	return max2(max2(max2(a,b),c),d);
	}

7.6 函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。(此部分需要大量练习)

7.7 数组作为函数参数

数组元素可以用作函数实参,其用法与变量相同,向形参传递数组元素的值。同时,数组名也可以作实参和形参,传递的是数组第一个元素的地址。

7.7.1 数组元素作函数实参

数组元素可以用作函数实参,但是不能用作形参。因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。在用数组元素作函数实参时,把实参的值传给形参,是 “值传递” 方式。数据传递的方向是 从实参传到形参,单向传递

7.7.2 一维数组名作函数参数

用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。
==注意:
(1)用数组名作函数参数,应该在主调函数和被调用函数分别定义数组。
(2)实参数组与形参数组类型应一致(今都为float型),如不一致,结果将出错。
(3)在定义函数时,声明形参数组的大小是不起任何作用的,因为C语言编译系统并不检查形参数组大小,只是将实参数组的首元素的地址传给形参数组名。形参数组名获得了实参数组的首元素的地址,前已说明,数组名代表数组的首元素的地址,因此,可以认为,形参数组首元素和实参数组首元素具有同一地址,它们共占同一存储单元。
(4)形参数组可以不指定大小,在定义数组时在数组名后面跟一个空的方括号,如:float average(float array[])效果是相同的。
注意:在学习了第8章(指针)以后,可以知道在对源程序编译时,编译系统把形参数组处理为指针变量(float array[]转换为float *array),该指针变量用来接收从实参数组传过来的地址。C语言允许用指针变量(如 float *array)或数组(如float array[])作为形参,二者是等价的。对数组元素的访问,用下标法和指针法也是完全等价的。用形参数组是为了便于理解,形参数组与实参数组各元素一一对应,比较形象好懂,即使未学过指针,也能方便地使用。在学习了指针后会对形参数组的本质有更深入的理解。

7.7.3 多维数组名作函数参数

可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。二者都合法而且等价。但是不能把第2维以及其他高维的大小说明省略。

7.8 局部变量和全局变量

作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。

7.8.1 局部变量

定义变量可能有3种情况:
(1)在函数的开头定义;
(2)在函数内的复合语句内定义;
(3)在函数的外部定义。
在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数以外是不能使用这些变量的。在复合语句内定义的变量只在本复合语句范围内有效,只有在本复合语句内才能引用它们。在该复合语句以外是不能使用这些变量的,以上这些称为 “局部变量”
注意:
(1)主函数中定义的变量也只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
(2)不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
(3)形式参数也是局部变量。
(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为 “分程序”“程序块”

7.8.2 全局变量

前已介绍,程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
在函数内部定义的变量是局部变量,在函数外定义的变量是全局变量
在一个函数中既可以使用本函数中的局部变量,也可以使用有效的全局变量。
设置全局变量的作用是增加了函数间数据联系的渠道。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值。相当于各个函数间有直接的传递通道。由于函数的调用只能带回一个函数返回值,因此有时可以利用全局变量来增加函数间的联系渠道,通过函数调用能得到一个以上的值。
为了便于区别全局变量和局部变量,在C程序设计人员中有一个习惯(但非规定),将全局变量名的第1个字母用大写表示。

但是,建议不在必要时不要使用全局变量,原因如下:
①全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。它使函数的通用性降低了,因为如果在函数中引用了全局变量,那么执行情况会受到有关的外部变量的影响,如果将一个函数移到另一个文件中,还要考虑把有关的外部变量及其值一起移过去。但是若该外部变量与其他文件的变量同名时,就会出现问题。这就降低了程序的可靠性和通用性。在程序设计中,在划分模块时要求模块的“内聚性”强、与其他模块的“耦合性”弱。即模块的功能要单一(不要把许多互不相干的功能放到一个模块中),与其他模块的相互影响要尽量少,而用全局变量是不符合这个原则的。一般要求把C程序中的函数做成一个相对的封闭体,除了可以通过“实参—形参”的渠道与外界发生联系外,没有其他渠道。这样的程序移植性好,可读性强。
③使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部变量的值。由于在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
注意:当全局变量和局部变量同名时,在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用

7.9 变量的存储方式和生存期

7.9.1 动态存储方式与静态存储方式

从变量的作用域(即空间上)的角度来看,变量可以分为全局变量和局部变量。
从变量值的存在的时间(即生存期)来看,变量的存储有两种方式:静态存储方式动态存储方式。静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。
内存中的供用户使用的存储空间分为三个部分:
在这里插入图片描述
数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。
在动态存储区中存放以下数据;
①函数形式参数。在调用函数时给形参分配存储空间。
②函数中定义的没有用关键字static声明的变量,即自动变量。
③函数调用时的现场保护和返回地址等。
对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的。
如果一个程序中包含若干个函数﹐每个函数中的局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分。在程序执行过程中,先后调用各个函数,此时会动态地分配和释放存储空间。
在C语言中,每一个变量和函数都有两个属性:数据类型数据的存储类别。对数据类型,读者已经熟知(如整型、浮点型等)。存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)。
在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(即如果用户不指定,系统会隐含地指定为某一种存储类别)。
C的存储类别包括4种:自动的(auto)、静态的(statis)、寄存器的(register)、外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。

7.9.2 局部变量的存储类别

1.自动变量(auto变量)

函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。如:

int f(int a){
	auto int b,c=3;
	.
	.
	.
	}

其中,a是形参,b和c是自动变量,对c赋初值3。执行完f函数后,自动释放a,b,c所占的存储单元。
实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”,它属于动态存储方式。程序中大多数变量属于自动变量。前面几章中介绍的例子,在函数中定义的变量都没有声明为auto,其实都隐含指定为自动变量。

2.静态局部变量(static局部变量)

有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。通过下面简单的例子可以了解它的特点。如:

#include<stdio.h>
int main(){
	int f(int);			//函数声明
	int a=2,i;			//自动局部变量
	for(i=0;i<3;i++){
		printf("%d\n",f(a));}
	return 0;

int f(int a){
	auto int b=0;
	static c=3;
	b++;
	c++;
	return (a+b+c);

最后的运行结果为:7 8 9。
在这里插入图片描述
注意:
(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,分配在动态存储区空间而不在静态存储区空间,函数调用结束后即释放。
(2)对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3)如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符’\o’(对字符变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的内容是不可知的。
(4)虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用。

但是应该看到,用静态存储要多占内存(长期占用不释放﹐而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存),而且降低了程序的可读性,当调用次数多时往往弄不清静态局部变量的当前值是什么。因此,若非必要,不要多用静态局部变量。

3.寄存器变量(register变量)

在这里插入图片描述

7.9.3 全局变量的存储类别

一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域。有以下几种情况。

1.在一个文件内扩展外部变量的作用域

如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作**“外部变量声明”**,表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法地使用该外部变量。如:

#include<stdio.h>
int main(){
	int max();
	extern int A,B,C;  //把外部变量ABC的作用域扩展到从此处开始
	printf("Please enter three integer numbers:");
	scanf("%d %d %d",&A,&B,&C);
	printf("max is %d\n",max());
	return 0;
	}

int A,B,C;

int max(){
	int m;
	m = A>B?A:B;
	if(C>m)m=C;
	return(m);
	}

提倡将外部变量的定义放在引用它的所有函数之前,这样可以避免在函数中多加一个extern声明。
用extern声明外部变量时,类型名可以写也可以省写。例如,“extern int A,B,C;”也可以写成“extern A,B,C;”。因为它不是定义变量,可以不指定类型,只须写出外部变量名即可。

2.将外部变量的作用域扩展到其他文件

如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一个文件中定义外部变量-Num,而在另一文件中用extern对Num作“外部变量声明”,即“extern Num;”。在编译和连接时,系统会由此知道Num有“外部链接”,可以从别处找到已定义的外部变量Num,并将在另一文件中定义的外部变量Num的作用域扩展到本文件,在本文件中可以合法地引用外部变量Num。
那么,extern 既可以用来扩展外部变量在本文件中的作用域,又可以使外部变量的作用域从一个文件扩展到程序中的其他文件,那么系统怎么区别处理呢?实际上,在编译时遇到extern 时,先在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件;如果再找不到,就按出错处理。

3.将外部变量的作用域限制在本文件中

有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。
这种加上 static声明、只能用于本文件的外部变量称为静态外部变量。在程序设计中,常由若干人分别完成各个模块,各人可以独立地在其设计的文件中使用相同的外部变量名而互不相干。只须在每个文件中定义外部变量时加上 static即可。这就为程序的模块化、通用性提供方便。如果已确认其他文件不需要引用本文件的外部变量,就可以对本文件中的外部变量都加上 static,成为静态外部变量,以免被其他文件误用。这就相当于把本文件的外部变量对外界“屏蔽”起来,从其他文件的角度看,这个静态外部变量是“看不见,不能用”的。至于在各文件中在函数内定义的局部变量,本来就不能被函数外引用,更不能被其他文件引用,因此是安全的。
注意:不要误认为对外部变量加 static声明后才采取静态存储方式(存放在静态存储区中),而不加 static的是采取动态存储(存放在动态存储区)。声明局部变量的存储类型和声明全局变量的存储类型的含义是不同的。对于局部变量来说,声明存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是在编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用域的扩展问题。
用static声明一个变量的作用是:
(1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
(2〉对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。
注意:用auto,register 和 static声明变量时,是在定义变量的基础上加上这些关键字,而不能单独使用。下面的用法不对:

int a;		//先定义整型变量a
static a;	//企图再将变量a声明为静态变量

编译时会被认为“重新定义”

7.9.4 存储类别小结

对一个数据的定义,需要指定两种属性:数据类型存储类别,分别使用两个关键字。
此外,可以用extern声明已定义的外部变量。
总计起来,有如下归纳:
(1)从作用域角度分,有局部变量和全局变量。它们采用的存储类别如下:
在这里插入图片描述
(2)从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。
在这里插入图片描述
(3)从变量值存放的位置来区分,可分为:
在这里插入图片描述
(4)关于作用域和生存期的概念。从前面叙述可以知道,对一个变量的属性可以从两个方面分析,一是变量的作用域,一是变量值存在时间的长短,即生存期。前者是从空间的角度,后者是从时间的角度。二者有联系但不是同一回事。图7.19是作用域的示意图,图7.20是生存期的示意图。
在这里插入图片描述
如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在专业书中称变量在此作用域内 “可见”,这种性质称为变量的可见性。例如图7.19中变量a和 b在函数f1中“可见”。如果一个变量值在某一时刻是存在的,则认为这一时刻属于该变量的 生存期,或称该变量在此时刻 “存在”。表7.2表示各种类型变量的作用域和存在性的情况。

在这里插入图片描述
(5)static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局变量来说,它使变量局部化(局部于本文件),但仍为静态存储方式。从作用域角度看,凡有static声明的,其作用域都是局限的,或者局限于本函收内(静态局部变量),或者局限于本文件内(静态外部变量)。

7.10 关于变量的声明和定义

一个函数一般由两部分组成:声明部分执行语句。声明部分的作用是对有关的标识符(如变量、函数,结构体、共用体等)的属性进行声明。对于函数而言,声明和定义的区别是明显的,在本章7.4节中已说明,函数的声明是函数的原型,而函数的定义是对函数功能的定义。对被调用函数的声明是放在主调函数的声明部分中的,而函数的定义显然不在声明部分的范围内,它是一个独立的模块。
对变量而言,声明与定义的关系稍微复杂一些。在声明部分出现的变量有两种情况:一种是需要建立存储空间的(如“int a;”)。另一种是不需要建立存储空间的(如“extern a")。前者称为定义性声明(defining declaration),或简称定义(definition);后者称为引用性声明(referencing declaration)。广义地说,声明包括定义,但并非所有的声明都是定义。对"int a;”而言,它既是声明,又是定义;而对“extern a;”而言,它是声明而不是定义。一般为了叙述方便,把建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。显然这里的声明是狭义的,即非定义性声明。
注意:有一个简单的结论,在函数中出现的对变量的声明(除了用extern声明的以外)都是定义。在函数中对其他函数的声明不是函数的定义。

7.11 内部函数和外部函数

根据函数能否被其他源文件调用,将函数区分为内部函数外部函数

7.11.1 内部函数

如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:
static 类型名 函数名(形参表);

内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰,不必担心所用函数是否会与其他文件模块中的函数同名。
通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。这就提高了程序的可靠性。

7.11.2 外部函数

如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用。
如函数首部可以为
extern int fun (int a,int b)
这样,函数fun就可以为其他文件调用。C语言规定,如果在定义函数时省略extern,则默认为外部函数。本书前面所用的函数都是外部函数。
在需要调用此函数的其他文件中,需要对此函数作声明(不要忘记,即使在本文件中调用一个函数,也要用函数原型进行声明)。在对此函数作声明时,要加关键字extern,表示该函数“是在其他文件中定义的外部函数”。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是Python语言实现一个简单的计算器的代码,其包含加法、减法、乘法、除法、阶乘等运算: ```python # 定义加法函数 def add(x, y): return x + y # 定义减法函数 def subtract(x, y): return x - y # 定义乘法函数 def multiply(x, y): return x * y # 定义除法函数 def divide(x, y): return x / y # 定义阶乘函数 def factorial(x): if x == 0: # 0的阶乘为1 return 1 else: return x * factorial(x-1) print("请选择要进行的运算:") print("1.加法") print("2.减法") print("3.乘法") print("4.除法") print("5.阶乘") # 用户输入选项 choice = input("请输入选项(1/2/3/4/5): ") if choice in ('1', '2', '3', '4'): # 加、减、乘、除运算 num1 = float(input("请输入第一个数字: ")) num2 = float(input("请输入第二个数字: ")) if choice == '1': print(num1, "+", num2, "=", add(num1, num2)) elif choice == '2': print(num1, "-", num2, "=", subtract(num1, num2)) elif choice == '3': print(num1, "*", num2, "=", multiply(num1, num2)) elif choice == '4': if num2 == 0: # 除数不能为0 print("除数不能为0") else: print(num1, "/", num2, "=", divide(num1, num2)) elif choice == '5': # 阶乘运算 num = int(input("请输入一个正整数: ")) print(num, "的阶乘为", factorial(num)) else: print("非法输入") ``` 代码注释如下: - 第1行:定义了一个名为`add`的函数,用于实现加法运算,接收两个参数`x`和`y`,返回它们的和。 - 第4行:定义了一个名为`subtract`的函数,用于实现减法运算,接收两个参数`x`和`y`,返回它们的差。 - 第7行:定义了一个名为`multiply`的函数,用于实现乘法运算,接收两个参数`x`和`y`,返回它们的积。 - 第10行:定义了一个名为`divide`的函数,用于实现除法运算,接收两个参数`x`和`y`,返回它们的商。 - 第14-18行:定义了一个名为`factorial`的函数,用于计算阶乘,接收一个参数`x`,递归调用自身,返回`x`的阶乘。 - 第20-25行:输出可选的运算类型。 - 第28行:让用户输入运算类型。 - 第30-31行:如果用户选择加、减、乘、除运算,则让用户输入两个数字,进行相应的运算,并输出结果。 - 第33行:如果用户选择阶乘运算,则让用户输入一个正整数,进行阶乘计算,并输出结果。 - 第35-40行:如果用户输入的选项不在1-5之间,则输出“非法输入”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值