0% found this document useful (0 votes)
27 views63 pages

9.C++ Programming An Object-Oriented Approach

This chapter discusses references and pointers as compound data types in C++, focusing on their memory management and usage in functions. It covers the creation and binding of reference variables, the implications of constant relationships, and the applications of pass-by-reference and return-by-reference. Additionally, it highlights the differences between pass-by-value and pass-by-reference, emphasizing memory efficiency and the ability to modify original variables through references.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views63 pages

9.C++ Programming An Object-Oriented Approach

This chapter discusses references and pointers as compound data types in C++, focusing on their memory management and usage in functions. It covers the creation and binding of reference variables, the implications of constant relationships, and the applications of pass-by-reference and return-by-reference. Additionally, it highlights the differences between pass-by-value and pass-by-reference, emphasizing memory efficiency and the ability to modify original variables through references.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 63

9 References, Pointers, and

Memory Management
In Chapter 8 we discussed one of the compound data types: arrays. In this chapter we dis-
cuss the other two compound data types: references and pointers. We also discuss memory
management related to storing pointed objects in heap memory.

Objectives
After you have read and studied this chapter, you should be able to:

• Discuss reference types and reference variables.


• Discuss how to create a permanent binding between a reference variable and the original
variable.
• Discuss how to retrieve and change data in the original variable through the reference
variable.
• Show the application of references in relation to functions: pass-by-reference and
return-by-reference.
• Discuss pointer types and pointer variables.
• Discuss how to create a permanent or temporary binding between a pointer variable and a
data variable.
• Discuss how to retrieve and change data in a data variable through the pointer variable.
• Show the application of pointers in relation to functions: pass-by-pointer and
return-by-pointer.
• Discuss relations between arrays and pointers, and discuss array arithmetic.
• Discuss four areas of memory available to programmers and their applications.
• Discuss when an object can be stored in stack memory and when it needs to be stored in heap
memory.
• Discuss dynamic memory allocation.

9.1 REFERENCES
A reference is an alternative name for an object. References have been added to C++ to sim-
plify communication between entities such as functions, as we will see later in the chapter.

9.1.1 Introduction
A variable declared as type& is an alternative name for a variable declared as type. When
we declare a reference variable, we do not create a new object in memory; we just declare
an alternative name for an existing variable. The original variable and the reference variable

380
9.1  References 381

type: int type: int&

Original variable score rScore Reference variable


92
name name

value

Figure 9.1 Original variable and reference variable

are in fact the same memory location called by different names. For this reason, we need to
bind a reference variable to the original variable immediately when we declare it. Binding is
done through initializing the reference variable with the name of the original variable. The
following shows the creation of an original variable of type int named score and the binding
of a reference variable of type int& to it.

int score = 92; // Declaring and initializing variable score of type int
int& rScore = score; // Declaring variable rScore of type int& and binding it

Figure 9.1 shows the situation in memory. We have only one memory location, but
with two names. One of the names defines the original variable; the other name defines a
reference to that variable.
The interesting point is that the same value, 92, is seen as an int type when accessed
through the score variable; it is seen as an int& type when accessed through the rScore variable.
The name we have used for a reference variable is the same as the original name, but
we add an r at the beginning and make the first letter of the original name uppercase. This is
not a mandate. This is our convention and helps us remember to which original variable our
reference variable is bound.

Compound Type
Although the original variable and the reference variable define the same location in memory
with the same value seen by both of them, the types of the original variable and the reference
variable are different. For example, in Figure 9.1, the type of variable score is int, but the
type of variable rScore is int&. In other words, the use of reference variables creates a new
compound type: reference type. A reference type is a compound type because we can have
a reference to an int (int&), a reference to a double (double&), a reference to a bool (bool&),
and so on. When we bind a variable to a reference variable, the type of the reference vari-
able should be a reference to the type of the original variable. The following is a compilation
error. We cannot initialize a reference variable of double& to refer to a variable of type int.

int num = 100;


double& rNum = num; // Compilation error. Type mismatch

Permanent Binding
After a reference variable has been declared and bound to a variable name, a reference
relationship has been created between them and cannot be broken until the variables are de-
stroyed (go out of scope). In C++ parlance, we say that the relationship between a variable
and the corresponding reference variable is a constant relation. Figure 9.2 shows that we
cannot change the reference relation after it has been created.
382 Chapter 9  References, Pointers, and Memory Management

constant relation

original name score value rScore reference name

Figure 9.2 The reference relation as a constant relation

We discuss more about the constantness in reference relationships later in this chapter.

After the reference relationship is established, it cannot be changed.

EXAMPLE 9.1
We will get a compilation error if we first bind rScore to score and then try to break this
relationship and bind rScore to num, as shown below:

int score = 92;


int& rScore = score;
int num = 80;
int& rScore = num; // Compilation error, breaking the reference relationship

EXAMPLE 9.2
Sometimes we see statements that should not be confused with breaking the reference rela-
tionship. For example, consider the following:

int score = 92;


int& rScore = score;
int num = 80;
rScore = num;

In this code segment, we are not breaking the relationship. We are storing a copy of value
in num to the common memory location established by the reference relationship. In other
words, the last statement is not binding; it is an assignment to the common variable. We
could have used the statement score = num with the same effect.

Multiplicity
We can have multiple reference variables bound to the same variable, but the reverse is not
possible. We cannot have a reference variable bound to more than one variable.

EXAMPLE 9.3
The following shows how to bind three reference variables (rNum1, rNum2, and rNum3) to
a single variable num.

int num = 100;


num& rNum1 = num; // rNum1 is bound to num
num& rNum2 = num; // rNum2 is bound to num
num& rNum3 = num; // rNum3 is bound to num

This means that a memory location can be called using four names: num, rNum1, rNum2,
and rNum3, as shown in Figure 9.3.
9.1  References 383

rNum2 Note:
A location in memory
num 100 rNum1 has four names.

rNum3

Figure 9.3 Multiple references to the same


variable are allowed.

error Note:
100 num1
rNum cannot be an alias
rNum name for two different locations,
error even if they hold the same value.
100 num2

Figure 9.4 A reference to multiple variables is not allowed.

EXAMPLE 9.4
We get a compilation error if we try to bind one reference variable to more than one variable
because doing so means breaking the constant reference relation and creating a new one,
which is not allowed.

int num1 = 100;


int num2 = 200;
num& rNum = num1; // rNum is bound to num1
num& rNum = num2; // Compilation error. rNum cannot be bound to num2

Figure 9.4 shows this unacceptable relationship.

No Binding to Values
Note that a reference variable cannot be bound to a value. For example, we get a compilation
error in the following statement.

int& x = 92; // Compilation error: no binding to values

9.1.2 Retrieving Value


When the reference relationship is established, the value stored in the common memory
locations can be retrieved either through the original variable or reference variable.

EXAMPLE 9.5
Program 9.1 shows the use of an original variable and its reference. However, this applica-
tion is only for demonstration; the actual relationship between a variable and its reference is
beneficial when we use them in two different functions, as we will see later in the chapter.

Program 9.1 Accessing value

1 /***************************************************************
2 * The program shows how to declare and initialize the original *
3 * and reference variables and then access the common value *
(continued)
384 Chapter 9  References, Pointers, and Memory Management

Program 9.1 Accessing value (Continued)


4 * through either of them. *
5 ***************************************************************/
6 #include <iostream>
7 using namespace std;
8
9 int main ( )
10 {
11 // Creation of reference relations
12 int score = 92;
13 int& rScore = score;
14 // Using data variable
15 cout << "Accessing value through data variable." << endl;
16 cout << "score: " << score << endl;
17 // Using reference variable
18 cout << "Accessing value through reference variable." << endl;
19 cout << "rScore: " << rScore;
20 return 0;
21 }
Run:
Accessing value through data variable.
score: 92
Accessing value through reference variable.
rScore: 92

9.1.3 Changing Value


There is only one value in a reference relationship, but the value can be changed either
through the original variable or through any of the reference variables, unless we use const
modifiers (Figure 9.5).

Constant (permanent) relationship

score 92 pScore

Data cannnot be changed at all. const int score = 92;

Data cannot be changed


const int& pScore = score;
through reference variable.

No const modifier is allowed here


because the relationship is already constant.

Figure 9.5 Preventing change in a reference relation


9.1  References 385

Table 9.1 Four possible combinations

Case Data variable Reference variable Status


1 int name = value; int& rName = name; OK

2 const int name = value; int& rName = name; Error

3 int name = value; const int& rName = name; OK

4 const int name = value; const int& rName = name; OK

The constant modifier can be put in front of the original variable or in front of the
reference variable. The relationship (binding) is by nature constant and cannot be broken.
Figure 9.5 shows the use of a const modifier for both the original and the reference variables.
Table 9.1 shows the four possible combinations, but the second one creates a compila-
tion error, as we describe shortly.

First Case
In the first case, there is no restriction to changing the value either through the original vari-
able or through the reference variable.

Second Case
The second case creates a compilation error because we try to bind a nonconstant reference
variable to a constant variable. Since the original variable is already constant, there is no way
that we can change its value through the reference variable.

Third Case
In the third case, the data can be changed through the data variable, but we want to restrict it
from being changed through the reference variable.

Fourth Case
In the fourth case, we want to create an original variable and a reference variable in a way
that neither the original variable nor the reference variable can change the common value.
This case has little application because the data variable and the reference variables can only
be used to retrieve data, not to change them.

9.1.4 Applications
Using references in the same namespace, such as in the same function, is unnecessary be-
cause we can always use the original variable instead of the reference variable. Both vari-
ables use the same memory location. The idea of references is beneficial when the two vari-
ables are in different scopes, like in a calling function and the called function. We can save
memory by using one memory location and accessing it in two functions using the original
variable and the reference variable.
In this section we discuss the use of references in the communication between two
functions. We discuss using references in passing data to a function and in returning data
from a function. The first application is called pass-by-reference; the second is called
return-by-reference.
386 Chapter 9  References, Pointers, and Memory Management

num1 num
Calling
function

copy all bytes share

Called
function
num2 rNum

Pass-by-value Pass-by-reference

Figure 9.6 Comparing pass-by-value and pass-by-reference

Pass-by-Reference
In the first case, pass-by-reference, a calling function has an object (or objects) that it needs
to send to the called function for processing. In Chapter 6 we used pass-by-value; now we
use pass-by-reference. Both methods are shown in Figure 9.6.
We write two small programs, side by side, to compare the two methods. Assume we
want a function that processes integers passed to it. The function can be designed as pass-
by-value or pass-by-reference as shown below:

#include <iostream> #include <iostream>


using namespace std; using namespace std;
// Prototype // Prototype
void doIt (int); void doIt (int&);

int main () int main ()


{ {
int num = 10; int num = 10;
doIt (num); doIt (num);
return 0; return 0;
} }
// Pass-by-value // Pass-by-reference
void doIt (int num) void doIt (int& rNum)
{ {
// Code // Code
} }

Characteristics of Pass-by-Value In the pass-by-value method, the calling function sends


a copy of its argument to the called function. The copy becomes the parameter in the called
function. In other words, the following statement is done in the background:

int num2 = num1;

∙∙ In pass-by-value, we have two independent objects: the argument and the parameter.
This means that changes in the parameter, intentional or accidental, cannot affect the
argument. This may be an advantage in one situation and a disadvantage in another
situation.
9.1  References 387

∙∙ Another issue in using pass-by-value is the cost of copying. Copying all bytes of the ar-
gument can be costly if the object to be copied is large. This means that if the object to
be passed is of a fundamental type, we should not worry because the number of bytes
to be copied is small (normally less that eight). However, if we need to copy an object
of class type with thousands of bytes, we should consider other methods.
Characteristics of Pass-by-Reference In the pass-by-reference method, the parameter is
only a reference to the argument. The binding between the two occurs in the background as
part of the running environment, as shown below:

int& rNum = num;

∙∙ In pass-by-reference, the argument and the parameter are exactly the same object; we
are saving memory allocation. Any change in the parameter means the same change in
the argument unless we use a constant reference, as we discussed previously.
∙∙ It is obvious that pass-by-reference eliminates the cost of copying. The argument and
the parameter are the same object. We must consider this method when we want to pass
a large object, such as an object of class type, to a called function.
Recommendation Now that we have discussed the pros and cons of each method of pass-
ing an argument to a function, we consider the following recommendations, which will help
us decide which method to use.
1. If we need to prevent change, we should use
a. pass-by-value for small objects.
b. pass-by-constant reference for large objects.

2. If there is a need for change, we should use pass-by-reference.

Warning We cannot bind a reference parameter to a value argument. For example, the fol-
lowing code creates a compilation error.

void fun (int& rX) { … } // Function definition


fun (5); // Function call (compilation error)

The parameter rX is a reference parameter. The argument of the function call must be a vari-
able name, not a value.

EXAMPLE 9.6
Assume we want to write a function to print the value of a fundamental data type. We do
not want to change the value of the data in the calling function. This is the first case in our
recommendation; we can use either pass-by-value or pass-by-constant reference. Since the
object is small, we can use pass-by-value. We have given several examples of this situation
previously.

EXAMPLE 9.7
Assume we want to write a copy constructor for a class. This is also the first case in our
recommendation because we do not want the function to change the original object. How-
ever, pass-by-value is costly (the object can be large) and, more important, it is impossible
because pass-by-value needs the call to the copy constructor to copy the object, which means
388 Chapter 9  References, Pointers, and Memory Management

we need the copy constructor to create a copy constructor (vicious circle). The C++ stan-
dard says we must use pass-by-constant reference as shown below:

// Pass-by-reference in copy constructor


Circle :: Circle (const Circle& circle)
: radius (circle.radius)
{
}

EXAMPLE 9.8
Assume we want to write a function to swap two data items. This is the second case in our
recommendation. We need change, so we use pass-by-reference as shown in Program 9.2.

Program 9.2 Using a swap function with pass-by-reference

1 /***************************************************************
2 * The program shows how to use pass-by-reference to allow a *
3 * called function to swap two values in the calling function. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 void swap (int& first, int& second) ; // Prototype
9
10 int main ( )
11 {
12 // Definition of two variables
13 int x = 10;
14 int y = 20;
15 // Printing the value of x and y before swapping
16 cout << "Values of x and y before swapping." << endl;
17 cout << "x: " << x << " " << "y: " << y << endl;
18 // Calling swap function to swap the values of x and y
19 swap (x , y);
20 // Printing the value of x and y after swapping
21 cout << "Values of x and y after swapping." << endl;
22 cout << "x: " << x << " " << "y: " << y;
23 return 0;
24 }
25 /***************************************************************
26 * The swap function swaps the values of the parameters and *
27 * pass-by-reference allows the corresponding arguments in main *
28 * to be swapped accordingly. *
29 ***************************************************************/
30 void swap (int& rX, int& rY)
31 {
(continued)
9.1  References 389

Program 9.2 Using a swap function with pass-by-reference (Continued)


32 int temp = rX;
33 rX = rY;
34 rY = temp;
35 }
Run:
Values of x and y before swapping.
x: 10 y: 20
Values of x and y after swapping.
x: 20 y: 10

Return-by-Reference
In the second case, return-by-reference, a called function has an object that must be re-
turned to the calling function.

Characteristics of Return-by-Value In return-by-value, the called function returns an


object of the desired type using the following prototype.

type function (...);

Return-by-value is simple and can be used anywhere. We can return the value of a parameter
or a local variable. The only drawback is the cost of copying. If the object to be returned is
a fundamental type, we should not worry; if it is an object of a class type, we know that the
copy constructor will be called and the cost may be high.

Characteristics of Return-by-Reference In return-by-reference, the type of the object


to be returned is a reference to another object, as shown in the following prototype.

type& function (...);

This method eliminates the cost of copying, but it has a drawback: We cannot return an ob-
ject by reference if it is a value parameter or a local variable (except static). The reason, as
we will discuss later in the chapter, is that when a function is terminated all local variables
and value parameters are destroyed. When an object is destroyed, we cannot have an alias
name to it, but we can return a reference to a reference parameter.

EXAMPLE 9.9
Assume we want to write a function to find the larger between two integers. We could use
either the combination of pass-by-value and return-by-value or the combination of pass-by-
reference and return-by-reference. The following shows the two programs side by side for
comparison.

#include <iostream> #include <iostream>


using namespace std; using namespace std;
390 Chapter 9  References, Pointers, and Memory Management

// Return-by-value // Return-by-reference
int larger (int x, int y ) int& larger (int& x, int& y )
{ {
if (x > y) if (x > y)
{ {
return x; return x;
} }
return y; return y;
} }
int main () int main ()
{ {
int x = 10; int x = 10;
int y = 20; int y = 20;
int z = larger (x, y); int z = larger (x, y);
cout << z; cout << z;
return 0; return 0;
} }
Run: Run:
20 20

We see examples of both practices in cases like this because the cost of copying is
small in pass-by-value and return-by-value.

EXAMPLE 9.10
In this example we find the larger between two objects of class types. In Chapter 7 we cre-
ated a fraction class. We do not repeat the interface and the implementation file here (sepa-
rate compilation). We just create an application file that uses pass-by-reference and return-
by-reference to find the smaller of two fractions. Program 9.3 shows the application file.

Program 9.3 Finding the larger of two fractions

1 /***************************************************************
2 * The program creates two pairs of fractions and then calls a *
3 * function named larger to find the larger in each pair *
4 ***************************************************************/
5 #include "fraction.h"
6
7 Fraction& larger (Fraction&, Fraction&); // Prototype
8
9 int main ( )
10 {
11 // Creating first pair of fractions and finding the larger
12 Fraction fract1 (3, 13);
13 Fraction fract2 (5, 17);
14 cout << "Larger of the first pair of fraction: " ;
15 larger (fract1, fract2).print ();
16 // Creating second pair of fractions and finding the larger
(continued)
9.2  Pointers 391

Program 9.3 Finding the larger of two fractions (Continued)


17 Fraction fract3 (4, 9);
18 Fraction fract4 (1, 6);
19 cout << "Larger of the second pair of fractions: " ;
20 larger (fract3, fract4).print ();
21 return 0;
22 }
23 /***************************************************************
24 * The function gets two fractions by reference, compares them *
25 * and returns the larger. *
26 ***************************************************************/
27 Fraction& larger (Fraction& fract1, Fraction& fract2)
28 {
29 if (fract1.getNumer() * fract2.getDenom() >
30 fract2.getNumer() * fract1.getDenom())
31 {
32 return fract1;
33 }
34 return fract2;
35 }
Run:
Larger of first pair of fractions: 5/17
Larger of second pair of fractions: 4/9

The interesting point is that in each call, the returned item is an lvalue, one of the ob-
jects that was sent to the function. This is why we can apply the print function defined in the
class to the object (lines 17 and 22).

9.2 POINTERS
A pointer type is a compound type representing the address of a memory location. A pointer
variable is a variable whose contents are of pointer type. In this section, we discuss ad-
dresses, pointer types, pointer variables, and some related issues.

9.2.1 Addresses
When we talk about addresses, we must consider two separate concepts: addresses in mem-
ory and the address of a variable.

Addresses in Memory
As we discussed briefly in Chapter 1, computer memory is a sequence of bytes. In other
words, the smallest accessible unit is a byte. When we say that a computer has a memory
(random access memory, or RAM) of 1 kilobyte, we mean that memory is made of 210 or
1024 bytes. Today’s computers have memories in the range of megabytes (220), gigabytes
(230), or even terabytes (240).
Each byte of memory has an address. The addresses are shown in hexadecimal format.
For example, in a computer with 1 kilobyte of memory, the bytes are numbered from 0x000
to 0x3ff (0 to 1023 in decimal) as shown in Figure 9.7.
392 Chapter 9  References, Pointers, and Memory Management

0x000 Address of the first byte


0x001
0x002
Memory

0x3fe
0x3ff Address of the last byte

Figure 9.7 Addresses in 1 kilobyte of RAM

Address of a Variable
In our programs, we define variables of different types: Boolean, character, integer, floating
point, and class. Each variable occupies one or more bytes of memory. A variable of type
bool or char normally occupies one byte of memory. A variable of type int may occupy four
or more bytes of memory. A variable of type double may also occupy four or more bytes of
memory (the sizeof operator can be used to determine the number of bytes). The address of
a variable is the address of the first byte it occupies. This is an important point we must re-
member when we are working with addresses or pointers, as we will see. To get the address
of a variable, we use the address operator (&) in front of the variable.

The address of a variable is the address of the first byte


occupied by that variable.

EXAMPLE 9.11
In this example we write a simple program that prints the addresses of three variables: bool,
int, and double (Program 9.4).

Program 9.4 Printing size, value, and addresses

1 /***************************************************************
2 * The program to define three variables and to print their *
3 * values and their addresses in the memory *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 int main ( )
9 {
10 // Declaration of three data variables
11 bool flag = true;
12 int score = 92;
13 double average = 82.56;
14 // Printing size, value, and address of the flag variable
15 cout << "A variable of type bool" << endl;
16 cout << "Size: " << sizeof (flag) << " " ;
17 cout << "Value: " << flag << " ";
18 cout << "Address: "<< &flag << endl << endl;
(continued)
9.2  Pointers 393

Program 9.4 Printing size, value, and addresses (Continued)


19 // Printing size, value, and address of the score variable
20 cout << "A variable of type int" << endl;
21 cout << "Size: " << sizeof (score) << " " ;
22 cout << "Value: " << score << " ";
23 cout << "Address: "<< &score << endl << endl;
24 // Printing size, value, and address of the average variable
25 cout << "A variable of type double" << endl;
26 cout << "Size: " << sizeof (average) << " " ;
27 cout << "Value: " << average << " ";
28 cout << "Address: "<< &average << endl;
29 return 0;
30 }
Run:
A variable of type bool
Size: 1 Value: 1 Address: 0x28fef0

A variable of type int


Size: 4 Value: 92 Address: 0x28fef1

A variable of type double


Size: 8 Value: 82.56 Address: 0x28fef5

There are three important points about Program 9.4:


∙∙ The extraction operator (<<) is overloaded so that it can accept an address. Therefore,
we can use the expression cout << &score to print the address of the variables.
∙∙ The addresses are displayed using hexadecimal notation. The addresses are not inte-
gers; they are pointer types, as we will see shortly.
∙∙ The sizes and addresses may be different in different systems. The addresses may start
from the smallest to largest or vice versa. There may be some gaps in the addresses.
Figure 9.8 shows the addresses as printed in Program 9.5.

Definition flag 1 0x28fef0 address of flag


score 0x28fef1 address of score
bool flag = true;
92
int score = 92;
double average = 82.56; 0x28fef5 address of average

average
82.56

Memory

Figure 9.8 Memory situation declared and defined in Program 9.5


394 Chapter 9  References, Pointers, and Memory Management

9.2.2 Pointer Types and Pointer Variables


We can manipulate addresses because C++ defines the pointer type and allows us to use
pointer variables.
Pointer Types
The pointer type is a compound type whose literal values are addresses. It is a compound
type in the sense that we have pointer to char (address of a char variable), pointer to int (ad-
dress of an int variable), pointer to double (address of a double variable), and so on. Note
that we only talk about the address of a variable, not a value, because a value is always stored
in a variable in memory. To create a pointer to a type, we add the asterisk symbol after the
type. The following shows some pointer types.

bool* // A pointer to an object of bool type


int* // A pointer to an object of int type
double* // A pointer to an object of double type
Circle* // A pointer to an object of Circle type

To create a pointer to a type, we add the asterisk symbol to the type.

Pointer Variables
We can store the value of a pointer type in a pointer variable. Although we can have differ-
ent pointer types, what is stored in a pointer variable is a 4-byte address. In other words, the
size of a pointer variable is fixed in C++, which means that a pointer variable can have an
address from 0x00000000 to 0xFFFFFFFF.

Declaration To use pointers, we need pointer variables. To declare a pointer variable, we


need to tell the computer that it is a pointer to a specific type. The following are declaration
examples. The selection of names is our choice because we later bind these pointer variables
to the corresponding data variables.

bool* pFlag; // pFlag is variable of pointer type to bool


int* pScore; // pScore is variable of pointer type to int
double* pAverage; // pAverage is variable of pointer type to double

We read the declaration from right to left (pFlag is a pointer to a bool).


Initialization A pointer variable, like a data variable, must be initialized before being
used. It must be initialized with a valid address in memory, which means that we cannot
initialize it with a literal address; the address must be that of an existing variable. To
meet this criterion, we use the address operator, the ampersand. We will discuss this
operator shortly, but the following shows how we can initialize our three declared pointer
variables.

bool* pFlag = &flag; // Initialize pFlag with address of flag


int* pScore = &score; // Initialize pScore with address of score
double* pAverage = &average; // Initialize pAverage with address of average

Pointer initialization has some restrictions. Since pFlag is a pointer to a variable of


type bool, it must be initialized with the address of a variable of type bool. Similarly, since
9.2  Pointers 395

address of score value

type: int* 0x28fef1 type: int 92 0x28fef1


pScore score
Actual situation

92
pScore score
Symbolic representation

Figure 9.9 The pointed and the pointer variables

pScore is a pointer to a variable of type int, it must be initialized with the address of a
type int. Figure 9.9 shows the actual and symbolic relationship between a pointer variable
(pScore) and the corresponding pointed variable (score).

EXAMPLE 9.12
The following is a compilation error. We cannot assign pointer literals to pointer variables.

double* pAverage = 0x123467; // Compilation Error. No literal addresses

EXAMPLE 9.13
The following is a compilation error. We cannot assign the address of an int variable to a
pointer of type double.

int num;
double* p = &num; // Compilation Error. Variable p is of type double*

Indirection (Dereference)
Declaration and initialization of pointer variables allows us to use the address stored in the
pointer variable to access the value stored in the pointed variable. This is done through the
indirection operator (also called the dereference operator) using the asterisk symbol as
shown below:

*pFlage; // value stored in flag


*pScore; // value stored in score
*pAverage; // value stored in average

You may ask why we need to use the indirection operator to retrieve or change the
value of the variable flag when we can do so directly. The answer, as we will see later in
the section, is that we use different approaches (direct and indirect accesses) in different
domains. For example, we use the direct approach in a calling function and the indirect ap-
proach in the corresponding called function.

Two Related Operators


The two operators we use in this section are the address-of operator and the indirection
operator. These two operators are unary operators defined in Appendix C and shown in
Table 9.2.
396 Chapter 9  References, Pointers, and Memory Management

Table 9.2 The address expression

Group Name Operator Expression Pred Assoc


Unary address-of & &lvalue 17 ←
indirection * * pointer

variable address

indirection
operator

operator
address
& *
address value

Figure 9.10 Address-of operator and indirection


operator

Figure 9.10 shows how these two operators are used. The address-of operator takes
a variable and returns its address; the indirection operator takes an address and returns the
value of the corresponding pointed variable.
C++ uses the ampersand and the asterisk symbols for three purposes as shown in
Table 9.3.
The ampersand symbol is used to define a reference type. It can also be used as a
unary operator to get the address of a variable. It is also used as a binary bitwise AND op-
erator (discussed in Appendix C). When it is used to define a reference type, it is put after
the type (int&). When it is used to get the address of a variable, it is put before the variable
name (&score). When it is used as a binary operator, it is used between the two operands.
The asterisk symbol can also be used in three situations: as a pointer type, as a unary
operator to get the value of a pointed variable, and as a binary multiplication operator. When
it is used to define a pointer type, it is put after the type (int*). When it is used to indirectly
access a value, it is put before the pointer (*pScore). When it is used as a binary operator, it
is used between the two operands.

9.2.3 Retrieving Value


After the pointer relationship is established, we can retrieve the value stored in the memory
location either through the data variable or pointer variable.

EXAMPLE 9.14
In this example we write a simple program that accesses the values of a data variable directly
and indirectly (Program 9.5).

Table 9.3 Uses of ampersand and asterisk operators

Symbol Type definition Unary operator Binary operator


& type& &variable x&y
* type* *pointer x*y
9.2  Pointers 397

Program 9.5 Direct and indirect retrieval of data

1 /***************************************************************
2 * The program shows how to access (retrieve or change) the value *
3 * of a data variable both directly and indirectly. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 int main ( )
9 {
10 // Declaration and initialization of variables
11 int score = 92;
12 int* pScore = &score;
13 // Retrieving value of data variable directly and indirectly
14 cout << "Direct retrieve of score: " << score << endl;
15 cout << "Indirect retrieve of score: " << *pScore;
16 return 0;
17 }
Run:
Direct retrieve of score: 92
Indirect retrieve of score 92

9.2.4 Using Constant Modifiers


One of the issues that is a source of confusion and error is how to control changes to a data
variable and a pointer. Changes are controlled through the use of const modifiers. We can
use up to three const modifiers, each with a different purpose, as shown in Figure 9.11. The
first two are related to changing data; the third is related to changing the relationship be-
tween the data variable and the pointer variable.

Controlling Data Change


Table 9.4 shows the four possible combinations that we can use to control data change.

Pointing relation
pScore 92 score

No data const int score = 92;


change
No indirect const int* pScore = & score;
change

No address int* const pScore = & score;


change

Figure 9.11 Three uses of constant modifiers in relation to pointers


398 Chapter 9  References, Pointers, and Memory Management

Table 9.4 Four possible combinations

Case Data variable Pointer variable


1 int name = value; int* pName = &name;
2 (forbidden) const int name = value; int* pName = &name;
3 int name = value; const int& pName = &name;
4 const int name = value; const int& pName= &name;

First Case In the first case, there is no restriction on changing the value either through the
data variable or through the pointer variable.

int score = 92;


int* pScore = &score;
score = 80; // Change through data variable
*pScore = 70; // Change through pointer variable

Second Case The second case is a forbidden case. We get a compilation error when we try
to bind the data variable to the pointer variable. Since the data variable is already constant,
there is no way that we can bind it to a nonconstant pointer type.

const int score = 92;


int* pScore = &score; // Compilation error

Third Case In the third case, the data can be changed through the data variable, but we
want to prevent it from being changed through the pointer variable. If we try to change it
through the pointer variable, we get a compilation error.

int score = 92;


const int* pScore = &score;
score = 80;
*pScore = 80; // Compilation Error

Fourth Case In the fourth case, we want to create both a data variable and a pointer vari-
able that cannot be changed. This case has little application because data variables and point-
er variables can only be used to retrieve data, not to change them.

const int score = 92;


const int* pScore = &score;
score = 80; // Compilation Error
*pScore = 80; // Compilation Error

Changing Pointers (Binding)


We can use the constant modifier to bind the data variable to the pointer variable for the life-
time of the program. In other words, each of the four previous cases can be combined with
either a nonconstant or a constant pointer (Table 9.5).
9.2  Pointers 399

Table 9.5 Eight possible combinations

Changing relationship between the data variable and the pointer variable
any-of-four-previous-cases pName = &name; // Changeable
any-of-four-previous-cases const pName = &name; // Unchangeable

6 var1 6 var1
int* p; p p
10 var2 10 var2
p = &var1; p = &var2;

6 var1 6 var1
int* const p; p p
10 var2 10 var2
p = &var1; p = &var2;

Figure 9.12 Constant pointing relationship cannot be broken.

Figure 9.12 shows that if the pointing relationship is constant, we cannot break the
relationship and make the pointer point to another data variable. Note that in this case, the
const modifier must be put in front of the name of the pointer variable.

9.2.5 Pointer to Pointer


A pointer variable is a location in memory. Can we store the address of this memory location
in another memory location? The answer is yes. We can have new types: pointer to pointer
to int, pointer to pointer to double, and so on. We can even go further and have a pointer to
pointer to pointer to int and so on. But more than two levels are rarely seen in practice. When
we use double asterisks after a type, we mean pointer to pointer to type, as shown below:

int score = 92; // Declaration and initialization of score


const int* pScore = &score; // Binding pScore to score
int** ppScore = &pscore; // Binding ppScore to pscore

Figure 9.13 shows the concept of pointer to pointer.

Declaration score 92 0x28fe2A


int score = 92
int* pScore= &score;
int** ppScore= &pScore;
pScore 0x28fe2A 0x28fe50

92 ppScore 0x28fe50 0x28fe63


ppScore pScore score
Symbolic representation Memory
layout

Figure 9.13 A pointer to pointer to int


400 Chapter 9  References, Pointers, and Memory Management

9.2.6 Two Special Pointers


Sometimes we see two special pointers: pointer to nowhere and pointer to void. We briefly
explain these two pointers; we will see more applications in future chapters.

Pointer to Nowhere: Null Pointer


A pointer to nowhere (sometimes called a null pointer) is a pointer that points to no memory
location. Although the C language used the word NULL to define such a pointer, the C++
language prefers to use the literal 0. In other words, if we want to show that a pointer is not
pointing to any memory location at this moment, we can bind it to 0 as shown below.

int* p1 = 0;
double* p2 = 0;

Note that neither of the previous two statements means that p1 or p2 is pointing to the
memory location at 0x0. The byte at this address is used for the system and is not available
to the user. The literal value 0 simply means that the pointer at this moment is pointing to
nowhere.
The programmer assigns 0 to a pointer to show that the pointer cannot be used until it
is bound to a valid address. Note that when a name is declared, it cannot become invalid until
it goes out of scope. Assigning 0 to a pointer variable means that declaration is still valid, but
we cannot use it. If a logic error does try use the pointer, the program aborts.
When a pointer is not null, its value can be interpreted as true; when it is null, its value
can be interpreted as false. This means we can always check to see if a pointer is null or not
as shown below:

int x = 7;
int* p = &x;
if (p) {…} // Test is true here (p is not null)
p = 0;
if (p) {…} // Test is false here (p null)

Pointer to Void: Generic Pointer


A void pointer is a generic pointer. It can point to an object of any type. The following
shows how we can use a generic pointer to point to any type. Assume that we first need to
point it to an int object and later to a double object. Note that a void pointer cannot be redi-
rected until it is cast to an appropriate type.

void* p; // Declaring a void pointer


int x = 10;
p = &x; // Make p to point to an int type
double y = 23.4;
p = &y; // Make p to point to a double type

9.2.7 Applications
Just like references, pointers can be used to create communication between functions. We
discuss the use of pointers in passing data to a function and returning data from a function.
The first application is called pass-by-pointer; the second is called return-by-pointer.
9.2  Pointers 401

Calling num num num


functions

copy
share copy address
all bytes

Called
functions
num rNum pNum

Pass-by-value Pass-by-reference Pass-by-pointer

Figure 9.14 Comparing three methods of passing data to a function

Pass-by-Pointer
Pointers can be used to send data from a calling function to a called function. We show pass-
by-value, pass-by-reference, and pass-by-pointer in Figure 9.14. In the third case, pass-
by-pointer, the calling function sends the address of the object (argument) to the called
function and the called function stores it in a pointer (parameter). Unlike the case of pass-by-
reference, there is no sharing here. The run-time system must copy the address of the argu-
ment in the calling function and send it to the called function. However, the cost of copying
is not as high in the case of pass-by-value. It is a fixed cost of copying a 4-byte address.
We write two small programs, side by side, to compare the two methods. Assume we
want to have a function that does something to an integer passed to it. The function can be
designed as pass-by-value or pass-by-pointer, as shown below:

#include <iostream> #include <iostream>


using namespace std; using namespace std;
// Prototype // Prototype
void doIt (int); void doIt (int*);

int main () int main ()


{ {
int num1 = 10; int num = 10;
doIt (num1); doIt (&num);
return 0; return 0;
} }
// Pass-by-value // Pass-by-pointer
void doIt (int num2) void doIt (int* pNum)
{ {
// Code // Code
} }

Characteristics of Pass-by-Pointer We discussed the characteristics of pass-by-value


and pass-by-reference previously. In the pass-by-pointer method, the parameter is the ad-
dress of the argument. The binding between the two occurs in the background as part of the
running environment, as shown below:

int* pNum = &num;


402 Chapter 9  References, Pointers, and Memory Management

∙∙ In pass-by-pointer, the argument and the parameter are bound together. Any change in
the parameter means the same change in the argument unless we use a constant pointer,
as we discussed previously.
∙∙ It is obvious that pass-by-pointer reduces the cost of copying. Only a 4-byte address is
copied. We must consider this method when we want to pass a large object, such as an
object of class type, to a called function.

Recommendation We augment the recommendation we made previously to three prac-


tices: pass-by-value, pass-by-reference, and pass-by-pointer.
1. If we need to prevent change, we must use
a. pass-by-value for small objects.
b. pass-by-constant reference or pass-by-constant pointer for large objects.

2. If there is a need for change, we must use pass-by-reference or pass-by-pointer.

EXAMPLE 9.15
Assume we want to write a function to print the value of a fundamental data type. We do
not want to change the value of the data in the calling function. This is the first case in our
recommendation. Since the object is small, we can use pass-by-value.

EXAMPLE 9.16
Assume we want to write a function to swap two data items. This is the second case in our
recommendation. We showed how to use pass-by-reference previously; in Program 9.6 we
show how to use pass-by-pointer.

Program 9.6 Using a swap function with pass-by-pointer

1 /***************************************************************
2 * The program shows how to use pass-by-pointer to allow a *
3 * called function to swap two values in the calling function. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 void swap (int* first, int* second) ; // Prototype
9
10 int main ( )
11 {
12 // Definition of two variables
13 int x = 10;
14 int y = 20;
15 // Printing the value of x and y before swapping
16 cout << "Values of x and y before swapping." << endl;
17 cout << "x: " << x << " " << "y: " << y << endl;
(continued)
9.2  Pointers 403

Program 9.6 Using a swap function with pass-by-pointer (Continued)

18 // Calling swap function to swap the values of x and y


19 swap (&x , &y);
20 // Printing the value of x and y after swapping
21 cout << "Values of x and y after swapping." << endl;
22 cout << "x: " << x << " " << "y: " << y;
23 return 0;
24 }
25 /***************************************************************
26 * The swap function swaps the values of parameters and *
27 * pass-by-pointer allows the corresponding arguments in main *
28 * to be swapped accordingly. *
29 ***************************************************************/
30 void swap (int* pX, int* pY)
31 {
32 int temp = *pX;
33 *pX= *pY;
34 *pY = temp;
35 }
Run:
Values of x and y before swapping.
x: 10 y: 20
Values of x and y after swapping.
x: 20 y: 10

Return-by-Pointer
In the second case, return-by-pointer, a called function has an object that must be returned
to the calling function. We can use return-by-pointer.

Characteristics of Return-by-Pointer
In this case, the type of the object to be returned is a pointer to a function parameter, which
itself is a pointer to an object in the calling function, as shown in the following prototype:

type* function (type* ...);

As we discussed in the case of return-by-reference, this practice has some drawbacks, most
notable the following: We cannot return a pointer to a value parameter or to a local variable.
When the called function is terminated, all local objects and value parameters are destroyed
and there is no longer an object to point to.

EXAMPLE 9.17
Assume we want to write a function to find the larger of two integers. We could use ei-
ther the combination of pass-by-value and return-by-value or the combination of pass-
by-pointer and return-by-pointer. The following shows the two programs side by side for
comparison.
404 Chapter 9  References, Pointers, and Memory Management

#include <iostream> #include <iostream>


using namespace std; using namespace std;

int larger (int x, int y ) int* larger (int* x, int* y )


{ {
if (x > y) if (*x > *y)
{ {
return x; return x;
} }
return y; return y;
} }
int main () int main ()
{ {
int x = 10; int x = 10;
int y = 20; int y = 20;
int z = larger (x, y); int z = *larger (&x, &y);
cout << z; cout << z;
return 0; return 0;
} }

Run: Run:
20 20

We will see examples of both practices in cases like this because the cost of copying is not
large in pass-by-pointer and return-by-pointer.

EXAMPLE 9.18
In this example we find the larger of two objects of class type. We used return-by-reference
previously; now we use return-by-pointer. We create an application file that uses pass-by-
pointer and return-by-pointer to find the smaller of two fractions. Program 9.7 shows the
application file.
Note that the member select expression, (object -> member), is in fact a shortcut for
the expression (*object.member). Both were discussed in Chapter 7. Also note that the ob-
ject returned by a pointer is an lvalue, which means we can apply the member function print
to the returned object as shown in lines 17 and 22.

Program 9.7 Finding the larger between two objects

1 /***************************************************************
2 * The program creates two pairs of fractions and then calls a *
3 * function named larger to find the larger in each pair. *
4 ***************************************************************/
5 #include "fraction.h"
6 #include <iostream>
7 using namespace std;
8
9 Fraction* larger (Fraction*, Fraction*); // Prototype
(continued)
9.3  Arrays and Pointers 405

Program 9.7 Finding the larger between two objects (Continued)

10
11 int main ( )
12 {
13 // Creating the first pair of fractions
14 Fraction fract1 (3, 13);
15 Fraction fract2 (5, 17);
16 cout << "Larger of the first pair of fraction: " ;
17 larger (&fract1, &fract2) -> print ();
18 // Creating the second pair of fractions
19 Fraction fract3 (4, 9);
20 Fraction fract4 (1, 6);
21 cout << "Larger of the second pair of fractions: " ;
22 larger (&fract3, &fract4) -> print ();
23 return 0;
24 }
25 /***************************************************************
26 * The function gets two fractions by pointer, compares them *
27 * and returns the larger of the two. *
28 ***************************************************************/
29 Fraction* larger (Fraction* fract1, Fraction* fract2)
30 {
31 if (fract1 -> getNumer() * fract2 -> getDenom() >
32 fract2 -> getNumer() * fract1 ->getDenom())
33 {
34 return fract1;
35 }
36 return fract2;
37 }
Run:
Larger of first pair of fractions: 5/17
Larger of second pair of fractions: 4/9

9.3 ARRAYS AND POINTERS


In C++, arrays and pointers are intertwined with each other. In this section we learn how
one-dimensional and two-dimensional arrays can be represented by pointers.

9.3.1 One-Dimensional Arrays and Pointers


When we declare an array named arr of type t and of capacity N, the system creates N mem-
ory locations of type t. The system then creates a constant pointer of type t that is pointed to
the first element, as shown in Figure 9.15.
That the pointer is constant means its contents (address) cannot be changed. It is al-
ways pointing to the first element. Since the address of the first element is fixed, we know
406 Chapter 9  References, Pointers, and Memory Management

int arr [5] = {10, 11, 12, 13, 14}; arr 10 [0]
Declaration 11 [1]
12 [2]
Note: 13 [3]
The pointer arr is a constant 14 [4]
pointer and cannot move.
Memory allocation

Figure 9.15 Relationship between an array and a pointer

the address of all the elements. The address of the element at index 0 is arr (or arr + 0); the
address of the element at index 1 is arr + 1; and so on.

The name of the array is a constant pointer to the first element.

To demonstrate, we write a program and print the addresses of the array elements
defined in Figure 9.15. We use the value of both the pointers and the address operator to
prove that they are the same (Program 9.8). The program demonstrates that we can access
the address of each element using either constant pointers or indexes. In fact, the indexes are

Program 9.8 A program to check the address of each array element

1 /***************************************************************
2 * The program proves that the system stores the address of each *
3 * element in the array in a constant pointer. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 int main ()
9 {
10 // Declaration of an array of five int
11 int arr [5];
12 // Printing the addresses through pointers and the & operator
13 for (int i = 0; i < 5; i++)
14 {
15 cout << "Address of cell " << i << " Using pointer: ";
16 cout << arr + i << endl;
17 cout << "Address of cell " << i << " Using & operator: ";
18 cout << &arr [i] << endl << endl;
19 }
20 return 0;
21 }
Run:
Address of cell 0 Using pointer: 0x28fee8
Address of cell 0 Using address operator: 0x28fee8
(continued)
9.3  Arrays and Pointers 407

Program 9.8 A program to check the address of each array element (Continued)

Address of cell 1 Using pointer: 0x28feec


Address of cell 1 Using address operator: 0x28feec

Address of cell 2 Using pointer: 0x28fef0


Address of cell 2 Using address operator: 0x28fef0

Address of cell 3 Using pointer: 0x28fef4


Address of cell 3 Using address operator: 0x28fef4

Address of cell 4 Using pointer: 0x28fef8


Address of cell 4 Using address operator: 0x28fef8

symbolic representations that make it easier to access elements. This means that arr[0] is the
same as *(arr + 0), but we must be careful to use parentheses because the asterisk operator
has priority over the addition operator, and using parentheses will allow us to change this.
It is also interesting that each address increases by 4 bytes, which demonstrates that the size
of an integer is 4.

When referring to array elements using pointers, we need


parentheses to give priority to the addition operator.

EXAMPLE 9.19
Program 9.9 shows that we can get the same result by referring to the elements of an array
using indexes and pointers.

Program 9.9 Using indexes and pointers with an array

1 /***************************************************************
2 * The program shows how to access the elements of an array *
3 * using either the indexes or pointers to elements. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 int main ( )
9 {
10 // Declaration and initialization of an array
11 int numbers [5] = {10, 11, 12,13, 14};
12 // Accessing elements through the indexes
13 cout << "Accessing elements through indexes" << endl;
14 for (int i = 0; i < 5; i++)
15 {
16 cout << numbers [i] << " ";
(continued)
408 Chapter 9  References, Pointers, and Memory Management

Program 9.9 Using indexes and pointers with an array (Continued)

17 }
18 cout << endl;
19 // Accessing elements through the pointers
20 cout << "Accessing elements through pointers" << endl;
21 for (int i = 0; i < 5; i++)
22 {
23 cout << *(numbers + i) << " ";
24 }
25 return 0;
26 }
Run:
Accessing elements through indexes
10 11 12 13 14
Accessing elements through pointers
10 11 12 13 14

Pointer Arithmetic
Pointer types are not integer types. However, pointer arithmetic allows a limited number
of arithmetic operators to be applied to pointer types. We must look at these operators with
a new definition in mind. When we use these operators, we must remember that because the
result is another pointer value, that value must point to a memory location in our control;
otherwise, the result is nonsense. This is why using these operators makes sense when we
apply them to pointers to the elements of the array: When we declare an array, the memory
locations involved are in our control.
Integer Addition or Subtraction We can use the addition operator to add an integer to, or
the subtraction operator to subtract an integer from, a pointer:

Addition: ptr1 = ptr + n Subtraction: ptr2 = ptr − n

Using the addition and subtraction operators this way creates a new definition for them. It
creates a new pointer n × m bytes forward or backward; m is the size of the pointed vari-
able, where m is the number of bytes occupied by each element. So the operation moves the
pointer n elements forward or backward as shown in Figure 9.16. Addition creates a pointer

0 [0] 0 [0] ptr2 0 [0]


10 [1] 10 [1] 10 [1]
ptr 20 [2] ptr 20 [2] ptr 20 [2]
30 [3] 30 [3] 30 [3]
40 [4] ptr1 40 [4] ptr1 40 [4]
50 [5] 50 [5] 50 [5]
arr arr arr

int* ptr = &arr [2]; int* ptr1 = ptr + 2; int* ptr2 = ptr − 2;
After creation of ptr After creation of ptr1 After creation of ptr2

Figure 9.16 Integer addition and subtraction of pointers


9.3  Arrays and Pointers 409

0 [0] 0 [0] 0 [0]


10 [1] 10 [1] 10 [1]
ptr 20 [2] 20 [2] ptr 20 [2]
30 [3] ptr 30 [3] 30 [3]
40 [4] 40 [4] 40 [4]
50 [5] 50 [5] 50 [5]
arr arr arr
int* ptr = &arr [2]; ptr++; ptr−−;

After creation of ptr After moving down After moving up

Figure 9.17 Increment and decrement operators apply to pointers

farther from the beginning of the array; subtraction creates a pointer closer to the beginning
of the array. The red arrow shows the original pointer.
Increment and Decrement These two operators are designed to be used with variables
(lvalues). They cannot be used with the name of the array because the name is a constant
value. The meaning of ptr++ is the same as ptr = ptr + 1, and the meaning of ptr−− is the
same as ptr = ptr − 1. In these cases the original pointer moves as shown in Figure 9.17.
After the increment operation, the original pointer is pointing to the arr[3]. After the decre-
ment operation, it is pointing back to arr[2].
Compound Addition and Subtraction We can also use compound addition and subtrac-
tion operators on pointer variables. If ptr is a pointer variable, the expression (ptr += 3) is
equivalent to (ptr = ptr + 3) and the expression (ptr −= 3) is equivalent to (ptr = ptr − 3).
These operations, like the increment and decrement operators, move the pointer toward the
beginning or toward the end, as shown in Figure 9.18.
Pointer Subtraction We can subtract one pointer from another. If ptr1 and ptr2 are point-
ers in which ptr1 points to an element of a lower index and ptr2 to an element of a higher
index, then the expression (ptr2 − ptr1) returns a positive integer and (ptr1 − ptr2) returns a
negative integer as shown in Figure 9.19. In other words, the results of these operations are
integers, not pointers.
No Pointer Addition The addition of pointer values is not allowed in C++ because it does
not make sense.

0 [0] 0 [0] 0 [0]


10 [1] 10 [1] ptr 10 [1]
ptr 20 [2] 20 [2] 20 [2]
30 [3] 30 [3] 30 [3]
40 [4] 40 [4] 40 [4]
ptr
50 [5] 50 [5] 50 [5]

arr arr arr

int* ptr = &arr[2]; ptr += 2; ptr −= 3;

After creation of ptr After moving ptr down After moving ptr up

Figure 9.18 Compound addition and subtraction


410 Chapter 9  References, Pointers, and Memory Management

0 [0]
10 [1] int n = ptr1 − ptr2 ;
ptr1 20 [2]
n is 2
30 [3]
ptr2 40 [4] int m = ptr2 − ptr1 ;
50 [5]
arr m is −2

Figure 9.19 Pointer subtraction

We cannot add two pointers.

Comparing Pointers We can compare two pointers as shown in Figure 9.20. If ptr1 and
ptr2 are pointers to elements of an array, the expression (ptr1 == ptr2) is true when ptr1
and ptr2 point to the same element. The expression (ptr1 != ptr2) is true when ptr1 and ptr2
point to different elements. The expression (ptr1 < ptr2) is true if ptr1 points to an element
with a lower index than pointer ptr2. The expression (ptr1 > ptr2) is true if ptr1 points to
a higher index than ptr2. The expression (ptr1 <= ptr2) is true if the two pointers point to
the same element or ptr1 points to an element closer to the beginning of the array than ptr2.
The expression (ptr1 >= ptr2) is true if the two pointers point to the same element or ptr1
points to an element closer to the end of the array than ptr2.
No Other Operations We cannot use any other arithmetic operations on pointers. For
example, multiplication, division, and modulo operators are not defined for pointer types.
A Warning We must use pointer arithmetic with caution. If the operation causes access
outside the array territory, we may destroy memory locations that are not part of the array.

Functions with Array Parameters


A pointer in a function can be used to represent an array. We discuss two cases.
Passing a Pointer to a Function for an Array We can pass a pointer to a function instead
of passing the array. In other words, the following two prototypes are the same.

int getSum (const array [ ], int size); // Using array


int getSum (const int* p, int size); // Using pointer

bool fact = (ptr1 == ptr2) ; bool fact = (ptr1 != ptr2) ;


0 [0]
10 [1] fact is false fact is true
ptr1 20 [2]
30 [3] bool fact = (ptr1 > ptr2) ; bool fact = (ptr1 >= ptr2) ;
ptr2 40 [4]
fact is false fact is false
50 [5]
numbers bool fact = (ptr1 < ptr2) ; bool fact = (ptr1 <= ptr2) ;
fact is true fact is true
Figure 9.20 Comparing pointers
9.3  Arrays and Pointers 411

We have used the constant modifier to prevent the function getSum from changing the value
of the elements.

EXAMPLE 9.20
Program 9.10 shows how we can use the second version in the code above to find the sum
of the elements in an array.
In Program 9.10, the first parameter of the getSum function is a constant to integer (so
that the function cannot change the values of the elements). However, this does not mean
that it is a constant pointer. We can move it. If we had made the parameter a constant pointer
with the prototype (const int* const, int), then we could not use p++ in line 27. We would
have to use *(p + 1) instead.

Program 9.10 Finding the sum of elements in an array

1 /***************************************************************
2 * The program shows how to access the elements of an array *
3 * using pointers. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 int getSum (const int*, int); // Prototype
9
10 int main ()
11 {
12 // Array declaration and initialization
13 int arr [5] = {10, 11, 12, 13, 14};
14 // Function call
15 cout << "Sum of elements: " << getSum (arr, 5);
16 return 0;
17 }
18 /***************************************************************
19 * The function gets a pointer to the first element of an *
20 * array and calculates and returns the sum of the elements. *
21 ***************************************************************/
22 int getSum (const int* p, int size)
23 {
24 int sum = 0;
25 for (int i = 0; i < size ; i++)
26 {
27 sum += *(p++);
28 }
29 return sum;
30 }
Run:
Sum of elements: 60
412 Chapter 9  References, Pointers, and Memory Management

EXAMPLE 9.21
Assume we want to write a function that reverses the elements of the array using pointers.
Program 9.11shows an example.

Program 9.11 Reversing the elements of an array

1 /***************************************************************
2 * The program shows how a function can reverse the elements *
3 * of an array using a pointer. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 void reverse (int* , int );
9
10 int main ()
11 {
12 // Array declaration and initialization
13 int arr [5] = {10, 11, 12, 13, 14};
14 // Calling function
15 reverse (arr, 5);
16 // Printing array after reversed
17 cout << "Reversed array: ";
18 for (int i = 0; i < 5; i++)
19 {
20 cout << *(arr + i) << " ";
21 }
22 return 0;
23 }
24 /***************************************************************
25 * The function uses a pointer to the first element of the array *
26 * and the size of the array to reverse the elements in place. *
27 ***************************************************************/
28 void reverse (int* pArr, int size)
29 {
30 int i = 0;
31 int j = size − 1;
32 while (i < size / 2)
33 {
34 int temp = *(pArr + i);
35 *(pArr + i) = *(pArr + j);
36 *(pArr + j) = temp;
37 i++;
38 j− −;
39 }
40 }
Run:
Reversed array: 14 13 12 11 10
9.3  Arrays and Pointers 413

int matrix [3] [4] = {{10, 11, 12, 13}, {20, 21, 22, 23}, {30, 31, 32, 33}};

Declaration

[0] [1] [2] [3] int*


10 11 12 13
[0] 10 11 12 13
[1] 20 21 22 23 int (*matrix) [4] 20 21 22 23

[2] 30 31 32 33
30 31 32 33
Seen by user
Seen by C++

Figure 9.21 A two-dimensional array seen by user and C++

Although arr is a constant pointer and cannot move, pArr does not have to be a con-
stant pointer; it is a separate pointer variable. Also note that pArr cannot be a pointer to a
constant integer because it is supposed to change the pointed element in swapping. We swap
only half of the elements. When half of the elements are swapped, we are done.
Returning an Array from a Function Although we may think about returning an array
from a function, we must remember that an array is a combination of two pieces of informa-
tion: the pointer to the first element and the size of the array. A function can return only one
piece of information (unless we bundle the pointer and the size in an object). As we men-
tioned in Chapter 8, we cannot return an array from a function.

9.3.2 Two-Dimensional Arrays and Pointers


A two-dimensional array in C++ is an array of arrays. If we keep this fact in mind, then it is
easy to use pointers with two-dimensional arrays. Figure 9.21 shows what we see as a two-
dimensional array and what C++ sees.
Contrary to some beliefs, the name of a two-dimensional array is not a pointer to
pointer to integer; it is a pointer to an array of four integers or int (matrix*) [4]. The paren-
theses around the array name mean that the array is read as “matrix is a pointer to an array
of four integers.” We must remember this fact when we pass a two-dimensional array to a
function. As usual, the column size must be given with the pointer; the row size should be
given separately as an integer.

EXAMPLE 9.22
In this example we write a short program and pass the array defined in Figure 9.21 to a function
that will print the elements as shown in Program 9.12.

Program 9.12 Passing a two-dimensional array to a function

1 /***************************************************************
2 * The program to show how to pass a two-dimensional array to a *
3 * function using pointer notations. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
7
8 void print (int (*) [4], int); // Prototype
9
(continued)
414 Chapter 9  References, Pointers, and Memory Management

Program 9.12 Passing a two-dimensional array to a function (Continued)

10 int main ()
11 {
12 int matrix [3][4] = {{10, 11, 12, 13}, {20, 21, 22, 23},
13 {31, 32, 33, 34}};
14 // Calling print function
15 print (matrix, 3);
16 return 0;
17 }
18 /***************************************************************
19 * The function accepts a pointer to any array of four integers *
20 * with the number of rows. *
21 ***************************************************************/
22 void print (int (*m) [4], int rows)
23 {
24 for (int i = 0; i < rows; i++)
25 {
26 for (int j = 0 ; j < 4; j++)
27 {
28 cout << m[i][j] << " ";
29 }
30 cout << endl;
31 }
32 }
Run:
10 11 12 13
20 21 22 23
31 32 33 34

9.4 MEMORY MANAGEMENT


When a program in C++ is running, it uses memory locations. The code must be stored in
memory and every object, fundamental or user-defined, must also be stored in memory. How-
ever, the C++ environment divides memory into different areas, as shown in Figure 9.22, to
make memory management more efficient. Note that the figure does not show the order of
different memory areas in the computer.

Code memory Stores the program code

Static memory Stores global or static objects

Stack memory Stores local variables or parameters

Heap memory Stores objects created during run time

Figure 9.22 Memory sections used by a program


9.4  Memory Management 415

In this section we discuss these areas of memory and how we can use them. Under-
standing the characteristics and knowing how to use each area enables us to write better
programs.

9.4.1 Code Memory


Code memory holds the program code. When the program is running, the running environ-
ment of C++ executes each statement, one after another, or branches to another statement.
No objects are stored in this area of memory. Code memory is released when the program
is terminated.

Code memory stores the program code;


it is released when the program terminates.

9.4.2 Static Memory


Static memory is used to hold global objects (those that are not part of any function, includ-
ing main) and static objects created anywhere in the program (in the global area or inside
functions). These objects are automatically destroyed and their memory is released when the
program is terminated.

Static memory stores the global and static objects;


it is released when the program terminates.

EXAMPLE 9.23
Program 9.13 shows a very simple program. The memory locations for three data variables,
first, second, and third, are created in static memory. The first is a global variable; the sec-
ond and the third are static variables.

Program 9.13 Using static memory

1 /***************************************************************
2 * The program shows how global and static objects are visible *
3 * at any point in the program. They last through the life of the *
4 * the program. *
5 ***************************************************************/
6 #include <iostream>
7 using namespace std;
8
9 int first = 20; // Global variable in static memory
10 static int second = 30 ; // Static variable created in static memory
11
12 int main ()
13 {
14 static int third = 50; // Static variable in static memory
15
16 cout << "Value of Global variable: " << first << endl;

(continued)
416 Chapter 9  References, Pointers, and Memory Management

Program 9.13 Using static memory (Continued)

17 cout << "Value of Global static variable: " << second << endl;
18 cout << "Value of local static variable: " << third;
19 return 0;
20 }
Run:
Value of Global variable: 20
Value of Global static variable: 30
Value of local static variable: 50

9.4.3 Stack Memory


The part of memory used by a program to hold local or parameter objects of functions is
stack memory. As we know from daily life, a stack is a last-in-first-out container. What-
ever is pushed last is popped first. This characteristic of a stack is well suited for storing
parameters and local variables in functions. When we call a function, the system pushes
the parameters and local variables into stack memory. When the function is terminated, the
system pops these variables and throws them away. Figure 9.23 shows the functions calls
and the behavior of stack memory for a simple program that calls the function first, which
in turn calls the function second.
Figure 9.23 shows pushing and popping at the right-hand side. The stack memory is
empty at the beginning.When the running environment calls the main function, it pushes its
only local variable (x) into the stack (main has no parameter). When main calls the function
first, the system pushes its only parameter (a) into the stack. When the function first calls
second, the system pushes its only parameter (b) into the stack.

Pushes Pops
Running
environment
Empty stack Empty stack

int main ()
{
Push
x int x = ... ; x
Stack after first (x); Stack before
Pop
x is pushed x is popped
return 0;
}

Push
a void first (int a) a
x { x
second (a);
Stack after Pop Stack before
a is pushed } return ; a is popped

Push
b void second (int b) b
a { a
... ; Pop
x return ; x
}
Stack after Stack before
b is pushed b is popped

Figure 9.23 Using stack memory


9.4  Memory Management 417

When the function second returns, the system pops its only parameter (b) and
throws it away (not needed anymore). When the function first returns, the system pops
its only parameter (a) and throws it away (not needed anymore). When the function
main returns, the system pops its only local variable (x) and throws it away (not needed
anymore). The stack memory is empty again at the end when the program is terminated.
For the sake of simplicity, we have not used local variables in function first or second;
otherwise, those local variables would also be pushed and popped from the stack.

Advantage
The system uses stack memory in every program. The last-in first-out operation of stack
memory makes it very efficient. Objects are stored and remain in stack memory only when
they are in a function’s scope. When an object goes out of the function’s scope, it is popped
and thrown away; it is not accessible anymore.

Limitations
The efficiency of using stack memory creates two restrictions on its use:
∙∙ The objects must have a name at compilation time. No unnamed object can be stored
in a stack.
∙∙ The size of the object must be defined at compilation time. The system cannot allocate
stack memory for an object unless it knows the exact size of the object. For a single
object, the size can be inferred from the type; for a list of objects (such as an array), the
number of elements must be defined at compilation time.
Based on the preceding discussion, we can call stack memory the compile-time memory.
Every object stored in stack memory must be clearly defined during compilation time.

An object created in stack memory must be given


a name and have a size during compilation.

EXAMPLE 9.24
Assume that we need a variable-size array, which means that every time we run the program,
the array size must be defined by the user as shown below:

#include <iostream>
using namespace std;

int main ()
{
int size;
cin >> size;
double array [size]; // Compilation error
...
return 0;
}

We get the compilation error shown above because the compiler must know the size
of the array before it can allocate memory in the stack. We solve this problem in the next
section.
418 Chapter 9  References, Pointers, and Memory Management

ptr
pointer (named) pointed object (no name)
Stack memory Heap memory
Note:
The pointer is in stack memory; it must have a name (ptr).
The pointed object is in heap memory; it cannot have a name.

Figure 9.24 A pointer in the stack and a pointed object in the heap

9.4.4 Heap Memory


Sometimes we need to create objects in memory during run time. This happens when we do
not know the size of the object when we create and compile the program. Heap memory
(also called free memory or dynamic memory) is used to store objects created during run
time. This situation occurs when an object, or a collection of objects, needs a lot of memory
or when the amount of memory cannot be calculated during the compilation. The objects
created in heap memory cannot have a name, so to access them, we need a pointer in stack
memory that can be pointed to them. In other words, we need both stack and heap memory
for this purpose. Stack memory is used to hold the pointer (a small object of 4 bytes); heap
memory is used to store the pointed object (usually a large object) as shown in Figure 9.24.
The pointed object in heap memory cannot have a name; it is referred to by the pointer
that is pointing to it. On the other hand, the pointer object is in the stack; it must have a name.
In other words, the name of the pointer helps us refer to it; the address of the pointed object
enables us to access it.

An object created in heap memory cannot have a name;


it can be accessed only through its address, which is reached
by a pointer in stack memory.

Two Operators: new and delete


Now the questions are how we can create an object in the heap during run time, and how
we can destroy it when it is not needed, if the object has no name. This is done through four
operators. These operators are defined in Appendix C and are shown in Table 9.6 for quick
reference.
The first operator in Table 9.6 is used to create memory in the heap for a single object.
The second operator is used to create an array of objects in the heap. The third operator
is used to delete the single object using its pointer. The fourth operator is used to delete

Table 9.6 Operators for memory allocation and release in the heap

Group Name Operator Expression Pred Assoc


Unary allocate object new new type 17 ←
allocate array new [ ] new type [size]
delete object delete delete ptr
delete array delete [ ] delete [ ] ptr
9.4  Memory Management 419

type type [size]

new new
pointer value pointer value
allocate single location allocate array of locations

pointer [ ] pointer

delete delete
void pointer void pointer
delete single location delete array of locations

Figure 9.25 The new and delete operators

allocated memory for an array in the heap. Figure 9.25 shows the four operators that create
the new and delete expressions.
Note that after delete, the pointer is a dangling pointer, which we discuss later in this
section. It cannot be used until the new operator is applied to it again.

EXAMPLE 9.25
In this example, we create an object in the heap. Assume we want to write a program that
creates a variable-size array each time the user runs the program. This array cannot be cre-
ated in stack memory because the size of the array is not defined during compilation time; it
is only defined during run time. Figure 9.26 shows this situation.
Program 9.14 show how we can achieve this situation. Before terminating the pro-
gram, we must delete the array created in the heap (line 34).

Stack memory
pArray
Heap memory

Figure 9.26 A pointer in the stack and an array in the heap

Program 9.14 Using heap memory to store a variable-size array

1 /***************************************************************
2 * The program shows how to create and access a variable-size *
3 * array in the heap and use pointers to access elements. *
4 ***************************************************************/
5 #include <iostream>
6 using namespace std;
(continued)
420 Chapter 9  References, Pointers, and Memory Management

Program 9.14 Using heap memory to store a variable-size array (Continued)

7
8 int main ( )
9 {
10 // Declaration of array size and the pointer in the stack
11 int size;
12 int* pArray;
13 // Validation of the size to be greater than zero
14 do
15 {
16 cout << "Enter the array size (larger than zero): ";
17 cin >> size;
18 } while (size <= 0);
19 // Creation of array in the heap
20 pArray = new int [size];
21 // Inputting the values of array
22 for (int i = 0; i < size ; i++)
23 {
24 cout << "Enter the value for element " << i << ": ";
25 cin >> *(pArray + i);
26 }
27 // Outputting the values of the array
28 cout << "The elements in the array are: " << endl;
29 for (int i = 0; i < size ; i++)
30 {
31 cout << *(pArray + i) << " ";
32 }
33 // Deleting the array in the heap
34 delete [ ] pArray;
35 return 0;
36 }
Run:
Enter the array size (larger than zero): 3
Enter the value for element 0: 6
Enter the value for element 1: 12
Enter the value for element 2: 5
The elements in the array are:
6 12 5

Issues Related to Heap Memory


When we work with heap memory and use the related operators (new and delete), we must
be aware of some problems that may occur.

Deleting without Allocating One of the errors that may occur in programming is that
we try to use the delete operator without using the new operator first. This means we try to
9.4  Memory Management 421

delete an object in the heap without allocating it. This normally occurs when the object is
allocated in the stack but we try to delete it from the heap, as shown below:

double x = 23.4;
double* pX = &x; // Allocation in the stack
delete pX; // Deletion in the heap may create run-time error

Allocating without Deleting (Memory Leak) A more serious problem, known as memory
leak, occurs when we allocate an object in the heap but we do not delete it, as shown below:

double* pX = new double;


… // using the allocated memory

This means that we have created a memory location in the heap and we have not deleted it.
Most of the operating systems delete the memory location when the pointer that is pointing
to it goes out of the scope. There is, however, a serious problem if the pointer is re-pointed
to another memory location (in the stack or the heap). In this case, there is no pointer pointed
to the object that goes out of scope and alerts the operating system to delete the allocated
memory. This serious problem is referred to as memory leak and should be avoided. A
memory leak makes the undeleted memory location unusable and may result in the collapse
of the computer system if it runs out of memory.
Dangling Pointer Another problem is a dangling pointer that, which may occur that may
occur when we delete the pointed object and then try to use it again, as shown below:

double* pX = new double;


… // using the allocated memory
delete pX;
*pX = 35.3; // Dangling pointer

The last line creates an unexpected error because the pointer is null and cannot be indirected.

When we work with heap memory, we must pair each new


operator with the delete operator and avoid dangling pointers.

Recommendation
Always explicitly delete any memory locations created in the heap.

9.4.5 Two-Dimensional Arrays


As we discussed previously, a two-dimensional array is made of rows and columns. We
have three choices when creating a two-dimensional array. We discuss these choices in this
section.

Using Only Stack Memory


If both the row size and the column size are known before compilation, we can create the
array totally in the stack as we have done previously.
422 Chapter 9  References, Pointers, and Memory Management

arr

stack heap

Figure 9.27 A two-dimensional


array in the stack and in the heap

Using Both Stack and Heap Memory


If the row dimension is know before the compilation, we can create an array of pointers in
the stack and then create each row in the heap as shown in Figure 9.27.
The following shows the code to create such an array. The number of rows is fixed
before compilation (4); the number of columns must be entered during run time.

int* arr [4]; // This is an array of four pointers to integers in the stack
cin >> colNums;
for (int i = 0; i < 4 ; i++)
{
arr[i] = new int [colNums]; // an array of colNums in the heap
}

Using Only Heap Memory


If neither of the dimensions is known before the compilation, we must create the whole two-
dimensional array in the heap as shown in Figure 9.28.
The following code creates such an array. The value of both dimensions is not known
during compilation.

cin >> rowNums;


cin >> colNums;
int** arr = new int* [rowNums];
for (int i = 0; i < n + 1 ; i++)
{
arr[i] = new int [colNums];
}

arr

stack

heap

Figure 9.28 A two-dimensional array totally in the


heap
9.4  Memory Management 423

arr

stack
heap

Figure 9.29 Ragged array

An Example: Ragged Array


We can create a two-dimensional array in which the number of elements in one row is dif-
ferent from the number of elements in another row, as shown in Figure 9.29.
In other words, can we have an array in which the first row has, for example, three ele-
ments, and the second row has four elements. This is referred to as a ragged array. This is
possible, but we cannot allocate the elements in stack memory; they must be created in the
heap. In other words, we must think about an array of pointers in which each pointer points
to an array of the desired size, as shown in Figure 9.29. This design is more memory efficient
than using a two-dimensional array, which leaves some of the elements empty.

EXAMPLE 9.26
Pascal’s triangle determines the coefficients of a binomial expansion. When a binomial like
(x + y) is raised to a positive integer power, we have:

(x + y)n = a0 xn + a1 xn−1y + a2 xn−2y2 + … + an−1 x yn−1 + anyn

The coefficient of each term (a0, a1, a2, …, an−1, an) can be calculated using a triangle
of n + 1 rows and n + 1 columns. Each cell in the array holds the coefficient for a term.
The number of rows is one more than the power value (n). Each element in the triangle
is calculated by adding the element in the previous row and the previous column with the
element in the previous row and the same column, as shown below (when n = 4). It is ob-
vious that to calculate the coefficients for any value of n (beyond 0 and 1), we must know
the value of the coefficients for the previous n. This justifies the use of a two-dimensional
array. We can use an array of pointers to dynamically create the array and calculate the
coefficients.

n=0 1
n=1 1 1
n=2 1 2 1
n=3 1 3 3 1
n=4 1 4 6 4 1

EXAMPLE 9.27
Program 9.15 finds the coefficients for any n between 0 and 9. The number of allocated
locations for each row is one greater than the row number. For example, when i = 0, we
must allocate one location from the heap. When i = 9, we must allocate 10 locations from
the heap.
424 Chapter 9  References, Pointers, and Memory Management

Program 9.15 Finding the Pascal coefficients

1 /***************************************************************
2 * The program shows how to create Pascal coefficients using *
3 * a ragged array dynamically allocated on the heap. *
4 ***************************************************************/
5 #include <iostream>
6 #include <iomanip>
7 using namespace std;
8
9 int main ( )
10 {
11 // Declaration
12 int maxPower = 10;
13 int n;
14 // Input validation
15 do
16 {
17 cout << "Enter the power of binomial : ";
18 cin >> n;
19 } while (n < 0 || n > maxPower);
20 // Allocate memory from heap
21 int** pascal = new int* [n + 1];
22 for (int i = 0; i < n + 1 ; i++)
23 {
24 pascal[i] = new int [i];
25 }
26 // Formation of the coefficient
27 for (int i = 0; i <= n ; i++)
28 {
29 for (int j = 0; j < i + 1; j++)
30 {
31 if (j == 0 || i == j)
32 {
33 pascal [i][j] = 1;
34 }
35 else
36 {
37 pascal [i][j] = pascal [i − 1] [j − 1] + pascal [i − 1][j];
38 }
39 }
40 }
41 // Print coefficients
42 cout << endl;
43 cout << "Coefficients for (x + y)^" << n << " are:";
44 for (int j = 0; j <= n ; j++)
(continued)
9.5  Program Design 425

Program 9.15 Finding the Pascal coefficients (Continued)


45 {
46 cout << setw (5) << pascal [n][j] ;
47 }
48 cout << endl;
49 // Delete allocated memory
50 for (int i = 0; i < n + 1 ; i++)
51 {
52 delete [ ] pascal [i];
53 }
54 delete [ ] pascal;
55 return 0;
56 }
Run:
Enter the power of binomial : 5
Coefficients for (x + y)^5 are: 1 5 10 10 5 1

In line 22 of this program, we created a variable named pascal of type int** in stack
memory. In the same line, we created an array of pointers in the heap and stored the returned
pointer in the variable pascal. The variable pascal now points to an array of pointers. We
then used a loop to create (n + 1) arrays, each of a different size in the heap. Each array is
pointed to by a pointer in the array pascal [i]. In lines 51–55, we first deleted the (n + 1)
arrays of integer in the heap. We then deleted the array pointed by the Pascal variable. Loca-
tions in the heap must be deleted in the reverse order of allocations.

9.5 PROGRAM DESIGN


In this section, we create two classes in which some of the data members are created in the heap.

9.5.1 Course Class


We create a Course class whose objects can be used by a professor in a school to create sta-
tistics about each of her courses. Since each course may have a different number of students,
we must have an array of variable size in each course to keep track of the scores. To save
each professor from having to write a program, the Course class has been created and saved
by the programmers in administration. Each professor is given the public interface with
which to create the application and run it. In this section, we show what has been done by
the programmer in administration and what should be done by the professors.
Data Members
Figure 9.30 shows the arrangement of data member in the Course class.
We must keep four pieces of information for each student: identity, score, grade, and
deviation from average. Instead of creating four parallel arrays in the heap, we have packed
these four pieces of information in a struct and created an array of structs in the heap.
Member Functions
Several private member functions are called by the constructor automatically when an ob-
ject of the class is instantiated. The two public member functions are the constructor and
destructor.
426 Chapter 9  References, Pointers, and Memory Management

numOfStds

inputFile Name

inputFile Note:
Student is a struct made of four members.
Student

students

averageScore
heap
standardDeviation

stack

Figure 9.30 Data members of the Course class

Input File
We assume that the input file contains the following data, in which the first column repre-
sents the student identity and the second column defines the score in the course.

1000 88
1001 100
1002 92
1003 77
1004 54
1005 82
1006 67
1007 95
1008 93
1009 100

Interface, Implementation, and Application Files


Programs 9.16, 9.17, and 9.18 show the files.

Program 9.16 Interface file for the Course class

1 /***************************************************************
2 * We have private data members and public member functions. *
3 * The private member functions are helper functions called *
4 * by the constructor to do its job. The constructor is *
5 * responsible for everything. The destructor deletes arrays *
6 * created in the heap and closes the input file. *
7 ***************************************************************/
8 #ifndef COURSE_H
9 #define COURSE_H
10 #include <iostream>
(continued)
9.5  Program Design 427

Program 9.16 Interface file for the Course class (Continued)


11 #include <fstream>
12 using namespace std;
13
14 class Course
15 {
16 private:
17 int numOfStds;
18 const char* inputFileName;
19 ifstream inputFile;
20 struct Student {int id; int score; char grade;
21 double deviation;};
22 Student* students;
23 double averageScore;
24 double standardDeviation;
25 // Private member functions
26 void getInput ();
27 void setGrades ();
28 void setAverage ();
29 void setDeviations();
30 void printResult() const;
31 public:
32 Course (int numOfStds, const char* inputFileName);
33 ~Course ();
34 };
35 #endif

Program 9.17 Implementation file

1 /***************************************************************
2 * The implementation file gives the definitions of all private *
3 * and public member functions. *
4 ***************************************************************/
5 #include "course.h"
6 #include <iomanip>
7 #include <cmath>
8
9 /***************************************************************
10 * The constructor is responsible for initializing student's *
11 * number and the name of the input file containing scores. *
12 * The constructor then opens the input file and creates *
13 * an array in the heap. The rest of the job is done by helper *
14 * functions that set the scores, the grades, the average and *
15 * deviations, and prints the results. *
16 ***************************************************************/
(continued)
428 Chapter 9  References, Pointers, and Memory Management

Program 9.17 Implementation file (Continued)


17 Course :: Course (int num, const char* ifn)
18 :numOfStds (num), inputFileName (ifn)
19 {
20 inputFile.open (inputFileName);
21 students = new Student [numOfStds];
22 getInput ();
23 setGrades ();
24 setAverage ();
25 setDeviations();
26 printResult();
27 }
28 /***************************************************************
29 * The destructor is responsible for deleting the array created *
30 * in the heap using the corresponding pointer. It also closes *
31 * the input file opened by the constructor. *
32 ***************************************************************/
33 Course :: ~Course ()
34 {
35 delete [ ] students;
36 inputFile.close ();
37 }
38 /***************************************************************
39 * The getInput function is responsible for reading the input *
40 * file containg the identity and score of students. *
41 ***************************************************************/
42 void Course :: getInput()
43 {
44 for (int i = 0; i < numOfStds; i++)
45 {
46 inputFile >> students [i].id;
47 inputFile >> students [i].score;
48 }
49 }
50 /***************************************************************
51 * The getGrades function uses the score for each student and *
52 * changes it to the grade using an array of chars. *
53 ***************************************************************/
54 void Course :: setGrades()
55 {
56 char charGrades [ ] =
57 {'F', 'F', 'F' , 'F' , 'F' , 'F', 'D' , 'C' , 'B' , 'A' , 'A'};
58 for (int i = 0; i < numOfStds; i++)
59 {
60 int index = students[i].score / 10;
(continued)
9.5  Program Design 429

Program 9.17 Implementation file (Continued)


61 students[i].grade = charGrades [index];
62 }
63 }
64 /***************************************************************
65 * The setAverage function processes scores in the array and *
66 * creates the average for the class. *
67 ***************************************************************/
68 void Course :: setAverage()
69 {
70 int sum = 0;
71 for (int i = 0; i < numOfStds; i++)
72 {
73 sum += students[i].score;
74 }
75 averageScore = static_cast <double> (sum) / numOfStds;
76 }
77 /***************************************************************
78 * The setDeviation function reprocesses the scores to determine *
79 * the deviation of each student's score from the average. *
80 ***************************************************************/
81 void Course :: setDeviations()
82 {
83 standardDeviation = 0.0;
84 for (int i = 0; i < numOfStds; i++)
85 {
86 students[i].deviation = students[i].score - averageScore;
87 standardDeviation += pow(students[i].deviation , 2);
88 }
89 standardDeviation = sqrt (standardDeviation) / numOfStds;
90 }
91 /***************************************************************
92 * The printResult function prints all information about the *
93 * the course. *
94 ***************************************************************/
95 void Course :: printResult() const
96 {
97 cout << endl;
98 cout << "Identity Score Grade Deviation" << endl;
99 cout << "-------- ----- ----- ---------" << endl;
100 for (int i = 0; i < numOfStds ; i++)
101 {
102 cout << setw (4) << noshowpoint << noshowpos;
103 cout << right << students[i].id;
104 cout << setw (14) << noshowpoint << noshowpos;
(continued)
430 Chapter 9  References, Pointers, and Memory Management

Program 9.17 Implementation file (Continued)


105 cout << right << students[i].score;
106 cout << setw (10) << right << students[i].grade;
107 cout << fixed << setw (20) << right << setprecision (2);
108 cout << showpoint << showpos;
109 cout << students[i].deviation << endl;
110 }
111 cout << "Average score: " << fixed << setw (4);
112 cout << setprecision (2) <<averageScore << endl;
113 cout << "Standard Deviation: " << standardDeviation;
114 }

Program 9.18 Application file

1 /***************************************************************
2 * The application program is very simple. The user instantiates *
3 * an object of the class and passes the number of students and *
4 * name of the input file where the scores are stored. *
5 * Everything is done by the constructor of the class. *
6 ***************************************************************/
7 #include "course.h"
8
9 int main ( )
10 {
11 // Instantiation of the Course object
12 Course course (10, "scores.dat");
13 return 0;
14 }
Run:
Identity Score Grade Deviation
-------- ----- ----- ---------
1000 88 B +3.20
1001 100 A +15.20
1002 92 A +7.20
1003 77 C -7.80
1004 54 F -30.80
1005 82 B -2.80
1006 67 D -17.80
1007 95 A +10.20
1008 93 A +8.20
1009 100 A +15.20
Average score: +84.80
Standard Deviation: +4.51
9.5  Program Design 431

A B C a1 b1 c1 a2 b2 c2
= +
D E F d1 e1 f1 d2 e2 f2
result matrix1 matrix2

Figure 9.31 Adding two matrices

9.5.2 A Matrix Class


A matrix is a table of values in mathematics. In computer programming, we can simulate a
matrix with a two-dimensional array. In this section, we define a Matrix class to show how
to apply selected operations on matrices.

Operation
We define three operations on matrices: addition, subtraction, and multiplication. Division
of matrices is very complex and requires the inversion of matrices.
Addition We can add two matrices if the number of rows in the first is the same as the num-
ber of rows in the second and the number of columns in the first is the same as the number of
columns in the second. In other words, r1 == r2 and c2 == c2. The resulting matrix consists
of r rows and c columns (Figure 9.31). Note that letters are symbolic values.
Each element in the resulting matrix is the sum of the corresponding elements in the
two matrices, as shown below:

A = a1 + a2
...
F = f1 + f2

Subtraction Subtraction is the same as addition but the values of the corresponding cells
are subtracted. (Figure 9.32).
Each element in the resulting matrix is the difference of the corresponding element in
the two matrices, as shown below:

A = a1 - a2
...
F = f1 - f2

Multiplication We can multiply two matrices if the number of columns in the first matrix
is the same as the number of rows in the second matrix (c1 == r2). The result is a matrix
with the number of rows equal to r1 and the number of columns equal to c2 as shown in
Figure 9.33.

A B C a1 b1 c1 a2 b2 c2
= -
D E F d1 e1 f1 d2 e2 f2
result matrix1 matrix2

Figure 9.32 Subtracting two matrices


432 Chapter 9  References, Pointers, and Memory Management

g h i j
A B C D a b c
= × k l m n
E F G H d e f
o p q r
result matrix1
matrix2

Figure 9.33 Multiplying two matrices

Each element in the resulting cell is the sum of the product of the corresponding row
and column.

A=a*g+b*k+c*o
B=a*h+b*l+c*p
...
H=d*j+e*n+f*r

Code
Now we define the interface, implementation, and application files.

Interface File Program 9.19 shows the interface file.

Program 9.19 The interface file for the Matrix class

1 /***************************************************************
2 * The interface file for the Matrix class. The only private data *
3 * members are the sizes of the matrix and the pointer that *
4 * points to the matrix in the heap. *
5 * The constructor creates a matrix in the heap and the destructor *
6 * deletes the allocated memory in the heap. *
7 * The setup member function fills the matrices randomly. *
8 * We have addition, subtraction, multiplication, and print *
9 * member functions. *
10 ***************************************************************/
11 #include <iostream>
12 #ifndef MATRIX_H
13 #define MATRIX_H
14 #include <cmath>
15 #include <cstdlib>
16 #include <iomanip>
17 #include <cassert>
18 using namespace std;
19
20 // Matrix class definition
21 class Matrix
22 {
23 private:
(continued)
9.5  Program Design 433

Program 9.19 The interface file for the Matrix class (Continued)
24 int rowSize;
25 int colSize;
26 int** ptr;
27 public:
28 Matrix (int rowSize, int colSize);
29 ~Matrix ();
30 void setup ();
31 void add (const Matrix& second, Matrix& result) const;
32 void subtract (const Matrix& second, Matrix& result) const;
33 void multiply (const Matrix& second, Matrix& result) const;
34 void print () const;
35 };
36 #endif

Implementation File Program 9.20 shows the implementation file. Note that the setup
member function randomly fills the matrix, but in real-life situations, we can read the values
from a file.

Program 9.20 The implementation file for the Matrix class

1 /***************************************************************
2 * We have implemented all member functions declared in the *
3 * interface file. *
4 * The implementation follows the description of operations *
5 * discussed before. *
6 ***************************************************************/
7 #include "matrix.h"
8
9 // Constructor: creates a matrix in the heap
10 Matrix :: Matrix (int r, int c)
11 : rowSize (r), colSize (c)
12 {
13 ptr = new int* [rowSize];
14 for (int i = 0; i < rowSize; i++)
15 {
16 ptr [i] = new int [colSize];
17 }
18 }
19 // Destructor: deletes memory locations in the heap
20 Matrix :: ~Matrix ()
21 {
22 for (int i = 0; i < rowSize ; i++)
23 {
24 delete [] ptr [i];
(continued)
434 Chapter 9  References, Pointers, and Memory Management

Program 9.20 The implementation file for the Matrix class (Continued)
25 }
26 delete [] ptr;
27 }
28 // The setup fills the cells with random values between 1 and 5.
29 void Matrix :: setup ()
30 {
31 for (int i = 0; i < rowSize; i++)
32 {
33 for (int j = 0; j < colSize ; j++)
34 {
35 ptr [i][j] = rand () % 5 + 1;
36 }
37 }
38 }
39 // The add function adds second to the host and creates result.
40 void Matrix :: add (const Matrix& second, Matrix& result) const
41 {
42 assert (second.rowSize == rowSize && second.colSize == colSize);
43 assert (result.rowSize == rowSize && result.colSize == colSize);
44
45 for (int i = 0; i < rowSize ; i++)
46 {
47 for (int j = 0; j < second.colSize; j++)
48 {
49 result.ptr[i][j] = ptr[i][j] + second.ptr[i][j];
50 }
51 }
52 }
53 // The subtract function subtracts second from host.
54 void Matrix :: subtract (const Matrix& second, Matrix& result) const
55 {
56 assert (second.rowSize == rowSize && second.colSize == colSize);
57 assert (result.rowSize == rowSize && result.colSize == colSize);
58 for (int i = 0; i < rowSize ; i++)
59 {
60 for (int j = 0; j < second.colSize; j++)
61 {
62 result.ptr[i][j] = ptr[i][j] − second.ptr[i][j];
63 }
64 }
65 }
66 // The multiply function multiplies second by host.
67 void Matrix :: multiply (const Matrix& second, Matrix& result) const
68 {
(continued)
9.5  Program Design 435

Program 9.20 The implementation file for the Matrix class (Continued)
69 assert (colSize == second.rowSize);
70 assert (result.rowSize = rowSize);
71 assert (result.colSize = second.colSize);
72 for (int i = 0; i < rowSize ; i++)
73 {
74 for (int j = 0; j < second.colSize; j++)
75 {
76 result.ptr[i][j] = 0;
77 for (int k = 0 ; k < colSize; k++)
78 {
79 result.ptr[i][j] += ptr[i][k] * second.ptr[k][j];
80 }
81 }
82 }
83 }
84 // The print function prints the values of cells.
85 void Matrix :: print () const
86 {
87 for (int i = 0 ; i < rowSize; i++)
88 {
89 for (int j = 0; j < colSize ; j++)
90 {
91 cout << setw (5) << ptr [i][j];
92 }
93 cout << endl;
94 }
95 cout << endl;
96 }

Application File Program 9.21 shows the application for using the Matrix class.

Program 9.21 Application to test the Matrix class

1 /***************************************************************
2 * We create several matrix objects in the heap, and we apply *
3 * some operations on them. *
4 ***************************************************************/
5 #include "matrix.h"
6
7 int main ()
8 {
9 // Instantiation and setup of matrix1
10 cout << "matrix1" << endl;
(continued)
436 Chapter 9  References, Pointers, and Memory Management

Program 9.21 Application to test the Matrix class (Continued)


11 Matrix matrix1 (3, 4);
12 matrix1.setup ();
13 matrix1.print();
14 // Instantiation and setup of matrix2
15 cout << "matrix2" << endl;
16 Matrix matrix2 (3, 4);
17 matrix2.setup ();
18 matrix2.print ();
19 // Instantiation and setup of matrix3
20 cout << "A new matrix3" << endl;
21 Matrix matrix3 (4, 2);
22 matrix3.setup ();
23 matrix3.print ();
24 // Adding matrix2 to matrix1 and printing the resulting matrix
25 cout << "Result of matrix1 + matrix2" << endl;
26 Matrix addResult (3, 4);
27 matrix1.add (matrix2, addResult);
28 addResult.print ();
29 // Subtracting matrix2 from matrix1 and printing the resulting matrix
30 cout << " Result of matrix1 - matrix2" << endl;
31 Matrix subResult (3, 4);
32 matrix1.subtract (matrix2, subResult);
33 subResult.print ();
34 // Multiplying matrix1 and matrix3 and printing the resulting matrix
35 cout << "Result of matrix1 * matrix3" << endl;
36 Matrix mulResult (3, 2);
37 matrix1.multiply (matrix3, mulResult);
38 mulResult.print();
39 return 0;
40 }
Run:
matrix1
3 2 3 1
4 4 5 1
4 1 1 5
matrix2
4 3 1 3
3 5 1 5
4 5 1 4
matrix3
4 1
5 5
3 4
2 1
(continued)
Problems 437

Program 9.21 Application to test the Matrix class (Continued)

Result of matrix1 + matrix2


7 5 4 4
7 9 6 6
8 6 2 9
Result of matrix1 − matrix2
−1 −1 2 −2
 1 −1 4 −4
 0 −4 0  1
Result of matrix1 * matrix3
33 26
53 45
34 18

K e y T e r m s

address operator pointer arithmetic


ampersand symbol pointer type
code memory ragged array
dangling pointer reference type
generic pointer reference variable
global object return-by-pointer
heap memory return-by-reference
indirection operator return-by-value
memory leak static memory
null pointer stack memory
pass-by-pointer static object
pass-by-reference

S u m m a r y

A reference type is a compound type that allows a memory location to be used with different
names.
A pointer type is a compound type representing an address in memory. A pointer vari-
able is a variable that contains an address.
The memory used by a C++ program is made of four distinct areas: code memory (hold-
ing programs), static memory (holding global and static objects), stack memory (holding the
parameter and local objects), and heap memory (holding the object created during runtime).

P r o b l e m s

PR-1. Given the following lines of code, show what would be printed.
int x = 10;
int& y = x;
cout << x << " " << y;
438 Chapter 9  References, Pointers, and Memory Management

PR-2. Given the following lines of code, show what would be printed.
int x = 100;
int& y = x;
int& z = x;
cout << x << " " << y << " " << z;

PR-3. What is wrong with the following lines of code?


int x = 1000;
int& y = 2000;

PR-4. What is printed from the following lines of code?


int x = 1;
int y = 2;
int& z = x;
z = y;
cout << x << " " << y << " " << z;

PR-5. What is wrong with the following lines of code?


const int x = 100;
double& y = x;

PR-6. What is wrong with the following lines of code?


const int x = 100;
int& y = x;

PR-7. What is wrong with the following lines of code?


int x = 1000;
const int& y = x;

PR-8. What is printed from the following program?


#include <iostream>
using namespace std;

void fun (int& y);

int main ()
{
int x = 10;
fun (x) ;
cout << x << endl;
return 0;
}

void fun (int& y)


{
y++;
}
Problems 439

PR-9. What is printed from the following program?


#include <iostream>
using namespace std;

void fun (int& y);

int main ()
{
fun (10) ;

cout << x << endl;


return 0;
}

void fun (int& y)


{
y++;
}

PR-10. What is printed from the following program?


#include <iostream>
using namespace std;

int& fun (int& yy, int& zz);

int main ()
{
int x = 120;
int y = 80;
cout << fun (x , y);
return 0;
}

int& fun (int& yy, int& zz)


{
if (yy > zz)
{
return yy;
}
return zz;
}

PR-11. What is printed from the following program?


#include <iostream>
using namespace std;

int& fun (int yy, int zz);


440 Chapter 9  References, Pointers, and Memory Management

int main ()
{
int x = 120;
int y = 80;
cout << fun (x , y);
return 0;
}

int& fun (int yy, int zz)


{
if (yy > zz)
{
return yy;
}
return zz;
}

PR-12. What is printed from the following program?


#include <iostream>
using namespace std;

int& fun (int& yy, int& zz)


{
if (yy > zz)
{
return yy;
}
return zz;
}
int main ()
{
cout << fun (120 , 80);
return 0;
}

PR-13. Given the following lines of code, show what would be printed.
int x = 10;
int* y = &x;
cout << x << " " << *y;

PR-14. Given the following lines of code, show what would be printed.
int x = 100;
int* y = &x;
int* z = &x;
cout << x << " " << *y << " " << *z;

PR-15. What is wrong with the following line of code?


int* x = 25;
Problems 441

PR-16. What is wrong with the following lines of code?


const int x = 100;
double* y = &x;

PR-17. What is wrong with the following lines of code?


const int x = 100;
int* y = &x;

PR-18. What is wrong with the following lines of code?


const int x = 100;
const int* y = &x;
int* y = &x;

PR-19. What is wrong with the following lines of code?


int x = 1000;
int y = 2000;
int* const z = &x;
z = &y;

PR-20. What is printed from the following lines of code?


int sample [5] = {0, 10, 20, 30, 40};
cout << *(sample + 2);

PR-21. What is printed from the following lines of code?


int sample [5] = {5, 10, 15, 20, 25};
cout << *sample + 2 << endl;
cout << *(sample + 2);

PR-22. What is printed from the following lines of code?


int sample [5] = {0, 10, 20, 30, 40};
cout << *(sample + 7);

PR-23. What is printed from the following program?


#include <iostream>
using namespace std;

void fun (int* x)


{
cout << *(x + 2);
}

int main ()
{
int sample [5] = {0, 10, 20, 30, 40};
fun (sample);
return 0;
}
442 Chapter 9  References, Pointers, and Memory Management

PR-24. What is created in the heap from the following statement? Draw a picture to show
the object in the heap.
int** arr = new int* [3];

PR-25. What is created in the heap from the following statements? Draw a picture to show
the object in the heap.
int** arr = new int* [3];
for (int i = 0; i < 3; i++)
{
arr[i] = new int [5];
}

PR-26. Write the code to fill the two-dimensional array created in PR-25 with values
entered at the keyboard.
PR-27. How should the delete operator be used to delete the array created in PR-25?

P r o g r a m s

PRG-1. Write a function that finds the largest of three integers using pass-by-reference
and return-by-reference. Your design can use the function we wrote for finding
the larger between two integers. Test your function in a program.
PRG-2. Write a function that finds the largest of three fractions using pass-by-reference
and return-by-reference. Your design can use the design we wrote to find the
largest between two fractions. Test your function in a program.
PRG-3. Write a function that multiplies two fractions and returns the result using pass-
by-reference and return-by-reference. Test your function in a program.
PRG-4. For greater control over how an array can be used, we can create an array class.
Design a class named Array with the data members capacity, size, and arr (a
pointer that points to the first element of the array in the heap). Also design a
member function named insert that adds elements to the end of the array, and
a function named print that prints the element of the array. Test your program
with different lists of array elements.
PRG-5. Redesign the Array class from PRG-4 to be a sorted array (with possible
duplicate values). In a sorted array, we may need to move some elements toward
the end of the array to insert a new elements at the correct position. We also
may need to move some elements toward the front of the array when we delete
an element.
PRG-6. In object-oriented programming, we can always create a class for the problem
to be solved and let the user instantiate objects from the class and use them.
Define a class that creates a multiplication table of any size up to 10. Then use
an application program to instantiate any multiplication table.
PRG-7. Redesign the Pascal triangle as a class in which we can create a list of
coefficients of a binomial of any size that is passed to the constructor. For
example, Pascal (5) prints the coefficients for (x + y)5.

You might also like