3Functions

C++ inherits all the C syntax, including the definition and usage of functions. In process-oriented programming (also known as structured programming), function is the basic unit of module division, and an abstract of the problem-solving process. Function is also important in object-oriented programming, in which it is an abstract of functionalities.

To develop or debug a complex system, engineers will usually divide it into several subsystems, and then develop or debug based on these subsystems. Subprograms in high-level program languages are used to realize this kind of module division. In C and C++, subprograms are embodied as functions. We usually abstract functions from independent and frequently used functionality modules. Once a function is written, we can reuse it only knowing its functions and usage, without needing to know its specific implementation. In this way code is reused, development efficiency and program reliability are improved, and collaboration, modification, and maintenance can be realized more easily.

3.1Definition and Use of Function

A C++ program consists of one main function and several subfunctions. A program is executed starting from its main function. The main function may call subfunctions, and subfunctions may in turn call other subfunctions.

The function that calls other functions is named a “calling function”, and the function called by others is named a “called function”. A function may call another function and also be called by another. Thus it can be a calling function in one occasion and a called function in another.

3.1.1Definition of Function

1.Syntax form of function definition

2.Type of function and return value

The type identifier defines the type of the function, and also the type of the return value of the function. The return value of the function is the result that the function returns to its calling function, which is given by a return statement, such as “return 0”.

The function without a return value has a type identifier of void, and there need not be a return statement in the function.

3.Formal parameters

Here is the form of a formal parameter list:

type1, type2, ..., type n are type identifiers that represent the types of formal parameters, and name1, name2, ..., name n are the names of formal parameters. Formal parameters are used to realize the connection between the called function and the calling function. We often let the data that needs to process, factors that affect the function’s behavior, or the processing results of the function be the function’s formal parameters. Functions without formal parameters should have void on the position of the parameter list.

The main function can also have formal parameters and a return value. The formal parameters of the main function are also called command line parameters, which are initialized by the operating system when starting the program. The return value of the main function is returned to the operating system. The types and number of formal parameters of the main function have a special format. Refer to the experiment instructions in <Student’s Book> to write programs with command line parameters.

A function is only a piece of text before it has been called, and its formal parameters at the time are just symbols, indicating what type of data should appear at the position of the formal parameter. A function starts execution when it is called, and at that time, the calling function assigns the actual parameters to the formal parameters. This is similar to the definition of a function in mathematics:

f(x)=x2+x+1

The function f will not be calculated until its argument has been assigned a value.

3.1.2Function Calls

1.Form of function calls

Before calling a function we first need to declare the function prototype. The declaration can be in the calling function or before all the functions, with the following form:

If a function prototype is declared before all the functions, it is effective in the whole program file. That is, we can call the corresponding function anywhere in the file according to the prototype. If a function prototype is declared inside a calling function, it is only effective in this calling function.

After declaring the function prototype, we can make a function call with the following form:

The actual parameter list should provide parameters that accurately match the formal parameters in number and in types. A function call can be used as a statement, where the return value of the function is not needed; a function call can also appear in an expression, where an explicit return value of the function is needed.

Similar to the declaration and definition of variable, declaring a function only tells the compiler its relevant information (function name, parameters, return type, etc.) without generating any codes, while defining a function mainly gives the function code in addition to its relevant information.

Example 3.1: Writing a function to calculate the nth power of x.

Running result:

Example 3.2: Enter an 8-bit binary number, convert it to its decimal form, and then output the result.

Analysis: To convert a binary number to its decimal form, we need to multiply every bit of the binary number with the corresponding weight, and then add them up. For example: 000011012 = 0(27)+0(26)+0(25)+0(24)+1(23)+1(22)+0(21)+1(20) = 1310. So when the input is 1101, the output should be 13.

Here we make a function call on the function power in Example 3.1 to calculate 2n.

Source code:

Running result:

Example 3.3: Write a program to compute the value of π using the following formula.

π=16arctan(15)4arctan(1239)

Use the following series to calculate the arctangent of a number:

Continue accumulating until the absolute value of one item in the series is less than 10−15. The type of π and x are both double.

arctan(x)=xx33+x55x77+

Source code:

Running result:

Example 3.4: Find the number m between 11 and 999 where m, m2, and m3 are all palindromes, and then output m.

Palindromes are numbers that have symmetrical number digits. For example: 121, 676, and 94249 are all palindromes. One instance that satisfies this subject’s requirement is: m = 11, m2 = 121, m3 = 1331.

Analysis: To check whether a number is a palindrome or not, we can get every digit of the number by continuously dividing it by 10 and obtaining the remainders. After getting all the digits, we reverse the digit order to get a new number, and compare the new number with the original one. The original number is a palindrome if and only if it is the same as the new number.

Source code:

Running result:

Example 3.5: Compute the value of the following formula and output the result.

k={sin2(r)+sin2(s)12sin(r×s)whenwhenr2s2r2>s2

Here the values of r and s are input from the keyboard. The approximate value of sin x is calculated using following formula:

sin x=x1!x33!+x55!x77!+=n=1(1)n1x2n1(2n1)!

The precision of the calculation is 10−6. Stop accumulating when the absolute value of one item is less than the precision, and the accumulated value is the approximate value of sin x.

Source code:

Running result:

Example 3.6: Game of casting dice.

Game rules: Dice has six faces – counting by the points, they are 1, 2, 3, 4, 5, and 6. The player inputs an unsigned integer, which is used as the seed to generate a random number at the beginning of the program.

In each turn the dice is casted twice, and we can get the total number of points. In the first turn, if the total number of points is 7 or 11, the player wins and the game is over; if the point total is 2, 3, or 12, the player loses and the game is over; otherwise the player records the point total as his point. In the following turns, if the point total is equal to the player’s point, the player wins and the game is over; if the point total is 7, the player loses and the game is over; otherwise, it goes on to the next round.

The function rolldice is used to simulate rolling the dice, getting the point total, and outputting it.

Note: The system function int rand( void) generates a pseudorandom number. The pseudorandom number is not really random. When we call this function continuously in a program in the hope that it will generate a sequence of random numbers, we may discover that it will generate the same sequence each time we run the program. This sequence is called a pseudorandom number sequence. It is because rand needs an initial number called “seed”, and different seeds will generate different sequences. Thus, if we give the program a different seed in each run, continuously calling rand will generate a different random number sequence. If the seed is not set, rand will use the default value 1 as the seed. Note that the method for setting the seed is somewhat special: it is not through the parameters of rand, but by calling another function void srand(unsigned int seed) to set the seed before calling rand, and the parameter seed in the function srand is the seed of rand.

Source code:

Running result 1:

Running result 2:

2.Procedure of calling a function

The compiler compiles a C++ program and outputs a piece of executable code, which is then stored as a file suffixed with exe in the external storage. When the program is started, the computer first loads the executable code from the external storage into the code area of the memory, and then executes the code from the entry address (the beginning of the main function). During the execution, the computer will stop executing the current function when a function call occurs. It will then save the address of the next instruction (return address is used as the entry point of execution when returned from the called function), save the execution scene, jump to the entry address of the called function, and execute the called function. When meeting a return statement or reaching the end of the called function, the computer will restore the scene previously stored, jump back to the return address, and continue the execution. Figure 3.1 shows the procedure of calling a function and returning from the call. The labels in the figure indicate the order of execution.

Fig. 3.1: Procedure of function call and function return.

3.Nested function call

A nested call is allowed in a function. For example, function A calls function B, then function B calls function C, and this forms a nested call.

Example 3.7: Input two integers and compute the sum of their squares.

Analysis: Although the problem is easy, we design two functions to show how a nested call works: The function named fun1 is used to compute the sum of squares, and the function named fun2 is used to compute the square of an integer. The main function calls fun1, and fun1 calls fun2.

Source code:

Fig. 3.2: The order of function calls in Example 3.7.

Running result:

Figure 3.2 shows the order of function calls in Example 3.7. The labels in the figure indicate the executing order.

4.Recursive call

A function can call itself directly or indirectly. This kind of function call is called a recursive call.

Calling oneself directly means that the body of a function contains a function call to itself, for example:

And here is another example of a function indirectly calling itself:

Here fun1 calls fun2 and fun2 in turn calls fun1. These two calls constitute an indirect recursive call.

The essence of recursion is that it decomposes the original problem into a new problem, which may use the solution of the original problem. Continue the decomposition according to this principle, and each new problem emerged is a simplified subset of the original problem. The ultimately decomposed problem should be one whose solution is already known. The procedure above is a finite recursive call. Only a finite recursive call makes sense; an infinite recursive call will not get any results and it makes no sense.

The procedure of a recursive call consists of two parts:

First stage: Recurrence. Decompose the original problem continuously into new subproblems until we reach a known situation, at which point the recurrence ends.

For example, to calculate 5!, we can make a decomposition as follows:

Second stage: Regression. Starting from the known situation, use the result of the decomposed problem to solve the previous (more complex) problem. Repeat this process regressively according to the reversed order of the recurrence stage, until we reach the start of the recurrence. The regression then ends and the whole recursion finishes.

The regression procedure of calculating 5! is:

Example 3.8: Compute n!.

Analysis: The formula for calculating n! is:

n!={1n(n1)!(n=0)(n>0)

This formula is recursive, since it calculates a factorial by using another factorial. Thus the program uses a recursive call. The ending condition of the recursion is n=0.

Source code:

Running result:

Example 3.9: Calculate the number of possible combinations (i.e., the combinatorial number) of selecting k person(s) out of n person(s) to form a committee.

Analysis: The combinatorial number of selecting k person(s) out of n person(s)

= The combinatorial number of selecting k person(s) out of n − 1 person(s)

+ The combinatorial number of selecting k − 1 person(s) out of n − 1 person(s)

Since the formula is recursive, it is easy to write a recursive function to implement the calculation. The ending condition of the recursion is n==k||k==0, at which time the combinatorial number is 1. Then the regression may start.

Source code:

Running result:

Example 3.10: Hanoi Tower Game.

There are three pillars A, B, and C. N plates of different sizes have been piled on pillar A, with larger plates being under smaller plates, as shown in Figure 3.3. The procedure of the game is to move the plates from pillar A to pillar C. The player can use pillar B during the game, though he can only move one plate at a time, and larger plates should always be under smaller plates during the movement.

Analysis: We could compress the procedure of moving n plates from pillar A to pillar C into three steps:

  1. Move n − 1 plates from pillar A to pillar B;
  2. Move the last plate on pillar A to pillar C;
  3. Move n − 1 plates from pillar B to pillar C;

Actually, the three steps contain two kinds of operations:

  1. Moving multiple plates from one pillar to another. This is a recursive operation.
  2. Moving one plate from one pillar to another.

The following program uses two functions to implement the two kinds of operations above – function hanoi for the first operation and function move for the second.

Fig. 3.3: Hanoi Tower Game.

Source code:

Running result:

3.1.3Passing Parameters Between Functions

Before a function is called, the formal parameters of this function neither take up any real memory space nor have real values. When a function call is made, the computer allocates memory for the formal parameters and assigns the actual parameters to the formal parameters. An actual parameter could be a constant, variable, or expression, and should match the type of the corresponding formal parameter (the parameter in the same position in its parameter list). Passing parameters between functions is the process of assigning formal parameters according to actual parameters. C++ has two ways to pass parameters: Call-by-Value and Call-by-Reference.

1.Call-by-Value

The procedure of Call-by-Value consists of two steps: allocating memory space for a formal parameter, and using the actual parameter to initialize the formal parameter (assigning the actual parameter to the formal parameter). This procedure just passes the value of the actual parameter to the formal parameter. The formal parameter does not have any relation to the actual parameter once it has been initialized, and any change of the formal parameters afterwards cannot affect the actual parameter.

Example 3.11: Swap and output two integers.

Fig. 3.4: The status of variables when the program in Example 3.11 is running.

Running result:

Analysis: From the running result we can see that the values of variable x and y have not been swapped. It’s because in the function call above we use Call-by-Value to pass parameters, only where the values of the actual parameters are passed to the formal parameters. Thus the change of the formal parameters afterwards will not affect the actual parameters. Figure 3.4 shows the status of the variables when the program is running.

2.Call-by-Reference

We have seen that passing parameters through Call-by-Value is unidirectional. So how can changes made in the called function on formal parameters affect the actual parameters in the calling function? We can use Call-by-Reference to achieve this.

Reference is a special type of variable; it can be viewed as an alias of another variable. Accessing the reference of a variable is the same as accessing the variable itself. Here is an example:

The following rules must be followed when using references:

A reference must be initialized to refer to an existing object when it is declared.

Once a reference is initialized, it cannot be changed to refer to other objects.

The rules above indicate that a reference should be fixed to refer to an object in its whole life, from its definition to its end.

Formal parameters can also be references. When a formal parameter is a reference, the situation is a bit different: the formal parameter of the reference type is not initialized during its type declaration, and it is only when the function is called that the computer allocates memory space for the formal parameter and initializes it to the actual parameter. In this way, the formal parameter of the reference type becomes an alias of the actual parameter, and every operation on the formal parameter directly affects the actual parameters.

The function call that uses references as formal parameters is called Call-by-Reference.

Example 3.12: Rewrite the program of Example 3.11, using Call-by-Reference to make the two integers swap correctly.

Fig. 3.5: The status of variables when the program in Example 3.12 is running.

Running result:

Analysis: We can see from the running result that the swap is successful when the program uses Call-by-Reference to pass parameters. The only difference between Call-by-Value and Call-by-Reference lies in the declaration of the formal parameters, while the function call statements in the calling function are the same. Figure 3.5 shows the status of variables when the program is running.

Example 3.13: An example of Call-by-Reference.

Running result:

Analysis: The first parameter in1 of function fiddle has type int, and is assigned the value of the actual parameter count when the function is called. The second parameter in2 is a reference, and is initialized by the actual parameter index to an alias of index. Thus, the change on in1 in the called function has no effect on the actual parameter count, while the change on in2 in the called function is in fact the change on the variable index in the main function. When returned back to the main function, the value of count has not been changed, while the value of index has been changed.

3.2Inline Functions

At the beginning of this chapter, we mentioned that using functions helps developers to reuse codes, improve the development efficiency and the reliability of the program, and facilitate the collaboration and modification of the program. But function calls can also reduce the execution efficiency of programs. When a function call is made, the computer needs to save the execution scene and return address before jumping to the entry address of the called function and starting execution; when returned from the called function, the computer needs to restore the scene and return address previously saved before continuing the execution. These procedures take time and memory space. For some simple, small, but frequently used functions, we can use inline functions. An inline function does not cause control transfer when it is called, but makes itself embedded in every place it is called during the compilation. In this way, the cost of passing parameters and control transfers can be saved.

Inline functions use the keyword “inline” in the function definition. The form is like this:

There are several points that require our attention when using inline functions:

Generally, loop statements and switch statements should not appear in an inline function.

Inline functions must be defined before their first call.

Inline functions do not support abnormal interface statements. (Abnormal interface statements will be discussed in Chapter 12)

Generally, inline functions should be simple functions, with simple structures and few statements. Defining a complex function as an inline function may lead to code bloat and also increase the cost. In this case, most compilers will automatically convert the inline function into a common function before processing. What kind of functions should be counted as complex? The answer depends on compilers. Generally, functions that have loop statements cannot be processed as inline functions.

Therefore, the keyword inline is just a request. The compiler does not promise that every function with the keyword inline will be processed as an inline function. Moreover, functions without the inline keyword can possibly be compiled as inline functions.

Example 3.14: An example of an inline function.

Running result:

3.3Default Formal Parameters in Functions

Functions can declare default values of formal parameters in its definition. In such cases, if the actual parameter is provided when the function is called, the formal parameter will be initialized by the actual parameter; otherwise it will use its default value. For example:

Default parameters must be declared from right to left. Formal parameters without default values should not appear at the right side of any formal parameter that has a default value. This is because formal parameters are initialized by the actual parameters from left to right. For example:

Default values should also be provided in the function prototype. For example:

Within the same scope, the declaration of default values for formal parameters should be uniform; but in different scopes, declarations of default values for formal parameters can be different. Here scope refers to the area enclosed by the braces that embodies the function prototype. More details about scope will be discussed in Chapter 5. For example:

Example 3.15: An example of a function using default parameters.

The function of the program is to calculate the volume of cuboids. The subfunction get_volume is used to calculate the volume, which has three parameters: length, width, and height. The parameters width and height have default values. The main function calls get_volume using different parameters. Analyze the running result of the program.

Running result:

Since the first parameter of the function get_volume has no default value, every function call on get_volume must provide the first actual parameter to initialize the formal parameter length. The parameters width and height have default values. So all three formal parameters will be initialized by actual parameters if the function call provides three actual parameters; the last formal parameter height will use the default value if the function call provides two actual parameters; the last two formal parameters width and height will use the default values if the function call only provides one actual parameter.

When overloading functions that have default values, we should avoid ambiguity. For example, the following two function prototypes will cause ambiguity in that they cannot be recognized as different overloaded forms by the compiler:

In other words, when the function fun is called in the following form, the compiler is unable to decide which overloaded function should be executed:

The compiler will count this as a compiling error.

3.4Function Overloading

In programs, a function name is essentially the name of an operation. With the help of names similar to natural languages, we can write programs that are easy to understand and modify. Here comes a problem: how to reflect the tiny differences in natural languages using programming languages? Usually, a word in a natural language can have different meanings in different contexts. This is called polysemy, which in programming language is called “overloading”. For example: in a natural language, play the piano, play chess and play basketball all use the same verb “play”, while the actions that play indicates in the three contexts are quite different. We can understand these phrases, since we have learned how to play different objects in our life. So nobody would say “Please play the piano just as you would play chess, or play the chess just as you would play basketball”. Is it possible for a computer to have the same ability? This is up to the programs we write. C++ provides the support for function overloading, which enables us to give different functions the same name. The compiler will choose an appropriate function to execute according to the context (number and types of parameters).

Function overloading means that two or more functions share the same name, albeit with differences in the number or in the types of their formal parameters. The compiler will automatically choose a function that best fits the calling context according to the number and the types of formal parameters.

Without function overloading, for the same operation on different data types, we need to define several functions with different names. For example, when defining the addition function, we need different function names for the addition of integers and the addition of float numbers:

It is quite inconvenient when making function calls.

C++ allows functions with similar functionalities to use the same name within the same scope, which allows for overloading. It is easy to use and remember. For example:

Notes:

The formal parameters of overloaded functions must have differences in the number or in the types of parameters, or in both. Compiler will choose the appropriate function according to the number and the types of parameters when there is a function call. If functions of the same name have the same number of parameters and the same types of parameters (no matter what their return types are), the compiler will count it as a compiling error (function redefinition). For example:

Do not set functions of different functionalities with the same name, in case any misunderstanding or confusion of the calling result may occur.

Here, let us see an example of function overloading.

Example 3.16: An example of function overloading.

Write three overloaded functions with the same name add, and make them implement the following operations respectively – the addition of two integers, the addition of two real numbers, and the addition of two complex numbers.

Source code:

Running result:

3.5Using C++ System Functions

C++ not only allows us to design functions ourselves, but also provides hundreds of functions in the C++ system library (system functions) for us to use. For example: the function to calculate square root (sqrt), the function to calculate absolute value (abs), etc.

We know that program must declare the function prototype before calling the function. The prototypes of system functions have all been provided by the C++ system library, which sorts and stores them in several header files. Programmers only need to embed the related header files into the code using the include instruction, and then they can use the system functions. For example, if you want to use mathematic functions, you should include the header file cmath.

Example 3.17: An example of using C++ system functions.

Input an angle value from the keyboard, and calculate its sine, cosine, and tangent values.

Analysis: The C++ system library provides the functions to calculate the sine, cosine, and tangent of an angle: sin(), cos() and tan(), and these function prototypes are contained in the header file cmath.

Source code:

Running result:

Making full use of system functions can greatly reduce the work of programming and improve the execution efficiency and reliability of the program. When using system functions, we should pay attention to two points:

  1. Be familiar with the system functions provided by your C++ development environment. Different compilers may provide different system functions. Even compilers fromthe same company, if they are in different versions, may provide different system functions. So programmers must consult the manual of the system library or the online help of the compiler, to check out the functionality, parameters, return value, and usage of system functions.
  2. Make clear which header file contains the declaration of the system function you want to use. This can also be found in the manual of the system library or in the online help.

For example, the way to find the category list of the system functions of VC++6.0 from MSDN Library Visual Studio 6.0 is: First select “Visual C++ Documentation” in “Activity Subset”; then go through the following path: Visual C++ Documentation → Using Visual C++ → Visual C++ Programmer’s Guide → Run-Time Library Reference → Run-Time Routines by Category → Run-Time Routines by Category, as shown in Figure 3.6.

The helping system lists the functions in the following groups:

Argument access

Floating-point support

Buffermanipulation

Input and output

Byte classification

Fig. 3.6: MSDN Library in Visual Studio 6.0.

Internationalization

Character classification

Memory allocation

Dataconversion

Process and environment control

Debugging

Searching and sorting

Directorycontrol

String manipulation

Error handling

System calls

Exception handling

Time management

File handling

3.6Summary

In object-oriented programming, the function is the basic unit of functionality abstraction.

A complex system usually needs to be divided into several subsystems, which then are developed and debugged separately. Subprograms are embodied as functions in C++. The functionality abstraction of objects is also implemented through functions. Functions can be reused once completed. We can use a function only knowing its behavior and usage, without the need to know its implementation. In this way, codes can be reused, the development efficiency and the reliability of the program can be improved, and collaboration, modification, and maintenance can be realized more easily.

A C++ programmay consist of amain function and several subfunctions. Themain function is the entry point of the program execution. The main function may call sub-functions, and subfunctions may call other subfunctions.

Function overloading allows functions with similar operations to share the same name. It is easy to use and can make the code more readable. The overloaded functions are discriminated by their formal parameters, and functions with the same name must have differences in the number of parameters or in the types of parameters, or in both.

In addition, the C++ system library provides hundreds of functions for programmers to use. The declarations of system function prototypes have all been provided in the C++ system library, which sorts and stores them in different header files. Programmers need to use include instruction to embed the related header files into their code, and then they can use the system functions.

Exercises

3.1 What is a function in C++? What is a calling function, and what is a called function?What is the relationship between them? How does one make a function call?

3.2 Look at the running result of the following program. Is there any difference from what you presumed ? Think over the usage of references.

Source code:

3.3 Compare Call-by-Value and Call-by-Reference, and list their similarities and differences.

3.4 What is an inline function?What are its characteristics?

3.5 Must the following three names be the same: the formal parameter name in the function prototype, the formal parameter name in the function definition, and the actual parameter name in the function call?

3.6 How does one differentiate overloaded functions?

3.7 Write a function that contains two unsigned short int parameters. When the second parameter is nonzero, it returns the result of the first parameter dividing by the second parameter, and the result is of the short int type; otherwise, it returns –1. Realize input and output in the main function.

3.8 Write a function to convert Fahrenheit degrees to Celsius degrees. The formula is: C = (F − 32) * 5/9. In the main function, prompt users to input a Celsius degree, convert it into the corresponding Fahrenheit degree, and output the result.

3.9 Write a function to judge whether a number is prime or not. Realize input and output in the main function.

3.10 Write a function to get the greatest common divisor and the least common multiple of two integers.

3.11 What is a nested call? What is a recursive call?

3.12 Input an integer n in the main function, and write a function to calculate 1 + 2 + ⋅⋅⋅ + n in a recursive way.

3.13 Write a recursive function Power(int x, int y) to calculate the y-th power of x. Realize input and output in the main function.

3.14 Write a function to calculate Fibonacci numbers in a recursive way. The formula is:

fib(n)=fib(n1)+fib(n2),n>2;fib(1)=fib(2)=1

Observe the procedure of making a recursive call.

3.15 Write a function to calculate the n-th Legendre Polynomial in a recursive way. Realize input and output in the main function. The recursion formula of the n-th Legendre Polynomial is:

pn(x)={1x((2n1)×x×pn1(x)(n1)×pn2(x))/n(n=0)(n=1)(n>1)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset