Post on 14-May-2018
Chapter 10
Pointers and Dynamic Arrays
0. Introduction
The topic of this chapter is the notion of pointer and some of the uses of pointers.
Benjamin Whorf said that the language one uses has a great effect on how you think,
even to the extent of determining what you can think. Few natural language experts think
the human language a person uses has quite that control over thoughts. Programming
languages on the other hand, have a quite strong influence on what we can code. Some
programming languages do not have the idea of an address in the language, making some
kinds of programming very difficult.1 Most modern programming languages support the
memory address abstraction we call pointer.
The notion of pointer gives us the abstraction of memory addresses in C and C++
programming. The main use of pointers is to construct and use linked structures and dynamically allocated arrays. (We will study linked structures in Chapter 17.) The C and C++ pointer construct gives almost full
reign to our ability to allocate, structure, and deallocate memory.
A pointer variable holds a pointer value. Pointer variables are typed, like all objects in
C++. Typed means the pointer variable’s pointer value can only point to an object of the
type with which the pointer variable was declared. Following a common abuse of the
language, we will use the word pointer indiscriminately to refer to either a pointer
variable or a pointer value.
1 For a discussion of programming language paradigms, see Ravi Sethi, Programming Languages, Concepts and Constructs, ISBN 0-201-59065-4 , or Robert W. Sebesta, Concepts of Programming Languages, third edition, ISBN 0-8053-7133-8, both available from Addison Wesley Longman. See George Orwell, 1984, appendices, for a serious discussion of social issues regarding language.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 2Chapter 10 Pointers and Dynamic Arrays
1. Outline of topics in the chapter
10.1 Pointers
Pointer Variables
Basic Memory Management
Dynamic Variables and Automatic Variables
Uses for Pointers
10.2 Dynamic Arrays
Array Variables and Pointer Variables
Creating and Using Dynamic Arrays
Example: A Function that Returns an Array
Pointer Arithmetic
Multidimensional Dynamic Arrays
10.3 Classes, Pointers, and Dynamic Arrays
The -> Operator
The this pointer
Overloading the Assignment Operator
Example: A class for Partially Filled Arrays
Destructors
Copy Constructors
2. General remarks on the chapter
From this point in the book, you will find it very difficult to use of any version of
Windows 9x or Windows Me to run programs you develop. Avoiding errors while
developing programs that use pointers is nearly impossible. With these operating
systems, almost any pointer error will either crash the system or worse, render the system
unstable so that it crashes shortly.
Preemptively rebooting after a pointer error isn’t easy. My Windows 98 2nd edition
system continues to claim I have a “program” it does not name that has to be ended
before rebooting is possible, but the system make killing these “programs” difficult.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 3Chapter 10 Pointers and Dynamic Arrays
I strongly recommend that you obtain and use some version of the (free) Linux operating
system or Windows NT, 2000 or XP. Some Linux distributions are Red Hat, Debian,
Suse, and Mandrake. I use and recommend Debian because of the ease of upgrading from
one release to the next. I have found Windows NT and 2000 stable enough for
development of student programs. I have not used XP. However, I have been told by XP
users that it is sufficiently stable for this use.
Most Linux distributions provide GCC, the Gnu Collections of Compilers, among which
are g++, the GNU c++ compiler. This is free software. There are a few commercial
compilers for Linux. (A few are inexpensive. These compete with free compilers, so it is
reasonable that these should all be very good compilers.
For Windows, Borland’s C++ command line compiler may be down loaded free from
Borland’s web site. There is no GUI development environment. Borland Builder uses this
same compiler, and is very good. Microsoft’s VC++6.0 is bundled with the book.
At the time of this writing, to use the introductory version of VC++ 6.0 bundled with the
text, you must to go to the Microsoft download web site2 down load and install the level 5
patch. You can compile the following program to determine whether your version needs
patching:
class A
{
public:
friend A operator+(A&, A&);
private:
int a;
};
A operator+(A& lhs, A& rhs)
2 To find the Microsoft download page for this patch, I use the Google search engine with search string Microsoft Visual Studio Service Patch. This usually finds a page at Microsoft from which you can navigate to the site that has the required patch.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 4Chapter 10 Pointers and Dynamic Arrays
{
int x;
x = lhs.a + rhs.a;
return A();
}
int main()
{
A u, v, w;
u = v + w;
return 0;
}
If your compiler requires the patch, the compiler will ignore the friend declaration. It
complains about the access to private class data by the friend function
operator+().
You must have a required Microsoft program, MDAC version 2.6 or later to successfully
install the patch for all Visual Studio components. Version 2.5 will install the patches for
VC++6.0. I do not know how to determine what version of MDAC is on a system other
than downloading the patch and trying to install it.
I do know nothing about Macintosh systems or development environments, so I refrain
from commenting on them.
10.1 Pointers
The text states that a pointer is the address of a variable. A variable is the name of a
memory location. A memory location has both an address and an extent. An example is
the array, which has an address, the address of the index 0 element, and extent, which is
the number of elements times the size of the base type. The two examples the text gives
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 5Chapter 10 Pointers and Dynamic Arrays
for pointers that we have already used are passing an array to a function and passing any
variable by reference.
Pointer Variables
A pointer variable holds a pointer value. A pointer value is the address of a variable in
memory. The C++ type system requires that there be a type for every object. Pointers and
pointer variables are no exception. A pointer can point to an address, and the type object
that a pointer is able to point at must be specified.
The following statement3 declares dPtr to be a pointer type (that is what the * says)
capable of holding the address of a double.
double *dPtr;
The strong typing of the C++ language requires that pointers declared to point to a
particular type object be used only to point to that type object. An attempt to assign a
variable of type pointer to int to a variable of type pointer to long will get a compiler
error message to the effect that there has been an "assignment between incompatible
pointer types." Each type object requires a pointer having type pointer-to-that-type. The
type dPtr is "the type pointer to double" or, in C++ parlance, "dPtr is the type
double*".
The text already warns of one of the pitfalls of pointer declarations:
int *x, y;
This is a declaration of x, with type pointer-to-int, and y, with type int. My students
tend to have a problem declaring several pointers in one declaration. The problem is that
the following appears to declare three pointer-to-int variables. Really, this declares only
one pointer-to-int, and two int variables.
int* p1,p2,p3;// p1 is pointer-to-int; but p2,p3 are int.
3 Ellis and Stroustrup, in The Annotated C++ Reference Manual, say that declarations are statements.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 6Chapter 10 Pointers and Dynamic Arrays
The reason for this is that the pointer declarator, *, binds to the identifier. If the student
will declare one variable per line, this problem does not occur. Of course, we could use a
typedef. For example,
typedef int* intPtr;
intPtr p1, p2, p3; //p1 p2 and p3 all have type int*
Programming Language Notes (This material is not in the text.)
We mentioned overloading of operators in C++. The language overloads the operators for
us with regard to the primitive types. For example, + is able to add any of char,
short, int, long, float, double, or long double. Most languages
have operator overloading at this level. This operator overloading is carried out by the
compiler, just as operator overloading is done by the programmer. The compiler
recognizes the type of the operands of the + operator (or whatever operator we are
discussing) then generates the appropriate machine instructions to carry out the operation.
The machine instruction is usually different for each primitive type.
A primitive type is a type that is built into the language. For C++, the list of primitive
types is bool, char, short, int, long, float, double, long
double, and the unsigned variants of these types. This is by contrast with user defined
types such as array, class, and enums. To build these requires use of machinery
provided in the language.
The * operator is overloaded in a more extensive way than + is overloaded. We have
the * for multiplication. The * also stands for 'pointer to' in a pointer declaration (a
pointer declarator), and it stands for 'dereference' or 'follow the pointer' when it occurs
before a pointer expression (a dereference operator). A dereferenced pointer can be either
an l-value or an r-value.4
The text points out that from the point of view of the C++ language, a pointer is not an
integer. An integer would be a char, short, int, or long, or perhaps an
4 An l-value is capable of being assigned, and an r-value has a value that can be fetched.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 7Chapter 10 Pointers and Dynamic Arrays
unsigned variant. A pointer type isn't any of these. It is declared a pointer to
something, and that object is an address of a type. The 'address of a type' is itself a type.
Pointer arithmetic does some special things that will be discussed in the text and in this
IRM shortly. Here is a place where adding 1, in a sense, adds more than 1, most of the
time. Adding and multiplying pointers themselves is not defined. Subtracting pointers
gives an integer in certain limited circumstances. Other times subtracting pointers give
undefined results.
Operators: pointer declarator: *, indirection operator: *, address of: &
If used in the declaration,
int * iPtr;
the * is called a "pointer declarator" by language lawyers. This statement declares
iPtr as a "pointer to int". Read it in reverse: iPtr is a pointer (*) to an int. This works
on all but the most complex declarations.
If used in the assignment,
*iPtr = 7;
we are using the * as the indirection operator or dereferencing operator. This statement
says, store 7 where the pointer, iPtr, points.
We haven't said where iPtr points. We should have. Let's do it:
int x = 49;
iPtr = &x;
The & in front of the x is called the "address of operator". It takes the
address of x. The assignment makes iPtr point at (have the value of the address of)
the int variable x. (The & is another operator overloading. If it is used between the
type and the formal parameter in a function declaration (prototype), language lawyers call
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 8Chapter 10 Pointers and Dynamic Arrays
this a "reference declarator" and it means in that context, "this is a reference parameter."
If it is used in an expression where it has two arguments, it is the bitwise AND operator.5
Now let us execute the above assignment to the dereferenced pointer:
*iPtr = 7;
We have changed the value stored in x from 49 to 7. As long as iPtr contains a
pointer that points to x, *iPtr and x refer to the same variable (or memory
location).
Incidentally, you cannot assign the address of a variable, that is the result of the “address
of” operator is not an l-value.
int x;
int *p;
&x = p; //invalid l-value in assignment
The text carefully distinguishes between assignment of pointers and assignment of the
places where the pointers point. Pay particular attention to these ideas, and to Display
10.1, 10.3 and 10.5 in the text, that illustrate these ideas. Here is an example:
int x = 7;
int y = -15;
int *int_ptr1 = x;
int *int_ptr2 = y;
int_ptr1 = int_ptr2; //Pointer assignment. This makes
//int_ptr1 point at the same place
//int_ptr2 points.
*int_ptr1 = *int_ptr2; // Dereferenced pointer 5 Bitwise AND is not discussed in the text. It does an AND between bits of each operand, bit by bit. The operands must be integers.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 9Chapter 10 Pointers and Dynamic Arrays
//assignment. This assigns the
//values stored where the pointers
//point.
The most important uses of pointers is the case where pointers point to variables that
have no name. The operator new is used to create variables that have no identifiers to
serve as their names.
The phrase "creating a variable" is used to denote a request to the operating
system to allocate a chunk of memory to the program during run time. In C++, this tells
the compiler to only allow storage of objects having the type specified in the argument
for new. If the type is a class, and a proper constructor is provided, the constructor is
called to initialize the variable so created.
A common description of these anonymous variables is "dynamic variables".
The word dynamic "dynamic" is used because these variables are not allocated
statically, that is, not allocated at compile time. Rather, they are allocated at the request
of the program, during execution. When and how much allocation is done is usually a
decision made during the execution of the program based on the state of the program.
The area of memory where dynamic variables are stored is called the heap, (the text's
usage) and free store. The student should be aware that both terms are used,
interchangeably. Heap seems to be usage inherited from the C language. The terms mean
exactly the same thing.
There is one caution. I find that there is a little confusion between 'heap', meaning a tree-
like data structure with certain order relationships imposed on the values in the nodes,
and a memory management heap, used for allocation of dynamic variables.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 10Chapter 10 Pointers and Dynamic Arrays
A memory allocation 'heap' is a linked list of chunks of memory waiting for allocation,
together with another collection of memory chunks that have been already allocated to
the program, usually with library functions that carry out the management.
(Aside: I use 'heap' as often as 'free store', partly because I am (still) a Pascal/C refugee,
and that was the usage there. There is a program language designer's maxim that the C++
developers have not violated. Perhaps they should have. The maxim reads, "Every
designer of a (new) programming language must invent new terminology for every (old)
language concept.")
Pitfalls:
Several Pitfall sections follow, some of which are in the text, and some are not. I have
attempted to supplement those in the text, and to provide a bit of my experience in the
others. I believe them all to be important.
Pitfall: Assigning a value to a dereferenced, uninitialized pointer variable.
A very common pitfall is failing to allocate space for a pointer then assigning the
dereferenced pointer variable.
The text points out that if you declare a pointer variable, you have memory allocated for
exactly what you declare: a pointer variable. Space is not allocated for the pointer to
point to. The programmer is responsible for allocating that space.
The text points out that the operator new, with a type name for argument will allocate
space appropriate for an object of that type and return a pointer that has type pointer-
to-type-name.
Failing calls to operator new
According to the ISO C++ Standard, if there is insufficient memory to allocate the
requested storage,6 the behavior of new is to throw an exception of type bad_alloc. 6 Most compilers track this ANSI C++ Standard required behavior. Read your compiler documentation and run test programs to determine your compilers behaviioujor.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 11Chapter 10 Pointers and Dynamic Arrays
The result is the program crashes if the exception is not handled. I mention this to my
classes, but do nothing about handling exceptions until Chapter 18.
Read your compiler documentation and run test programs to determine your compilers
behavior. If your compiler still returns NULL (the int value 0), the following code is
appropriate.
int *p; //only space for the pointer is allocated
p = new int; //space for the pointer to point to,
//and an assignment to p of the pointer
//returned from the new operator.
if ( p == NULL ) //new did return a null pointer when
//there is insufficient memory to
{ //allocate requested memory.
cout << "Error: Insufficient memory.\n";
exit(1); // be sure to #include <cstdlib> for this
}
If you have a recent compiler that throws the exception, there is a version of new that
exhibits the earlier behavior; that is, it returns 0 (NULL).
Here is some sample code. (This code follows example code in the ANSI Standard,
Chapter 18, §18.4.1.1.)
#include <iostream>
#include <new>
using namespace std;
int main()
{
int* p1 = new int; //throws bad_alloc if new fails
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 12Chapter 10 Pointers and Dynamic Arrays
int* p2 = new(nothrow) int; //returns 0 if new fails
*p1 = 1;
*p2 = 2;
cout << *p1 << endl;
cout << *p2 << endl;
return 0;
}
Pitfall: Applying delete to a pointer that has already been deleted
An error you will make repeatedly in C++ is to apply the delete operator to a pointer
that points to memory that has already had the delete operator applied to it. This is a
guaranteed segmentation violation.
In short: dynamic variables destroyed with delete must have been created with new,
and (only) dynamic variables created with new should be destroyed (with delete)
after you are through with them.
Pitfall: Deleting a pointer not obtained from the new operator
Deleting memory not allocated with new will corrupt the heap (free store) organization.
At best, the program will give a runtime error message. Under Linux, using g++, I get a
'segmentation fault' -- sometimes. Corrupting the free store is usually a run time disaster.
One positive word: The ARM (Annotated C++ Reference Manual, by Ellis and
Stroustrup) points out, "Deleting a pointer that has the null pointer value is guaranteed to
be harmless."
An example: int main()
{
int i;
int * p = &i;
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 13Chapter 10 Pointers and Dynamic Arrays
delete p; //A runtime error which hangs the machine with some
//compilers.
//Linux/g++ gives is a segmentation error.
p = new int[10];
p++; // p now points to the next int on the heap
delete p; // error, but some compilers do not detect this
// Linux/g++ doesn't detect this either
p = 0;
delete p; // OK
return 0;
}
To determine the effect of delete on pointers and on the contents of memory being
deallocated, I added initialization to this code, and saved the value of p in an int
pointer variable, q, as well as some output statements. The result is that both g++ and
Borland C++ change the values originally pointed to by p. 7 Neither compiler detects the
error, but Borland C++ binary will give a "null pointer assignment" runtime
error, and occasionally will hang the machine. (This machine runs Windows 98 2nd
edition.)
Ellis and Stroustrup in the ARM point out that deleting a pointer variable that can be
assigned may change the pointer's value and it is likely to change the memory at which
the undeleted pointer pointed. Neither of these is guaranteed. Compilers are allowed to
change these, not required. Using deleted or dangling pointers is a recipe for disaster. Go
to extremes to avoid these errors.
The ARM continues: "In general, catching bad deletions at compile time is impossible;
catching them at run time implies time and space overhead. Therefore, the results of such
deletions are subtle and usually disastrous. Bad deletions are not detected immediately,
and programs containing them are therefore among the nastiest to debug. Almost any
effort to avoid such bad deletions is worthwhile." --Emphasis mine.
7 As expected, different compilers produce different changes.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 14Chapter 10 Pointers and Dynamic Arrays
Use of a dangling pointer is unlikely to be detected by any compiler. The program may
work fine until someone (a maintenance programmer) writes additional code and the
program mysteriously breaks, because you used memory that belongs to the free store
manager, but the added code allocated that memory.
This is the reason the text recommends in the Dangling Pointer Pitfall paragraph that you
should to hunt down all pointers that point to the same dynamic variable as the pointer
you want to delete. Once found, you should delete one of them, and assign 0 to the other
pointers. (The text recommends that you use NULL.) Further deletion of any of these is
rendered harmless, and attempting to use them is more likely to result in an error message
such as "NULL Pointer Dereferenced."
We pointed out above that deleting a pointer that has the null pointer value is harmless,
but deleting a non-null pointer the second time can produce a runtime disaster.
Aside: The NULL preprocessor symbol
The ISO C++ Standard says NULL will be defined in compliant compilers in the
<cstdio> header.
My curiosity was piqued about how (and where) NULL is defined on my system, so I
used the UNIX grep ( (g)et (re)gular ex(p)ression ) facility to look through some of
the header files. Under Linux and gnu C++, NULL is defined in many header files. I
found NULL defined with my compiler when I included the header files referred to in
these header files: iostream, cstdio, cstdlib, and cstddef. In the
following, I have quoted the definition of NULL from several header files. The lines,
#ifndef NULL ... #endif, prevent multiple definitions of NULL, just as in
complete header files.
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 15Chapter 10 Pointers and Dynamic Arrays
#else
#define NULL (void*)0
#endif
#endif
The specifics of the definitions is dependent on decisions made by the compiler and
library writers, but the effect is exactly the same. The ANSI C++ Standard, Chapter 4
subsection 10 and Chapter 10 subsection 1, specifies that NULL is defined to be 0 or 0L.
The preprocessor symbol NULL is defined to be (void*)0 in C. The reason for this is
the stronger typing of the C++ language. (The book does not deal with void pointers,
so I won't deal with them further in this document..)
Borland C++ has declarations that are essentially the same as this. There is added detail
to account for the various 80x86 memory models that MS Windows use.
The reader interested in more details on why things were done this way in C++ should
refer to Stroustrup, The Design and Evolution of C++, for specific details. It would take
us too far afield to go further into that corner of C++ design and evolution.
Static, Dynamic, and Automatic Variables
The words static and auto are keywords in C and C++ that refer to storage classes
(not class in the data structuring sense.) The classes are auto, static, register
and extern.
Variables declared auto (automatic) are, as the text indicates, 'created' as the declaration
is encountered and 'destroyed' as the scope of the declaration is exited - automatically.
'Created' and 'destroyed' really mean allocation and initialization (if any), and
deallocation of variables. Automatic variables are the ordinary variables we declare and
use in main, in functions, in classes, and in function members of classes. You will only
occasionally see the keyword auto in code. It isn't much used, since this is the default
for variables.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 16Chapter 10 Pointers and Dynamic Arrays
Declaring a variable with the register keyword is a hint to the compiler that the
programmer thinks the variable so declared might be profitably stored in a CPU register.
(A register is memory in the CPU that is much faster than system memory.) The compiler
will make the register variables auto unless it accepts the register suggestion.
Compilers are much better than programmers at deciding which variables should go into
registers. Consequently, the ANSI C++ Standard leaves compilers free to ignore the
keyword register. Consequently, this keyword is little used. The register keyword is
not treated in the text.
The keyword extern is used to declare an identifier in one file, and indicate to the
compiler that the variable this identifier refers to will be defined in another file. These
files are to be separately compiled. I cannot find extern or register in the text. I
mention because extern and register are reserved keywords. It is possible that
a student may ask why it is an error if either of these is used as an identifier.
Local variables declared with the static keyword are 'created' before the main
function of the program starts, and 'destroyed' after the main function exits. These
variables exist for (or, have lifetime of) the entire run of the program. The visibility or
scope of these variables is determined by the C++ scope rules. In spite of the fact that
static variables exist for the entire time the program runs, the variables can be used
only within their scope. See page 145 of the text, the Local Variables side bar, for a brief
discussion of the scope rules of C++. Briefly, the scope of a variable starts at the
declaration of the variable and runs to the end of the block in which the variable is
declared.
A declaration introduces a name into a program and specifies how the name is to be
interpreted. A definition is a declaration that causes allocation of an appropriate amount
of storage, and any appropriate initialization to be done.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 17Chapter 10 Pointers and Dynamic Arrays
A function declaration (or prototype) provides the return type, the name of the function
and the list of types of formal parameters that must be supplied in a call to the function.
Stroustrup (The C++ Programming Language) says "A function definition is a function
declaration in which a function body is presented."
Global variables that are declared with the static keyword are inaccessible from files
outside the one that declared them. We have pointed out already that the ISO C++
Standard states that this use of static is deprecated. See the section on namespaces in
the previous chapter of this IRM for a (brief) discussion of the use of anonymous
namespaces to conceal names within a file.
A global variable is accessible in any file of the program in which that variable is
declared outside any function. Hence, only one definition of a variable is allowed, all the
rest must be declarations.
The text doesn't use global variables. The reason is that use of global variables ties
program components together in a way that makes analysis of an individual component
independently of other components impossible. The result is rapid growth of complexity
of the program with the growth of number of components that use global variables.
Operating systems are the only programs that I know about that use global variables, and
operating systems use global variables sparingly.
A Cautionary Note on typedef:
In Pascal, the TYPE statement introduces a new type. In C++, a typedef statement only
renames an existing type. I was bitten by this one in my early days in C. In C++ this is
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 18Chapter 10 Pointers and Dynamic Arrays
exactly the same as in C. The gain in readability of the program is usually well worth the
effort to use the typedef.
An example of the use of the TYPE statement in Pascal is:
TYPE FEET = integer; (* declares a new, strongly enforced type *)
VAR X: FEET; (* X has type FEET, incompatible with integer *)
Y: integer;
BEGIN (*equivalent to { C++ *)
Y := 17;
X := Y; (* illegal: incompatible types *)
Here, you cannot assign X to Y or conversely.
A corresponding typedef statement in C/C++ is
typedef int feet;
int X;
feet Y; // declare Y to be of type feet
X = Y; // OK, feet is only a renaming of int
Here the identifier 'feet' is only another name for int. It is not, as in Pascal, a new,
enforced, separate type. You gain readability, but not type safety.
The gain in readability of a program is usually well worth the effort to use a typedef.
However, you cannot expect the types to be distinct as they are in Pascal.
Basic Memory Management
The area of memory where dynamic variables are stored is called the free store. The free
store is a linked list of chunks of memory waiting for allocation, together with another
collection of memory chunks that have been already allocated to the program. There are
library and support functions to carry out the management.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 19Chapter 10 Pointers and Dynamic Arrays
The synonym, heap seems to be usage inherited from the C language. The terms free
store and heap mean exactly the same thing. The student should be aware that both terms
are used. We will follow the text’s use and mostly use free store.
There is one caution if you use the term 'heap'. I find that there is some confusion
between the 'heap' data structure, meaning a tree-like data structure with certain order
relationships imposed on the values in the nodes, and a memory management 'heap', used
for allocation of dynamic variables. There is a program language designer's maxim that
the C++ developers have not violated. Perhaps they should have. The maxim reads,
“Every designer of a (new) programming language must invent new terminology for
every (old) language concept.”
If you allocate memory (with new), you should deallocate memory (by applying the
delete operator to a pointer pointing to the memory) when you are through with the
piece of memory. The delete operator takes a pointer argument that is required to point
to memory that was allocated by the new operator. The action is to release memory (to
which the pointer pointed) to the free store manager for reallocation. Applying delete
to a pointer that points to memory not allocated with the new operator causes free store
corruption, and usually causes a segmentation violation. Applying delete to a pointer
that points to already deleted memory also corrupts the free store. This error may be
indicated by a very hard-to-find run-time error that appears after the program terminates.
Note that it is harmless to apply the delete operator to a pointer that has the null
pointer value. Consequently, when you apply the delete operator to a pointer, it is wise
to hunt down all the pointers that point to memory about to be deleted. Once the delete
operator has been applied, the remaining pointers are dangling. Prevent trouble by assign
all the pointers to the same memory the integer constant 0. You can use the preprocessor
macro NULL, which evaluates to the integer constant 0. After all, there is no legal use for
pointers pointing to deleted memory. 8
8 The common usage is “deleted pointer” but it should be “deleted memory”, for it is the memory that is “deleted,” not the pointer.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 20Chapter 10 Pointers and Dynamic Arrays
Pitfall: Assigning a pointer for which memory has not been allocated.
When you declare a pointer variable, memory is allocated for exactly what you have
declared, a pointer variable. Space is not allocated for the pointer to point to.9 The
programmer is responsible for allocating that space. The text points out that the operator
new, with a type name for argument will allocate space appropriate for an object of that
type and return a pointer that has type pointer-to-type-name.
int *p; //Only space for the pointer is allocated here
p = new int; //Space is allocated here for the pointer to point to,
//and an assignment to p of the pointer returned by the
//new operator.
Dynamic Variables and Automatic Variables
The programmer uses the new operator with the name of a type for its argument to direct
the free store manager to allocate an amount of memory the size of the type. The new
operator returns a pointer that points to the dynamically allocated memory. Dynamically
allocated memory remains allocated until either it is released by the program or the
program terminates. The programmer uses the delete operator with argument a pointer
to dynamically allocated memory. Allocated memory is unavailable for any other use
while the program is running, so it is important that allocated memory be released as soon
as the program is through with it. We will see that a properly written class automatically
allocates memory in its constructors and automatically release the allocated memory in its
destructor.
The pointers used in dynamic arrays and linked structures point to memory in the free
store that has been dynamically allocated.
Uses for Pointers
The main use of pointers is to construct and use dynamically allocated arrays and linked
structures. Linked structures are typically linked lists and trees. We will study linked
structures in Chapter 17.
9 This has a cognate in Java with the declaration of Java references. In Java, you don’t get an object by declaring a variable of class type. You only get a reference that can refer to a class object. Until the programmer writes code to do the allocation, there is no object.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 21Chapter 10 Pointers and Dynamic Arrays
Memory is among the scarcest resources in a computer. There are tens of thousands of
megabytes (MB) of disk space, but only 64 MB (minimal) to perhaps 128 MB (optimal?)
of memory available. Some systems will have 256 megabytes of memory and more.
Most computer systems use virtual memory. Virtual memory enables the system to keep
only part of the executing code body in memory and to keep the rest of the executing
code (and data) on secondary storage. This has the effect of making the memory appear
large as the disk but fast as memory.
At least that’s the theory. The down side of virtual memory comes when memory is “over
committed”, i.e., when more memory is needed than the amount of RAM available. As
the program continues to execute, the data and code must frequently be retrieved and
stored again, requiring a significant number disk accesses during code execution. As
memory use increases, the number of disk accesses increases dramatically. 10 The system
continues to run but runs more and more slowly. If your system has insufficient memory
left to allow your program to allocate memory, the system will already be running so
slowly as to be frozen.
10.2 Dynamic Arrays
Array Variables and Pointer Variables , Creating and Using Dynamic Arrays
This section introduces the dynamic array concept. A dynamic array is an array that is
allocated on the free store in amounts that may be determined by the program. These
ideas can be used to implement a dynamic array of any base type. More usefully, we
could implement a dynamic array class.
The critical issues in this section are the allocation of free store (or heap) memory and the
deallocation of the memory once the program is through with it.
10 The word “thrashing” is used to describe this situation. When disk drives were really noisy, the disk drive sounded like a thrashing machine when this situation was encountered.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 22Chapter 10 Pointers and Dynamic Arrays
int *p = new int[10];
int q[10];
When these variables go out of scope, the memory allocated for q is released. There
are three differences between these two declarations.
The first difference is that only the memory allocated (on the stack) for p, is
released, but the memory allocated (on the free store) by new that p to points
to is not released. The memory allocated by new is orphaned if not deallocated
with delete. It can never be retrieved by this program, and will only be
available to other programs after the program terminates.
The second difference is that the sizeof operator11 gives different results
applied to p and to q. The sizeof operator has not been discussed so far in
the text. The value of sizeof p is the size of an int pointer, sizeof
(int*), whereas sizeof q is 10 * sizeof int. Why? The variable
p is a pointer, and q is an array. Yet, q carries the same address information
that p does, but they are subtly different.
The third difference is discussed in the text. You can assign to p, but not to q.
In other words, an array name is not an l-value. A pointer variable is an l-value.
The text presents a dynamic array in Display 10.7, A Dynamically Allocated Array. Here,
space is allocated as specified at runtime. (Ordinary arrays require that the size be known
at compile time.) In this program, there are three points.
The typedef (first line after the #include statements) makes
IntArrayPtr behave as a 'super' int*,
The new statement (line 22, in the main function) allocates the requested
amount of memory, and
The delete statement syntax must be:
11 The sizeof operator applied to an expression returns the number of bytes of memory that the expression requires. If applied to a type, sizeof requires that the type name be placed in parentheses.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 23Chapter 10 Pointers and Dynamic Arrays
delete [] pointerVariable;.
Note that the variable, pointerVariable, must point to memory allocated
with new Type[size].
Note that the functions fillArray and sort ask for ordinary array parameters and
accept the pointers as array parameters.
In programming languages, a first class type is a type that can be used exactly like the
primitive types (char, short, int, long, float, double, etc). This means that the
first class type can be a class or struct member type, a function parameter type, a function
return type, and a base type for an array. An array type fails the function return type test.
An array cannot be a function return type. Arrays also fail the function parameter test.
When passed to a function, an array deteriorates to pointer to base type. The size
information is lost. Consequently, all C++ passes to an array parameter is the address of
the array. The programmer must tell the function other information: the base type is
specified in the declaration of the array formal parameter and the size is passed in the
int formal parameter.
Example: A Function that Returns an Array
This is illegal. The return type must be a pointer to an array’s base type, and have value
pointing to the array’s first element to carry this out. Care must be exercised to ensure
that the pointer does not point to an object that has a short life time or otherwise be
destroyed when the returned value is used.
Pointer Arithmetic
Pointer arithmetic is scaled. This means that when 1 is added to a pointer value, ptr,
the result is a pointer that points to the next object in memory beyond where the pointer
originally pointed. What happens is that the numeric value of the pointer has
sizeof(type) added to the pointer. If some other int value, say k, is added to a
pointer value, the result is a pointer that points to the kth object beyond where the
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 24Chapter 10 Pointers and Dynamic Arrays
original pointer pointed. The value of the resulting pointer expression is the original
numeric value of the pointer with the value, k* sizeof(type).
Multidimensional Dynamic Arrays
Here is a body of code from which I learned a lot about dynamically allocated
multidimensional arrays and the use of nested vector types to implement this data type.
Ideas here are heavily modified from this URL:http://www.cplus-zone.com/free/articles/toptips/toptips6.asp#tip19
#include <cstdlib>
#include <vector>
#include <iostream>
using namespace std;
vector<vector<double> >
operator+(const vector<vector<double> >& lhs,
const vector<vector<double> >& rhs);
vector<vector<double> >
operator+(const vector<vector<double> >& lhs,
const vector<vector<double> >& rhs)
{
unsigned int i, j;
//declare and allocate rows
vector<vector<double> > sum; /* two dimensions */
// create rows 0 through 4, rows have zero size
for(i = 0; i < 5; i++)
sum.push_back(vector <double>());
// check number of rows
if(lhs.size() != rhs.size())
{
cout << "size of lhs != size of rhs. Aborting!\n";
abort();
}
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 25Chapter 10 Pointers and Dynamic Arrays
//check the row lengths.
for(i = 0; i < lhs[i].size(); i++)
{
if(lhs[i].size() != rhs[i].size())
{
cout << "size of lhs[" << i << "]"
<< " != size of rhs[" << i << "]. Aborting!\n";
abort();
}
}
for(i = 0; i < lhs.size(); i++)
{
for(j = 0; j < lhs[i].size(); j++)
sum[i].push_back(lhs[i][j] + rhs[i][j]);
}
return sum;
}
int main()
{
int i, j; //These are use as "for loop" control
//variables. We must declare them outside block
//to avoid multiply defined for "for loop"
//variables when compiling with MS VC++ 6.0.
//Example using int variables and dynamic allocation
double *ppd [5];
//In this code, the parentheses around the *ppd are required
//for double (*ppd)[5] to be parsed as a declaration of ppd
//as pointer to an array of double rather than an array of
//pointers to double. (*ppd)[0] is a pointer to an array
//of double. We can allocate in pieces that will not //(necessarily) be
in contiguous memory
ppd[0] = new double[3];
ppd[1] = new double[3];
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 26Chapter 10 Pointers and Dynamic Arrays
ppd[2] = new double[3];
ppd[3] = new double[3];
ppd[4] = new double[3];
/* We could assign each component individually
ppd[0][0] = 1;
ppd[1][1] = 1;
ppd[2][2] = 1;
ppd[3][3] = 1;
ppd[4][4] = 1;
*/
// But we may (more flexibly) assign in nested loops.
for(i = 0; i < 5; i++)
for (j = 0; j < 3; j++)
ppd[i][j] = i + j;
for(i = 0; i < 5; i++)
{ cout << " ";
for (j = 0; j < 3; j++)
cout << ppd[i][j] << " ";
}
cout << "finished output" << endl;
cout << "deleting memory" << endl;
// Deleting this requires 5 delete statements.
for(i = 0; i < 5; i++)
delete [] ppd[i];
//Or we can allocate in contiguous memory:
double (*ppd1) [5]=new double[4][5]; // fill array contiguously.
//assign some members
ppd1[0][0] = 6.5;
ppd1[0][1] = 6.6;
ppd1[0][2] = 6.7;
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 27Chapter 10 Pointers and Dynamic Arrays
//then use them
//and delete them.
delete [] ppd1;
//This will properly deallocate memory that is allocated in
//this manner.
//However, this style is tedious and error prone. You must
//parenthesize ppd to ensure that the compiler parses the
//declaration correctly, and you must delete the allocated
//memory. Worse, you can easily cause buffer overflows,
//writing to memory beyond what has been reserved for our
//use.
//Using a vector of vectors to simulate a multidimensional
//array is a significantly better alternative:
vector <vector <double> > v; /* two dimensions */
// create rows 0 through 4, rows have zero size
for(i = 0; i < 5; i++)
v.push_back(vector <double>());
vector <vector <double> > v1; /* two dimensions */
// create rows 0 through 4, rows have zero size
for(i = 0; i < 5; i++)
v1.push_back(vector <double>());
cout << v.size() << " ";
for(i = 0; i < 5; i++)
cout << v[i].size() << " ";
cout << endl;
//insert some values in the array
for(i = 0; i < 5; i++)
for(j = 0; j < 4; j++)
v[i].push_back(2*i+j);
for(i = 0; i < 5; i++)
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 28Chapter 10 Pointers and Dynamic Arrays
for(j = 0; j < 4; j++)
v1[i].push_back(3);
//Because vector overloads operator[], you can use the [][]
//notation as if you were using a built-in two-dimensional array:
cout << v[0][0] << " "
<< v[1][0] << endl;
for(i = 0; i < 5; i++)
{
for(j = 0; j < 4; j++)
cout << v[i][j] << " ";
cout << endl;
}
cout << endl;
vector<vector<double> > sum;
sum = v + v1;
for(i = 0; i < 5; i++)
{
for(j = 0; j < 4; j++)
cout << v1[i][j] << " ";
cout << endl;
}
cout << endl;
for(i = 0; i < 5; i++)
{
for(j = 0; j < 4; j++)
cout << sum[i][j] << " ";
cout << endl;
}
cout << endl;
return 0;
}
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 29Chapter 10 Pointers and Dynamic Arrays
The main advantages of using a vector of vectors are:
The STL vector automatically allocates memory as needed.
The STL vector takes care of deallocating memory so you don't have to worry about
memory leaks.
10.3 Classes, Pointers, and Dynamic Arrays
The -> Operator
If pointer p points to a structure or class object, access to a member of the structure or
class object member through the pointer is (*p).member. The expression
(*p).member and p->member have the same meaning.
The parentheses in the expression (*p).member are necessary because the structure
member access operator . binds more closely than the dereferencing operator *. The
general rule is that postfix operators bind more closely than prefix operators. The
structure access operator . is a postfix operator, and the dereference operator, *, is a
prefix operator.
The this pointer
In a member function definition the predefined this pointer variable is provided
(implicitly) to every non-static member function. This pointer points to the calling
object. Any member function or variable that may be used in the implementation of a
member function has this-> implicitly prefixed. If the function is declared to be
static, then this is not supplied, and direct access to non-static members is
unavailable.
Overloading the Assignment Operator
The assignment operator must be implemented as a member function. There are several
operators whose overloaded operator functions must be implemented as non-static
member functions. These are [], (), ->, and =.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 30Chapter 10 Pointers and Dynamic Arrays
The assignment operator should return *this so that the overloaded operator provides
the expected behavior of assignment
a = b = c;
If these continued assignments aren’t required, a void return type suffices.
Destructors
A destructor is a member function that is called implicitly when a class object passes out
of scope. If a class is properly designed, the class can allocate memory on the free store
and its destructor will automatically deallocate the memory when the automatic variable
goes out of scope.
Copy Constructors
Copying is not quite intuitive in C++. C++ considers these three situations to be copying:
when a class object is declared and initialized by another class object of the same
type.
when a function return value has class type.
when class is the type for a call-by-value parameter.
The Big Three
If you need any one of the copy constructor, operator assignment, or destructor, you
probably need all three. For this reason, these three members are called the big three.
When either the copy constructor or operator assignment is needed, then deep copy is
needed. (See the Shallow Copy and Deep Copy on page 445 in the text.)
3. Solutions to, and remarks on, selected Programming Projects
1. class TwoD: A two dimensional dynamic array of double
Based on code from Display 10.9, write a class TwoD that implements a two-
dimensional dynamic array of double.
Student should supply:
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 31Chapter 10 Pointers and Dynamic Arrays
Default constructor for which you choose a default maximum rows and maximum
columns
Parameterized constructor, which sets maximum rows and maximum columns
A void member function that allows setting a particular row-column entry
A double member function that allows retrieving a particular row-column entry
Remark: Suggest accessor and mutator member functions for these accesses.
Overload + as friend to add to matrices of the same size
Overload =
Copy constructor
Destructor
Use const for members that do not change
Notes: I overloaded the function call operator to serve as index. That way, matrix(i, j)
returns the (i, j) element, while matrix(i) returns the ith row. (I did not implement this last
overloading. I only supplied the declaration.)
#include <iostream>
#include <cstdlib>
using std::exit;
using std::cin;
using std::cout;
using std::endl;
typedef double* DoubleArrayPtr;
class TwoD
{
public:
TwoD(); // sets maxRows and maxCols each to 10
TwoD(int maxR, int maxC);
~TwoD();
TwoD(const TwoD&);
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 32Chapter 10 Pointers and Dynamic Arrays
const TwoD& operator=(const TwoD& rhs);
double& operator()(int r, int c); // returns row r, col c element
double* operator()(int r); //returns pointer to row r. Ordinary index
//extracts elements from this operator.
friend TwoD operator+(const TwoD& lhs, const TwoD& rhs);
friend int myRows(const TwoD&);
friend int myCols(const TwoD&);
private:
DoubleArrayPtr * matrix;
int maxRows;
int maxCols;
};
int main( )
{
int d1, d2, i, j;
cout << "Enter the row and column dimensions of the array\n";
cin >> d1 >> d2;
TwoD matrix1(d1, d2);
cout << "Enter " << d1 << " rows of "
<< d2 << " doubles each\n";
for (i = 0; i < d1; i++)
for (j = 0; j < d2; j++)
cin >> matrix1(i,j);
cout << "Echoing the 2 dim. array, matix1\n";
for (i = 0; i < d1; i++)
{
for (j = 0; j < d2; j++)
cout << matrix1(i,j) << " ";
cout << endl;
}
cout << "Enter the row and column dimensions of the array\n";
cin >> d1 >> d2;
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 33Chapter 10 Pointers and Dynamic Arrays
TwoD matrix2(d1, d2), matrix3;
cout << "Enter " << d1 << " rows of "
<< d2 << " doubles each\n";
for (i = 0; i < d1; i++)
for (j = 0; j < d2; j++)
cin >> matrix2(i,j);
cout << "Echoing the 2 dim. array, matrix2\n";
for (i = 0; i < myRows(matrix2); i++)
{
for (j = 0; j < myCols(matrix2); j++)
cout << matrix2(i,j) << " ";
cout << endl;
}
cout << "assigning matrix 2 to matrix 3 " << endl;
matrix3 = matrix2;
cout << "Displaying the 2 dim array, matrix3 "
<< "resulting from assignmnet.\n";
cout << "rows " << myRows(matrix3) << " "
<< "cols " << myCols(matrix3) << endl;
for (i = 0; i < myRows(matrix3); i++)
{
for (j = 0; j < myCols(matrix3); j++)
cout << matrix3(i,j) << " ";
cout << endl;
}
matrix3 = matrix2 + matrix1;
cout << "Echoing the 2 dim array, sum of matrix 1 and 2\n";
cout << "rows " << myRows(matrix3) << " "
<< "cols " << myCols(matrix3) << endl;
for (i = 0; i < myRows(matrix3); i++)
{
for (j = 0; j < myCols(matrix3); j++)
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 34Chapter 10 Pointers and Dynamic Arrays
{
// cout << "i,j " << i << " " << j << " ";
cout << matrix3(i,j) << " ";
}
cout << endl;
}
return 0;
}
int myRows(const TwoD& arg){ return arg.maxRows;}
int myCols(const TwoD& arg){ return arg.maxCols;}
TwoD operator+(const TwoD& lhs, const TwoD& rhs)
{
if(lhs.maxRows != rhs.maxRows || lhs.maxCols != rhs.maxCols)
{
cout << "Matrices not same size "
<< " lhs matrix row, col sizes "
<< lhs.maxRows << " " << lhs.maxCols << endl
<< "rhs matrix row, col sizes "
<< rhs.maxRows << " " << rhs.maxCols << endl;
exit(1); // Die if matrices not same.
}
//sizes are same
TwoD sum(lhs.maxRows, lhs.maxCols);
for(int i = 0; i < lhs.maxRows; i++)
for(int j = 0; j < lhs.maxCols; j++)
sum.matrix[i][j] = rhs.matrix[i][j] + lhs.matrix[i][j];
return sum;
}
double& TwoD::operator()(int r, int c) // returns row r, col c element
{
return matrix[r][c];
}
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 35Chapter 10 Pointers and Dynamic Arrays
const TwoD& TwoD::operator=(const TwoD& rhs)
{
if(matrix == rhs.matrix)
return rhs; // lhs == rhs, do nothing
//lhs != rhs, blow away lhs
for(int i = 0; i < maxRows; i++)
delete[] matrix[i];
delete[] matrix;
maxRows = rhs.maxRows;
maxCols = rhs.maxCols;
//reallocate
matrix = new DoubleArrayPtr[maxRows];
for(int i = 0; i < maxRows; i++)
matrix[i] = new double[maxCols];
//deep copy
for(int i = 0; i < maxRows; i++)
for(int j = 0; j < maxCols; j++)
matrix[i][j] = rhs.matrix[i][j];
return rhs;
}
TwoD::TwoD(const TwoD& rhs)
: maxRows(rhs.maxRows), maxCols(rhs.maxCols)
{
matrix = new DoubleArrayPtr[maxRows];
for(int i = 0; i < maxRows; i++)
matrix[i] = new double[maxCols];
for(int i = 0; i < maxRows; i++)
for(int j = 0; j < maxCols; j++)
matrix[i][j] = rhs.matrix[i][j];
}
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 36Chapter 10 Pointers and Dynamic Arrays
TwoD::~TwoD()
{
for(int i = 0; i < maxRows; i++)
delete[] matrix[i];
delete[] matrix;
}
TwoD::TwoD() : maxRows(10), maxCols(10)
{
matrix = new DoubleArrayPtr[maxRows];
for(int i = 0; i < maxRows; i++)
matrix[i] = new double[maxCols];
for(int i = 0; i < maxRows; i++)
for(int j = 0; j < maxCols; j++)
matrix[i][j] = 0;
}
TwoD::TwoD(int maxR, int maxC ) : maxRows(maxR), maxCols(maxC)
{
matrix = new DoubleArrayPtr[maxRows];
for(int i = 0; i < maxR; i++)
matrix[i] = new double[maxCols];
for(int i = 0; i < maxRows; i++)
for(int j = 0; j < maxCols; j++)
matrix[i][j] = 0;
}
/*
I usually use the command line to execute with input redirected
from a file. It is easier than typing in the data every time:
ch10prog1 < ch10prog1Data.txt > ch10prog1out.txt
With this data:
3 4
1.0 2.0 3.0 4.0
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 37Chapter 10 Pointers and Dynamic Arrays
5.0 6.0 7.0 8.0
9.0 8.0 1.0 2.0
3 4
1.0 1.0 1.0 1.0
-1.0 -1.0 -1.0 -1.0
2.0 2.0 2.0 2.0
A run of this produces this output:
Enter the row and column dimensions of the array
Enter 3 rows of 4 doubles each
Echoing the 2 dim. array, matix1
1 2 3 4
5 6 7 8
9 8 1 2
Enter the row and column dimensions of the array
Enter 3 rows of 4 doubles each
Echoing the 2 dim. array, matrix2
1 1 1 1
-1 -1 -1 -1
2 2 2 2
assigning matrix 2 to matrix 3
Displaying the 2 dim array, matrix3 resulting from assignmnet.
rows 3 cols 4
1 1 1 1
-1 -1 -1 -1
2 2 2 2
Echoing the 2 dim array, sum of matrix 1 and 2
rows 3 cols 4
2 3 4 5
4 5 6 7
11 10 3 4
*/
2. Polynomial class.Using dynamic arrays, implement a polynomial class with infix operators
+, -, *.
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 38Chapter 10 Pointers and Dynamic Arrays
Discussion:
In a polynomial, a variable is placeholder for coefficient. If term is missing, the
coefficient is 0. For example, the cubic polynomial
2x3 -3x + 4
or, written out as C++ code,
2*x*x*x –3*x + 4
has the coefficients for terms as listed:
degree 3 term has coefficient 2,
degree 2 term has coefficient 0,
degree 1 term has coefficient -3, and
degree 0 term has coefficient 4.
Note that the term with degree 2 is missing, but we will use a coefficient of 0 to indicate
that. Observe that the size of the coefficient array is 4, one more than the degree.
Use of sparse matrix techniques not recommended. We will assume that there are few
missing terms.
The student is to provide these member functions:
default constructor,
copy constructor,
operator=
destructor
parameterized constructor to create an arbitrary
polynomial
operator+
operator-
operator*
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 39Chapter 10 Pointers and Dynamic Arrays
assign and inspect function (or functions) for
coefficients, indexed by exponent
function to evaluate polynomial as a value of type double
The student is to decide on whether these are to be member, friend, or neither
(standalone).
NOTES:
The default constructor creates an empty polynomial. A zero polynomial has degree 0,
since it has only the zero degree coefficient.
In the coefficient array, the index is the value of the exponent of term having this
coefficient. For example, the index 0 entry is the constant coefficient, the index 1 entry is
coefficient of the linear term, the index 2 entry is the coefficient of the quadratic term
(term in x2), etc.
The size of the coefficient array include a degree 0 entry, so the size is the degree of the
polynomial + 1.
Odd error messages associated with overloading some of the operators occur if you try to
pass a Polynomial object to one of the functions by const reference and do not
implement both these operator[] implementations,
//This version of operator[] is used when an indexed
//expression is used as an l-value.
double& operator[](int degree);
//This version of operator[] is used when an indexed
//expression is used as an r-value.
const double& operator[](int degree)const;
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 40Chapter 10 Pointers and Dynamic Arrays
Instead of difficult-to-understand compiler error messages, you may get linker errors,
which is an even harder situation, since linker errors are not associated with any
particular line in your source.
The alternative is to pass by value. In this example, that is not a problem but in general,
passing a class object has the potential for a large amount of copying of data, so const
reference is the desirable way to do it.
Neither of the operator functions for multiply, add or subtract check for zero lead
coefficients. For polynomials that have many zero terms, these functions should account
for the zero entries. Two polynomials of high degree with few nonzero terms will waste
considerable time multiplying zero entries using this technique. The fix is the use of a
linked list (Chapter 17), or the STL list (Chapter 19.)
My solution follows:
//file: ch10Prog2.cpp
//Polynomial class -- Chapter 10 Programming Problem #2
#include <iostream>
using namespace std;
class Polynomial
{
public:
Polynomial(); // creates an empty polynomial
Polynomial(const Polynomial&);
// The size of the coefficient array is degree of the polynomial + 1.
Polynomial(double coefficient[], int size);
~Polynomial();
//Use indexed polynomial as r-value to inspect coefficient
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 41Chapter 10 Pointers and Dynamic Arrays
//and as l-value to assign coefficient
double& operator[](int degree);
//This is required if we are to have const correctness
const double& operator[](int degree)const;
const Polynomial& operator=(const Polynomial & rhs);
int misuse();
//friend functions:
friend double evaluate(const Polynomial& ploy, double arg);
friend Polynomial operator+(const Polynomial& lsh,
const Polynomial& rhs);
friend Polynomial operator-(const Polynomial& lsh,
const Polynomial& rhs);
friend Polynomial operator*(const Polynomial& lsh,
const Polynomial& rhs);
private:
double * coef;
int size;
};
int main()
{
Polynomial empty;
double one[] = {1};
Polynomial One(one, 1);
double quad[] = {3, 2, 1};
double cubic[] = {1, 2, 0, 3};
Polynomial q(quad, 3); // q is 3 + 2*x + x*x
Polynomial c(cubic, 4);// c is 1 + 2*x + 0*x*x + 3*x*x*x
Polynomial p = q; // test copy constructor
Polynomial r;
r = q; //test operator=
r = c;
cout << "Polynomial q " << endl;
{for(int i = 0; i < 3; i++)
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 42Chapter 10 Pointers and Dynamic Arrays
cout << "term with degree " << i
<< " has coefficient " << q[i] << endl;
}
cout << "Polynomial c " << endl;
{for(int i = 0; i < 4; i++)
cout << "term with degree " << i
<< " has coefficient " << c[i] << endl;
}
cout << "value of q(2) is " << evaluate(q, 2) << endl;
cout << "value of p(2) is " << evaluate(p, 2) << endl;
cout << "value of r(2) is " << evaluate(r, 2) << endl;
cout << "value of c(2) is " << evaluate(c, 2) << endl;
r = q + c;
cout << "value of (q + c)(2) is " << evaluate(r, 2) << endl;
r = q - c;
cout << "value of (q - c)(2) is " << evaluate(r, 2) << endl;
r = q * c;
cout << "size of q*c is " << r.mySize() << endl;
cout << "Polynomial r (= q*c) " << endl;
for(int i = 0; i < r.mySize(); i++)
cout << "term with degree " << i
<< " has coefficient " << r[i] << endl;
cout << "value of (q * c)(2) is " << evaluate(r, 2) << endl;
return 0;
}
int Polynomial::mySize()
{
return size;
}
// creates an empty polynomial
Polynomial::Polynomial():coef(0), size(0)
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 43Chapter 10 Pointers and Dynamic Arrays
{// deliberately empty
}
const Polynomial& Polynomial::operator=(const Polynomial & rhs)
{
if(rhs.coef == coef) //if both coefficient arrays start at the same
return rhs; //place our two Polynomials are the same.
else
{
delete [] coef;
coef = new double[rhs.size];
for(int i = 0; i < rhs.size; i++)
coef[i] = rhs.coef[i];
size = rhs.size;
}
return rhs;
}
Polynomial::Polynomial(const Polynomial& rhs) : size(rhs.size)
{
coef = new double[rhs.size];
for(int i = 0; i < rhs.size; i++)
coef[i] = rhs.coef[i];
}
Polynomial::Polynomial(double coefficient[],
int newSize) : size(newSize)
{
coef = new double[size];
for(int i = 0; i < size; i++)
coef[i] = coefficient[i];
}
Polynomial::~Polynomial()
{
delete [] coef;
}
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 44Chapter 10 Pointers and Dynamic Arrays
//return the coefficient of term in variable to exponent 'degree'
const double& Polynomial::operator[](int degree) const
{
return coef[degree];
}
double& Polynomial::operator[](int degree)
{
return coef[degree];
}
double max(double lhs, double rhs)
{
return (lhs > rhs) ? lhs : rhs;
}
// friend function and operator function implementations
Polynomial operator+(const Polynomial& lhs, const Polynomial& rhs)
{
const int sumSize = max(lhs.size, rhs.size);
double* sumCoefs = new double[sumSize];
for(int i = 0; i < sumSize; i++)
sumCoefs[i] = lhs.coef[i] + rhs.coef[i];
return Polynomial(sumCoefs, sumSize);
}
Polynomial operator-(const Polynomial& lhs, const Polynomial& rhs)
{
int sumSize = max(lhs.size, rhs.size);
double* sumCoefs = new double[sumSize];
for(int i = 0; i < sumSize; i++)
sumCoefs[i] = lhs.coef[i] - rhs.coef[i];
return Polynomial(sumCoefs, sumSize);
}
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 45Chapter 10 Pointers and Dynamic Arrays
// Notes:
// The multiplication routine does not check for zero lead
// coefficients. It also assumes few zero terms.
Polynomial operator*(const Polynomial& lhs, const Polynomial& rhs)
{
int i; int j;
int prodSize = lhs.size + rhs.size;
double* prodCoefs = new double[prodSize];
for(i = 0; i < prodSize; i++)
prodCoefs[i] = 0;
for(i = 0; i < lhs.size; i++)
for(j = 0; j < rhs.size; j++)
prodCoefs[i + j] += lhs[i] * rhs[j];
return Polynomial(prodCoefs, prodSize);
}
double evaluate(const Polynomial& poly, double arg)
{
double value = 0;
int i;
for(i = poly.size - 1; i >= 0; i--)
value = poly[i] + arg * value;
return value;
}
This is the output from this program follows.
Polynomial q
term with degree 0 has coefficient 3
term with degree 1 has coefficient 2
term with degree 2 has coefficient 1
Instructor’s Resource Manual for Savitch Absolute C++ 05/08/23 Page 46Chapter 10 Pointers and Dynamic Arrays
Polynomial c
term with degree 0 has coefficient 1
term with degree 1 has coefficient 2
term with degree 2 has coefficient 0
term with degree 3 has coefficient 3
value of q(2) is 11
value of p(2) is 11
value of r(2) is 29
value of c(2) is 29
value of (q + c)(2) is 40
value of (q - c)(2) is -18
size of q*c is 7
Polynomial r (= q*c)
term with degree 0 has coefficient 3
term with degree 1 has coefficient 8
term with degree 2 has coefficient 5
term with degree 3 has coefficient 11
term with degree 4 has coefficient 6
term with degree 5 has coefficient 3
term with degree 6 has coefficient 0
value of (q * c)(2) is 319