应用计算机求解复杂的实际问题时,总是把一个任务按功能分成若干个子任务,每个子任务还可以进一步细分。一个子任务称为一个功能块,在C语言中用函数(function)实现。对与反复要用到的某些程序段,如果在每次需要时都重复书写,则十分繁琐。但如果把这些程序段写成函数,则当需要时直接调用即可,而不需要重新书写。
函数的概念
函数是C语言程序的重要组成元素。C语言中,把由相关的语句组织在一起、有自己的名称、可实现独立功能、能在程序中被调用的程序块称为函数。一个较大的程序由若干个程序模块组成,每个程序模块用于实现一个特定的功能。在高级语言中,用子程序来实现模块的功能;而在C语言中,子程序的功能用函数来实现。一个C语言程序由一个主函数和若干个其他的函数组成,主函数调用其他函数,而其他函数之间可以相互调用,同一个函数也可以被其他一个或若干个函数多次调用。像变量一样,函数也是先定义后使用。
函数的分类
在C语言中,函数(function)是构成程序的基本模块。程序的执行从main函数的入口开始,到main函数的出口结束,中间循环、往复、迭代地调用一个又一个函数。每个函数分工明确,各司其职。对这些函数而言,main函数就像是一个总管。根据用户使用的情况,函数可分为标准函数和用户自定义函数。 (1)标准函数。标准函数即库函数,它是由系统提供的,用户不必自己定义,可以直接使用它们。需要注意的是,不同的C语言编译系统提供的库函数的数量和功能会有一些不同。C语言标准库中提供了丰富的库函数,如标准输入/输出函数、数学函数等。为了正确使用库函数,应注意以下几点。 1)类别不同的库函数被包含在不同的头文件中。头文件是以“.h”为扩展名的一类文件,如已经接触过的stdio.h、math.h等。这些头文件中包含了对应标准库中所有函数的函数原型和这些函数所需数据类型和常量的定义。库函数的函数原型说明了该库函数的名称、参数个数及类型、函数返回值的类型。例如,数学库函数sin的原型如下:doublesin(doublex); 2)当需要使用某个库函数时,应在程序开头用“#include”预处理命令将对应的头文件包含进来。例如,为了使用数学库函数,在程序开头添加了以下预处理命令。#include<math.h> 3)调用库函数时,应遵循下面的格式。函数名 (函数参数) 函数名通常代表了函数的功能。函数参数是要参与函数运算的数据,可以是常量、变量或者表达式。在调用函数时,函数名、函数参数以及参数的类型必须与函数原型一致。 (2)用户自定义函数。如果库函数不能满足程序设计者的编程需要,那么就需要程序设计者自己编写函数来完成所需要的功能,这类函数称为用户自定义函数。本章主要讨论用户自定义函数。
函数的定义
就像变量在使用前必须先定义一样,函数在使用前也必须先定义。在C语言中,函数定义的一般格式如下。 函数类型 函数名 ([形参列表]) { 说明部分 语句部分 }
说明:(1)“函数类型”指的是“函数返回值”的类型,省略是默认为int型,如果函数不需要返回值,可指定返回类型为 void。 (2)“形参列表”用于接受从函数外部传递来的数据。函数在定义时参数的值并不能确定,但它规定了参数的个数、次序和每个参数的类型,所以又称为形式参数,简称“形参”。函数名和形参都是用户命名的标识符,必须符合标识符的命名规则。 (3)函数类型、函数名和形参列表统称为函数头。一对花括号({ })内的部分称为函数体,函数体内包括变量说明和语句两部分。 (4)C语言中所有函数都是平行的,一个函数并不从属于另一个函数,即C语言中不允许函数嵌套定义。 (5)有返回值的函数中至少有一个 return 语句。在C语言程序中,一个函数的定义可以放在任意位置,既可以放在主函数之前,也可以放在主函数之后。
函数的声明
函数的声明是指利用它在程序的编译阶段对调用函数的合法性进行全面检查。它把函数名称、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统可按此声明进行对照检查。C语言编译程序默认函数的返回值为int类型。对于返回值为其他类型的函数,若把函数的定义放在调用之后,应该在调用之前对函数进行声明。函数的声明是一条语句。C语言中使用函数原型对函数进行说明。它标识了函数返回的数据类型、函数的名称、函数所需的参数个数及类型,但不包括函数体。函数原型的一般形式有两种,分别如下: 数据类型 函数名(参数类型1,参数类型2,…); 或 数据类型 函数名(参数类型1 形参名1,参数类型2 形参名2,…); 为了便于阅读程序,一般采用第一种形式。但由于编译器不检查参数名,所以第二种形式和第一种形式实际上是等效的。 在程序中,提倡先声明函数、在使用函数。函数原型符合声明函数的形式,所以通常使用函数原型来声明函数,以便编译器对函数调用的合法性进行全面检查。 说明:(1)实际上,如果被调函数的定义出现在主调函数之后,却没有对函数做声明,则编译系统会把第一次遇到的该函数形式作为函数的声明,并将函数类型默认为int型。 (2)如果被调函数的定义出现在主调函数之前,可以不加声明。因为编译系统已经知道了以定义的函数的类型,会根据函数首部提供的信息对函数的调用做正确性检查。 (3)如果在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必对所定义的函数再做声明。 为了便于阅读程序,一般采用第一种形式。但由于编译器不检查参数名,所以第二种形式和第一种形式实际上是等效的。在程序中,提倡先声明函数、再使用函数。函数原型符合声明函数的形式,所以通常使用函数原型来声明函数,以便编译器对函数调用的合法性进行全面检查。
函数的调用和返回语句
函数的调用
一个函数被定义后,程序中的其他函数就可以使用这个函数了,这个过程称为函数的调用。调用一个已经定义的函数就意味着在程序的调用处完成该函数的功能。 1.函数调用的一般格式: 函数名(实际参数列表); 在实际参数列表中的参数称为实际参数(简称“实参”),实参可以是常数、变量或表达式,各参数之间用逗号隔开,若被调函数是无参函数,则实际参数列表消失,但一对圆括号不能省略。例如: void Func(int a,int b) //函数定义 {…} int main( ) { int a=2,b=1; ... Func (a,b); //调用Func函数 return0; }
2. 函数调用方式 按函数在程序中的位置来分,函数调用方式有以下3种: (1)函数语句。把函数作为一个语句。例如: printstar(); 此时不要求函数带回返回值,只要求函数完成一定的操作。 (2)函数表达式。函数出现在一个表达式中,这种表达式称为函数表达式。这时,要求函数带回一个确定的值以参加表达式的运算。例如: m=3*max(a,b); 函数max是表达式的一部分。 (3)函数参数。函数调用作为一个函数的参数。例如: printf("%d",max(m,n) ); 3. 函数调用过程 (1)程序从main函数开始执行,当遇到函数调用时,为被调函数分配存储空间,并将实参值赋值给形参变量。 (2)暂停执行主调函数,转而执行被调函数。 (3)执行完被调函数后(遇到return语句或函数右面的花括号),返回主调函数,释放被调函数占用的内存空间,程序从主调函数原来暂停的位置继续执行。
函数的返回值
函数的返回值就是函数执行的结果,当函数返回主调函数时,有时会有数据带回给主调函数,也可以没有任何数据返回给主调函数。根据返回值的有无,可以将函数分为有返回值函数和无返回值函数两类。 1.有返回值函数 如果函数有返回值,则函数体中必须包含 return语句(通过return语句将值返回给主调函数)。return语句的一般格式如下: 函数类型 函数名() //函数首部 { 函数实现过程 //函数体 return(表达式); //或return 表达式; } return 语句有以下3个功能: (1)返回一个值给主调函数,其中的一对圆括号为可选项。 (2)释放在函数的执行过程中所分配的内存空间。 (3)结束被调函数的运行,返回主调函数,继续执行主调函数调用处下面的语句。 说明: 一个函数可以有一个或多个 return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。 当一个函数有返回值时,必须在函数定义时指定函数的返回值类型。如果省略函数的返回值类型,则系统默认函数返回值类型为int 型。 函数返回值类型应该和 return语句中表达式值的类型一致。如果两者不一致,则返回值类型以函数返回类型为准。 2. 无返回值函数 void 函数名() { 函数实现过程 } 当函数声明为void型时,函数体中不应出现 return语句或return语句后面不带任何表达式。 通常执行函数调用时,函数体中的语句自动按顺序执行,当遇到函数体右花括号“}”时结束,返回主调函数。但如果函数体中包含 return语句,则执行到该语句时,也将返回主调函数,实现控制流程的转移。
函数的参数传递
调用一个函数时,主调函数和被调函数之间会发送参数传递,传递方式有两种:一种是值传递,另一种时地址传递。 1. 值传递 C语言规定,简单变量作为函数参数时,把实参的值复制一个副本传递给形参的形式称为“值传递”。在被调函数中,形参可以被改变,但这只影响副本中形参,而不影响调用函数的实参数值。所以这类函数对原始数据有保护作用。 2. 地址传递 (1)向函数传递一堆数组 向函数传递一堆数组,就是传递整个一堆数组给另一个函数,可以将数组的首地址作为参数传递过去。数组名代表数组的首地址,实际上就是数组名作为函数的参数。数组元素即下标变量,它的使用与普通变量并无区别。数组元素只能用作函数实参,其用法和普通变量完全相同。与数组元素作为函数参数不同,数组名作为函数参数时,既可以作为形参,也可以作为实参。 数组名作为函数参数时,要求形参和相对应的实参都必须是类型相同的数组,有明确的数组说明。参数传递的形式为“地址传递”,即把实参数组的起始地址传递给形参数组,形参数组的改变也是对实参数组的改变。 说明: (1)用数组名作为函数参数时,应该在主调函数和被调函数中分别定义数组,且数组类型必须一致,否则结果出错。 (2)用数组名作为函数实参时,其中形参数组可以不指定大小,因为C语言编译器不检查形参数组的大小,只是把实参数组的首地址传递给形参数组。由于首地址相同,因此形参数组与实参数组共用同一段内存单元。 (3)用数组名作为函数参数时,形参数组中元素的改变会使实参数组中元素的值也跟着改变。利用数组名作为函数参数可以改变主调函数中数组元素值的特性能够解决很多问题。
(2)向函数传递二维数组 向函数传递二维数组可以用二维数组名作为函数参数,实际传送的是数组第一个元素的地址。数组名作为函数参数时,既可以作为形参,也可以作为实参。在被调函数中,当对形参数组定义时可以指定每一维的大小,也可以省略第一维的大学说明。例如: int array[3][8]; 或 int array[ ][8]; 但不能省略第二维的大小说明。因为数组元素在存储器中都是按行的顺序连续存储的,数组的第二行总是存储在第一行之后。C语言编译器必须知道一行中有多少元素(即列的长度)。这样才能知道跳过多少个存储单元来确定数组元素在存储器中的位置,从而准确地找到要访问的数组元素,否则编译程序无法确定第二行从哪里开始。
函数的嵌套与递归
函数嵌套的调用
C语言的函数定义都是平行的、独立的,即在定义函数时,一个函数内不包含另一个函数。C语言不能嵌套定义函数,但是可以嵌套调用函数。函数的嵌套调用就是在被调用的函数中又调用另一个函数。一个简单的函数嵌套调用过程如下:
其执行过程如下: (1)执行main函数的开头部分。 (2)遇到调用A函数的操作语句,流程转去A函数。 (3)执行A函数的开头部分。 (4)遇到调用B函数的操作语句,流程转去B函数。 (5)执行B函数,若无其他嵌套函数,则完成B函数的全部操作。 (6)返回调用B函数处,即返回A函数。 (7)继续执行A函数中尚未执行的部分,直到A函数结束。 (8)返回 main函数中调用A函数处。 (9)继续执行main函数的剩余部分直到结束。
函数的递归调用
递归是一种描述和解决问题的基本方法,用于解决可归纳描述的问题,或者说可分解为结构自相似的问题。所谓结构自相似,是指构成问题的部分与问题自身在结构上相似。这类问题的特点是,整个问题的解决可以分为两部分:第一部分是一种特殊情况,可直接解决;第二部分与原问题相似,可用类似的方法解决,但比原问题的规模要小。 由于第二部分比整个整个问题的规模小,因此每次递归,第二部分的规模都在缩小,如果最终缩小为第一部分的情况,则可以结束递归。因此,第一部分和第二部分都是必不可少的,否则,程序无法用递归方法解决。
1. 递归的概念
在调用一个函数的过程中又直接或间接的调用该函数本身,这称为函数的递归调用。在实际应用中,许多问题的求解方法具有递归特征。利用递归描述这种求解算法,思路清晰简洁。C语言的特点之一就是允许使用函数的递归调用。 在调用fn函数的过程中,又再次调用fn函数,这就是函数的递归调用。fn函数称为递归函数。像fn函数这样直接调用自身的,称为函数的直接递归调用,如下图a所示。 如果在调用f1函数的过程中要调用f2函数,而在调用f2函数的过程中又要调用f1函数,则称为函数的间接递归调用,如下图b所示。
程序中不应该出现这种无终止的递归调用,而应出现有限次、有终止的递归调用。由上图可以看出它们都是无终止的自身调用。要避免这种递归调用,应特别注意递归的结束条件,仔细推敲它是否真正有效,通常在函数内部加上一个条件判断语句,在满足条件时停止递归调用,然后逐层返回。
2. 递归调用的执行过程
用一个简单的递归程序来分析递归调用的执行过程。 例 当n为自然数时,求n的阶乘(n!)对应的递归公示如下: n!=1....n=0,1 n*(n-1)!....n>1 分析,从数学角度来说,如果要计算出n!,就必须先计算出(n-1)!,而要求(n-1)!就必须先求出(n-2)!……这样递归下去直到计算0!为止。若已知0!,就可以计算出1!,在计算出2!,一直回退计算出n!。根据n!的递归表示公式,用递归函数描述如下:
#include<stdio.h>
int main()
{
int factorial (int n); //函数声明
int n;
printf("please input n(n>=0): ")
scanf("%d",&n);
printf("%d!=%d\n",n,factorial (n));//调用 factorial函数,求n的阶乘
}
/*定义 factorial函数,函数的功能是用递归法计算整型变量n的阶乘*/
int factorial (int n)
{
if(n<=1) return 1; //递归终止条件
else return n*factorial(n-1); //递归调用,利用(n-1)!计算n!
}
在函数中使用了n*factoeial(n-1)表达式,该表达式中调用了 factorial函数,这是一种函数自身调用,是典型的直接递归调用,factorial函数是递归函数。显然,就程序的简洁性来说,函数用递归描述比用循环控制结构描述更自然、更清楚。但是,对初学者来说,递归函数的执行过程比较难以理解。下面以5!为例,讲解递归过程。 因为5!=5*4!,所以要求出5!,就必须先求出4!;而4!=4*3!,所以要求出4!,就必须先求出3!;同理,要求出3!,就必须先求出2!;要求出2!,就必须先求出1! 。而根据递归公式可知,1!=1,从而依次求出2!,3!,4!,5!。下图描述了求5!的递归过程:
从上图可以看出,求n!的过程实际上分成了两个阶段:递推阶段和回归阶段。在递推阶段,一个复杂的问题被一个更简单、规模更小的类似问题所替代,经过逐步分解,直到达到递归的结束条件,停止递推过程;在回归阶段,从递归结束条件出发,按递推的相反顺序,逐层解决上一级问题,直至最后顶层的复杂问题得以解决。实际上,只要是递归问题,都可以分成这两个阶段。 注意 在递归过程中,必须使用if语句建立递归的结束条件,使程序能够在满足一定条件时结束递归,逐层返回。如果没有if语句,在调用该函数进入递归过程后,就会不停地执行下去,这是编写递归程序时经常出现的错误。在上例中,n<=1就是递归的结束条件。
3 .数值型递归问题的求解方法
针对数值型问题,编写递归程序的一种方法:建立递归数学模型,确定递归终止条件,将递归数学模型转换为递归函数。数值型问题由于可以表达为对数学公式的求解,因此可以从数学公式入手,退出问题的递归定义,然后确定问题的边界条件,这样就可以较容易地确定递归的算法和递归结束条件。
4. 非数值型递归问题的求解方法
针对非数值型问题,编写递归程序的一般方法:确定问题的最小模型并使用非递归算法解决,分解原来的非数值问题并建立递归模型,确定递归模型的终止条件,将递归模型转换为递归函数。 由于非数值型问题本身难以用数学公式表达,因此求解非数值型问题的一般方法是要设计一种算法,确定解决问题的一系列操作步骤。如果能够确定解决问题的一系列递归的操作步骤,就可以用递归的方法解决这些非数值型问题。确定非数值型问题的递归算法可从分析问题本身的规律入手,按照下列步骤进行分析。 (1)化简问题。对问题进行简化,使问题规模缩到最小,分析在最简单情况下的求解方法。此时找到的求解方法应十分简单。 (2)对于一般的问题,可将一个大问题分解为两个(或若干个)小问题,使原来的大问题变成这两个(或若干个)小问题的组合。其中至少有一个小问题与原来的问题有相同的性质,只是在问题的规模上与原来的问题相比有所缩小。 (3)将分解后的每个小问题作为一个整体,描述用这些较小的问题来解决原来大问题的算法。 由步骤(3)得到的算法就是一个解决原来问题的递归算法,步骤(1)将问题的规模缩到最小时的条件就是该递归算法的递归结束条件。
变量和函数的作用域
C语言程序由函数组成,每个函数都要用到一些变量。需要完成的任务越复杂,组成程序的函数就越多,涉及的变量也越多。一般情况下要求各函数的数据各自独立,但有时候,又希望各函数有较多的联系,甚至组成程序的各文件之间共享某些数据。因此,在程序设计中,必须重视变量的作用域和生存期。 在C语言中,变量要求“先声明,后使用”。那么,声明的语句应该放在程序中的什么位置?声明了的变量是否随处可用?声明的变量什么时候获得内存,又在什么时候获得内存?这些问题都涉及变量的作用域和存储空类型。
1. 局部变量 局部变量也称内部变量,是指在一个函数内部或复合语句内部定义的变量。它的作用域是定义该变量的函数内或定义该变量的复合语句内。也就是说,局部变量只在定义它的函数或复合语句范围内有效,因此只能在定义它的函数或复合语句内才能使用。 每一个程序块都有它自己的命名规则,不同程序块中的变量可以同名。由于局部变量的作用域是本函数,因此在不同函数中定义的变量互不干扰,即使是相同名称的变量,也代表不同的对象。形式参数也是局部变量,它虽然在函数的头部定义,但也只能在本函数的范围内有效。
2. 全局变量 全局变量也称外部变量,是指在函数外部任意位置上声明的变量。全局变量的作用域是从声明变量的位置开始到本源文件结束。全局变量不属于哪一个函数,它可以被本源程序文件的其他函数所共用,即全局变量可以被有效范围内的多个函数共用。 如果全局变量在文件开头声明,则在整个文件范围内都可以使用,否则,按上述规定的作用范围只限于声明点到文件结束。如果在声明点之前的函数想引用该外部变量,则应该在该函数中用关键字exterm做外部变量声明,其作用是告诉编译器,该变量是在程序中的其他位置定义的(很可能是在不同的源文件中),不需要为变量分配空间。外部变量的声明方法如下: extern 类型名外部变量名; extern为声明外部变量的关键字。 说明 在程序中,可能会出现变量同名的情况。在C语言中,对于同名变量的处理应遵循以下规则: (1)在同一个作用域内不允许出现同名变量的声明。 (2)相互独立的两个作用域内的同名变量分配不同的存储单元,代表不同的变量,互不影响。 (3)如果在一个作用域和其所包含的子作用域内出现同名变量,则在子作用域中,内层变量有效,外层变量被屏蔽。
变量的存储类型
变量是程序中数据的传递着,它具有可访问性和存在性两种基本属性。前面介绍的变量作用域是指在程序的某个范围内的所有语句都可以通过变量名访问该变量,即代表变量的可访问性。存在性是指变量的存储类型,指数据的存储位置与生存期。在计算机中,保存变量当前值的存储单元有两类:一类是内存,另一类是CPU中的寄存器。变量的生存期是指在程序执行过程中,变量实际占用内存或寄存器的时间。变量的作用域描述的是变量的使用范围,生存期则描述的是当程序执行时变量的存在时间。 从变量存在时间(生存期)的角度划分,变量的存储方式可以分为静态存储方式和动态存储方式。静态存储方式指在程序运行期间分配固定的存储空间的方式。动态存储方式指在程序运行期间根据需要动态分配存储空间的方式。变量生存期是由变量定义时为变量选择的存储类型决定的。变量的存储类型也同时影响着编译程序为变量分配内存单元的方式以及为变量设置的初始值。 根据变量的存储类型,变量可以分为自动变量(auto)、静态变量(static)、寄存器变量(register)和外部变量(extern)。 对变量进行定义时,除了应说明变量的数据类型外,还应说明其存储类型。定义变量的格式如下:存储类型数据类型变量名;例如:auto int x; static float m; 1.auto变量 auto变量也称自动变量,它以关键字auto作为存储类型的声明,其中关键字auto可以省略。当在函数或复合语句内部定义变量时,如果没有指定存储类型,或使用了auto说明符,则系统认为所定义的变量具有自动类别。只有内部变量才能定义为自动变量,外部变量不能定义为自动变量。 当程序执行到定义自动变量的语句时,系统会自动为它们在内存中分配存储空间,即开始它们的生存期,而当包含它们的函数或复合语句执行结束后,系统就会自动释放这些存储空间,即结束它们的生存期。每一次执行到定义自动变量的语句时,系统都会为它们在内存中分配新的内存空间,并重新初始化。 2.static变量 static变量也称静态变量,它以关键字static作为存储类型的声明,其中关键字static不能省略。静态变量根据作用范围的不同又可分为静态局部变量和静态全局变量。静态变量示例如下: static int b,c=3; 静态变量有如下几个特点:(1)内存分配。程序运行时为其分配内存单元,只分配一次存储空间并初始化一次,在整个程序运行期间,静态变量在内存的静态存储区中占据着永久性的存储单元。即使退出函数,下次再进入该函数时,静态局部变量仍使用原来的存储单元。由于并不释放这些存储单元,因此这些存储单元中的值得以保留,可以继续使用存储单元中原来的值。 (2)变量的初值。全局变量和静态局部变量在程序开始时初始化,且初始化只在程序启动时执行一次。普通局部变量则在其每次进入其定义块时都必须重新初始化。如果在定义时没有给初始值,则全局变量和静态局部变量自动赋初值为0(数值型)或空字符(字符型),而普通局部变量则被赋予一个毫无意义的随机值。 (3)生存期。全局变量和静态局部变量在整个程序的执行区间都存在。而普通局部变量只在函数内部起作用,所以当调用某函数时,该函数内部变量就可在内存中被分配单元,在内存中存在;当该函数调用结束时,内部变量就会从内存中消失。 (4)作用域。静态局部变量的作用域是他所在的函数(或复合语句)。 3.register变量 register变量也称寄存器变量,它以关键字register作为存储类型的声明,其中关键字register不能省略。register变量与auto变量的区别在于:用register说明变量是建议编译程序将变量的值保留在CPU的寄存器中,而不是像一般变量那样占用内存单元。程序运行时,访问存于寄存器内的值要比访问存于内存中的值快得多。因此当程序对运行速度有较高要求时,若把那些频繁引用的少量变量指定为register变量,将有助于提高程序的运行速度。例如:register int b,c;寄存器变量有如下几个特点:(1)只有函数内定义的变量或形参可以定义为寄存器变量。 (2)受寄存器长度的限制,寄存器变量只能是char、int和指针类型的变量。 (3)CPU中寄存器的个数是有限的,因此只能说明少量的寄存器变量。当没有足够的寄存器用来存放指定的变量时,编译系统将其按自动变量来处理。 (4)由于寄存器变量的值是存放在寄存器中而不是内存中,因此寄存器变量没有地址,也不能对它进行求地址运算,同时静态局部变量不能定义为寄存器变量。 (5)寄存器变量的说明应尽量靠近其使用的地方,使用完之后,尽快释放其对寄存器的占用,以便提高寄存器的利用率,还可以通过把对寄存器变量的定义和使用放在复合语句中来实现。 实际上,CPU中寄存器的数目是有限的,不能定义任意多个寄存器变量。现在的优化编译器能够识别使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序指定,因此register变量很少用到。 4.extern变量 如果在所有函数之外定义的变量没有指定其存储类型,那么它就是一个外部变量,外部变量是全局变量。它的有效范围为从定义变量的位置开始到本源文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字extern对该变量进行“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。格式为 extern 类型名 变量名 extern说明的全局变量具有以下基本特点: (1)内存变量。编译时,将其分配在静态存储区,程序运行结束,释放存储单元。 (2)变量的初值。若定义变量时未赋初值,则在编译时,数值型变量系统自动赋初值为0,字符型变量系统自动赋初值0'。 (3)生存期。整个程序执行期间。extern说明符的主要作用是扩展了全局变量的作用域。我们可以从以下两个方面进行思考:①在同一编译单位内用extern说明符来扩展全局变量的作用域;②在不同的编译单位内用extern说明符来扩展全局变量的作用域。
函数按其存储类型可以分为两类:内部函数和外部函数。 1. 内部函数 内部函数是只能在定义它的文件中被调用的函数,而在同一个程序的其他文件中不可调用。定义内部函数时,在函数类型前加 static,所以内部函数也称为静态函数。内部函数定义的格式如下: static 函数类型 函数名(参数列表) { 函数体 } 内部函数的作用域只限于定义它的文件,所以在同一个程序的不同文件中可以有相同命名的函数,它们互不干扰。 2. 外部函数 外部函数是可以在整个程序的各个文件中被调用的函数。定义外部函数时,在函数类型前加 extern,其格式如下: extern 函数类型 函数名(参数列表) { 函数体 } 如果定义时没有声明函数的存储类型,则系统默认为extern型。