Bootstrap

7 函数——C++的编程模块

1 函数基本知识

要使用C++函数,必须完成如下工作:

  • 提供函数定义
  • 提供函数原型
  • 调用函数

如下例子用一个简单的示例演示了这三个步骤:

#include<iostream>

//函数原型
void simple();

int main(){
    using namespace std;
    cout<<"main() will call the simple() function:\n";
    //函数调用
    simple();
    cout<<"main() is finished with the simple() function.\n";
    return 0;
}

//函数定义
void simple(){
    using namespace std;
    cout<<"I'm but a simple function.\n";
}

程序输出:

main() will call the simple() function:
I'm but a simple function.
main() is finished with the simple() function.

执行函数simple()时,将暂停执行main()中的代码;等函数simple()执行完毕后,继续执行main()中的代码。在每个函数定义中,都使用了一条using编译指令,因为每个函数都使用了cout。另一种方法是,在函数定义之前放置一条using编译指令或在函数中使用std::cout

1.1 定义函数

可以将函数分成两类:没有返回值的函数和有返回值的函数。没有返回值的函数被称为void函数,其通用格式如下:
在这里插入图片描述
可选的返回语句标记了函数的结尾;否则,函数将在右花括号处结束。

有返回值的函数将生成一个值,并将它返回给调用函数。这种函数的类型被声明为返回值的类型,其通用格式如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2 函数原型和函数调用

下面的例子在一个程序中使用了函数cheer()cube(),留意其中的函数原型:

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

2 Function Arguments and Passing by Value

It’s time to take a closer look at function arguments. C++ normally passes arguments by value.That means the numeric value of the argument is passed to the function, where it is assigned to a new variable. For example, Listing 7.2 has this function call:

double volume=cube(side);

Here side is a variable that, in the sample run, had the value 5.The function header for cube(), recall, was this:

double cube(double x)

When this function is called, it creates a new type double variable called x and initializes it with the value 5.This insulates data in main() from actions that take place in cube() because cube() works with a copy of side rather than with the original data.

You’ll see an example of this protection soon.A variable that’s used to receive passed values is called a formal argument or formal parameter.The value passed to the function is called the actual argument or actual parameter.To simplify matters a bit, the C++ Standard uses the word argument by itself to denote an actual argument or parameter and the word parameter by itself to denote a formal argument or parameter. Using this terminology,argument
passing initializes the parameter to the argument (see Figure 7.2).

在这里插入图片描述
Variables, including parameters, declared within a function are private to the function. When a function is called, the computer allocates the memory needed for these variables. When the function terminates, the computer frees the memory that was used for those variables. (Some C++ literature refers to this allocating and freeing of memory as creating and destroying variables.That does make it sound much more exciting.) Such variables are called local variables because they are localized to the function.As mentioned previously, this helps preserve data integrity. It also means that if you declare a variable called x in
main() and another variable called x in some other function, these are two distinct, unrelated variables (see Figure 7.3). Such variables are also termed automatic variables because they are allocated and deallocated automatically during program execution.

在这里插入图片描述

2.1 Multiple Arguments

A function can have more than one argument.In the function call, you just separate the arguments with commas:

n_chars('R', 25);

This passes two arguments to the function n_chars(), which will be defined shortly.

Similarly, when you define the function, you use a comma-separated list of parameter declarations in the function header:

void n_chars(char c, int n) // two arguments

This function header states that the function n_chars() takes one type char argument and one type int argument.The parameters c and n are initialized with the values passed to the function. If a function has two parameters of the same type, you have to give the type of each parameter separately.You can’t combine declarations the way you can when you declare regular variables:

void fifi(float a, float b) 	// declare each variable separately
void fufu(float a, b) 			// NOT acceptable

As with other functions, you just add a semicolon to get a prototype:

void n_chars(char c, int n); // prototype, style 1

As with single arguments, you don’t have to use the same variable names in the prototype as in the definition,and you can omit the variable names in the prototype:

void n_chars(char, int); // prototype, style 2

However, providing variable names can make the prototype more understandable, particularly if two parameters are the same type.Then the names can remind you which argument is which:

double melon_density(double weight, double volume);

Listing 7.3 shows an example of a function with two arguments. It also illustrates how changing the value of a formal parameter in a function has no effect on the data in the calling program.

Listing 7.3 twoarg.cpp

// twoarg.cpp -- a function with 2 arguments
#include <iostream>
using namespace std;
void n_chars(char, int);

int main(){
	int times;
	char ch;
	
	cout << "Enter a character: ";
	cin >> ch;
	while (ch != 'q'){ // q to quit
		cout << "Enter an integer: ";
		cin >> times;
		n_chars(ch, times); // function with two arguments
		cout << "\nEnter another character or press the"
				" q-key to quit: ";
		cin >> ch;
	}
	cout << "The value of times is " << times << ".\n";
	cout << "Bye\n";
	return 0;
}

void n_chars(char c, int n){ // displays c n times
	while (n-- > 0) 	// continue until n reaches 0
		cout << c;
}

The program in Listing 7.3 illustrates placing a using directive above the function definitions rather than within the functions. Here is a sample run:

Enter a character: W
Enter an integer: 50
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
Enter another character or press the q-key to quit: a
Enter an integer: 20
aaaaaaaaaaaaaaaaaaaa
Enter another character or press the q-key to quit: q
The value of times is 20.
Bye
2.1.1 Program Notes

The main() function in Listing 7.3 uses a while loop to provide repeated input (and to keep your loop skills fresh). Note that it uses cin >> ch rather than cin.get(ch) or ch = cin.get() to read a character.There’s a good reason for this. Recall that the two
cin.get() functions read all input characters, including spaces and newlines, whereas cin >> skips spaces and newlines.When you respond to the program prompt, you have to press Enter at the end of each line, thus generating a newline character.The cin >> ch approach conveniently skips over these newlines, but the cin.get() siblings read the newline following each number entered as the next character to display.You can program around this nuisance, but it’s simpler to use cin as the program in Listing 7.3 does.

The n_chars() function takes two arguments:a character c and an integer n. It then uses a loop to display the character the number of times the integer specifies:

while (n-- > 0) // continue until n reaches 0
	cout << c;

Notice that the program keeps count by decrementing the n variable, where n is the formal parameter from the argument list.This variable is assigned the value of the times variable in main().The while loop then decreases n to 0, but,as the sample run demonstrates, changing the value of n has no effect on times. Even if you use the name n instead of times in main(), the value of n in main() is unaffected by changes in the value of n in n_chars().

2.2 Another Two-Argument Function

Let’s create a more ambitious function—one that performs a nontrivial calculation. Also the function illustrates the use of local variables other than the function’s formal arguments.

Many states in the United States now sponsor a lottery with some form of Lotto game. Lotto lets you pick a certain number of choices from a card. For example, you might get to pick six numbers from a card having 51 numbers.Then the Lotto managers pick six numbers at random. If your choice exactly matches theirs, you win a few million dollars or so. Our function will calculate the probability that you have a winning pick. (Yes,a
function that successfully predicts the winning picks themselves would be more useful, but C++,although powerful, has yet to implement psychic faculties.)

待补充 332

3 Functions and Arrays

So far the sample functions in this book have been simple, using only the basic types for arguments and return values. But functions can be the key to handling more involved types, such as arrays and structures. Let’s take a look now at how arrays and functions get along with each other.

Suppose you use an array to keep track of how many cookies each person has eaten at a family picnic. (Each array index corresponds to a person,and the value of the element corresponds to the number of cookies that person has eaten.) Now you want the total. That’s easy to find; you just use a loop to add all the array elements. But adding array elements is such a common task that it makes sense to design a function to do the job.Then you won’t have to write a new loop every time you have to sum an array.

Let’s consider what the function interface involves. Because the function calculates a sum, it should return the answer. If you keep your cookies intact, you can use a function with a type int return value. So that the function knows what array to sum, you want to
pass the array name as an argument.And to make the function general so that it is not restricted to an array of a particular size, you pass the size of the array.The only new ingredient here is that you have to declare that one of the formal arguments is an array name. Let’s see what that and the rest of the function header look like:

int sum_arr(int arr[], int n) // arr = array name, n = size

This looks plausible.The brackets seem to indicate that arr is an array,and the fact that the brackets are empty seems to indicate that you can use the function with an array of any size. But things are not always as they seem: arr is not really an array; it’s a pointer! The good news is that you can write the rest of the function just as if arr were an array. First, let’s use an example to check that this approach works,and then let’s look into why it works.

Listing 7.5 illustrates using a pointer as if it were an array name.The program initializes the array to some values and uses the sum_arr() function to calculate the sum. Note that the sum_arr() function uses arr as if it were an array name.

Listing 7.5 arrfun1.cpp

// arrfun1.cpp -- functions with an array argument
#include <iostream>
const int ArSize = 8;
int sum_arr(int arr[], int n); // prototype

int main(){
	using namespace std;
	int cookies[ArSize] = {1,2,4,8,16,32,64,128};
	// some systems require preceding int with static to
	// enable array initialization
	int sum = sum_arr(cookies, ArSize);
	cout << "Total cookies eaten: " << sum << "\n";
	return 0;
}

// return the sum of an integer array
int sum_arr(int arr[], int n){
	int total = 0;
	for (int i = 0; i < n; i++)
		total = total + arr[i];
	return total;
}

Here is the output of the program in Listing 7.5:

Total cookies eaten: 255

As you can see, the program works. Now let’s look at why it works.

3.1 How Pointers Enable Array-Processing Functions

The key to the program in Listing 7.5 is that C++, like C, in most contexts treats the name of an array as if it were a pointer. Recall from Chapter 4,“Compound Types,” that C++ interprets an array name as the address of its first element:

cookies == &cookies[0] // array name is address of first element

(There are a few exceptions to this rule. First, the array declaration uses the array name to label the storage. Second,applying sizeof to an array name yields the size of the whole array, in bytes.Third,as mentioned in Chapter 4,applying the address operator & to an array name returns the address of the whole array; for example, &cookies would be the address of a 32-byte block of memory if int is 4 bytes.)

Listing 7.5 makes the following function call:

int sum = sum_arr(cookies, ArSize);

Here cookies is the name of an array, hence by C++ rules cookies is the address of the array’s first element.The function passes an address. Because the array has type int elements, cookies must be type pointer-to-int, or int *.This suggests that the correct function header should be this:

int sum_arr(int * arr, int n) // arr = array name, n = size

Here int *arr has replaced int arr[]. It turns out that both headers are correct because in C++ the notations int *arr and int arr[] have the identical meaning when (and only when) used in a function header or function prototype. Both mean that arr is a
pointer-to-int. However, the array notation version (int arr[]) symbolically reminds you that arr not only points to an int, it points to the first int in an array of ints.This book uses the array notation when the pointer is to the first element of an array,and it
uses the pointer notation when the pointer is to an isolated value. Remember that the notations int *arr and int arr[] are not synonymous in any other context. For example, you can’t use the notation int tip[] to declare a pointer in the body of a function

Given that the variable arr actually is a pointer, the rest of the function makes sense.As you might recall from the discussion of dynamic arrays in Chapter 4, you can use the bracket array notation equally well with array names or with pointers to access elements of an array.Whether arr is a pointer or an array name, the expression arr[3] means the fourth element of the array.And it probably will do no harm at this point to remind you of the following two identities:

arr[i] == *(ar + i) 	// values in two notations
&arr[i] == ar + i 		// addresses in two notations

Remember that adding one to a pointer, including an array name,actually adds a value equal to the size, in bytes, of the type to which the pointer points. Pointer addition and array subscription are two equivalent ways of counting elements from the beginning of an array.

3.2 The Implications of Using Arrays as Arguments

Let’s look at the implications of Listing 7.5.The function call sum_arr(cookies, ArSize) passes the address of the first element of the cookies array and the number of elements of the array to the sum_arr() function.The sum_arr() function initializes the
cookies address to the pointer variable arr and initializes ArSize to the int variable n.
This means Listing 7.5 doesn’t really pass the array contents to the function. Instead, it tells
the function where the array is (the address), what kind of elements it has (the type),and
how many elements it has (the n variable). (See Figure 7.4.) Armed with this information,
the function then uses the original array. If you pass an ordinary variable, the function
works with a copy. But if you pass an array, the function works with the original.Actually,
this difference doesn’t violate C++’s pass-by-value approach.The sum_arr() function still passes a value that’s assigned to a new variable. But that value is a single address, not the
contents of an array.

10 函数指针

与数据项类似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。通常,这些地址对用户而言,既不重要,也没有什么用处,但对程序而言,却很有用。例如,可以编写将另一个函数的地址作为参数的函数。这样第一个函数将能够找到第二个函数,并运行它。与直接调用另一个函数相比,这种方法很笨拙,但它允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。

10.1 函数指针的基础知识

通过一个例子来阐述这一过程。假设要设计一个名为estimate()的函数,估算编写指定行数的代码所需的时间,并且希望不同的程序员都将使用该函数。对于所有的用户来说,estimate()中一部分代码都是相同的,

待补充 259

Variations on the Theme of Function Pointers

With function pointers, the notation can get intimidating. Let’s look at an example that illustrates some of the challenges of function pointers and ways of dealing with them. To begin, here are prototypes for some functions that share the same signature and return type:

const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(const double *, int);

The signatures might look different, but they are the same. First, recall that in a function prototype parameter list const double ar[] and const double * ar have exactly the same meaning. Second, recall that in a prototype you can omit identifiers. Therefore, const double ar[] can be reduced to const double [], and const double * ar can be reduced to const double *. So all the function signatures shown previously have the same meaning. Function definitions, on the other hand, do provide identifiers, so either const double ar[] or const double * ar will serve in that context.

如果你想要声明一个指针,这个指针可以指向三个函数之一。实现这一点的方法是:如果pa是你想要的指针,就需要取一个目标函数的原型,并将函数名替换成(*pa)

const double * (*p1)(const double *, int);

This can be combined with initialization:

const double * (*p1)(const double *, int) = f1;

With the C++11 automatic type deduction feature, you can simplify this a bit:

auto p2 = f2; // C++11 automatic type deduction

Now consider the following statements:

cout << (*p1)(av,3) << ": " << *(*p1)(av,3) << endl;
cout << p2(av,3) << ": " << *p2(av,3) << endl;

Both (*p1)(av,3) and p2(av,3) represent calling the pointed-to functions (f1() and f2(), in this case) with av and 3 as arguments. Therefore, what should print are the return values of these two functions. The return values are type const double * (that is, address of double values). So the first part of each cout expression should print the address of a double value. To see the actual value stored at the addresses, we need to apply the * operator to these addresses, and that’s what the expressions *(*p1)(av,3) and
*p2(av,3) do.

With three functions to work with, it could be handy to have an array of function pointers. Then one can use a for loop to call each function, via its pointer, in turn. What would that look like? Clearly, it should look something like the declaration of a single pointer, but there should be a [3] somewhere to indicate an array of three pointers.The
question is where. And here’s the answer (including initialization):

const double * (*pa[3])(const double *, int) = {f1,f2,f3};

381

Simplifying with typedef

C++ does provide tools other than auto for simplifying declarations. The typedef keyword allows you to create a type alias:

typedef double real; 	// makes real another name for double

The technique is to declare the alias as if it were an identifier and to insert the keyword typedef at the beginning. So you can do this to make p_fun an alias for the function pointer type used in Listing 7.19:

typedef const double *(*p_fun)(const double *, int); 	// p_fun now a type name
p_fun p1 = f1; 											// p1 points to the f1() function

You then can use this type to build elaborations:

p_fun pa[3] = {f1,f2,f3}; 	// pa an array of 3 function pointers
p_fun (*pd)[3] = &pa; 		// pd points to an array of 3 function pointers

Not only does typedef save you some typing, it makes writing the code less error prone,and it makes the program easier to understand.

11 Summary

;