Chapter 10 Pointers and Dynamci Arrayswps.aw.com/wps/media/objects/80/82902/SavitchIMCh10.doc ·...

64
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 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.

Transcript of Chapter 10 Pointers and Dynamci Arrayswps.aw.com/wps/media/objects/80/82902/SavitchIMCh10.doc ·...

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