9.C++ Programming An Object-Oriented Approach
9.C++ Programming An Object-Oriented Approach
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:
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
value
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.
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
We discuss more about the constantness in reference relationships later in this chapter.
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:
EXAMPLE 9.2
Sometimes we see statements that should not be confused with breaking the reference rela-
tionship. For example, consider the following:
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.
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
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
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.
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.
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.
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
score 92 pScore
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
Called
function
num2 rNum
Pass-by-value 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:
∙∙ 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:
∙∙ 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.
Warning We cannot bind a reference parameter to a value argument. For example, the fol-
lowing code creates a 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:
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.
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
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.
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.
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.
// 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.
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
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
0x3fe
0x3ff Address of the last byte
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.
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).
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
average
82.56
Memory
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.
92
pScore score
Symbolic representation
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.
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 = # // 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:
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.
variable address
indirection
operator
operator
address
& *
address value
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.
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).
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
Pointing relation
pScore 92 score
First Case In the first case, there is no restriction on changing the value either through the
data variable or through the 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.
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.
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.
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 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.
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)
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
copy
share copy address
all bytes
Called
functions
num rNum pNum
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:
∙∙ 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.
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.
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
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:
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
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.
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
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
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
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.
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
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)
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.
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.
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
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:
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
int* ptr = &arr [2]; int* ptr1 = ptr + 2; int* ptr2 = ptr − 2;
After creation of ptr After creation of ptr1 After creation of ptr2
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.
After creation of ptr After moving ptr down After moving ptr up
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
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.
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.
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.
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
[2] 30 31 32 33
30 31 32 33
Seen by user
Seen by 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.
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.
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
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
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.
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.
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
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
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
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.
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
Table 9.6 Operators for memory allocation and release in the heap
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
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
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
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
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:
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:
The last line creates an unexpected error because the pointer is null and cannot be indirected.
Recommendation
Always explicitly delete any memory locations created in the heap.
arr
stack heap
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
}
arr
stack
heap
arr
stack
heap
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:
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
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
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.
numOfStds
inputFile Name
inputFile Note:
Student is a struct made of four members.
Student
students
averageScore
heap
standardDeviation
stack
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
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
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
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
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
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
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.
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.
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.
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
K e y T e r m s
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;
int main ()
{
int x = 10;
fun (x) ;
cout << x << endl;
return 0;
}
int main ()
{
fun (10) ;
int main ()
{
int x = 120;
int y = 80;
cout << fun (x , y);
return 0;
}
int main ()
{
int x = 120;
int y = 80;
cout << fun (x , y);
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;
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.