Tải bản đầy đủ - 0 (trang)
Chapter 7. Programming With C++ Functions

Chapter 7. Programming With C++ Functions

Tải bản đầy đủ - 0trang

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



ϒΠ



C++ Functions as Modularization Tools



ϒΠ



Argument Promotions and Conversions



ϒΠ



Parameter Passing in C++



ϒΠ



Inline Functions



ϒΠ



Parameters with Default Values



ϒΠ



Function Name Overloading



ϒΠ



Summary



In the previous chapters, we looked at the basics of the C++ language that allow us to implement

any complex requirements a computer system might face.

The C++ built-in data types allow the programmer to cater computational objects to the task at

hand. They provide the necessary choices for numeric ranges and precision. The C++ operators

allow the programmer to combine input values into powerful and flexible expressions to compute

required output values. The C++ control structures allow the programmer to organize computations

into proper sequences, change the flow of computation when some conditions become true or false,

and repeat computations iteratively if necessary.

We also looked at the C++ features that support aggregation of components. We discussed

programmer-defined data types. They let the programmer combine individual data values that

logically belong together. Combining individual values into aggregates allows us to handle them as

a unit and helps the designer to pass on to the maintainer the designer's knowledge that these

components belong together. We also discussed arrays. They let the programmer combine related

components that undergo similar processing in the program. Finally, we discussed dynamic

memory management and file management. They expand the power and flexibility of ordinary

arrays and allow us to overcome their limitations.

Next, you are going to look at yet another C++ aggregation and modularization tool: functions.

Combining individual statements into functions allows the programmer to treat them as a single

logical unit. Breaking the program's functionality into separate functions is a powerful tool of labor

division: Different programmers develop different functions in parallel.

In this chapter, you will study the techniques for writing C++ functions. The main emphasis is

going to be on function communications: how functions exchange data. You will learn various

techniques of passing parameters and returning values from functions. These techniques differ

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (342 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



depending on whether the function arguments are modified by the function or keep the value they

had before the function call was made. These techniques also vary depending on whether the

function parameter is of a built-in C++ type, an array, or a programmer-defined structure (or class).

You will also learn other techniques of function design directed toward alleviating the restrictions

on function names, such as default parameter values and function name overloading. These

techniques significantly expand the set of choices that a C++ programmer has to use in his or her

implementation decisions. In addition, you will learn how to eliminate the performance overhead of

function calls using inline functions. You will also see what happens when actual arguments

supplied in the function call are not of exactly the same type as the formal parameters defined in the

function header.

This is an ambitious program. C++ functions are flexible and powerful, and they leave the

programmer with an almost bewildering array of choices of how to go about implementation. We

will try to make some sense out of it.

All the material in this chapter is vitally important for mastering C++ classes. Do not give up, type

in the chapter examples, experiment with them, and you will see that it is not that hard.



C++ Functions as Modularization Tools

In C++, as in other languages, the programmer hides the complexity of computer algorithms in

relatively small units of modularity: functions. Each function is a collection of language statements

directed toward achieving a specific goal. These statements can be simple assignments, complex

control constructs, or calls to other functions. These other functions can be standard library

functions that come with the compiler, specific library functions that come from previous projects,

or programmer-defined functions that are custom-made for this particular project.

From the programmer's point of view, the difference between different kinds of functions is that the

implementation code of custom-made project functions is available for inspection. As far as library

functions are concerned, the programmer who uses these functions as servers for the function he or

she is writing does not know their implementation. What the programmer knows is the description

of the server function interface: what parameters the caller should supply, what values the function

computes, how the output values are computed from the input values, and what restrictions and

exceptions apply.

It is not that the code for library functions is a trade secret. Sometimes it is, but often it is freely

available. It is that limiting the programmer's knowledge to the function interface and excluding the

function code is beneficial; this decreases the code complexity that the programmer faces. Studying

function code is justifiable only if the function might contain errors that have to be corrected. This

is the case with the programmer-defined functions that are custom made for this particular project.

Even for these functions, the task of analysis of function cooperation should be limited to studying

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (343 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



function interfaces rather than function implementations.

This is how we are going to evaluate different methods of function communications. Those

methods that allow the maintainer (or another designer) to study the function interface only,

without reading the function code, will be considered superior to those methods that require

inspection of the function code as well.

The callers (client functions) handle the called function (a server function) as a single unit. In the

function call, the caller specifies the function name and the actual arguments (if any). The caller of

the function does not know how the function (the server) does its job. The client knows only what

job the server function does and what the interface specification is. Hence, using function calls

streamlines the client code. It is directed toward its own goal by removing detailed steps and

abstracting them in the form of the function call.

A function is the smallest unit of modularization; using functions allows the designers to organize a

large program into smaller, more-manageable units. Different functions can be assigned to different

programmers to speed up development of a large application.

If an algorithm is needed in several places in the program, implementing it as a function allows the

designers to call it from different places in the program instead of reproducing all the details in the

client code. This makes object code smaller and contributes to code reuse. During maintenance,

smaller functions are easier to understand and manage than is a huge monolithic program.

Functions used as modular units for organizing program code can be put into a library; their use by

other applications also improves the amount of code reuse.

Good functional design is crucial for code readability, for independence of program parts, and

hence for reducing the application complexity. However, function communications add to program

complexity. When using functions, the programmer has to coordinate code in three different places

in the program.

ϒΠ



function declaration (function prototype) including the function name, its return type,

and types of its parameters



ϒΠ



function definition, including the function header and the implementation of the function



body

ϒΠ



the function call, including the function name and the names (or values) of actual

arguments



These three elements have to coordinate. This probably does not sound like much, because it is

only three elements and no more. And indeed most programmers most of the time get it right. The

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (344 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



problem is that the cases when the programmers do not get it right, however rare as a percentage of

the total, are numerous enough to cause serious problems.



Function Declarations

C++ requires that the compiler see either a function declaration or function definition before it

processes a function call. Hence, the source file where a function is called has to declare (or define)

this function before it is called. This is why the issue of supplying necessary function prototypes is

an important component of C++ programming.

In the function declaration, the types of parameters and the function return value (if any) should be

described along with the function name. If the function is called in several files, the function must

be declared in each file.

returnType functionName(type1 param1, type2 param2, ...);



If the function returns no value, the return type is specified as void rather than just omitted. If

return type is omitted, it is still not a syntax error. The compiler assumes that you wanted to make it

int rather than void. Omitting the return type used to be popular in C programming. This is why it

is allowed in C++. However, it is confusing, and the maintenance programmer has to spend extra

effort to figure out what is going on. If the return type is int, the code has to say that the return

type is int. This is why omitting the return type is frowned on in C++ programming, and some

compilers might issue a warning that this style of function definition is obsolete.

add(int x, int y);

void PutValues(int val, int cnt);



// int return value: bad style

// no return value: void type



A function can return only one value. If the client code needs more than one value from the

function, the function can return a structure variable, although this slows down program execution.

A function can also modify any number of global variables that are defined outside of any function

in the file. As we are going to see, neither of these techniques is a good software engineering

practice: They are prone to error. The function can also modify the values of its arguments. As we

are going to see, these techniques are complex. Do not feel depressed yet. All this is doable.



Function Definitions

In the function definition, the function algorithm is implemented in C++ code. The function

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (345 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



definition starts with a header line that specifies the return value type, the function name (a

programmer-defined identifier), and the types and names of parameters in a comma-separated

argument list. The difference between the function header and the function prototype is that the

prototype ends with a semicolon, and the header does not. Another difference is that the parameter

names are optional in prototypes but mandatory in the function headers. Actually, if the parameter

is not used in the function body, its name is optional in the header, too, but I hope you will never

write such a poorly designed function.

The function body is a block with its own scope. As in any C++ code, the statements in the function

body are executed sequentially unless control constructs or function calls are used.

void PutValues(int val, int cnt)

{ cout << "Value " << val << " is found ";

cout << cnt << " times" << endl;

return; }

// optional; no return value in a void

function

int add (int x, int y)

{ count++;

// global variable is modified

return x+y; }

// return statement and return value are mandatory



For a void function, return statements are optional. They can be used any place in the code but

are not allowed to return a value. The execution of any return statement terminates the execution

of the function and returns control to the caller. For a non-void function, at least one return

statement is mandatory; more than one return statement can be used. Each return statement must

return a value of the type specified in the function header (or of the type that can be converted to

the return type).



Function Calls

Pascal, Ada, and other modern languages distinguish between procedures and functions. Procedures

in these languages do not return values but are allowed to have side effects in their arguments and

global variables. In the client code, they can be used as separate statements only, not as part of

another expression. Functions in these languages return values but can have no side effects. In the

client code, they cannot be used as separate statements but must be used as part of an expression (or

as an rvalue in the assignment).

a = add(b,c) * 2;

PutValues(a,5);

b = PutValues(a,5)*2;



// the use of return value in expression

// function call as a statement

// nonsence: there is no value to return



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (346 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



C++ does not distinguish between procedures and functions. C++ functions can both provide return

values and have side effects. A returned type can be of any built-in or programmer-defined type,

but arrays are not allowed as returned values. If the function is void, it works as a procedure and

returns no value to the caller, it cannot be used in an expression. Such a function should be called in

a separate statement in the caller code.

Unlike Pascal or Ada, C++ allows the client to ignore a return value in a function call and use a

function call as a procedure call. This means that a non-void C++ function can be used as a part of

an expression and as a separate statement. When the caller uses such a function as a statement, the

only purpose of the function call is its side effects on global variables.

add(b,c);



// correct syntax even if it makes no sense



This is not a good programming practice. If a function returns a value, it should be used in the

client code. However, there are many C++ library functions that have non-void return values that

are rarely used, e.g., strcpy() and strcat(), among others.

The function body in braces specifies actions performed when the body is evaluated during the

function call. We say that the call operator () is applied to the function name with comma-separated

arguments of the call.

PutValues(17,14);



// the call operator is applied



Most of us do not think of a function call in terms of applying a call operator. It is sufficient to

think about the list of arguments in parentheses. In advanced C++ programming, however, it is

important to remember that in C++ a function call is the use of the call operator. Moreover, we can

use this operator in other contexts, giving it a different meaning.

If a definition of the server function appears lexically before the definition of the client function,

then the compiler has already seen the definition of the server before compiling the function call. In

these cases, the server definition can also serve as its declaration. Most programmers do not rely on

the lexical order of functions in source code and use prototypes as a matter of habit.

A function may be defined only once in a program. Function prototypes can be repeated as many

times as needed (or more). Function prototypes are often placed in header files in a separate project

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (347 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



directory. These files are included in the source files that call these functions. Often, programmers

include header files and functions that are not used in the file; this is simpler than studying who

calls whom and in what file. This is OK for the compiler, because it ignores extra prototypes.

Header files cannot and should not, however, be ignored by maintenance programmers.

Indiscriminate use of prototypes makes the understanding of mutual dependencies between

different parts of the program more difficult.

C++ allows us to omit parameter names in function prototypes. They are really needed in function

definitions only. Many programmers omit the names of parameters because the compiler does not

need them.

void PutValues(int, int);



// what do parameters do?



This is adequate when the types of the parameters are different, the roles of the parameters are well

understood by the designer and maintainer (e.g., in a library function that is used frequently), and

the prototype is hidden away in the header file. For a programmer-defined function, using

parameter names might provide a helpful hint about their roles.

Some programmers declare prototypes in client code not at the start of the file, but immediately

inside the client function that makes the call, as a documentation aid. This clearly tells the

maintainer that it is this function (as opposed to many other functions in the same file) that uses the

server function. Do not be distracted by the simplicity of the examples that I use to illustrate these

points. This is a serious software engineering issue.

void Client(void)

{ void PutValues(int value, int count);

// list of dependencies

int val, cnt;

cout << "Please enter the value and its count: ";

cin >> val >> cnt;

PutValues(val, cnt); }



If a function has no parameters, its prototype and its definition can use either empty parentheses or

the keyword void between parentheses.

int foo();



int f(void);



// functions with no parameters



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (348 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



In a function call, however, only the empty parentheses are used to indicate the function call

operator.

foo();



f();



// parentheses are allowed and mandatory



Why is the keyword void allowed in the function definition and the function declaration but not in

a function call? This is done to make the life of the compiler writer easier. If the keyword void

were allowed in the function call, this could mislead the compiler into thinking that this is a

prototype of a function in the middle of the client code.

f(void);



// this is not a call, it is a prototype



But there is no return type¡Xwhy would the compiler think this is a prototype? Because it thinks

that the programmer just omitted the integer return type: It is not a good programming practice but

is still allowed. By the way, to make sure that you know that I keep my word when I make a

promise, this is the answer to the question I promised to answer in Chapter 2, "Getting Started

Quickly: A Brief Overview of C++."



Argument Promotions and Conversions

Since C++ is a strongly typed language, a C++ function call should use correct types and the

number of actual arguments for each of the function formal parameters. Within the function body,

the values of actual arguments are used as the values of corresponding formal parameters. If the

number or the order of arguments does not match the number or order of formal parameters, it is a

syntax error¡Xno questions asked.

PutValues(25);



// one argument is missing: error



If the number and the order of the arguments is correct, but the types are incompatible with

corresponding parameter types, matching between arguments and parameters results in a syntax

error. Types are called incompatible if a conversion between their values does not make sense. For

example, if one of the types is a programmer-defined type (structure or class) and another is either a

simple built-in type, an array, or another programmer-defined type, one value cannot be used

instead of another.

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (349 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



For example, let us assume that a1 is of a programmer-defined type Account (a structure), and a2

is an array (it does not matter what type). Then this function call represents two syntax errors.

PutValues(a1,a2);



// incompatible types: two errors



The reason why C++ takes such a harsh point of view is that inside the PutValues() function code

deals with integer parameters. The operations that are legal for integers are not legal for Account

objects or for arrays. A structure or an array variable cannot do what a number can (being added,

multiplied, compared, etc.). Their individual components can, but this is a different story.

Similarly, let us consider a function that draws a square using a parameter of some programmerdefined type Square.

void draw(Square);



It does not matter what composition or properties the type Square has. This client code is incorrect.

draw(5);



// incompatible types: syntax error



Again, this is understandable. A number cannot do what a structure can (access a component

through the dot selector operator). Here, the stand taken by C++ is firm and uncompromising,

similar to other modern languages.

However, if there is only a mismatch between a declared and an actual type, not incompatibility as

described above, promotions and conversions can be applied. A mismatch means that the types are

different, but they have common operations, and hence the values of one type can be used instead

of values of another type. These types are viewed as compatible types.

Promotions from "smaller" to "larger" numeric types are performed implicitly for some types

before any computations are done. Arguments of enum types are promoted to int, and types char,

unsigned char, and short are promoted to type int. Similarly, the unsigned short type is

promoted to int (or to unsigned int on a machine where an int is not larger than a short).

Arguments of type float are promoted to type double. These argument promotions are

"safe"¡Xthere is no danger of the loss of accuracy or danger of applying an operation that is not

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (350 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



defined on a "smaller" type.

If after promotion the argument type does not match the formal parameter type, or the argument is

not eligible for promotion (of type int, long, or double), then implicit conversions are used:

Any numeric type (including unsigned) can be converted to any other numeric type. This is done

even if the conversion results in a potential loss of accuracy (e.g., conversions from double to int).

The actual argument zero can be converted to a formal parameter of any numeric type or to a

pointer type even when a loss of accuracy is possible.

Consider, for example, the function PutValues() again. What happens if you pass arguments of

type double rather then int? They are silently converted to int. Some compilers could issue a

warning, but this is legal C++.

double x = 20, y = 5;

PutValues(x,y);



// integers are converted to double

// double are converted to integers



How can you tell the compiler that this mismatch is not an oversight and that you know what you

are doing? A common way to say that is to use an explicit cast that converts the value of one type

into a value of another type.

PutValues((int)x,(int)y);



// explicit cast for compiler, maintainer



Another way to do that is to use the function-like syntax for the cast.

PutValues(int(x),int(y));



// alternative syntax for explicit cast



Notice that the explicit cast passes the designer's intent not only to the compiler but to the

maintainer as well. There is no need to figure out what is going on.

The same rules of conversions apply if there is a mismatch between the declared return type and the

type of the actual return value. If the actual return type is "smaller" than the declared return type,

the actual value is promoted to the declared type. If the actual return type is not "smaller" or the

promotion cannot be applied (the actual value is of type int, long, or double), then the actual

value is converted to the declared return type.

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (351 of 1187) [8/17/2002 2:57:52 PM]



file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm



These argument promotions and conversions are the same as promotions and conversions that C++

uses in expression evaluation. The goal is to make them as legal as possible. Here, C++ takes a

softer stand than other modern languages do. Moreover, the use of inheritance, constructors, and

overloaded conversion operators (described later) makes C++ even more lenient about argument

conversions. This is great if these conversions are exactly what the designer had in mind. This is

not so great if the designer made a mistake and the compiler does not stand by, telling the designer

about the error. In all the cases, the use of implicit conversions makes the life of the maintenance

programmer more difficult.

It is a good idea to match arguments and return values exactly or to use explicit casts to help the

maintenance programmer to understand what is going on.



Parameter Passing in C++

There are three parameter-passing modes in C++: by value, by pointer, and by reference.

When a parameter is passed by value, the changes made to the parameter within the function do not

affect the value of the actual argument used in the function call. When a parameter is passed by

pointer or by reference, changes made to the parameter do affect the actual arguments in the client

space. In addition, there is a special mode of passing array parameters.

We will consider different modes of parameter passing, their syntax, and their semantics, and we

will try to formulate guidelines for the use of C++ parameter-passing modes that provide the best

performance and the best transmission of designer ideas to the maintenance programmer.



Calling by Value

When a function is called, argument values can be specified as variables (or symbolic constants),

expressions, or literal values of appropriate types

int n = 22, cnt = 20;

PutValues(n,cnt);

PutValues(2*n,cnt-11);

PutValues(18,14);



// arguments as variables

// arguments as expressions

// arguments as literal values



During execution, function parameters are treated as local variables whose scope is the function

body. The name of a parameter is known to the compiler and refers to a specific memory location

between the opening and the closing braces of the function. Outside of the function scope, this

name is not known. Even if the name itself can be defined outside of the function for some other

purpose, it never refers to the same memory location as the function parameter.

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (352 of 1187) [8/17/2002 2:57:52 PM]



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 7. Programming With C++ Functions

Tải bản đầy đủ ngay(0 tr)

×