Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]

.pdf
Скачиваний:
126
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Arrays, Strings, and Pointers

A pointer is a variable that contains the address of another variable. A pointer is declared as a ‘pointer to type’ and may only be assigned addresses of variables of the given type.

A pointer can point to a constant object. Such a pointer can be reassigned to another object. A pointer may also be defined as const, in which case it can’t be reassigned.

A reference is an alias for another variable, and can be used in the same places as the variable it references. A reference must be initialized in its declaration.

A reference can’t be reassigned to another variable.

The operator sizeof returns the number of bytes occupied by the object specified as its argument. Its argument may be a variable or a type name between parentheses.

The operator new allocates memory dynamically in the free store in a native C++ application. When memory has been assigned as requested, it returns a pointer to the beginning of the memory area provided. If memory cannot be assigned for any reason, an exception is thrown that by default causes the program to terminate.

The pointer mechanism is sometimes a bit confusing because it can operate at different levels within the same program. Sometimes it is operating as an address, and at other times it can be operating with the value stored at an address. It’s very important that you feel at ease with the way pointers are used, so if you find that they are in any way unclear, try them out with a few examples of your own until you feel confident about applying them.

The key points that you learned about in relation to programming for the CLR are:

In CLR program, you allocate memory of the garbage-collected heap using the gcnew operator.

Reference class objects in general and String objects in particular are always allocated on the CLR heap.

You use String objects when working with strings in a CLR program.

The CLR has its own array types with more functionality that native array types.

CLR arrays are created on the CLR heap.

A tracking handle is a form of pointer used to reference variables defined on the CLR heap. A tracking handle is automatically updated if what it refers to is relocated in the heap by the garbage collector.

Variable that reference objects and arrays on the heap are always tracking handles.

A tracking reference is similar to a native reference except that the address it contains is automatically updated if the object referenced is moved by the garbage collector.

An interior pointer is a C++/CLI pointer type to which you can apply the same operation as a native pointer.

The address contained in interior pointer can be modified using arithmetic operations and still maintain an address correctly even when referring to something stored in the CLR heap.

229

Chapter 4

Exercises

You can download the source code for the examples in the book and the solutions to the following exercises from http://www.wrox.com.

1.Write a native C++ program that allows an unlimited number of values to be entered and stored in an array allocated in the free store. The program should then output the values five to a line followed by the average of the values entered. The initial array size should be five elements. The program should create a new array with five additional elements when necessary and copy values from the old array to the new.

2.Repeat the previous exercise but use pointer notation throughout instead of arrays.

3.Declare a character array, and initialize it to a suitable string. Use a loop to change every other character to upper case.

Hint: In the ASCII character set, values for uppercase characters are 32 less than their lowercase counterparts.

4.Write a C++/CLI program that creates an array with a random number of elements of type int. The array should have from 10 to 20 elements. Set the array elements to random values between 100 and 1000. Output the elements five to a line in ascending sequence without sorting the array; for example find the smallest element and output that, then the next smallest, and so on.

5.Write a C++/CLI program that will generate a random integer greater than 10,000. Output the integer and then output the digits in the integer in words. For example, if the integer generate was 345678, then the output should be:

The value is 345678

three four five six seven eight

6.Write a C++/CLI program that creates an array containing the following strings:

“Madam I’m Adam.”

“Don’t cry for me, Marge and Tina.” “Lid off a daffodil.”

“Red lost soldier.”

“Cigar? Toss it in a can. It is so tragic.”

The program should examine each string in turn, output the string and indicate whether it is or is not a palindrome (that is, a sequence of letters reading backwards or forwards, ignoring spaces and punctuation).

230

5

Introducing Structure

into Your Programs

Up to now, you haven’t really been able to structure your program code in a modular fashion because you have only been able to construct a program as a single function, main(); but you have been using library functions of various kinds as well as functions belonging to objects. Whenever you write a C++ program, you should have a modular structure in mind from the outset and, as you’ll see, a good understanding of how to implement functions is essential to object-oriented programming in C++. In this chapter, you’ll learn:

How to declare and write your own C++ functions

How function arguments are defined and used

How arrays can be passed to and from a function

What pass-by-value means

How to pass pointers to functions

How to use references as function arguments, and what pass-by-reference means

How the const modifier affects function arguments

How to return values from a function

How recursion can be used

There’s quite a lot to structuring your C++ programs, so to avoid indigestion, you won’t try to swallow the whole thing in one gulp. After you have chewed over and gotten the full flavor of these morsels, you’ll move on to the next chapter, where you will get further into the meat of the topic.

Chapter 5

Understanding Functions

First take a look at the broad principles of how a function works. A function is a self-contained block of code with a specific purpose. A function has a name that both identifies it and is used to call it for execution in a program. The name of a function is global but is not necessarily unique in C++, as you’ll see

in the next chapter; however, functions that perform different actions should generally have different names.

The name of a function is governed by the same rules as those for a variable. A function name is, therefore, a sequence of letters and digits, the first of which is a letter, where an underscore counts as a letter. The name of a function should generally reflect what it does, so for example, you might call a function that counts beans count_beans().

You pass information to a function by means of arguments specified when you invoke it. These arguments need to correspond with parameters that appear in the definition of the function. The arguments that you specify replace the parameters used in the definition of the function when the function executes. The code in the function then executes as though it was written using your argument values. Figure 5-1 illustrates the relationship between arguments in the function call and the parameters specified in the definition of the function.

Arguments

cout << add_ints( 2 , 3 );

Argument values replace corresponding parameters in the function definition

 

int add_ints( int i, int j )

Function

{

 

 

 

 

 

 

 

 

return i + j ;

 

 

 

 

 

 

 

Definition

 

 

 

}

Parameters

Value 5 returned

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 5-1

232

double power(double x, int n)
It consists of three parts:
// Function header
The Function Header
Let’s first examine the function header in this example. The following is the first line of the function.
}
// ...and ends here
Introducing Structure into Your Programs
In this example, the function returns the sum of the two arguments passed to it. In general, a function returns either a single value to the point in the program where it was called, or nothing at all, depending on how the function is defined. You might think that returning a single value from a function is a constraint, but the single value returned can be a pointer that might contain the address of an array, for example. You will see more about how data is returned from a function a little later in this chapter.
Why Do You Need Functions?
One major advantage that a function offers is that it can be executed as many times as necessary from different points in a program. Without the ability to package a block of code into a function, programs would end up being much larger because you would typically need to replicate the same code at various points in them. But the real reason that you need functions is to break up a program into easily manageable chunks for development and testing.
Imagine a really big program — let’s say a million lines of code. A program of this size would be virtually impossible to write without functions. Functions enable you to segment a program so that you can write the code piecemeal, and test each piece independently before bringing it together with the other pieces. It also allows the work to be divided among members of a programming team, with each team member taking responsibility for a tightly specified piece of the program, with a well-defined functional interface to the rest of the code.
Structure of a Function
As you have seen when writing the function main(), a function consists of a function header that identifies the function, followed by the body of the function between curly braces containing the executable code for the function. Let’s look at an example. You could write a function to raise a value to a given power, that is, to compute the result of multiplying the value x by itself n times, which is xn:
// Function to calculate x to the power n, with n greater than or equal to 0 double power(double x, int n) // Function header
{ // Function body starts here...
double result = 1.0; // Result stored here for(int i = 1; i <= n; i++)
result *= x;
return result;

The type of the return value (double in this case)

The name of the function, (power in this case)

The parameters of the function enclosed between parentheses (x and n in this case, of types double and int respectively)

233

Chapter 5

The return value is returned to the calling function when the function is executed, so when the function is called, it results in a value of type double in the expression in which it appears.

Our function has two parameters: x, the value to be raised to a given power which is of type double, and the value of the power, n, which is of type int. The computation that the function performs is written using these parameter variables together with another variable, result, declared in the body of the function. The parameter names and any variables defined in the body of the function are local to the function.

Note that no semicolon is required at the end of the function header or after the closing brace for the function body.

The General Form of a Function Header

The general form of a function header can be written as follows.

return_type function_name(parameter_list)

The return_type can be any legal type. If the function does not return a value, the return type is specified by the keyword void. The keyword void is also used to indicate the absence of parameters, so a function that has no parameters and doesn’t return a value would have the following function header.

void my_function(void)

An empty parameter list also indicates that a function takes no arguments, so you could omit the keyword void between the parentheses like:

void my_function()

A function with a return type specified as void should not be used in an expression in the calling program. Because it doesn’t return a value, it can’t sensibly be part of an expression, so using it in this way causes the compiler to generate an error message.

The Function Body

The desired computation in a function is performed by the statements in the function body following the function header. The first of these in our example declares a variable result that is initialized with the value 1.0. The variable result is local to the function, as are all automatic variables declared within the function body. This means that the variable result ceases to exist after the function has completed execution. What might immediately strike you is that if result ceases to exist on completing execution of the function, how is it returned? The answer is that a copy of the value being returned is made automatically, and this copy is available to the return point in the program.

The calculation is performed in the for loop. A loop control variable i is declared in the for loop which assumes successive values from 1 to n. The variable result is multiplied by x once for each loop iteration, so this occurs n times to generate the required value. If n is 0, the statement in the loop won’t be executed at all because the loop continuation condition immediately fails, and so result is left as 1.0.

As I’ve said, the parameters and all the variables declared within the body of a function are local to the function. There is nothing to prevent you from using the same names for variables in other functions for

234

Introducing Structure into Your Programs

quite different purposes. Indeed, it’s just as well this is so because it would be extremely difficult to ensure variables names were always unique within a program containing a large number of functions, particularly if the functions were not all written by the same person.

The scope of variables declared within a function is determined in the same way that I have already discussed. A variable is created at the point at which it is defined and ceases to exist at the end of the block containing it. There is one type of variable that is an exception to this(variables declared as static. I’ll discuss static variables a little later in this chapter.

Be careful about masking global variables with local variables of the same name. You first met this situation back in Chapter 2 where you saw how you could use the scope resolution operator :: to access global variables.

The return Statement

The return statement returns the value of result to the point where the function was called. The general form of the return statement is:

return expression;

where expression must evaluate to a value of the type specified in the function header for the return value. The expression can be any expression you want, as long as you end up with a value of the required type. It can include function calls — even a call of the same function in which it appears, as you’ll see later in this chapter.

If the type of return value has been specified as void, there must be no expression appearing in the return statement. It must be written simply as:

return;

Using a Function

At the point at which you use a function in a program, the compiler must know something about it to compile the function call. It needs enough information to be able to identify the function, and to verify that you are using it correctly. Unless you the definition of the function that you intend to use appears earlier in the same source file, you must declare the function using a statement called a function prototype.

Function Prototypes

A prototype of a function provides the basic information that the compiler needs to check that you are using a function correctly. It specifies the parameters to be passed to the function, the function name, and the type of the return value — basically, it contains the same information as appears in the function header, with the addition of a semicolon. Clearly, the number of parameters and their types must be the same in the function prototype as they are in the function header in the definition of the function.

The prototypes for the functions that you call from within another function must appear before the statements doing the calling and are usually placed at the beginning of the program source file. The header files that you’ve been including for standard library functions contain the prototypes of the functions provided by the library, amongst other things.

235

Chapter 5

For the power() function example, you could write the prototype as:

double power(double value, int index);

Don’t forget that a semicolon is required at the end of a function prototype. Without it, you get error messages from the compiler.

Note that I have specified names for the parameters in the function prototype that are different from those I used in the function header when I defined the function. This is just to indicate that it’s possible. Most often, the same names are used in the prototype and in the function header in the definition of the function, but this doesn’t have to be so. You can use longer more expressive parameter names in the function prototype to aid understanding of the significance of the parameters and then use shorter parameter names in the function definition where the longer names would make the code in the body of the function less readable.

If you like, you can even omit the names altogether in the prototype, and just write:

double power(double, int);

This provides enough information for the compiler to do its job; however, it’s better practice to use some meaningful name in a prototype because it aids readability and, in some cases, makes all the difference between clear code and confusing code. If you have a function with two parameters of the same type (suppose our index was also of type double in the function power(), for example), the use of suitable names indicates which parameter appears first and which second.

Try It Out

Using a Function

You can see how all this goes together in an example exercising the power() function.

//Ex5_01.cpp

//Declaring, defining, and using a function #include <iostream>

using std::cout; using std::endl;

double power(double x, int n);

// Function prototype

int main(void)

 

{

 

int index = 3;

// Raise to this power

double x = 3.0;

// Different x from that in function power

double y = 0.0;

 

y = power(5.0, 3);

// Passing constants as arguments

cout << endl

 

<< “5.0 cubed = “ << y;

 

cout << endl

 

<< “3.0 cubed = “

 

<< power(3.0, index);

// Outputting return value

x = power(x, power(2.0, 2.0)); // Using a function as an argument

236

// Function body starts here...
// Result stored here
// ...and ends here

 

Introducing Structure into Your Programs

 

 

cout << endl

// with auto conversion of 2nd parameter

<< “x = “ << x;

 

cout << endl;

 

return 0;

 

}

//Function to compute positive integral powers of a double value

//First argument is value, second argument is power index double power(double x, int n)

{

double result = 1.0; for(int i = 1; i <= n; i++)

result *= x; return result;

}

This program shows some of the ways in which you can use the function power(), specifying the arguments to the function in a variety of ways. If you run this example, you get the following output:

5.0 cubed = 125

3.0 cubed = 27 x = 81

How It Works

After the usual #include statement for input/output and the using declarations, you have the prototype for the function power(). If you were to delete this and try recompiling the program, the compiler wouldn’t be able to process the calls to the function in main() and would instead generate a whole series of error messages:

error C3861: ‘power’: identifier not found

and the error message:

error C2365: ‘power’ : redefinition; previous definition was ‘formerly unknown identifier’

In a change from previous examples, I’ve used the new keyword void in the function main() where the parameter list would usually appear to indicate that no parameters are to be supplied. Previously, I left the parentheses enclosing the parameter list empty, which is also interpreted in C++ as indicating that there are no parameters; but it’s better to specify the fact by using the keyword void. As you saw, the keyword void can also be used as the return type for a function to indicate that no value is returned. If you specify the return type of a function as void, you must not place a value in any return statement within the function; otherwise, you get an error message from the compiler.

You gathered from some of the previous examples that using a function is very simple. To use the function power() to calculate 5.03 and store the result in a variable y in our example, you have the following statement:

y = power(5.0, 3);

237

Chapter 5

The values 5.0 and 3 here are the arguments to the function They happen to be constants, but you can use any expression as an argument, as long as a value of the correct type is ultimately produced. The arguments to the power() function substitute for the parameters x and n, which were used in the definition of the function. The computation is performed using these values and then a copy of the result, 125, is returned to the calling function, main(), which is then stored in y. You can think of the function as having this value in the statement or expression in which it appears. You then output the value of y:

cout << endl

<< “5.0 cubed = “ << y;

The next call of the function is used within the output statement:

cout <<

endl

 

<<

“3.0 cubed = “

 

<<

power(3.0, index);

// Outputting return value

Here, the value returned by the function is transferred directly to the output stream. Because you haven’t stored the returned value anywhere, it is otherwise unavailable to you. The first argument in the call of the function here is a constant; the second argument is a variable.

The function power() is used next in this statement:

x = power(x, power(2.0, 2.0)); // Using a function as an argument

Here the power() function is called twice. The first call to the function is the rightmost in the expression, and the result supplies the value for the second argument to the leftmost call. Although the arguments in the sub-expression power(2.0, 2.0) are both specified as the double literal 2.0, the function is actually called with the first argument as 2.0 and the second argument as the integer literal, 2. The compiler converts the double value specified for the second argument to type int because it knows from the function prototype (shown again below) that the type of the second parameter has been specified as int.

double power(double x, int n);

// Function prototype

The double result 4.0 is returned by the first call to the power() function, and after conversion to type int, the value 4 is passed as the second argument in the next call of the function, with x as the first argument. Because x has the value 3.0, the value of 3.04 is computed and the result, 81.0, stored in x. This sequence of events is illustrated in Figure 5-2.

This statement involves two implicit conversions from type double to type int that were inserted by the compiler. There’s a possible loss of data when converting from type double to type int so the compiler issues warning message when this occurs, even though the compiler has itself inserted this conversion. Generally relying on automatic conversions where there is potential for data loss is a dangerous programming practice, and it is not at all obvious from the code that this conversion is intended. It is far better to be explicit in your code by using the static_cast operator when necessary. The statement in the example is much better written as:

x = power(x, static_cast<int>(power(2.0, 2)));

238