C_Primer第9章 函数

9.1复习函数

9.1.1 创建并使用简单的函数

/* lethead1.c*/
#include <stdio.h>
#define NAME "GIGATHINK,INC."
#define ADDRESS "101 Megabck Plaza"
#define PLACE "Megapolis,CA 94904"
#define WIDTH 40

void starbar(void); /*函数原型*/

int main(void)
{
	starbar();
	printf("%s\n",NAME);
	printf("%s\n",ADDRESS);
	printf("%s\n",PLACE);
	starbar(); /*使用函数*/
	
	return 0;
}

void starbar(void) /*定义函数*/
{
	
	int count;
	
	for(count = 1;count <= WIDTH;count++)
		putchar('*');
		putchar('\n');
}

输出:

****************************************
GIGATHINK,INC.
101 Megabck Plaza
Megapolis,CA 94904
****************************************

9.1.2分析程序

注意以下几点

函数原型(function prototype),函数调用(function call),函数定义(function definition)明确地指定函数要做什么。

ANSI C风格函数原型:void starbar(void);

一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息被称为该函数的签名(signature)

9.1.3  函数参数

#define SPACE ' '

void show_n_char(char ch,int num);

int main(void)
{
	int spaces;
	show_n_char('*',WIDTH);						/*用符号常量作为参数*/
	putchar('\n');
	show_n_char(SPACE,12);						/*用符号常量作为参数*/
	printf("%s\n",NAME);
	spaces = (WIDTH - strlen(ADDRESS)) / 2;		/*计算要跳过多少个空格*/
	
	show_n_char(SPACE,spaces);					/*用一个变量做为参数*/
	printf("%s\n",ADDRESS);
	
	show_n_char(SPACE,(WIDTH - strlen(PLACE)) / 2);/*用一个表达式作为参数*/	
	printf("%s\n",PLACE);
	show_n_char('*',WIDTH);
	putchar('\n');

	
	return 0;
}
/*show_n_char()函数的定义*/
void show_n_char(char ch,int num) /*定义函数*/
{
	int count;
	for (count = 1; count <=num; count++)
		putchar(ch);
	
}

输出

****************************************
            GIGATHINK,INC.
           101 Megabck Plaza
           Megapolis,CA 94904
****************************************

9.1.4 定义带形式参数的函数

9.1.5 声明带形式参数函数的原型

9.1.6 调用带实际参数的函数

9.1.7 墨盒视角

9.1.8 用return 从函数中返回值

/* lesser.c -找出两个整数中较小的一个*/
#include <stdio.h>
int imin(int,int);

int main(void)
{
	int evil1,evil2;
	
	printf("Enter a pair of integers (q to quit):\n");
	
	while(scanf("%d %d", &evil1,&evil2) == 2)
	{
		printf("THe lesser of %d and %d is %d,\n ",evil1,evil2,imin(evil1,evil2));
		printf("Enter a pair of integers (q to quit):\n");
		
	}
	printf("Bye,\n");
	
	return 0;
}

//返回最小值第1个版本
/*int imin(int n,int m)
{
	int min;
	
	if(n < m)
		min = n;
	else
		min = m;
	
	return min;
}*/


//返回最小值第2个版本
/*int imin(int n,int m)
{
	return (n < m ) ? n: m;
}*/

//返回最小值第3个版本
/*int imin(int n,int m)
{
	if (n < m)
		return n;
	else
		return m;
}*/

int imin(int n,int m)
{
	if (n < m)
		return n;
	else
		return m;
	
	printf("Professor Fleppard is like totally a fopdoodle.\n");
}

9.1.9 函数类型

声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值类型相同,而没有返回值的函数应该声明为void类型。

函数的定义一定要放在使用函数的前面。

9.2 ANSIC 函数原型

9.2.1 问题所在

/* misuse.c --错误地使用函数*/
#include <stdio.h>
int imax();			/*旧式函数声明*/

int main(void)
{
	printf("The maximum of %d and %d is %d.\n",3,5,imax(3));
	printf("The maximum of %d and %d is %d.\n",3,5,imax(3.0,5.0));
	return 0;
}
int imax(n,m)
int n,m;
{
	return (n > m ? n :m);
}

9.2.2

/* misuse.c --使用函数原型*/
#include <stdio.h>
int imax(int,int);			/*函数原型*/

int main(void)
{
	printf("The maximum of %d and %d is %d.\n",3,5,imax(3,5));
	printf("The maximum of %d and %d is %d.\n",3,5,imax(3.0,5.0));
	return 0;
}
int imax(n,m)
int n,m;
{
	return (n > m ? n :m);
}

double转换成int可能会导致丢失数据。

9.2.3 无参数和未指定参数

对于printf()可以使用下面的原型:

int printf(const char *,...);

这种原型声明,第1个参数是一个字符串(第11章中将详细介绍),可能还有其他未指定的参数。

C库通过stdarg.h头文件提供了一个定义这类函数的标准方法。

9.2.4

函数原型是C语言的一个强有力的工具,它让编译器捕获在使用函数时可能出现的许多错误或疏漏。

有一种方法可以省略函数原型却保留函数原型的优点。把事个函数定义主在第1 次调用函数之前。

9.3递归

9.3.1 演示递归

/* RECUR.C -- 递归演示 */
#include <stdio.h>
void up_and_down(int);

int main(void)
{
	up_and_down(1);
	return 0;
}

void up_and_down(int n)
{
	printf("level %d :n location %u\n",n,&n);  //#1
	if (n < 4)
		up_and_down(n + 1);
	printf("LEVEL %d :n location %u\n",n,&n); //#2
}

输出

level 1 :n location 2686768
level 2 :n location 2686736
level 3 :n location 2686704
level 4 :n location 2686672
LEVEL 4 :n location 2686672
LEVEL 3 :n location 2686704
LEVEL 2 :n location 2686736
LEVEL 1 :n location 2686768

我们来仔细分析程序中的递归是如何工作的。首先main()调用了带参数1的up_and_down()函数。执行结果是up_and_down()中的参数n的值是1,所以打印语句#1打印Level1。然后,由于n小于4,up_and_down()(第1级)调用实际参数为n + 1(或2)的up_and_down()第2级。于是第2级调用中的n的值是2,打印语言#1打印Level2。丐此类似,下面两次调用打印的分别是Level3和Level 4。

当执行第4级时,n的值是4.,所以if测试条件为假。up_and_down()函数不再调用自己。第4级调用接着执行打印语句#2,即打印LEVEL 4,因为n的值是4.此时,第4级调用结。控制被传回它主调函数(即第3级调用)。在第3级调用中,执行的最后一条语句是调用if语句中的第4级调用。被调函数(第4级调用)把控制返回在这个位置,因此,第3级调用继续执行后面的代码,打印语句#2打印LEVEL 3。然后第3级调用结束,控制被传回第2级调用,接着打印LEVEL 2,以此类推。

注意,每级递归的变量n都属于本级递归私有。这从程序输出的地址值可以看出(当然,不同的系统表示的地址格式不同,这里关键要注意,level 和LEVEL 1 的地址相同,level1 和LEVEL 2的地址相同)。

如果觉得不好理解,可以假设有一条函数调用链-----fun1()调用fun2、fun2()调用fun3()、fun3()调用fun4()。当fun4()结束时,控制伟回fun3();当fun3()结束时,控制传回fun2();当fun2()结束时,控制伟回fun1()。递归的情况与类似只不过fun1()、fun2()、fun3() 、和fun4()都是相同的函数。

9.3.3 尾递归

//factor.c --使用循环和递归计算阶乘
#include <stdio.h>
long fact(int n);
long rfact(int n);
int main(void)
{
	int num;
	printf("This program calculates factorials.\n");
	printf("Enter a value in the range 0-12 (q to quit:\n");
	while (scanf("%d",&num) == 1 )
	{
		if (num < 0)
			printf("No negative number,please.\n");
		else if (num > 12)
			printf("Keep input under 13.\n");
		else 
		{
			printf("loop: %d factorial = %ld\n",num,fact(num));
			printf("recursion: %d factorial = %ld\n",num,rfact(num));
		}
	}
	
}

long fact(int n)	//使用循环的函数
{
	long ans;
	
	for (ans = 1; n > 1; n--)
		ans *= n;
	
	return ans;
}


long rfact(int n)	//使用递归的函数
{
	long ans;
	
	if (n > 0)
		ans = n * rfact(n - 1);
	else
		ans = 1;
	
	return ans;
}

9.3.4  递归和倒序计算

/* binary.c --以二进制形式打印10进制整数*/
#include <stdio.h>
void to_binary(unsigned long n);

int main(void)
{
	unsigned long number;
	printf("Enter an integer (q to quit)\n");
	while (scanf("%lu", &number) == 1)
	{
		printf("Binary equivalent:");
		to_binary(number);
		putchar('\n');
		printf("Enter an integer (q to quit):\n");
	}
	printf("Done.\n");
	
	return 0;
	
}

void to_binary(unsigned long n)  //递归函数
{
	int r ;
	
	r = n % 2;
	if (n >= 2)
		to_binary(n / 2);
	putchar (r == 0 ? '0' : '1');
	
	return;
}

9.3.5 递归的优缺点。

为某些编程问题提供了简单的解决方案

消耗计算机内存。

9.4 编译多源代码文件的程序

9.4.1 UNIX

9.4.2 Linux

9.4.3 DOS命令行编译器

9.4.4 Windows和苹果的IDE编译器

9.4.5 使用头文件

把函数原型和已定义的字符常量放在头文件中是一个良好的编程习惯。我们考虑一个例子:假设要管理4家酒店的客户服务,每家酒店的房价不同,但是每家酒店所有房间的房价相同。对于预订住宿多天的客户,第2天的房费是第1天的95%,第3天是第2天的95%,以此类推(暂不考虑这种策略的经济效益)。设计一个程序让用户指定酒店和入住天数,然后计算并显示总费用。同时,程序要实现一份菜单,允许用户反复输入数据,除非用户选择退出。

程序清单9.9程序清单9.10和程序清单9.11演示了如何编写这样的程序。第1个程序清单包含main()函数,提供整个程序的组织结构。第2个程序清单包含支持函数,我们假设这些函数在独立的文件中。最后,程序清单9.11列出了一个头文件,包含了该程序所有源文件中使用的自定义符号常量和函数原型。前面介绍过,在UNIX和DOS环境中,#include "hotels.h"指令中的双引号表明被包含的文件位于当前目录中(通常是包含源代码的目录)。如果使用IDE,需要知道如何把头文件合并成一个项目。

程序清单 9.9 usehotel.c控制模块

/*usehotel.c --房间费率程序*/
/*与程序清单9.10 一起编译*/
#include <stdio.h>
#include "hotel.h" /*定义符号常量,声明函数*/
int main(void)
{
	int nights;
	double hotel_rate;
	int code;
	
	while ((code = menu()) != QUIT)
	{
		switch (code)
		{
			case 1: hotel_rate = HOTEL1;
					break;
			case 2: hotel_rate = HOTEL2;
					break;
			case 3: hotel_rate = HOTEL3;
					break;
			case 4: hotel_rate = HOTEL4;
					break;
			default:hotel_rate = 0.0;
			printf("Oops!\n");
			break;
		}
		//printf("%f\n",hotel_rate);
		nights = getnights();
		//printf("%d\n",nights);
		showprice(hotel_rate,nights);
	}
	
	printf("Thank you and goodbye.\n");
	
	return 0;
}

程序清单9.10 hotel.c函数支持模块

/*hotel.c --酒店管理函数*/
#include <stdio.h>
#include "hotel.h"

int menu(void)
{
	int code,status;
	printf("Enter the number of ths desired hotel1:\n");
	printf("1) Fairfield Arms            2) Hotel Olympic\n");
	printf("3) Chertworthy Plaza         4) The Stockton\n");
	printf("5) quit\n");
	printf("%s%s\n",STARS,STARS);
	while((status = scanf("%d",& code)) != 1 || 
		   (code < 1 || code >5))
	{
		if (status != 1)
		scanf("%*s");	//处理非整数输入
		printf("Enter an integer from 1 to 5,poease\n");
	}
	return code;
}

int getnights(void)
{
	int nights;
	printf("How many nights are needed?");
	while(scanf("%d",&nights) !=1)
	{
		scanf("%*s"); //处理非整数输入
		printf("Please enter an integer ,such as 2.\n");
	}
	return nights;
}
void showprice(double rate,int nights)
{
	int n;
	double total = 0.0;
	double factor = 1.0;
	
	for (n = 1; n <= nights; n++, factor *= DISCOUNT)
		total += rate * factor;
	printf("THe total cost will be $%0.2f.\n",total);
	printf("%s%s\n",STARS,STARS);
}

程序清单9.11 hotel.h头文件

/* hotel.h --符号常量和hotel.c中所有函数的原型*/
#define QUIT	5
#define HOTEL1 180.00
#define HOTEL2 225.00
#define HOTEL3 255.00
#define HOTEL4 355.00
#define DISCOUNT 0.95
#define STARS "**************************"

//显示选择列表
int menu(void);

//返回预订天数
int getnights(void);

//根据费率、入住天数计算费用
//并显示结果
void showprice(double rate,int nights);

多个文件程序运行示例:

Enter the number of ths desired hotel1:
1) Fairfield Arms            2) Hotel Olympic
3) Chertworthy Plaza         4) The Stockton
5) quit
****************************************************
3
How many nights are needed?1
THe total cost will be $255.00.
****************************************************
Enter the number of ths desired hotel1:
1) Fairfield Arms            2) Hotel Olympic
3) Chertworthy Plaza         4) The Stockton
5) quit
****************************************************
4
How many nights are needed?3
THe total cost will be $1012.64.
****************************************************
Enter the number of ths desired hotel1:
1) Fairfield Arms            2) Hotel Olympic
3) Chertworthy Plaza         4) The Stockton
5) quit
****************************************************
5
Thank you and goodbye.

 顺带一提,该程序中有几处编写得很巧妙。尤其是,menu()和getnights()函数通过测试scanf()的返回值来跳过非数值数据,而且调用scanf("%*s")跳至下一个空白字符。注意,menu()函数中是如何检查非数值输入和超出范围的数据:

while((status = scanf("%d",& code)) != 1 || (code < 1 || code >5))

以上代码段利用了C语言的两个规则::从左往右对逻辑表达式求值:一但求值结果为假,立即停止求值。在该例是,只有在scanf()成功读入一个整数值后,才会检查code的值。

有不同的函数处理不同的任务时应检查数据的有效性。当然,首次编写menu()或getnights()函数时可以暂不添加这一功能,只写一个简单的scanf()即可。待基本版运行正常后,再逐步改善各模块。

9.5 查找地址:&运算符

程序清单9.12中使用了这个运行符查看不同函数中的同名变量分别储存在什么位置。

程序清单9.12 loccheck.c程序

/* loccheck.c --查看变量被储存在何处 */
#include <stdio.h>
void mikado(int); 	/*函数原型*/
int main(void)
{
	int pooh = 2, bah = 5; //main()的局部变量
	
	printf("In main(),pooh = %d and &pooh = %p\n",pooh,&pooh);
	printf("In main(),pah = %d and &pah = %p\n",bah,&bah);
	mikado(pooh);
	
	return 0;
	
}
void mikado(int bah) /*定义函数*/
{
	int pooh = 10;		//mikado()的局部变量
	
	printf("In mikado(),pooh = %d and &pooh = %p\n",pooh,&pooh);
	printf("In mikado(),pah = %d and &pah = %p\n",bah,&bah);
}

程序输出

In main(),pooh = 2 and &pooh = 0028FF3C
In main(),pah = 5 and &pah = 0028FF38
In mikado(),pooh = 10 and &pooh = 0028FF0C
In mikado(),pah = 2 and &pah = 0028FF20

9.6 更改主调函数中的变量

/*swap1.c --第1个版本的交换函数*/
#include <stdio.h>
void interchange(int u,int v); /*声明函数*/

int main(void)
{
	int x = 5, y = 10;
	
	printf("Originally x = %d and y = %d.\n",x,y);
	interchange(x,y);
	printf("Now x = %d and y = %d.\n",x,y);
	
	return 0;
}
void interchange(int u,int v) /*定义函数*/
{
	int temp;
	
	temp = u;
	u = v;
	v = temp;
}

程序输出

Originally x = 5 and y = 10.
Now x = 5 and y = 10.

两个变量的值并未交换!

程序清单9.14 swap2.c程序

/*swap2.c --查找swap1.c的问题*/
#include <stdio.h>
void interchange(int u,int v); /*声明函数*/

int main(void)
{
	int x = 5, y = 10;
	
	printf("Originally x = %d and y = %d.\n",x,y);
	interchange(x,y);
	printf("Now x = %d and y = %d.\n",x,y);
	
	return 0;
}
void interchange(int u,int v) /*定义函数*/
{
	int temp;
	printf("Originally u = %d and v = %d.\n",u,v);
	temp = u;
	
	u = v;
	v = temp;
	printf("Originally u = %d and v = %d.\n",u,v);
}

输出结果

Originally x = 5 and y = 10.
Originally u = 5 and v = 10.
Originally u = 10 and v = 5.
Now x = 5 and y = 10.

看来,interchange()没有问题。

ruturn语句只能把被调函数中的一个值传回主调函数,但是现在要传回两个值。这没问题!不过,要使用指针。

9.7 指针简介

从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)

9.7.1 间接运行符:*

ptr = &bah;

val = *ptr; //找出ptr指向的值

语句ptr = &bah;和val = *ptr;放在一起相当于下面的语句:

val = bah;

由此可见,使用地址和间接运算符可以间接完成上面这条语句的功能,这也是“间接运算符”名称的由来。

地址运算符:&

一般注解:

后跟一个变量名时,&给出变量的地址。

示例:

&nurse表示变量nurse的地址。

地址运算符:*

一般注解:

后跟一个指针名或地址时,*给出储存在指针指向地址上的值。

示例:

nurse = 22;

ptr = &nurse;//指向nurse的指针

val = *ptr;//把ptr 指向的地址上的值赋给val

执行以上3条语句的最终结果把22赋给val。

9.7.2 声明指针

声明指针时必须指定指针所指向变量类型,因为不同的变量类型占用不同的存储空间。

int * pi; //pi是指向int类型变量的指针

char * pc;//pc是指向char 类型变量的指针

float *pf, *pg;//pf、pg都是指向float类型的指针

* 和指针名之间的空格可有可无。通常,程序员在声明时使用空格,在解引用变量时省略空格。

pc指向的值(*pc)是char类型。pc本身是什么类型?我们描述它的类型是“指向char类型的指针”。pc的值是一个地址。在大部分系统内部,该地址由一个无符号整数表示。

9.7.3 使用指针在函数间通信

/*swap3.c --使用指针解决交换函数的问题*/
#include <stdio.h>
void interchange(int * u,int * v); /*声明函数*/

int main(void)
{
	int x = 5, y = 10;
	
	printf("Originally x = %d and y = %d.\n",x,y);
	interchange(&x,&y);	//把地址发送给函数
	printf("Now x = %d and y = %d.\n",x,y);
	
	return 0;
}
void interchange(int * u,int * v) 
{
	int temp;
	temp = *u;	//temp 获得u所指向对象的值
	*u = *v;	
	*v = temp;
}

程序输出

Originally x = 5 and y = 10.
Now x = 10 and y = 5.

9.8 关键概念

如果想用C编出高效灵活的程序,必须理解函数。把大型程序组织成苦于函数非常有用,甚至很关键。如果让一个函数处理一个任务,程序会更好理解,更方便调度。要理解函数是如何把信息从一个函数传递到另一个函数,也就是说,要理解函数参数和返回值的工作原理。另外,要明白函数形参和其他局部变量都属于函数私有,因此,声明在不同函数中的同名变量是完全不同的变量。而且,函数无法直接访问其他函数中的变量。这种限制访问保护了数据的完整性。但是,当确实需要在函数中访问另一个函数时,可以把指针作为函数的参数。

9.9 本章小结

函数可以作为组成大型程序的构件块。每个函数都应该有一个单独且定义好的功能。使用参数把值传给函数,使用关键字return把值返回函数。如果函数返回的值不是int类型,则必须在函数定义和函数原型中指定函数的类型。如果需要在被调函数中修改主调函数的变量。使用地址或指针作为参数。

ANSI C 提供了一个强大的工具----函数原型,允许编译器验证函数调用中使用的参数个数和类型是否正确。

C函数可以调用本身,这种调用方式被称为递归。一些编程问题要用递归来解决,但是递归不仅消耗内存多,效率不同,而且费时。

猜你喜欢

转载自blog.csdn.net/tjjingpan/article/details/84965173