Standard C++ With Object-Oriented Programming BOOK CH 4
Standard C++ With Object-Oriented Programming BOOK CH 4
Exercises 169
CHAPTER FOUR I
Arrays. Pointers.
and Generic Code
u
The array is one of the most useful data structures in programming. Arrays
are often encapsulated in classes as internal mechanisms. A single array can
group many related data items of the same type for easy processing. Random
access to stored elements is most efficient through array indexing. Arrays are usu-
ally one-dimensional (with one index). Multidimensional arrays are also pos-
sible. A matrix multiplication example illustrates the use of two-dimensional
arrays.
The length of an array is static, or fixed at compile time. The vector class
in the Standard Library is a versatile container class that works like an array
with dynamically changing length. The vector class also supplies many useful
member functions, including efficient random access to stored elements.
A pointer is a value that points to the memory location (address) of another
value. Through such indirection, pointers provide flexibility in organizing and
accessing data stored in a program. Arrays and pointers have a very intimate
relationship, and pointers are made easier through array concepts. Pointer
arithmetic is presented carefully and clearly. Pointer and reference parameters
for functions, multiple indirection, and pointer arrays are also discussed.
A set of well-chosen applications demonstrates how arrays and pointers
are used effectively in practice. Sorting text lines with objects provides a com-
plete example that combines many of the constructs and techniques presented.
Pointers to functions, formal functional parameters, and the void * type
are explained individually and then combined to write generic programs, which
are programs that can be used on a multitude of data types.
The array is the simplest data structure beyond the basic types such as char,
int, and float. In earlier chapters, we have already seen some use of arrays.
In general, an array is a section of consecutive memory cells, each large enough
to hold a data element of the same predetermined type. Each cell in an array
is also referred to as an array entry or array element. The declaration
When an array of objects is declared, the no-args constructor is always used first
to initialize each array element. Declaring an array involving objects whose
class has no default constructor is an error. For example, the preceding frac_arr
cannot be declared if Fraction has no default constructor.
The index notation str [n) refers to the (n + l)th entry of str. In general, if
there are k entries, the index goes from a to k - 1. The index notation is used
to store and retrieve values in an array:
str[O)='A' ;
cout.put(str[O));
str[l)='B' ;
cout.put(str[l));
In other words, each array entry is used just like a variable of the declared
type. The advantage is that array entries are indexed and can therefore be used
effectively in loops. Although each array entry is like a variable, the array name
is a constant representing the address (memory location) of the first entry of the
array. Thus, str is a constant whose value is the location of str (0). Because the
array name is a constant, its value cannot be changed. Hence, an array name
cannot be used on the left-hand side of an assignment or with a decrement or
increment operator such as str++.
As an address, an array name can be assigned to a pointer variable of the
appropriate type. Thus,
char *s;
s = str;
s [0] = Z' I ;
is an example.
Up to this point, we have used indexing with arrays and pointers. The
index notation is easy to read and understand, but pointers can also be manip-
ulated directly, as explained in Section 4.2.
int *ptca;
int m[]={1,2,3,4}; II m is an integer array
ptr_a = m; II pointer ptr_a assigned address of m
q
1200
results in m [3] being 11. Since ptr3 is a pointer variable of type int, it can be
assigned the address of an integer array m. For a Vector2D pointer, the same
guidelines apply:
Two unary operators are important in dealing with pointers: &,the address-of
operator, and *, the value-of operator. The address-of operator &produces a
pointer to, or an address of, an lvalue: a variable or an array entry in memory.
The statement
assigns to ap the address of the int array entry m [3] and causes ap to point to
m [3] . Similarly,
assigns the address of the object v [1] to vp. The address-of operator &can
be applied only to data stored in memory and does not work with constants,
register variables, or expressions such as (a + b).
Taking the address of a reference gives the address of the variable refer-
enced. For example,
Note that Cirbuf& is a reference type declaration and has nothing to do with
the address-of operator &.
The value-of operator * is used to access a data item through a pointer.
The * can be used on a pointer variable, a pointer constant, or any expression
that produces a valid pointer value. After int *ptr3 = &ai, the notation
stands for the variable a and behaves exactly the same. Thus, *ptr3 has the
value of a, and
is the same as saying a=5 because it stores 5 at the address where a stores its
value. Note that the value of the pointer variable ptr3 itself is not changed by
this assignment. In general, if ptr is a pointer of a certain type, then *ptr can
be used as a variable of that type. The following code further illustrates this
concept:
The unary operators * and &have the same precedence as unary arithmetic
operators and have higher precedence than binary arithmetic and relational
operators. The parentheses are necessary in the last example because unary
operators such as *, H, and -- associate right to left. Hence,
*ptr_a-- and *(ptr_a--)
are equivalent and would decrement the pointer variable ptr_a rather than the
integer *ptr3.
Another general observation that can be made of the *operator is that *ptr
is always equivalent to ptr [0]. In fact, the compiler automatically converts the
latter notation to the former. This is another reason why pointers are closely
related to arrays.
For an integer pointer variable ptr_a, it is clear what *ptca means. But what
is &ptr3? By definition, it is the address of ptr3. In other words, &ptr3 is a
pointer to an integer pointer. Thus, the artificial sequence
results in the variable k being assigned the value 15,as illustrated in Figure 4.3.
Here is how it works:
1. Since ptr_b points to ptr_a, *ptr_b is ptr_a.
2. Since ptr3 points to k, **ptr_b is k.
3. Thus, **ptr_b = 15 is the same as k = 15.
The same reasoning can be applied to unravel multiple indirections (Sec-
tion 4.10).
+
Pointer Integer Adding an integer quantity to a pointer is not as mys-
terious as it may seem. In fact, we have been doing it implicitly all along. The
familiar array notation b [3] retrieves the desired data by calculating its ad-
dress based on the information that it is the third item after the cell at b, namely,
b [0] . With explicit address arithmetic, the same address can be computed by
The result of this address addition is a pointer to b [3], as shown in Figure 4.4.
Note that this is not adding 3; rather, it is adding three times the size of the
data cell to the address represented by b.
Thus, if b is an int pointer and b is 15084, the pointer b+3 has value
15084 + 12 = 15096, assuming that int takes 4 bytes. But if b is a char pointer,
b+3 becomes 15084 + 3 = 15087. When an address arithmetic expression such
as b+3 is encountered by the compiler, it takes the size of the data cell into
account and produces the appropriate code. This arrangement is convenient
for programming and makes the program independent of the data type sizes
on different computers.
As a result, the general rule holds: If ptr is a pointer and n an integer, then
is the pointer for the nth data cell from the one pointed to by ptr. And the
expression
is the same as ptr [n],where ptr can be a pointer variable or an array name.
To further illustrate pointer usage, let's consider a pointer version of the
library function strcmp, which returns an integer value greater than, equal to,
or less than zero if the C-style string r is greater than, equal to, or less than the
string s, respectively:
t t t t
b b+l b+2 b+3
The definition of strcmp depends on the fact that the string terminator is a
zero-valued character.
As another example, consider a pointer implementation of the library
function strcpy, which makes a copy of its second argument into the first
argument:
char *strcpy(char *s, const char *cs)
{ char *tmp = Si
while (*cs != '\O') * (tmp++) = *(cs++); II copy next character
*tmp = '\0' i
return Si
The pointer variable tmpis first declared and initialized to s. Next, the while
loop copies each character on the string cs until' \0' is encountered. Finally,
the terminator ' \ 0 ' is copied, and the pointer s is returned. The variable tmpis
technically unnecessary because s can serve as a return parameter (Section 4.8).
A common source of error in copying by passing pointers is insufficient
space to receive the data being copied. In this example, s is assumed to point
to the beginning of a reserved space large enough to hold the entire string. The
minimum number of bytes needed is
strlen(cs) + 1
where the '\ 0 terminator occupies the final byte. In this case, it is the respon-
I
sibility of the calling function to ensure that enough space has been provided.
If this is undesirable, the copying function may dynamically allocate space as
in the function dstrcpy (dynamic string copy):
char *dstrcpy(const char *cs}
{ char *s, *tmp;
unsigned size = strlen(cs)+l; II or size t size
tmp = s = new(char[size]); II allocate space
while (*cs != '\0'1 *(tmp++)= *(cs++); II copy next character
*tmp = '\0';
return s;
Note that the pointer returned by dstrcpy can later be freed with delete.
The Standard c++ string class (Section 6.3) provides many operations
that are easier and safer to use than the preceding functions.
Immediately after the while loop, the last step in determining a match is to
pair the terminator of runwith the field delimiter =, one character before *nv.
The pointer subtraction *(nv-l) gives the exact character needed.
For functions, such as match, that return pointers, it is conventional to re-
turn an invalid pointer NULL when the computation fails. The symbolic constant
NULL is defined as zero in the header <stddef. h>.But if you include <iostream>
or other frequent headers, you already have NULL defined. Although NULL can
be assigned to any pointer variable, dereferencing it with * is a run-time error,
which can crash the program.
With pointer subtraction, we have the alternative of going backward on a
sequence of data cells. For instance, a loop may go from the end of an array
to the beginning. This flexibility and power do not come without danger. Be
careful not tofall off the end of the data cells by going beyond the proper range.
contain another type of data or may even be outside of the address space of
the program. But this is only a problem if access is attempted - say, with * t.
In this section, valid pointer operations are summarized for easy reference. The
material here also contains some details not previously mentioned, as well as
topics yet to come in this chapter.
Up to this point, all of the arrays we have seen use a single index or subscript.
Such arrays are one-dimensional. It is possible to have arrays with more than
one subscript. For example,
Now let's consider writing a simplified Matrix class. The example illustrates
the practical use of double arrays in numeric computing.
IIIIIII Matrix.h
#include <iostream>
class Matrix
public:
Matrix() { mat = NULL; }
Matrix(int r, int c); I I (I)
Matrix(double* m, int r, int c); I I (2)
-Matrix() { delete mat; }
double getElement(int i, int j) const;
void setElement(int i, int j, double e);
int rows() const { return nr; }
int cols() const { return nc; }
void times (const Matrix& b, Matrix& ans) const;
void display() const;
private:
double rowTimesCol(int i, double* b, int j, int bc) const;
void setUp(int r, int c);
double* mat; II the matrix
int nr, nc; II rows and cols
IIIIIII Matrix.C
#include <math.h>
#include "Matrix.h"
void Matrix: :setUp(int r, int c)
{ if ( r > 0 && c > 0 )
{ nr = ri nc = Ci
mat = new double[r * C]i
}
else
{ mat NULLi nr=nc=O;
Functions for retrieving dimensions are simple and given in the header file.
Functions to get/ set entries check subscript ranges. The illegaldouble symbol
HUGE3AL (from <math. h» isreturned by getElement for illegalindices.
1 2) ( a + 2c
+ 4c
b + 2d )
3b + 4d
(53 4
6
x (: ;) = 3a
5a + 6c 5b + 6d
A fully developed Matrix class would have many other functions. The
following program can be used to test matrix multiplication and display.
IIIIIII testMatrix.C
#include "Matrix.h"
int main ()
{ double a[2) [3]= { {1.0,-2.0,5.0}, {1.0,2.0,3.0}};
doub 1e b [3] [2]= { {9 .0 ,7 .O}, {-2 .0 ,3 .0},{-1. 0 ,4 .0}};
Matrix x(*a, 2, 3); II pass *a, not a
Matrix y(*b, 3, 2);
Matrix z(2,2);
x. times (y,z);
z .display ();
return 0;
And the output displayed is
8 21)
2 25)
At this juncture, let's study some typical applications of arrays and pointers
to help sharpen the concepts presented. This example with exceptions added
can be found in Section 8.11.
Arrays and pointers are now applied in building a polynomial class. It is im-
portant that pointers are not studied in isolation but in conjunction with other
constructs to solve problems. By doing so, the abstract rules of pointer usage
become concrete and easy to grasp. The polynomial class again demonstrates
techniques for data abstraction and program encapsulation.
With the exception of numbers, polynomials are the most basic math-
ematical structures. They are widely used in many fields of study. Consider
establishing a class Poly for one-variable polynomials with integer coefficients.
Such a polynomial has the familiar form
anxn + an_lxn-1 + ... + alx + ao
where x is the variable and an, an-I, ... , ao are the coefficients. The polynomial
has degree n, and the leading coefficient an 1= O.For example,
public:
Poly() { pol = NULLi } II default constructor
Poly(const int *p, int terms)i II constructor
Poly operator+(const Poly& q) consti II poly addition
Poly operator-(const Poly& q) consti II poly subtraction
Poly operator*(const Poly& q) consti II poly multiplication
unsigned int deg() const II degree
{ return (pol [0] > 0 ? pol [0] : 0); }
void display() consti
1* other members *1
private:
int length() consti II length of pol
int *pol;
The constructor makes sure that the incoming terms are copied into free
storage. The pointer p supplies n terms with 2*n integers but no end marker,
which isstrictlyfor internaluse. A zero polynomial isinstantiatedifn iszero:
IIIIIII Poly.C
#include "Poly.h"
while ( *p >= 0 )
{ cout « *p « " " « *(p+1);
p += 2 ;
if (*p != -1) cout « " " . I
Standard C++ supplies vector objects that can be used just like arrays but have
certain advantages. The dimension of a vector grows dynamically. Random
access to elements through indexing as well as inserting new elements at the
end are very efficient.
You include the header file <vector> to use vectors. The code
#include <vector>
using std: :vector;
establishes arr as a vector object for size elements of the given type. For
example,
iv[O] 10;
iv[1J iv[O] - 4;
If the index is invalid, the value returned may be wrong. To keep indexing efficient,
vector does not detect out-of-bounds indices.
Initial value can be supplied when instantiating a vector:
A vector records its size. Thus, you can write code such as
for ( int i=O; i < dv.size(); i++ )
The vector has another important advantage over primitive arrays: You
can insert elements at the end of a vector, and the size of the vector will grow
as needed:
i v . insert (i v . end ( ), i); Inserts i at back of i v (same as i v . push..back (i) ;)
A vector stores the value, a copy, of whatever is put on the vector. Thus, a copy
of the account susan, not the object itself, is placed in avo
These operations and the fact that it grows in size automatically make a
vector object easier to use than a primitive array in many situations. More
information on vector as part of the Standard Template Library can be found
in Section 11.2. Let's put vector to use in a practical application.
It reads all lines with cin, sorts the lines by comparing the given key in the
lines, and then writes out the sorted lines with couto Each line is assumed to
contain one or more fields separated by white spaces (SPACE or TAB). The key
is an integer indicating which field to use for ordering the lines. If the key is
unspecified, whole lines are compared.
Laura Wang A
Paula Kline C
Richard Brown B
The command mysort can be used on such files: Use key position 2 to sort by
last name and key position 3 to sort by letter grade.
Taking an object-oriented view, we can identify two useful objects:
1. A TextLines object, which holds the input text lines, provides access to
individual lines, knows how to interchange any two lines, and can
output the ordered lines to any given output stream.
2. A SortKey object, which compares the designated keys within any two
text lines.
All of this is tied together with a main program which first processes the
command-line arguments and then performs the three steps just listed.
This example is more extensive than any we have seen so far. It puts
pointers, strings, vector, and many other constructs to use in one example. Its
description is organized according to the preceding outline. The example also
shows how to break down a complicated program into independent objects
that interact to perform the task at hand.
After a TextLines object is initialized, lines can be read and stored within
the object. A vector of string*, with a default capacity of 512, is used to
hold the text lines (lines 1 and 2). Because a vector can grow dynamically,
there is no prior upper limit to the number of lines. Storing string pointers
makes interchanging lines easy and efficient. The operations provided include
reading text lines (input), interchanging lines (swap), inserting and removing
a line,accessing individual text lines by indexing ([)),reporting the number
of lines (length), and displaying the lines (display):
IIIIIII TextLines.h IIIIIII
#include <iostream>
#include <vector>
#include <string>
using std::string; using std: :istream; using std: :vector;
class TextLines
public:
TextLines(int cap=512) { line.reserve(cap);
int length() { return line.size();
void swap(int i, int j); II swaps lines
void input(istream& in); II reads lines into
void remove(int i); II removes line i
-TextLines() ; II destructor
string* operator[) (int i) const II gets line i (2)
{ return (i>=O && i<line.size()) ? line [i) : NULL;
}
void display(ostream& out) const;
bool insert(const string& 1, int i);
private:
int readLines(istream& in); II performs line reading
vector<string*> line; II vector of string*
is a convenient way to obtain line n. The operator [] defined here checks the
index and returns NULL for index out of range.
Also swap and remove are inline functions defined in the header filebut
outside of the class declaration.
inline void TextLines::swap(int i, int j)
{ string* tmp = line[i];
line[i) line[j);
line[j) = tmp;
The member input calls the active routine readLines to read lines. The function
readLines returns the number of lines read if successful. If the value returned
is -lor 0, appropriate error messages are displayed, and the badbi t of the
input stream is set (line A). Setting badbi t causes in. bad () to return true.
The readLines function reads each line into a string using the get line
function (line B) from the Standard Library (Section 2.5). The string is then
inserted at the end of the vector to hold the lines (line D). Input failure is
checked (line C), and an error code or the number of lines read is returned
(line E).
A simple loop displays all lines contained in a TextLines object. The de-
structor frees up dynamically allocated storage.
void TextLines::display(ostream& out) const
{ for ( int i = OJ i < line.size() j i++ )
out « *line[i] « endl;
TextLines::-TextLines()
{ int len = line.size();
while(len-- > 0) delete line[len];
Line sorting has two aspects: quicksort and key comparison. A SortKey object
supplies the capability to identify and compare specific key positions.
When initialized, a SortKeyobjectholds the key position and the delimiter
string, quantities used by the private member function key to identify the sort
key in a text line (string). An established SortKey object is used to compare
two text lines.
Letting the user ofmysort specify the key field and implementing a separate
key-comparison object provide the kind of flexibility that characterizes good
programs. On top of this, the field delimiters are also settable rather than
hard-coded. Other useful delimiters include =, :, ., and, .
IIIIIII SortKey.h
#include <string>
using std: :string;
public:
explicit SortKey(int pos
{ delim = dlm;
position = pos;
}
void setDelim(const char* s) { delim = s; }
II compare lines
int lineCompare(const string& a, const string& b) const;
int keyCompare(const string& k, const string& 1) const
{ return k.compare(key(l)); }
private:
string delim; II token delimiters
int position; II key token position
II extract key
const string key(const string& s) const;
Care has been taken in using reference parameters and in declaring them const
appropriately. You should do the same in your C++ programs.
The function key extracts the sort keys with member functions of string:
str1. find_first_of (str2, i) ;
str1. find_first-.not_of (str2, i) ;
Examining str1 from position i, the index of the firstcharacter that is (isnot)
contained in s tr2 isreturned. Ifno such character isfound in s tr1,the constant
string: :npos is returned. The argument str2 can be given either as a string
object or a C-style string.
The functions are put to good use skipping fieldpositions (lineF) to extract
the desired key (line G). An empty string object is returned ifthe line s does
not contain the key fieldindicated by position.
11/1111 SortKey.C
#include <iostream>
# include <string>
#include "SortKey.h"
The objective of this part of the program is to take the text lines in txtobj and
sort them into order by comparing the appropriate keys.
The approach is to adapt the quicksort procedure used for integer arrays
(Section 3.4) to the TextLines object txtobj. The recursive function quicksort
itself is mostly the same as before. However, now txtobj . swap interchanges
lines. Furthermore, the partition function uses lineCompare of the given
SortKey to compare two lines using the user-specified keys:
#include <iostream>
using std: :endl; using std: :cout; using std: :cerr;
The library function atoi converts a numeric string to an integer. The test on
line H checks the condition set on line A in function TextLines: :input.
All of the parts are now in place. The main program processes command-
line arguments and sets up the SortKey object. The program then gets a
TextLines object txtobj, reads input lines into it, applies quicksort pass-
ing to it sk to make comparisons, and then asks txtobj to display itself. The
simple and clear structure of the main program testifies to the advantages of
the OOP approach.
Compile the binary files TextLines .a and SartKey. a first and then simply
combine mysart .a with them to get mysort. For example,
g++mysart.a SartKey.a TextLines.a -a mysort
gets you the executable file mysort. Run it with
mysort 2 < somefile
Let's now turn our attention to the usage of pointers and arrays in function calls.
All aspects of pointer usage related specifically to function calls are collected
in this section for easy reference.
When a pointer is passed to a function, the called function knows the type
and the starting location of a piece of data but not necessarily where it ends.
For example, the formal parameter int *y can be used to receive the address of
a single integer or an array of integers. Clearly, some other arrangement must
be made. Using a special terminator is a convenient way to define the extent
of a data item. The conventional terminator \0 for C-style character strings
1 1
The data in question may be global, local to the calling function, or allocated
in the scope of some function in the chain of function calls. More details on
these two modes of usage are discussed next.
Just like the operator new,a function can also produce a pointer as a return
value. However, be careful when you define a function that returns a pointer.
The returned pointer must not point to a local variable in the scope of the
returning function. An automatic variable in a function is destroyed after
the function returns. Certainly, it does not make sense to return a pointer to
something that disappears. For example, it is incorrect to use
int *bad(int x)
int Yi
y = x * Xi
return &y;
This function produces no syntax error when compiled and will actually exe-
cute. Unfortunately, when the returned pointer is used, it may be pointing to a
memory cell that has been destroyed (used for other purposes). A pointer that
has lost its object in memory is referred to as a dangling pointer and should be
avoided.
When a function returns a pointer, make sure that it points to one of the
following:
A C++ reference is not a basic data type but an alias for a variable or a class
object. In many situations where a pointer formal parameter is needed, a
reference parameter can be used instead. Certainly, passing an argument by
reference involves no copying, and changes made to a reference modify the
original. When passing a reference argument, make sure it is an lvalue. A
reference parameter is sometimes simpler to use than a pointer because there
is no need for any indirection notations such as *ptr or ptr->member.
When passing arrays, it is advisable to pass pointers rather than references.
An array name, not being an lvalue, actually cannot be passed to a reference
parameter or used to initialize a reference variable. A pointer variable should
be used instead. Thus,
void f(int* &ptr_ref);
int arr [] {l, 2,3,4} ;
f( arr ); II error; arr is a constant pointer
int* ap = arr; II pointer variable ap
f( ap ); II o.k.
A function can also return a reference (Section 3.8). A local variable in the
returning function is a bad choice as a reference return value. The variable is
destroyed after the function returns. In addition, there can be neither pointers
to references nor arrays of references.
A variable x may be used to access a memory cell directly (Figure 4.7). The value
of x is stored in its associated data cell. As stated before, however, the cell of a
pointer variable ptr stores the address of another data cell whose content can
be accessed indirectly through the value of ptr using the value-of operator *.
The quantity *ptr can be treated just like a variable and can be used on the
left-hand side of an assignment.
A pointer provides one level of indirection. It is possible to have double
indirection if the value of *ptr is also a pointer. Referring again to Figure 4.7,
the value of ptra is the pointer variable *ptra, and the value of *ptra is an int
variable **ptra, which has the value 15.
15
x
0 .. 15
ptr *ptr
declares the variable ptr to be of type int *, not *prt of type int as some
programmers may mistakenly suppose. Similarly, the declarations
char **Ci
char ***di
give c type char ** and d type char ***,respectively. A handy example of the
type char ** is the array char *argv [] for command-line arguments. Thus,
is possible. In fact, we have already used a few variables of type char **,
including the lines in the sorting example (Section 4.7).
To understand the meaning of d, think of it as the address of an array of
different groups of text lines:
d[O] is line_groupl
d[l] is line_group2
d[2] is line_group3
is fine, too. This notation is simpler and is used from now on. In case average
is an overloaded function, a pointer to the correct version will be produced by
deducing it from the signature used in declaring fn.
Declaring a functional variable may look complicated, but it is really sim-
ple. Just use the function prototype with the function name replaced by the
notation (* var ). Specifically,the general form
declares the variable var to be a pointer to a function that takes the specified
arguments and returns values of the given type.
A functional variable declaration looks strange because it deviates from the
normal syntax of variable declarations. What confuses people is the position
of the declared variable relative to the other parts in such a declaration. It
may take a little getting used to, but the position is perfectly well defined: The
variable is put where the function name would be in a function prototype. The
examples in Figure 4.8 should help as well. More generally, complicated C++
declarations can be deciphered by realizing that a declaration always spells out
how the declared variable is used. For example, the first declaration in Figure 4.8
shows that (*fn3) (3.5, 6.7) is a function call that returns a float.
Since a functional variable declaration is somewhat long and complicated
to look at, we can simplify things greatly by using typedef. For instance,
defines the type name INT]N. Note that the type name is placed where the
variable would be. INT]N can then be used to declare any functional variable
of that type. In particular, we can declare fn in the average example with
l
float
l
(* fn_a)
l
(float, float) ;
float (* fn_b) (int) ;
void (* fn_c) (char *, int);
char * (* in_d) ();
Formal Functional Parameters The purpose of a function variable,
almost exclusively, is to pass a function name to another function or class
object. To make things easy to understand, let's examine an artificial example:
int mystery(int a, int b, int (* fn) (int, int))
{ return fn(a,b);
}
Here the function mystery takes three arguments: two integers and a functional
parameter fn. The declaration of the formal parameter fn is, as it should be,
the same as declaring a variable of the intended type. By using the typedef
INT_FN, we can code a simpler looking version of mystery:
The function mystery simply calls the function pointed to by fn and returns
the value of the call. Here are some functions, in addition to average, that can
be passed to mystery:
extern int gcd (int a, int b); (The gcd function is defined in Section 3.4.)
Here is how to make calls to mystery. Note that the names average, gcd,
and sqsumare pointers to the definitions of the functions:
int main()
{ cout« mystery(16, 30, average) « endl; II is 23
cout « mystery(3, 4, sqsum) « endl; II is 25
cout « mystery(312, 253, gcd) « endl; II is 1
return 0;
In fact, any function that takes two int arguments and returns an int can be
passed to mystery.
The one topic that remains on our discussion list is the void * pointer, a
mechanism to pass data or objects of arbitrary type and a necessary comple-
ment to the functional argument facility.
1. An arbitrary array.
2. A test function for the end of the array.
The function arblen computes the length of the array any without knowing
anything about its type. Thus, arblen is a generic array-length function. The
functional parameter isEnd is the supplied function that detects the termination
of the given array. What arblen does is simply call isEnd repeatedly and count
the number of calls. The count is then returned when isEnd detects the end of
the array:
Let's consider how void *and functional arguments combine to define generic
functions. Our first example is a function that checks to see whether all entries
of an array satisfy some given condition. Questions such as
• Is every entry an even/ odd number?
• Is every entry positive?
• Is every entry zero?
• Is every character lowercase?
are often asked. We can write one generic function, and_test, that takes care
of them all. The and in the name expresses the concept of logical-and: All tests
must be true before the answer is true:
Our next example further illustrates the flexibility that functional arguments
provide. Let's write a sorting program that can rearrange a sequence of items
of any type into any specified order, thus making the sorting program generic
and very reusable. The strategy is to modify the quicksort program to use the
following three arguments:
1. An arbitrary array.
2. A caller-specified comparison function crop.
3. A supplied element-interchange function swap.
typedef int (* CMP_FN) (void *, int, int);
typedef void (* SWAP_FN) (void *, int, 'int);
extern void quicksort
(void *any, II arbitrary array to be sorted
int 1, I I start index
int r, I I end index
CMP_FN cmp, II supplied comparison function
SWAP_FN swap II supplied interchange function
) ;
1111111 arbqsort.C
# include "arbqsort.h"
void quicksort (void *any, int 1, int r, CMP_FN cmp, SWAP_FN swap)
{ if ( 1 >= r II 1 < 0 ) return;
II call with supplied functions
int k = partition (any, 1, r, cmp, swap);
II recursive calls
quicksort (any, 1, k-l, cmp, swap);
quicksort (any, k+l, r, cmp, swap);
The parti tion function, which is placed before quicksort in the actual
file,now becomes ~
static
int partition(void *any, int 1, int r, CMP_FN cmp, SWAP_FN swap)
{ register int i=l, j=r;
II choose middle element as pe
swap (any, (i+j)12, r); II pe moved to r
while (i < j)
{ while (cmp(any, i, r) <= 0 && i < j) iH; II use supplied cmp
while(j > i && cmp(any, j, r) >= 0) j--; II use supplied cmp
if (i < j) swap(any,i++,j); II use supplied swap
}
if (i != r) swap(any,i,r); II use supplied swap
return i;
Note that indexing of any is not possible in parti tion because of its type
void *. But once its value is passed to the function cmpor swap, the formal
pointer parameter there can be used normally.
With these modifications, the generic quicksort can sort arbitrary arrays
when given appropriately supplied 'Comparison and swap functions. To sort
integer arrays, the following set of functions can be defined:
II in increasing order
quicksort (a, 0, 5, reinterpret_cast< CMP_FN
>(cmp_bigger) ,
reinterpret_cast< SWAP_FN
>(intswitch))i
II in decreasing order
quicksort (a, 0, 5, reinterpret_cast< CMP_FN
>(cmp_smaller),
reinterpret_cast< SWAP_FN
>(intswitch))i
to sort the TextLines object txtobj. Note that here we are sorting text lines
encapsulated in an object with the same generic quicksort function, which drives
horne the point of handling data of arbitrary type.
The qsort library function «stdlib. h» implements quicksort for an ar-
bitrary array with elements stored in consecutive memory locations. The
quicksort defined in this section does not make assumptions on how the ele-
ments to be sorted are stored. Clearly, the basic sorting mechanism is in place
and will remain unchanged. The header arbqsort. h provides the interface to
client files of the generalized sorting facility.The application of this mechanism
in a new area is simply a matter of providing the appropriate comparison and
swap functions.
Standard C++ supplies a set of predefined generic functions (include
<algori thm» that can be used with generic container classes. The generic func-
tions take functional arguments to perform tasks. The header <functional>
supplies a variety of functions ready to use in such applications. These topics
are covered in Chapter 11.
We have already seen some uses of the operator new for dynamic storage
allocation. In this section, let's consider how dynamic storage is applied in
relation to arrays and pointers.
are not possible. However, enough consecutive cells from free storage can
accommodate all elements of a two-dimensional array and can be fabricated
into the desired array structure.
The function dyn_2d returns a double** that can be used as a two-
dimensional double array:
Enough storage for all consecutive data cells is allocated. In addition, storage
for pointer cells for each row is allocated. The array and pointer cells are then
initialized before the array name is returned. An overloaded version can also
take care of freeing the storage properly:
void dyn_2d(double** a)
delete [] *a; II free data cells
delete [] a; II free pointer cells
IIIIIII Month.h
#include <iostream>
using std: :ostream;
class Month
{ public:
typedef short Date;
typedef Date *Week; II Week is short *
enum Day {SUN=O,MON,TUE, WED, THU, FRI, SAT};
Month(Date ndays, enum Day firstday);
void display(ostream& out) const;
-Month () ;
private:
Week *month; II internal representation
The constructor initializes a month object when given the number of days
in the month (ndays) and the day of the week for the first day of the month:
For each week, seven Dates are allocated (lines 1 and 4) to record the days of
the week where pointers returned by new are automatically cast to type Week
(short *). Days unused in the first and last week are assigned zero (lines 2
and 5). The dates are filled in by the for loop (line 3). Note that arithmetic
results involving enumvariables are cast properly (lines 2 and 5).
The automatic array wrecords the weeks created. A dynamic array month
is allocated (line 6)/ and the entries of ware copied into the data member
month. Note that month is terminated by a NULLpointer. Figure 4.9 illustrates
the organization of month.
To establish a Month object, use something like
Here the class scope notation is used to specify an enumconstant defined in the
Monthclass. (In Section 6.7/ displaying a monthly calendar is again considered.)
168 Chapter 4 ARRAYS, POINTERS, AND GENERIC CODE
month •. wk[O] •. 0 0 1 2 3 4 5
wk[l]
wk[2]
--------- 6 7 8 9 10 11 12
~ 13 14 15 16 17 18 19
The array and the pointer are closely related constructs that help store and
manipulate data of any given type. Their mastery is essential for the low-level
coding that is required of you. Such details should be encapsulated by objects
when possible so that other parts of the program can ignore them.
An array stores elements of the same type in consecutive memory locations.
The array name is a constant pointer to the first array cell. A two-dimensional
array is actually a one-dimensional array whose elements are pointers to one-
dimensional arrays of the same length. This scheme can be generalized into
arrays of higher dimensions. Arrays allocated at compile time have fixed max-
imum dimensions. Arrays whose dimensions are known only at run time can
be allocated dynamically.
The vector template supplies an attractive alternative to basic arrays. A
vector is not limited to a fixed size and can be subscripted just like arrays. Be-
sides, vector offers many useful operations such as size (), insert (), erase ( ),
pushJ>ack ( ), and popJ>ack ( ) .
A pointer provides the ability to manipulate addresses and supplies a level
of indirection to data or function access. The unary address-of operator & can
be applied to any memory-based variable, class object, or array element to
obtain a pointer. The unary value-of operator * is applied to a pointer, ptr,
to access the value stored at that memory address. Furthermore, the notation
*ptr can be used on the left-hand side of an assignment to store a new value
at that location. Hence, the combination *ptr can be thought of and used as
a variable of the appropriate type. Both pointer arithmetic and indexing are
convenient to step through the cells of an array.
The TextLines class is a substantial 00 programming example that uses
a vector of pointers to represent a sequence of text lines internally. The class
supplies access, interchange, input, and output functions for text lines, hiding
the implementation details. The associated SortKeyclass helps locate and com-
pare keys in text lines. The program mysort uses these objects and quicksort to
sort text lines using user-specified keys.
A generic program is one that works for many different types of data
items. The function pointer, functional arguments, the void * type, and im-
plicit/ explicit type-casting combine to provide one way to write generic pro-
grams. With these constructs, a generic quicksort is written to sort arbitrary
data items or objects using any supplied comparison function. The generic
quicksort is shown reused for integer and text-line sorting.
4. Rewrite the circular buffer class given in Section 3.12with pointers and pointer
arithmetic instead of indexing.
5. Consider the SortKey class. In practice, there is a need for a composite key, a
key consisting of a primary key, a secondary key, and so on. When two items
are compared under a composite key, the primary key is applied first. The
secondary key is applied only if the two items are equal under the primary
key and so on. Define a CompsitKey class, and use it in a sorting program that
allows multiple sort keys. (Hint: A CompositKeycontains several SortKeys.)
6. Does the C++ compiler on your computer allow you to increment a void *
pointer? If it does, give a good reason why you should not use it.
7. Is it possible to pass a void * actual argument to, say, an int * formal pa-
rameter in a function call? If this is not possible, can you specify two distinct
ways to make such a call by explicit casting? Show your solution with actual
working-code examples. (Hint: Cast the argument or the function.)
int a = 56;
int*& ptr_ref = &a;
int i;
int & j i;
int&* ptrl = &j;
int* ptr2 = &j;
10. Modify the quicksort routine to remove duplicate entries in the input array.
The routine should return the length of the final sorted array, which may be
shorter than the given array before sorting. (Hint: parti tion should record
positions of duplicate entries.)
11. Consider the address-of operator &.Listthe type of quantities to which it cannot
be applied. Also consider the value-of operator *. List the type of quantities to
which it cannot be applied.
12. Discuss the differences and equivalence of the following two notations. In what
situations are these notations interchangeable?
type x [] i and type *x;
15. If a class Xyzhas no default constructor, can you declare an array, a vector,
with Xyzcells? Why?
16. Write a test program that inserts instances of class Abcinto an empty vector
making it grow. Detect how many Abccopy and destructor calls are made as
the vector grows. What happens if you reserve a number of entries for the
vector before running the same experiment?
17. Consider the destructor of TextLines. It deletes all strings kept on line. Is this
correct? How can the class be certain that the strings have been allocated by
new?
18. Write a simple class Matrix with double entries. Support the following pub-
lic operations: (a) to create a matrix, given dimensions m and n, and a two-
dimensional array of entries; (b) to delete any rows or columns; (c) to inter-
change any rows or columns; and (d) to display the matrix.
20. Write a function toEach that takes an array of arbitrary data items and applies
a supplied unary function to each value stored in the array. For example, it can
be used to increment each value by 1.
While a plethora of books is used to teach the C++ programming course, many are
technical reference books, which generally don't include examples and exercises. Wang's text
treats C++ as a tool for bridging real-world application, addressing basic theoretical concepts of
object-oriented programming. The material is organized and presented in a simple, concise, and
ea.sy-to-follow manner. Wang has developed interesting exam~s and challenging exercises that
remforce the text's hands-on approach. '",