9 C语言函数

本文详细介绍了函数的概念、创建与使用、参数传递方式,包括带形式参数的函数和函数原型。通过实例探讨了ANSIC函数原型的作用,以及如何处理参数匹配问题。还讲解了递归和使用头文件组织代码的重要性,涉及C语言中不同编译环境的编译指令。
摘要由CSDN通过智能技术生成

1 复习函数

函数(function)是完成特定任务的独立程序代码单元。首先,使用函数可以省去编写重复代码的苦差。其次,即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。

1.1 创建并使用简单函数

这里我们创建一个在一行打印40个星号的函数,并在一个打印表头的程序中使用该函数。如程序清单9.1所示,该程序由mian和startbar组成。

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

void starbar(void); /* prototype the function */

int main(void){
 	starbar();
 	printf("%s\n", NAME);
 	printf("%s\n", ADDRESS);
 	printf("%s\n", PLACE);
 	starbar(); /* use the function */
 	return 0;
}
 
void starbar(void){ /* define the function */
 	int count;
 	for (count = 1; count <= WIDTH; count++)
 	putchar('*');
 	putchar('\n');
}

该程序的输出如下:

****************************************
GIGATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
**************************************** 
1.2 分析程序

使用该程序时注意以下几点:

  • 程序共有3处使用了starbar标识符:函数原型(function prototype)告诉编译器函数starbar的类型;函数调用(function call)表明在此处执行函数;函数定义(function definition)明确地指定了函数要做什么。
  • 函数和变量不一样,有多种类型。任何程序在使用函数之前都要声明该函数的类型。因此,在main函数定义的前面出现了下面的ANSI C风格的函数原型:
    void starbar(void);
    圆括号表明starbar是一个函数名。第一个void是函数类型,表明函数没有返回值。第二个void(在圆括号中)表明该函数不带参数。分号表明这是在声明函数,不是定义函数。也就是说,这行声明了程序将使用一个名为starbar,没有返回值,没有参数的函数,并告诉编译器在别处查找该函数的定义。对于不识别ANSI C风格原型的编译器,只需声明函数的类型,如下所示:
    void starbar();
    注意,一些老版本的编译器甚至连void都识别不了。如果使用这种编译器,就要把没有返回值的函数声明为int类型。当然,最好还是换一个新的编译器。
  • 一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。这些信息称为该函数的签名(signature)。对于starbar函数而言,其签名是该函数没有返回值,没有参数。
  • 程序把starbar原型置于main的前面,当然也可以放在main里面的声明变量处。
  • 在main中,执行到下面的语句时调用了starbar函数:
    starbar();
    这是调用void类型函数的一种形式。当计算机执行到starbar();语句时,会找到该函数的定义并执行其中的内容。执行完starbar中的代码后,计算机返回主调函数(calling function)继续执行下一行(本例中,主调函数是mian),如图9.1所示
    在这里插入图片描述
  • 程序中starbar和main的定义形式相同。首先函数头包括函数类型、函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右花括号(如图9.2)结束。
    在这里插入图片描述
  • main中的右花括号告诉编译器main函数结束的位置,后面的starbar函数头告诉编译器starbar是一个函数。
  • starbar函数中的变量count是局部变量(local variable)。我们可以在程序中的其他地方(包括main中)使用count,这不会引起名称冲突,它们是同名的不同变量。
1.3 函数参数

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
程序9.2 lethead2.c

/* lethead2.c */
#include <stdio.h>
#include <string.h> /* for strlen() */
#define NAME "GIGATHINK, INC."
#define ADDRESS "101 Megabuck Plaza"
#define PLACE "Megapolis, CA 94904"
#define WIDTH 40
#define SPACE ' '

void show_n_char(char ch, int num);

int main(void){
 	int spaces;
 	
 	show_n_char('*', WIDTH); /* using constants as arguments */
 	putchar('\n');
 	show_n_char(SPACE, 12); /* using constants as arguments */
 	printf("%s\n", NAME);
 	spaces = (WIDTH - strlen(ADDRESS)) / 2;
 	// Let the program calculate how many spaces to skip
 	
 	show_n_char(SPACE, spaces);/* use a variable as argument */
 	printf("%s\n", ADDRESS);
 	show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2);
 	/* an expression as argument */
 	printf("%s\n", PLACE);
 	show_n_char('*', WIDTH);
 	putchar('\n');
 	return 0;
}

/* show_n_char() definition */
void show_n_char(char ch, int num){
 	int count;
 	for (count = 1; count <= num; count++)
 		putchar(ch);
}

函数运行结果如下:

****************************************
 			  GIGATHINK, INC.
 		    101 Megabuck Plaza		
 		    Megapolis, CA 94904
****************************************
1.4 定义带形式参数的函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

1.6 调用带实际参数的函数

在这里插入图片描述
在这里插入图片描述
待补充 270

2 ANSI C 函数原型

在这里插入图片描述

2.1 问题所在
2.2 ANSI的解决方案

针对参数不匹配的问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型(function prototype)来声明函数的返回类型、参数的数量和每个参数的类型。以下是两种函数原型:

int imax(int, int);
int imax(int a, int b); 

第一种形式使用以逗号分隔的类型列表,第二种形式在类型后面添加了变量名。注意,这里的变量名是假名,不必与函数定义的形式参数一致。

有了这些信息后,编译器可以检查函数调用是否与函数原型匹配,函数的数量是否正确,参数的类型是否匹配。以imax函数为例,如果两个参数都是数字,但是类型不匹配,编译器会把实际参数的类型转换成形式参数的类型。例如,imax(3.0, 5.0)会被转换成imax(3, 5)。用函数原型替换程序9.4 中的函数声明,如程序9.5所示:

程序9.5 proto.c

/* proto.c -- uses a function prototype */
#include <stdio.h>

int imax(int, int); /* prototype */

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(int n, int m){
 	return (n > m ? n : m);
} 

编译程序清单9.5时,编译器会给出调用的imax函数参数太少的错误信息。

如果类型不匹配又会怎样,为了探究这个问题,我们使用imax(3, 5)替换imax(3),然后再次编译该程序。这次编译器没有给出任何错误信息,程序的输出如下:

The maximum of 3 and 5 is 5.
The maximum of 3 and 5 is 5. 

在这里插入图片描述

2.3 无参数和未指定参数

在这里插入图片描述
在这里插入图片描述

2.4 函数原型的优点

在这里插入图片描述

3 Recursion

4 Compiling Programs with Two or More Source Code Files

4.1 Unix
4.2 Linux

This assumes the Linux system has the GNU C compiler GCC installed. Suppose that file1.c and file2.c are two files containing C functions. Then the following command will compile both files and produce an executable file called a.out:

gcc file1.c file2.c

In addition, two object files called file1.o and file2.o are produced. If you later change file1.c but not file2.c, you can compile the first and combine it with the object code version of the second file by using this command:

gcc file1.c file2.o
4.3 DOS Command-Line Compilers
4.4 Windows and Apple IDE Compilers
4.5 Using Header Files

If you put main() in one file and your function definitions in a second file, the first file still needs the function prototypes. Rather than type them in each time you use the function file,
you can store the function prototypes in a header file. That is what the standard C library does, placing I/O function prototypes in stdio.h and math function prototypes in math.h, for example. You can do the same for your function files.

Also, you will often use the C preprocessor to define constants used in a program. Such definitions hold only for the file containing the #define directives. If you place the functions of a program into separate files, you also have to make the #define directives available to each file. The most direct way is to retype the directives for each file, but this is time-consuming and increases the possibility for error. Also, it poses a maintenance problem: If you revise a #define value, you have to remember to do so for each file. A better solution is to place the #define directives in a header file and then use the #include directive in each source code file.

So it’s good programming practice to place function prototypes and defined constants in a header file. Let’s examine an example. Suppose you manage a chain of four hotels. Each hotel charges a different room rate, but all the rooms in a given hotel go for the same rate. For people who book multiple nights, the second night goes for 95% of the first night, the third night goes for 95% of the second night, and so on. You want a program that enables you to specify the hotel and the number of nights and gives you the total charge. You’d like the program to have a menu that enables you to continue entering data until you choose to quit.

Listings 9.9, 9.10, and 9.11 show what you might come up with. The first listing contains the main() function, which provides the overall organization for the program. The second listing contains the supporting functions, which we assume are kept in a separate file. Finally, Listing 9.11 shows a header file that contains the defined constants and function prototypes for all the program’s source files. Recall that in the Unix and DOS environments, the double quotes in the directive #include "hotels.h" indicate that the include file is in the current working directory (typically the directory containing the source code). If you use an IDE, you’ll need to know how it incorporates header files into a project.

Listing 9.9 The usehotel.c Control Module

/* usehotel.c -- room rate program */
/* compile with Listing 9.10*/

#include <stdio.h>
#include "hotel.h" /* defines constants, declares functions */

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;
		}
		nights = getnights();
		showprice(hotel_rate, nights);
	}
	printf("Thank you and goodbye.\n");
	return 0;
}

Listing 9.10 The hotel.c Function Support Module

/* hotel.c -- hotel management functions */

#include <stdio.h>
#include "hotel.h"

int menu(void){
	int code, status;
	
	printf("\n%s%s\n", STARS, STARS);
	printf("Enter the number of the desired hotel:\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"); 	// dispose of non-integer input
	printf("Enter an integer from 1 to 5, please.\n");
	}
	return code;
}

int getnights(void){
	int nights;
	printf("How many nights are needed? ");
	while (scanf("%d", &nights) != 1){
		scanf("%*s");
		// dispose of non-integer input
		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);
}

Listing 9.11 The hotel.h Header File

/* hotel.h -- constants and declarations for 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 "**********************************"

// shows list of choices
int menu(void);

// returns number of nights desired
int getnights(void);

// calculates price from rate, nights
// and displays result
void showprice(double rate, int nights);

Here’s a sample run:

********************************************************************
Enter the number of the desired hotel:
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 the desired hotel:
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 the desired hotel:
1) Fairfield Arms		2) Hotel Olympic
3) Chertworthy Plaza	4) The Stockton
5) quit
********************************************************************
5
Thank you and goodbye.

Incidentally, the program itself has some interesting features. In particular, the menu() and getnights() functions skip over nonnumeric data by testing the return value of scanf() and by using the scanf("%*s") call to skip to the next whitespace. Note how the following excerpt from menu() checks for both nonnumeric input and out-of-limits numerical input:

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

This code fragment uses C’s guarantee that logical expressions are evaluated from left to right and that evaluation ceases the moment the statement is clearly false. In this instance, the values of code are checked only after it is determined that scanf() succeeded in reading an
integer value.

Assigning separate tasks to separate functions encourages this sort of refinement. A first pass at menu() or getnights() might use a simple scanf() without the data-verification features that have been added. Then, after the basic version works, you can begin improving each module.

5 Finding Addresses: The & Operator

One of the most important C concepts (and sometimes one of the most perplexing) is the
pointer, which is a variable used to store an address. You’ve already seen that scanf() uses
addresses for arguments. More generally, any C function that modifies a value in the calling
function without using a return value uses addresses. We’ll cover functions using addresses
next, beginning with the unary & operator. (The next chapter continues the exploration and
exploitation of pointers.)

The unary & operator gives you the address where a variable is stored. If pooh is the name of a variable, &pooh is the address of the variable. You can think of the address as a location in
memory. Suppose you have the following statement:

pooh = 24;

Suppose that the address where pooh is stored is 0B76. (PC addresses often are given as hexadecimal values.) Then the statement

printf("%d %p\n", pooh, &pooh);

would produce this (%p is the specifier for addresses):

24 0B76

//367

6 Altering Variables in the Calling Function

7 Pointers: A First Look

Basically, a pointer is a variable (or, more generally, a data object) whose value is a memory address. Just as a char variable has a character as a value and an int variable has an integer as a value, the pointer variable has an address as a value. Pointers have many uses in C; in this chapter, you’ll see how and why they are used as function parameters.

If you give a particular pointer variable the name ptr, you can have statements such as the following:

ptr = &pooh;	// assigns pooh's address to ptr

We say that ptr “points to” pooh. The difference between ptr and &pooh is that ptr is a variable, and &pooh is a constant. Or, ptr is a modifiable lvalue and &pooh is an rvalue. If you want, you can make ptr point elsewhere:

ptr = &bah;		// make ptr point to bah instead of to pooh

Now the value of ptr is the address of bah.

To create a pointer variable, you need to be able to declare its type. Suppose you want to declare ptr so that it can hold the address of an int. To make this declaration, you need to use a new operator. Let’s examine that operator now.

7.1 The Indirection Operator: *

Suppose you know that ptr points to bah, as shown here:

ptr = &bah;

Then you can use the indirection operator * (also called the dereferencing operator) to find the value stored in bah (don’t confuse this unary indirection operator with the binary * operator of multiplication—same symbol, different syntax):

val = *ptr;		// finding the value ptr points to

The statements ptr = &bah; and val = *ptr; taken together amount to the following statement:

val = bah;

Using the address and indirection operators is a rather indirect way of accomplishing this result, hence the name “indirection operator.”

7.2 Declaring Pointers

You already know how to declare int variables and other fundamental types. How do you declare a pointer variable? You might guess that the form is like this:

pointer ptr;	// not the way to declare a pointer/

Why not? Because it is not enough to say that a variable is a pointer. You also have to specify the kind of variable to which the pointer points. The reason is that different variable types take up different amounts of storage, and some pointer operations require knowledge of that storage size. Also, the program has to know what kind of data is stored at the address. A long and a float might use the same amount of storage, but they store numbers quite differently. Here’s
how pointers are declared:

int * pi;	// pi is a pointer to an integer variable
char * pc;	// pc is a pointer to a character variable
float * pf, * pg; 	// pf, pg are pointers to float variables

The type specification identifies the type of variable pointed to, and the asterisk (*) identifies the variable itself as a pointer. The declaration int * pi; says that pi is a pointer and that *pi is type int (see Figure 9.5).

在这里插入图片描述
The space between the * and the pointer name is optional. Often, programmers use the space in a declaration and omit it when dereferencing a variable.

The value (*pc) of what pc points to is of type char. What of pc itself? We describe it as being of type “pointer to char.” The value of pc is an address, and it is represented internally as an unsigned integer on most systems. However, you shouldn’t think of a pointer as an integer type. There are things you can do with integers that you can’t do with pointers, and vice versa. For example, you can multiply one integer by another, but you can’t multiply one pointer by
another. So a pointer really is a new type, not an integer type. Therefore, as mentioned before, ANSI C provides the %p form specifically for pointers.

7.3 Using Pointers to Communicate Between Functions

We have touched only the surface of the rich and fascinating world of pointers, but our concern here is using pointers to solve our communication problem. Listing 9.15 shows a
program that uses pointers to make the interchange() function work. Let’s look at it, run it, and then try to understand how it works.

Listing 9.15 The swap3.c Program

/* swap3.c -- using pointers to make swapping work */
#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); 	// send addresses to function
	printf("Now x = %d and y = %d.\n", x, y);
	return 0;
}

void interchange(int * u, int * v){
	int temp;	// temp gets value that u points to
	temp = *u;
	*u = *v;
	*v = temp;
}

After all this build-up, does Listing 9.15 really work?

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

Yes, it works.

Now, let’s see how Listing 9.15 works. First, the function call looks like this:

interchange(&x, &y);

Instead of transmitting the values of x and y, the function transmits their addresses. That means the formal arguments u and v, appearing in the prototype and in the definition of interchange(), will have addresses as their values. Therefore, they should be declared as pointers. Because x and y are integers, u and v are pointers to integers, so declare them as follows:

void interchange (int * u, int * v)

Next, the body of the function declares

int temp;

to provide the needed temporary storage. To store the value of x in temp, use

temp = *u;

Remember, u has the value &x, so u points to x. This means that *u gives you the value of x, which is what we want. Don’t write

temp = u;	/* NO */

because that would assign temp the address of x rather than its value, and we are trying to interchange values, not addresses.

Similarly, to assign the value of y to x, use

*u = *v;

which ultimately has this effect:

x = y;

You can omit the variable names in the ANSI C prototype. Then the prototype declaration looks like this:

void interchange(int *, int *);

In general, you can communicate two kinds of information about a variable to a function. If you use a call of the form

function1(x);

you transmit the value of x. If you use a call of the form

function2(&x);

you transmit the address of x. The first form requires that the function definition includes a formal argument of the same type as x:

int function1(int num)

The second form requires the function definition to include a formal parameter that is a pointer to the right type:

int function2(int * ptr)

Variables: Names, Addresses, and Values
The preceding discussion of pointers has hinged on the relationships between the names, addresses, and values of variables. Let’s discuss these matters further.
When you write a program, you can think of a variable as having two attributes: a name and a value. (There are other attributes, including type, but that’s another matter.) After the program has been compiled and loaded, the computer also thinks of the same variable as having two attributes: an address and a value. An address is the computer’s version of a name.
In many languages, the address is the computer’s business, concealed from the programmer. In C, however, you can access the address through the & operator.
For example, &barn is the address of the variable barn.
You can get the value from the name just by using the name.
For example, printf("%d\n", barn) prints the value of barn.
You can get the value from the address by using the * operator.
Given pbarn = &barn;, *pbarn is the value stored at address &barn.
In short, a regular variable makes the value the primary quantity and the address a derived quantity, via the & operator. A pointer variable makes the address the primary quantity and the value a derived quantity via the * operator.
Although you can print an address to satisfy your curiosity, that is not the main use for the & operator. More important, using &, *, and pointers enables you to manipulate addresses and their contents symbolically, as in swap3.c (Listing 9.15).

8 Key Concepts

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值