C程序设计(第五版)谭浩强 学习笔记—重点及易错点(第7章-函数)

用函数实现模块化程序设计

目录

用函数实现模块化程序设计

一、定义函数

二、调用函数

三、函数的递归调用

四、数组作为函数参数

五、局部变量和全局变量

六、局部变量的存储类别

七、全局变量的存储类别

八、内部函数和外部函数


一、定义函数

定义函数应包括以下几个内容:

(1) 指定函数的名字,以便以后按名调用。

(2) 指定函数的类型,即函数返回值的类型。

(3) 指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这项。

(4) 指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能。这是最重要的,是在函数体中解决的。
 

类型名  函数名(形式参数表列)

{     

函数体 

}

例如:

int max(int x,int y)
{	int z;		//声明部分
	z=x>y?x:y;	//执行语句部分
	return(z);
}

当然函数可以没有返回值或者形式参数。

二、调用函数

1.首先要对要调用的函数进行声明

2.采用合适的方式调用函数

(1). 函数调用语句 把函数调用单独作为一个语句。如printf_star(); 这时不要求函数带回值,只要求函数完成一定的操作。

(2). 函数表达式 函数调用出现在另一个表达式中,如c=max(a,b); 这时要求函数带回一个确定的值以参加表达式的运算。

(3). 函数参数 函数调用作为另一个函数调用时的实参。如m=max(a,max(b,c));,又如:printf (″%d″, max (a,b));

3.实参和形参间的数据传递:实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。

例如:

#include 
int main()
{	float add(float x, float y);		//对add函数作声明
	float a,b,c;
	printf("Please enter a and b:");	//提示输入
	scanf("%f,%f",&a,&b);			//输入两个实数
	c=add(a,b); 					//调用add函数
	printf("sum is %f\n",c);			//输出两数之和
	return 0;
}


float add(float x,float y)		//定义add函数
{	float z;
	z=x+y;
	return(z); 			//把变量z的值作为函数值返回
}

三、函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。程序中不应出现无终止的递归调用,而只应出现有限次数的、有终止的递归调用,这可以用if语句来控制,只有在某一条件成立时才继续执行递归调用;否则就不再继续。

例如:有5个学生坐在一起,问第5个学生多少岁,他说比第4个学生大2岁。问第4个学生岁数,他说比第3个学生大2岁。问第3个学生,又说比第2个学生大2岁。问第2个学生,说比第1个学生大2岁。最后问第1个学生,他说是10岁。请问第5个学生多大。

#include 
int main()
{	int age(int n);					//对age函数的声明
	printf("NO.5,age:%d\n",age(5)); 	//输出第5个学生的年龄
	return 0;
} 

int age(int n) 						//定义递归函数
{	int c; 						//c用作存放函数的返回值的变量
	if(n==1) 						//如果n等于1
		c=10;					//年龄为10
	else 							//如果n不等于1
		c=age(n-1)+2;			//年龄是前一个学生的年龄加2(如第4个学生年龄是第3个学生年龄加2)
	return(c); 					//返回年龄
}

四、数组作为函数参数

1.数组元素作为函数实参

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

例如:输入10个数,要求输出其中值最大的元素和该数是第几个数。

#include 
int main()
{	int max(int x,int y);			//函数声明
	int a[10],m,n,i;
	printf("enter 10 integer numbers:");
	for(i=0;i<10;i++)			//输入10个数给a[0]~a[9]
		scanf("%d",&a[i]);
	printf("\n");
	for(i=1,m=a[0],n=0;i<10;i++)
	{	if(max(m,a[i])>m)		//若max函数返回的值大于m
		{	m=max(m,a[i]);	//max函数返回的值取代m原值
			n=i;		//把此数组元素的序号记下来,放在n中
		}
	}
	printf("The largest number is %d\nit is the %dth number.\n",m,n+1);
}

int max(int x,int y)		//定义max函数
{	return(x>y?x:y);	//返回x和y中的大者
}

2.数组名称作为函数实参

用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量) 传递的是数组首元素的地址。

例如:有一个一维数组score,内放10个学生成绩,求平均成绩。

#include 
int main()
{	float average(float array[10]);	//函数声明
	float score[10],aver;
	int i;
	printf("input 10 scores:\n");
	for(i=0;i<10;i++)
		scanf("%f",&score[i]);
	printf("\n");
	aver=average(score);			//调用average函数
	printf("average score is %5.2f\n",aver);
	return 0;
} 

float average(float array[10])		//定义average函数
{	int i;
	float aver,sum=array[0];
	for(i=1;i<10;i++)
		sum=sum+array[i];		//累加学生成绩
	aver=sum/10;
	return(aver);
}

如果实参为二维或者更高维数组时,形参数组array第1维的大小省略,第2维大小不能省略,而且要和实参数组a的第2维的大小相同。在主函数调用max_value函数时,把实参二维数组a的第1行的起始地址传递给形参数组array,因此array数组第1行的起始地址与a数组的第1行的起始地址相同。由于两个数组的列数相同,因此array数组第2行的起始地址与a数组的第2行的起始地址相同。a[i][j]与array[i][j]同占一个存储单元,它们具有同一个值。实际上,array[i][j]就是a[i][j],在函数中对array[i][j]的操作就是对a[i][j]的操作。

五、局部变量和全局变量

在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数以外是不能使用这些变量的。在复合语句内定义的变量只在本复合语句范围内有效,只有在本复合语句内才能引用它们。在该复合语句以外是不能使用这些变量的,以上这些称为“局部变量”。

程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。

六、局部变量的存储类别

在C语言中,每一个变量和函数都有两个属性: 数据类型和数据的存储类别。 存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)。 在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(即如果用户不指定,系统会隐含地指定为某一种存储类别)。 C的存储类别包括4种: 自动的(auto)、静态的(statis)、寄存器的(register)、外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。

函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。 实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”,它属于动态存储方式。程序中大多数变量属于自动变量。

有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。(1) 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,分配在动态存储区空间而不在静态存储区空间,函数调用结束后即释放。 (2) 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新给一次初值,相当于执行一次赋值语句。 (3) 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符′\0′(对字符变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的内容是不可知的。 (4) 虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用。

七、全局变量的存储类别

全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程。 一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域。如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。 在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。 这种加上static声明、只能用于本文件的外部变量称为静态外部变量。在程序设计中,常由若干人分别完成各个模块,各人可以独立地在其设计的文件中使用相同的外部变量名而互不相干。只须在每个文件中定义外部变量时加上static即可。这就为程序的模块化、通用性提供方便。如果已确认其他文件不需要引用本文件的外部变量,就可以对本文件中的外部变量都加上static,成为静态外部变量,以免被其他文件误用。至于在各文件中在函数内定义的局部变量,本来就不能被函数外引用,更不能被其他文件引用,因此是安全的。

注意:用static声明一个变量的作用是: (1) 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。 (2) 对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。

八、内部函数和外部函数

函数本质上是全局的,因为定义一个函数的目的就是要被另外的函数调用。如果不加声明的话,一个文件中的函数既可以被本文件中其他函数调用,也可以被其他文件中的函数调用。但是,也可以指定某些函数不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。

1.内部函数

static 类型名 函数名(形参表);               static int fun(int a,int b)

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

2.外部函数

extern 类型名 函数名(形参表);             extern int fun(int a,int b)

使用extern声明就能够在本文件中调用在其他文件中定义的函数,或者说把该函数的作用域扩展到本文件。extern声明的形式就是在函数原型基础上加关键字extern。 由于函数在本质上是外部的,在程序中经常要调用其他文件中的外部函数,为方便编程,C语言允许在声明函数时省写extern。 用函数原型能够把函数的作用域扩展到定义该函数的文件之外(不必使用extern)。只要在使用该函数的每一个文件中包含该函数的函数原型即可。函数原型通知编译系统: 该函数在本文件中稍后定义,或在另一文件中定义。 利用函数原型扩展函数作用域最常见的例子是#include指令的应用。在#include指令所指定的“头文件”中包含调用库函数时所需的信息。

你可能感兴趣的:(C语言,二级,计算机)