C++ and Object-Oriented
C++ and Object-Oriented
ii
vi
Preface
This book is intended to be an easy, concise, but rather complete, introduction to the ISO/ANSI C++ programming language with special emphasis on object-oriented numeric computation for students and professionals
in science and engineering. The description of the language is platformindependent. Thus it applies to dierent operating systems such as UNIX,
Linux, MacOS, Windows, and DOS, as long as a standard C++ compiler
is equipped. The prerequisite of this book is elementary knowledge of calculus and linear algebra. However, this prerequisite is hardly necessary if
this book is going to be used as a textbook for teaching C++ and all the
sections on numeric methods are skipped. Programming experience in another language such as FORTRAN, C, Ada, Pascal, Maple, or Matlab will
certainly help, but is not presumed.
All C++ features are introduced in an easy way through concepts such
as functions, complex numbers, vectors, matrices, and integrals, which are
familiar to every student and professional in science and engineering. In
the nal chapter, advanced features that are not found in FORTRAN, C,
Ada, or Matlab, are illustrated in the context of iterative algorithms for
linear systems such as the preconditioned conjugate gradient (CG) method
and generalized minimum residual (GMRES) method. Knowledge of CG,
GMRES, and preconditioning techniques is not presumed and they are explained in detail at the algorithmic level. Matrices can be stored in full (all
entries are stored), band (entries outside a band along the main diagonal
are zero and not stored to save memory), and sparse (only nonzero entires
are stored to save memory) formats and exactly one denition for CG or
GMRES is needed that is good for all three matrix storage formats. This is
viii
in contrast to a procedural language such as FORTRAN, C, Ada, or Matlab, where three denitions of CG and GMRES may have to be given for the
three matrix formats. This is one of the salient features of object-oriented
programming called inheritance: The CG and GMRES methods are dened for a base class and can be inherited by classes for full, band, and
sparse matrices. If one later decides to add, for example, a symmetric (full,
band, or sparse) storage format (only half of the entries need be stored for a
symmetric matrix to save memory), the code of CG and GMRES methods
can be reused for it without any change or recompilation.
Another notable feature is generic programming through templates, which
enables one to dene a function that may take arguments of dierent data
types at dierent invocations. For example, the same denition of CG and
GMRES can deal with matrices with entries in single, double, and extended
double (long double) precisions and complex numbers with dierent types
for real and imaginary parts. Again, using C or FORTRAN would require
dierent versions of the same function to handle dierent data types when
the implementation of the function is going to be changed later for robustness or eciency, then every version has to be changed, which is tedious and
error-prone. The operator overloading feature of C++ enables one to add
and multiply matrices and vectors using + and in the same way as adding
and multiplying numbers. The meaning of the operators is user-dened and
thus it provides more
exibility than Matlab. For example, the operators
+ and can be used to add and multiply full, band, and sparse matrices.
These and other features of C++ such as information hiding, encapsulation, polymorphism, error handling, and standard libraries are explained in
detail in the text. With increasingly complicated numeric algorithms, many
sophisticated data structures can be relatively easily implemented in C++,
rather than in FORTRAN or C this is a very important feature of C++.
The C++ compiler also checks for more type errors than FORTRAN, C,
Ada, Pascal, and many other languages do, which makes C++ safer to use.
However, there are trade-os. For example, templates impose compiletime overhead and inheritance (with dynamic type binding) may impose
run-time overhead. These features could slow down a program at compileand run-times. A good C++ compiler can minimize these overheads and a
programmer can still enjoy programming in C++ without suering noticeable compile- and run-time slowdowns but with spending possibly much
less time in code development. This is evidenced by the popularity of C++
in industry for database, graphics, and telecommunication applications,
where people also care dearly about the speed of a language. The fact is
that C++ is getting faster as people are spending more time optimizing the
compiler. Some performance comparisons on nite element analysis have
shown that C++ is comparable to FORTRAN 77 and C in terms of speed.
On the other hand, C++ has the potential of outperforming FORTRAN 77
and C for CPU-intensive numeric computations, due to its built-in arithmetic compound operators, template mechanisms that can, for example,
ix
tial dierential equations (x11.5) with real and complex coecients. The
coverage of numeric algorithms is far from being complete. It is intended
to provide a basic understanding of numeric methods and to see how C++
can be applied to program these methods eciently and elegantly. Most
of them are covered at the end of a chapter. People with an interest in
learning how to program numeric methods may read them carefully, while
readers who just want to learn C++ may wish to skip them or read them
brie
y.
C++ is not a perfect language, but it seems to have all the features and
standard libraries of which a programmer can dream. My experience is that
it is much easier to program than FORTRAN and C, because FORTRAN
and C give too many run-time errors and have too few features and standard
libraries. Many such run-time errors are hard to debug but can easily be
caught by a C++ compiler.
After all, C++ is just a set of notation to most people and a person does
not have to know all the features of C++ in order to write good and useful
programs. Enjoy!
xi
Acknowledgments:
This book has been used as the textbook for a one-semester undergraduate
course on C++ and numeric computing at Wayne State University. The
author would like to thank his students for valuable suggestions and discussions. Thanks also go to the anonymous referees and editors whose careful
review and suggestions have improved the quality of the book. Readers'
comments and suggestions are welcome and can be sent to the author via
email (dyang@na-net.ornl.gov).
Daoqi Yang
Wayne State University
Detroit, Michigan
June 28, 2000
xii
Contents
Preface
1 Basic Types
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
vii
1
1
6
8
8
10
11
14
14
15
19
19
20
21
25
25
26
27
28
28
29
xiv
Contents
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Derived Types
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
30
31
33
33
34
35
35
36
36
37
41
44
47
51
51
52
54
57
57
60
64
64
67
68
70
70
72
72
72
73
73
74
75
79
81
81
82
83
84
85
86
88
92
94
Contents
xv
4.1 Namespaces . . . . . . . . . . . . . . . . . .
4.1.1 Using Declarations and Directives .
4.1.2 Multiple Interfaces . . . . . . . . . .
4.1.3 Namespace Alias . . . . . . . . . . .
4.1.4 Unnamed Namespaces . . . . . . . .
4.1.5 Name Lookup . . . . . . . . . . . . .
4.2 Include Files . . . . . . . . . . . . . . . . .
4.2.1 Include Files for Standard Libraries
4.2.2 User's Own Include Files . . . . . .
4.2.3 Conditional Include Directives . . .
4.2.4 File Inclusion . . . . . . . . . . . . .
4.3 Source Files and Linkages . . . . . . . . . .
4.3.1 Separate Compilation . . . . . . . .
4.3.2 External and Internal Linkages . . .
4.3.3 Linkage to Other Languages . . . . .
4.4 Some Useful Tools . . . . . . . . . . . . . .
4.4.1 How to Time a Program . . . . . . .
4.4.2 Compilation Options and Debuggers
4.4.3 Creating a Library . . . . . . . . . .
4.4.4 Makele . . . . . . . . . . . . . . . .
4.5 Standard Library on Strings . . . . . . . . .
4.5.1 Declarations and Initializations . . .
4.5.2 Operations . . . . . . . . . . . . . .
4.5.3 C-Style Strings . . . . . . . . . . . .
4.5.4 Input and Output . . . . . . . . . .
4.5.5 C Library on Strings . . . . . . . . .
4.6 Standard Library on Streams . . . . . . . .
4.6.1 Formatted Integer Output . . . . . .
4.6.2 Formatted Floating Point Output .
4.6.3 Output Width . . . . . . . . . . . .
4.6.4 Input and Output Files . . . . . . .
4.6.5 Input and Output of Characters . .
4.6.6 String Streams . . . . . . . . . . . .
4.7 Iterative Methods for Nonlinear Equations .
4.7.1 Bisection Method . . . . . . . . . . .
4.7.2 Newton's Method . . . . . . . . . . .
4.8 Exercises . . . . . . . . . . . . . . . . . . .
5 Classes
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
113
113
117
119
120
121
121
122
122
124
126
128
129
129
129
133
134
134
136
138
139
142
142
143
144
144
145
146
146
148
149
150
153
157
158
159
165
167
173
xvi
Contents
5.3
5.4
5.5
5.6
5.7
5.8
5.9
5.10
Friends . . . . . . . . . . . . . . . . . . . . . . . . . . .
Static Members . . . . . . . . . . . . . . . . . . . . . .
Constant and Mutable Members . . . . . . . . . . . .
Class Objects as Members . . . . . . . . . . . . . . . .
Array of Classes . . . . . . . . . . . . . . . . . . . . .
Pointers to Members . . . . . . . . . . . . . . . . . . .
Numeric Methods for Ordinary Dierential Equations
Exercises . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6 Operator Overloading
7 Templates
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
184
185
187
189
191
193
194
199
203
204
206
206
207
207
211
220
223
226
229
231
231
233
234
235
235
235
239
240
241
242
243
245
246
247
248
250
251
252
253
256
257
258
259
Contents
8 Class Inheritance
9 Exception Handling
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
xvii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
262
263
263
264
264
265
266
270
273
273
275
279
283
283
286
286
287
287
288
289
291
296
296
297
300
301
302
303
304
305
306
308
309
309
316
319
319
323
325
326
326
327
327
xviii
Contents
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
References
Index
333
333
334
341
344
347
348
348
355
360
365
365
366
366
367
368
371
372
372
372
374
375
385
385
390
398
401
401
406
408
414
424
427
430
This is page 1
Printer: Opaque this
Basic Types
This chapter starts with a sample C++ program and then presents basic
data types for integral and
oating point types, and two special types
called bool and void. Towards the end of the chapter, numeric limits are
introduced such as the largest integer and smallest double precision number
in a particular C++ implementation on a particular computer. Finally,
identiers and keywords are discussed.
1. Basic Types
main() {
int n, m
// declare n and m to be integers
cout << "Enter two integers: \n"
// output to screen
cin >> n >> m
// input will be assigned to n, m
if (n
int
n =
m =
}
> m) {
temp = n
m
temp
//
//
//
//
The rst three lines in the program are comments enclosed by = and
are usually used for multiline comments. Other comments in the
program are short ones and preceded by two slashes == they can start from
the beginning or middle of a line extending to the rest of it.
Input and output are not part of C++ and their declarations are provided
in a header le called iostream that must be included at the beginning of
the program using
=, which
#include <iostream>
lets the program gain access to declarations in the namespace std. All
standard libraries are dened in a namespace called std. The mechanism
namespace enables one to group related declarations and denitions together and supports modular programming see Chapter 4 for more details.
Before standard C++, these two statements could be replaced by including
<iostream:h>. The compiler will not recognize the identiers cin and cout
if the standard library <iostream> is not included. Similarly, the math
library < math:h > must be included when mathematical functions like
sin (sine), cos (cosine), atan (arctangent), exp (exponential), sqrt (square
root), and log (logarithm) are used. The header les for standard libraries
reside somewhere in the C++ system, and a programmer normally does not
have to care where. When they are included in a program, the system will
nd them automatically. See x4.2 for more information on how to include
a le.
The symbol << is called the output operator and >> the input operator.
The statement
cout << "Enter two integers: \n"
outputs the string "Enter two integers:" followed by a new line to the
screen, telling the user to type in, on the keyboard, two integers separated
by a whitespace. Then the statement
cin >> n >> m
causes the computer to pause while characters are entered from the keyboard and stored in memory at locations identied by n and m. It is equivalent to the following two statements.
cin >> n
cin >> m
Notice that a variable must be declared before it can be used. The integer
variables n and m are declared by the statement:
int n, m
// sum is initialized to 0
This statement not only declares sum to have type double, but also assigns
it an initial value 0. A variable may not have to be initialized when it is
declared.
In the if conditional statement
if (n > m) {
1. Basic Types
int temp = n
n = m
m = temp
the statements inside the braces will be executed if the condition n > m
(n is strictly larger than m) is true and will be skipped otherwise. This if
statement instructs the computer to swap (interchange) the values of n and
m if n is larger than m. Thus, after this if statement, n stores the smaller
of the two input integers and m stores the larger. Notice that a temporary
variable temp is used to swap n and m. The braces in an if statement can
be omitted when there is only one statement inside them. For example, the
following two statements are equivalent.
if (n > m) m = n + 100
if (n > m) {
m = n + 100
}
// one statement
// an equivalent statement
The second statement above can also be written in a more compact way:
if (n > m) { m = n + 100 }
rst declares variable i of type int and assigns the value of n to i. If the value
of i is less than or equal to the value of m (i.e. i <= m), the statement inside
the braces sum += i will be executed Then statement i ++ (equivalent to
i = i + 1 here) is executed and causes the value of i to be incremented by
1. The statement sum += i (equivalent to sum = sum + i, meaning sum
is incremented by the value of i) is executed until the condition i <= m
becomes false. Thus this for loop adds all integers from n to m and stores
the result in the variable sum. The braces in a for loop can also be omitted
if there is only one statement inside them. For example, the for loop above
can also be written in a more compact way:
for (int i = n i <= m i++) sum += i
Except for possible eciency dierences, this for loop can be equivalently
written as
for (int i = n i <= m i = i + 1) sum = sum + i
First the character string "Sum of integers from " is output, then the value
of n, and then string " to ", value of m, string " is: ", value of sum, and
nally a newline character 'nn'.
Suppose the above program is stored in a le called sample:cc. To compile
it, at the UNIX or Linux command line, type
c++ -o add sample.cc
the object code will be automatically written in a le called a:out in UNIX
(or Linux) and sample:exe in DOS. On other operating systems, compiling
or running a program may just be a matter of clicking some buttons. Now
you may type the name of the object code, namely, add or a:out at the
UNIX command line, and input 1000 and 1. You shall see on the screen:
Enter two integers:
1000 1
Sum of integers from 1 to 1000 is: 500500
In this run, the input number 1000 is rst stored in n and 1 in m. Since
n is larger than m, the if statement interchanges the values of n and m so
that n = 1 and m = 1000. The for loop adds all integers from 1 to 1000.
Here is another run of the program:
Enter two integers:
1 1000
Sum of integers from 1 to 1000 is: 500500
In the second run, the input number 1 is stored in n and 1000 in m. Since
n is smaller than m, the if statement is skipped.
A user may not have to input values from the terminal screen. Alternatively, this sample program can also be written as
#include <iostream>
using namespace std
1. Basic Types
main() {
int n = 1
int m = 1000
double sum = 0
for (int i = n i <= m i++) sum += i
cout << "The sum is: " << sum << '\n'
}
Note that the denition of the function f () can not be put inside the
function main(). See x3.8 for more details on functions.
Basic types are discussed in the next section. Additional types including
functions and user-dened types are discussed in subsequent chapters.
1. Basic Types
The integer types are short int, int, and long int. An int is of natural
size for integer arithmetic on a given machine. Typically it is stored in
one machine word, which is 4 bytes (32 bits) on most workstations and
mainframes, and 2 bytes (16 bits) on many personal computers. A long int
normally occupies more bytes (thus stores integers in a wider range) and
short int fewer bytes (for integers in a smaller range).
Integers are stored in binary bit by bit in computer memory with the
leading bit representing the sign of the integer: 0 for nonnegative integers
and 1 for negative integers. For example, the nonnegative int 15 may be
represented bit by bit on a machine with 4 bytes for int as
sign bit
#
//
//
//
//
//
//
iii = 123456789
// a big integer
10
1. Basic Types
ii = short(iii)
cout << ii
Note that explicit type conversion requires the use of the name of the
type. For example, long(i) converts explicitly an int i to long. Explicit
type conversion is also called cast. Notice that when the value of long
integer iii = 123456789 is assigned to short integer ii, the space occupied
by ii (sizeof(short) bytes) may not be enough to hold the value of iii.
Truncation may occur and lead to errors. For example, on one machine
with sizeof (short) = 2, this results in ii = ;13035. The negative sign is
caused by the leading (leftmost) bit in the binary representation of ii (after
over
ow), which happens to be 1.
The sux U is used to explicitly write unsigned integer constants and L
for long integer constants. For example, 5 is a signed int, 5U is an unsigned
int and 5L is a long int. They may occupy dierent numbers of bytes in
memory.
unsigned int i1 = 5U
long int i2 = 5L
int i = 5
11
//
In ASCII, cc = 65.
// assign a new value to cc.
//
In ASCII, cc = 10
// i = 10, implicit type conversion
// ii = 10, explicit type conversion
A char, occupying 8 bits, can range from 0 to 255 or from ;128 to 127,
depending on the implementation. Fortunately, C++ guarantees that a
signed char ranges at least from ;128 up to 127 and an unsigned char at
least from 0 up to 255. The types char, signed char, and unsigned char are
three distinct types and the use of char could cause portability problems
due to its implementation dependency. For example,
char c = 255
int i = c
// i = 65 in ASCII
outputs AB65CD to the screen (assuming the ASCII character set is used).
Inherited from the C programming language, a constant string always
ends with a null character. For example, the string "CD" consists of three
characters: C, D, and the null character (n0), and "nn" (notice the double
quotation marks) is a string of two characters: the newline character (nn)
and the null character (n0). In contrast, 'nn' (notice the single quotation
marks) is a single character. This can be checked by the sizeof operator:
int i = sizeof("CD")
int j = sizeof("\n")
int k = sizeof('\n')
// i = 3
// j = 2
// k = 1
The
oating point types are float double, and long double, which correspond to single precision, double precision, and an extended double precision, respectively. The number of 8-bit bytes of storage for each of them is
given by the operator sizeof . In general
sizeof (float) sizeof (double) sizeof (long double):
12
1. Basic Types
f = 0:d1 d2 d6 10n
where ;38 n 38 and d1 6= 0 (here 0:d1 d2 d6 is called the fractional
part of f ) a double number d is roughly represented in the form
// include <math.h>
give dierent precisions of the value . Note the function call atan(1) (for
computing the arctangent of 1) gives the value (=4) of the mathematical
function arctan(x) with x = 1. Trigonometric and other functions are in
the library <math:h> see x3.11. To see their dierence when being output
13
to the screen, we can use the precision function to specify the accuracy
and use the width function to specify the number of digits in the output
(see x4.6 for more details). For example, the statements
cout.precision(30)
cout.width(30)
cout << fpi << '\n'
cout.width(30)
cout << dpi << '\n'
cout.width(30)
cout << ldpi << '\n'
// include <iostream>
// output occupies 30 characters
14
1. Basic Types
A Boolean, represented by the keyword bool, can have one of the two values:
true and false, and is used to express the results of logical expressions
(x2.2.3). For example,
bool flag = true
// ... some other code that might change the value of flag
double d = 3.14
if (flag == false) d = 2.718
The operator == tests for equality of two quantities. The last statement
above means that, if flag is equal to false, assign 2:718 to variable d.
By denition, true has the value 1 and false has the value 0 when
converted to an integer. Conversely, nonzero integers can be implicitly converted to true and 0 to false. A bool variable occupies at least as much
space as a char. For example,
bool b = 7
int i = true
int m = b + i
15
as the base type for pointers to objects of unknown type. These points are
explained later. See, for example, x3.8 and Exercise 3.14.24.
16
1. Basic Types
<< numeric_limits<float>::round_error() << '\n'
cout << "float rounding style = "
<< numeric_limits<float>::round_style << '\n'
Some explanations are given now to the terms epsilon and mantissa. A
machine epsilon is the smallest positive
oating point number such that
1 + epsilon 6= 1. That is, any positive number smaller than it will be
treated as zero when added to 1 in the computer. It is also called the unit
roundo error. When a
oating point number x is represented in binary
as x = q 2m , where q = 0:q1 q2 qn with q1 = 1 and qi = 0 or 1 for
i = 2 3, : : : n, and m is an integer, then q is called the mantissa of x, m
is called the exponent of x, and n is the number of bits in the mantissa.
Due to nite precision in computer representation, not all numbers, even
within the range of the computer, can be represented exactly. A number
that can be represented exactly on a computer is called a machine number.
When a computer can not represent a number exactly, rounding, chopping,
over
ow, or under
ow may occur.
On a hypothetical computer called Marc-32 (see !CK99, KC96] a typical
computer should be very similar to it if not exactly the same), one machine
17
word (32 bits) is used to represent a single precision oating point number:
x = 1:b1b2 b23 2m :
The leftmost bit in the machine word is the sign bit (0 for nonnegative and
1 for negative), the next 8 bits are for the exponent m, and the rightmost
23 bits for the fractional part (b1 , b2 , : : :, and b23 ) of the mantissa. Notice
that, to save one bit of memory, the leading bit 1 in the fractional part is
not stored. It is usually called the hidden bit. The exponent m takes values
in the closed interval !;127 128]. However, m = ;127 is reserved for 0,
and m = 128 for 1 (if b1 = b2 = = b23 = 1) and NaN (otherwise).
Thus the exponent of a nonzero machine number must be in the range
;126 m 127. On such a machine, the single precision machine epsilon
is then 2;23
1:2 10;7 , the smallest positive machine number is 2;126
1:798 10308 .
It can be shown (see !CK99, KC96]) that for any nonzero real number x
and its
oating point machine representation x# (assuming x is within the
range of the computer and thus no over
ow or under
ow occurs), there
holds
x ; x
# epsilon
x ; x# 1 epsilon
x 2
in the case of rounding to the nearest machine number. That is, the relative
roundo error in representing a real number in the range of a computer with
a particular precision is no larger than epsilon in that precision.
The machine epsilon and number of bits in binary (or equivalent digits
in decimal) for representing the mantissa of a
oating point number on a
particular computer are given in the template class numeric limits< T>
for a
oating point type T .
In addition, C library <limits:h> has macros (see x3.1 for some rules
and examples for dening macros) such as those listed in Table 1.1, and
C library <float:h> has macros such as those listed in Table 1.2. These
macros are better avoided. However, the C++ library <limits> may not
be available on early (nonstandard) compilers. In this case, numeric limits
can be obtained as in the following program.
18
1. Basic Types
INT MAX
INT MIN
LONG MAX
LONG MIN
ULONG MAX
UINT MAX
SHRT MAX
USHRT MAX
SCHAR MIN
UCHAR MAX
CHAR MAX
CHAR MIN
WORD BIT
CHAR BIT
largest int
smallest int
largest long int
smallest long int
largest unsigned long int
largest unsigned int
largest short int
largest unsigned short int
smallest signed char
largest unsigned char
largest char
smallest char
number of bits in one word
number of bits in char
TABLE 1.1. Limits of integral numbers from C library <limits.h>.
DBL MAX
DBL MIN
DBL EPSILON
DBL MANT DIG
DBL DIG
DBL MAX 10 EXP
LDBL MAX
LDBL MIN
LDBL EPSILON
LDBL MANT DIG
LDBL DIG
LDBL MAX 10 EXP
FLT MAX
FLT MIN
FLT EPSILON
FLT MANT DIG
FLT DIG
FLT MAX 10 EXP
largest double
smallest double
double epsilon
number of binary bits in mantissa
number of decimal digits in mantissa
largest exponent
largest long double
smallest long double
long double epsilon
number of binary bits in mantissa
number of decimal digits in mantissa
largest exponent
largest float
smallest float
float epsilon
number of binary bits in mantissa
number of decimal digits in mantissa
largest exponent
TABLE 1.2. Limits of oating point numbers from C library <oat.h>.
19
#include <limits.h>
#include <float.h>
main() {
int i = INT_MIN
long j = LONG_MAX
double x = DBL_MAX
long double y = LDBL_MAX
float z = FLT_MAX
double epsdbl = DBL_EPSILON
float epsflt = FLT_EPSILON
long double epsldb = LDBL_EPSILON
}
//
//
//
//
//
//
//
//
smallest int
largest long int
biggest double
biggest long double
biggest float
double epsilon
float epsilon
long double epsilon
For headers ending with the :h sux such as float:h and math:h, the
include directive
#include <float.h>
is not necessary.
Note that the C++ library <limits> is dierent from the C library
<limits:h>, which C++ inherited from C. All C libraries can be called from
a C++ program provided that their headers are appropriately included. See
x4.2 for more details on all C and C++ standard header les.
In our rst program, we have used variables such as sum and m to hold
values of dierent data types. The name of a variable must be a valid
identier. An identier in C++ is a sequence of letters, digits, and the
underscore character . A letter or underscore must be the rst character
of an identier. Upper and lower case letters are treated as being distinct.
Some identiers are:
double sum = 0
double product
bool flag
int Count
int count
// different from Count
int this_unusually_long_identifier // legal, but too long
20
1. Basic Types
and
bitand
case
compl
default
dynamic cast
export
for
inline
namespace
operator
protected
return
static
template
try
union
void
xor
int _aAbBcCdD
and eq
bitor
catch
const
delete
else
extern
friend
int
new
or
public
short
static cast
this
typedef
unsigned
volatile
xor eq
asm
bool
char
const cast
do
enum
false
goto
long
not
or eq
register
signed
struct
throw
typeid
using
wchar t
auto
break
class
continue
double
explicit
oat
if
mutable
not eq
private
reinterpret cast
sizeof
switch
true
typename
virtual
while
1.5.2 Keywords
Keywords such as int, double, and for are explicitly reserved identiers that
have a strict meaning in C++. They can not be redened or used in other
contexts. For example, a programmer can not declare a variable with the
name double. A keyword is also called a reserved word. A complete list of
C++ keywords is in Table 1.3.
Note that cin and cout, for example, are not keywords. They are part of
the input and output (I/O) library <iostream>.
1.6 Exercises
21
1.6 Exercises
1.6.1. Modify the program in x1.1 to compute the sum of the squares of
all integers between two given integers. That is, nd the sum n2 +
(n + 1)2 + + m2 for two given integers n and m with n < m. In
the sample program in x1.1, the variable sum should be declared as
double or long double in order to handle large values of n and m. In
this exercise, try to compute sum in two ways: as a long double and
as an int, and compare the results. On a computer with 4 bytes for
storing int, the second way calculates the sum 12 +22 +32 + +50002
as ;1270505460. Why could a negative number as the output be
possible?
1.6.2. Modify the program in x1.1 to multiply all integers between two given
small positive (e.g., less than or equal to 12) integers. When one of
them is 1 and the other is a positive integer n, the program should
nd the factorial n! = 1 2 3 (n ; 1) n. The program to nd
n! may be written as
#include <iostream>
using namespace std
main() {
int n
cout << "Enter a positive integer: \n"
cin >> n
int fac = 1
for (int i = 2 i <= n i++) fac *= i
cout << n << "! is: " << fac << '\n'
// fac = fac*i
22
1. Basic Types
// input/output library
// math library for sin
main() {
double sum = 0
// sum initialized to 0
1.6 Exercises
23
1.6.8. Write a program that outputs the largest and smallest numbers,
epsilon, the number of decimal digits used to store the mantissa,
and the largest exponent for double precision
oating point numbers
on your computer. Repeat this for float and long double as well.
1.6.9. Compile and run the program
#include <iostream>
using namespace std
main() {
long g = 12345678912345
short h = g
// beware of integer overflow
int i = g - h
cout << "long int g = " << g << '\n'
cout << "short int h = " << h << '\n'
cout << "their difference g - h = " << g - h << '\n'
}
on your computer. Does your compiler warn you about integer overow? It may not always do so. Does your computer give g ; h = 0?
Beware of over
ow and truncation in converting a larger type to a
smaller one.
1.6.10. Calculate the value of on your computer following the steps in x1.3.3
in single, double, and extended double precisions. How many digits of
accuracy does your computer give in each of the three
oating point
types?
1.6.11. What is the value of i in the following statement?
int i = 3.8 + 3.8
24
1. Basic Types
z0
z1
z2
z3
z4
z5
z6
<<
<<
<<
<<
<<
<<
<<
<<
<<
=
=
=
=
=
=
=
// biggest double
// double epsilon
x + x
x * 2
epsilon/9
y/zero
zero/zero
z3 - z3
x + y
"outputting results:\n"
z0 << '\n'
z1 << '\n'
z2 << '\n'
z3 << '\n'
z4 << '\n'
z5 << '\n'
z6 << '\n'
1 + z2 << '\n'
Run the program on your computer to check the results. You may
see Innity, NaN, and other unusual outputs.
This is page 25
Printer: Opaque this
26
Unlike C and FORTRAN, a declaration is a statement that can occur anywhere another statement can, not necessarily at the beginning of a program.
If it occurs within a block enclosed by a pair of braces, it then declares a
local variable to the block. This block introduces a scope: Variables local to
a scope are inaccessible outside the block dening the scope. For example,
int n
{
int m = 10
}
n = m
int m = 20
There is no way to access a hidden local variable. For example, the local
variable x = 25 is hidden and inaccessible from the scope where integer t
is dened. Note that the local variables x = 25 and y have the same scope
as z , the local variable introduced in the last statement of the program.
An automatic variable is created at the time of its declaration and destroyed at the end of the block in which it is declared, as the variables y
t and z above. When the block is entered, the system sets aside memory
27
for automatic variables. When the block is exited, the system no longer
reserves the memory for these variables and thus their values are lost. A
local variable is automatic by default. It can also be declared explicitly
by the keyword auto: For example, the declaration int x = 38 above is
equivalent to: auto int x = 38 :
This declaration tells the system that variable x is dened externally, perhaps in another le (see x4.3.2 for more details). However, it does not
allocate space for the external variable x and simply means that x is accessible in all les that have the statement extern double x : It must be
declared as usual in one and only one le:
double x
It is this statement that creates an object and allocates space for x: (See
x4.4.2 on how to compile a program consisting of more than one le.)
External variables never disappear they exist throughout the execution life
of the program. To improve the modularity and readability of a program,
the use of external variables should be kept to the minimum. In particular,
a function (see x3.8) should not change an external variable within its body
rather than through its parameter list or return value. An external variable
can sometimes be localized:
{
// some code
{
extern double x
double y = x
}
28
However, modern compilers can normally do a better job than a programmer as to which variable should be assigned to a register.
Thus every variable has two attributes: type and storage classication.
Three storage classications have been talked about in this section. They
are automatic, external, and register, which are represented by keywords
auto, extern, and register. How these variables are stored distinguishes them
from each other. The use of C-style static storage classication, dening
a variable local to only one le, is discouraged in C++, since its use is
subtle and the keyword static is overused in C++. However, C++ provides
namespaces for dening variables local to a le see x4.1.4.
2.2 Expressions
If we dene int x = 25 int y = 10 then x ; y is called an expression
having operands x and y and operator ; the value of which is 15: In this
case, ; is called a binary operator since it takes two operands. It can also
be used as a unary operator as in x = ;5 which takes only one operand.
Dierent expressions are introduced in this section.
Arithmetic expressions contain arithmetic operations for addition, subtraction, multiplication, and division, whose corresponding operators are
+ ; and = respectively. + and ; are also unary operators. These operators are dened for all integer and
oating point types and there are
appropriate type conversions between dierent types. The reminder of one
integer divided by another is given by the modulo operator %: Note that
the division of one integer by another has an integer value in C++, where
the reminder (zero or not) is ignored. For example,
double m = 13/4
double n = 3/4
double x = double(3)/4
double y = 3.0/4
double z = 3/4.0
int t = 13%4
//
//
//
//
//
//
m
n
x
y
z
t
=
=
=
=
=
=
3, reminder is ignored
0, it just gives quotient
0.75, 3 is converted to 3.0
0.75
0.75
1, reminder of 13/4 is 1
// m = 3 + 12 = 15
2.2 Expressions
m = 1 + (2 + 3)*4
m = exp1 + (exp2 - exp3)
29
// m = 1 + 5*4 = 21
Relational operators are > (bigger than), < (smaller than), >= (bigger
than or equal to), <= (smaller than or equal to), == (equal), and != (not
//
//
//
//
//
// b = true
m = 20
bool c = 3 < m < 9
// c = true
Due to the left to right associativity for relational operators, the C++
expression 3 < m < 9 is equivalent to (3 < m) < 9: Thus the C++
30
x <x+y
is always true for any positive real number y and any real number x while
the C++ expression
x < x + y
evaluates to false for some large value of x (e.g., x = 5e+31 in 12-byte long
double precision) and small value of y (e.g., y = 0.1) since, in this case, x
and x + y are treated as being equal (due to nite precision and roundo
error) on the computer. Similarly, the expression x + y > x ; y is always
true for any positive numbers x and y in mathematics, but not in C++.
Logical operators are && (and) and jj (or). A logical expression has value
of type bool: If exp1 and exp2 are two expressions, then exp1 && exp2 has
value true if both exp1 and exp2 are true and has value false otherwise
and exp1 jj exp2 has value true if at least one of exp1 and exp2 is true and
has value false otherwise. This can be more easily illustrated by using the
so-called truth table.
true
true
false
false
&&
&&
&&
&&
true
false
true
false
= true
true
= false true
= false false
= false false
jj
jj
jj
jj
true
false
true
false
= true
= true
= true
= false
b
d
e
f
g
h
=
=
=
=
=
=
true, c = false
b && c
b || c
(e == false) && c
(d == true) || c
(d = true) || c
//
//
//
//
//
d
e
f
g
h
=
=
=
=
=
false
true
false
false
true
2.2 Expressions
31
j
=
=
=
= 2
((i = 0) && (j = 3))
((i = 4) || (j = 5))
((i = 0) || (j = 6))
// i = 0, k = false, j = 2
// i = 4, m = true, j = 2
// i = 0, j = 6, n = true
In the second statement above, i is assigned to be 0 and the left subexpression (i = 0) is converted to false. Thus k = false no matter what value
the right subexpression (j = 3) has, and the right subexpression (j = 3)
is skipped without evaluation. In the third statement, i is assigned to be
4 and (i = 4) is converted to true and the right subexpression (j = 5) is
skipped.
Notice that this kind of left to right evaluation is not true for arithmetic
expressions. For example, in m = exp1 + exp2 it is not guaranteed that
exp1 is evaluated before exp2:
Referring to an example in x2.2.2, the correct way of translating the
mathematical expression 3 < m < 9 into C++ is
(3 < m) && (m < 9)
Since operator < has higher precedence than operator && the two pairs
of parentheses above may be omitted.
It is dened that the operator && has higher precedence than jj: For
example, the following two statements are equivalent,
bool m = i || j && k
bool m = i || (j && k)
32
integral type (bool char short int long and their unsigned counterparts).
In these expressions, operands are rst implicitly represented in binary
(string of binary bits) and then operation is performed on them bit by bit.
Bitwise operations can be machine-dependent and may not be portable,
since, for example, dierent machines may represent integers dierently
and use dierent bytes for storing them.
Binary bitwise operations & j and^are performed by comparing their
operands bit by bit. If two bits are both 1, then & and j give 1 and ^ gives
0: If two bits are both 0, then & ^ and j all give 0: If one bit is 0 and
the other is 1, then j and^give 1 and & gives 0: This can be more easily
illustrated by the following table.
1&1 =1 1&0 =0 0&1 =0 0&0 =0
1 j 1 =1 1 j 0 =1 0 j 1 =1 0k 0 =0
1 ^1 =0 1 ^0 =1 0 ^1 =1 0 ^0 =0
For example, 13 and 7 are in binary 0 01101 and 0 00111 respectively.
Then
int a = 13 & 7
// b = 10
// c = 15
// m = 64
// k = 4
2.3 Statements
33
the integer 0 has all bits equal to 0 in its binary representation and~0 has
all bits equal to 1:
The value of ~n is called one's complement of n. Two's complement of
an integer n is the value of adding 1 to its one's complement. On a two's
complement machine, a nonnegative integer n is represented by its bit representation and a negative integer ;n is represented by the two's complement of n: For example, 7 is represented by the binary string 0000 0111
and ;7 is represented by 1111 1001 which is obtained from adding 1 to
1111 1000 the complement of 7.
It can be checked that the two's complement of a negative integer ;n
has value n: For example, ;7 is represented by 1111 1001: Its two's complement is 0000 0111 which is 7: In two's complement, the integer 0 has
all bits o and ;1 has all bits on. Notice that the leftmost bit represents
the sign of the integer: 1 for negative and 0 for nonnegative. On a two's
complement machine, the hardware that does addition can also do subtraction. For example, the operation a ; b is the same as a + (;b) where ;b is
obtained by taking b's two's complement.
In Exercise 2.5.15, a program is given to print out the bit representation
of any integer on a computer.
i = 3, i + 2
is called a comma expression with a comma used to separate subexpressions. Its value is the value of the last subexpression, and in this example
its value is i + 2 = 5: A comma expression is guaranteed to be evaluated
from left to right. Thus, the subexpression i = 3 is evaluated rst and then
the subexpression i + 2: Consequently, the statement
j = (i = 3, i + 2)
gives j = 5:
2.3 Statements
A program consists of a collection of statements. Each statement must
end with a semicolon. The null statement is simply a semicolon, which is
used when the syntax requires a statement but no action is desired. In this
section, we deal with various statements: declarations, assignments, compound statements, conditional statements, iteration statements (loops),
jump statements, and selection statements. Later we encounter return statements and function calls (see Chapter 3).
34
declares the variable n to be an integer and reserves for it sizeof (int) bytes
of addressable memory, the content of which is in general undened. The
system may initialize n with a default value in some situations, but it is
safe to always assume the value of n is undened. To give it a meaningful
value, an assignment such as
n = 10
2.3 Statements
35
x3.6,
2.3.2 Assignments
The assignment operator may be combined with any of the binary arithmetic or bitwise operators to form a compound assignment operator. If @
is one of these operators, then x @= y means x = x @ y except for possible
eciency dierences and side eects (see Exercises 2.5.6 and 3.14.12). For
example, x == 5 means x = x = 5 (division), and x &= 5 means x = x & 5
(bitwise and). Notice that there is no whitespace between the two operators
and they form a single operator. The complete set of compound assignment
operators are += ;= = == %= &= j= ^= <<= >>=:
Compound assignment (also increment and decrement see x2.3.4) operators have the potential to be implemented eciently. For example, an
intermediate value of x ; 5 is usually obtained and stored and then assigned
to x in x = x ; 5 while in x ;= 5 such an intermediate process can be
omitted and the value of x is just decremented by 5. They may also save
some typing and be more readable as in the following two statements.
my_very_long_variable = my_very_long_variable - 5
my_very_long_variable -= 5
// messy
// better
36
//
//
//
//
//
//
//
// unpredictable result
is a legal statement but its behavior is unpredictable and thus should not
be used, since it is nondeterministic to evaluate the left operand i ;; or the
right operand ++ i rst. Suppose i = 5: Then evaluating the left operand
i ;; before the right operand ++ i would give j = 10: Reversing the order
of evaluation would give j = 12: See Exercises 2.5.6 and 3.14.12. Compound
operators should be used in a straightforward way. Otherwise they can
easily lead to less readable and error-prone code.
is a compound statement, and it creates a new scope. Notice that no semicolon is needed after the right brace symbol. If there were a semicolon,
it would be a null statement following the compound statement. Since
2.3 Statements
37
// n = n | 5 (bitwise or)
// a nested compound statement
if-else Statements
The simplest conditional statement is the if-statement, whose syntax is:
if (condition) statement
// when n > 0, do x *= 10
// when n is 0, do x *= 5
// when n != 0, do y *= 7
if (condition) statement1
else statement2
38
// warning msg
// do division
//
//
//
//
//
#include <stdlib.h>
r is declared in condition part
if r is nonzero, do a division
if r is zero, do an addition
r is also within scope here
The function rand(), declared in the standard header <stdlib:h> generates a pseudorandom number between 0 and RAND MAX: The macro
RAND MAX is also dened in <stdlib:h>. To generate a pseudorandom
2.3 Statements
39
if (a > b) i = a
else i = b
// if-else statement
goto Statements
The labeled statement (a colon is needed after the label) can occur before
or after the goto jump. A goto statement is useful when a jump is needed
out of deeply nested blocks. For example,
repeat: i = 9
// statement labeled by "repeat"
{
// ...
{
// ...
if (i == 0) goto repeat
if (i == 8) goto done
}
}
done: i = 1
// colon needed after label "done"
switch Statements
A switch statement is often used for multiple choice situations. It tests the
value of its condition, supplied in parentheses after the keyword switch:
Then this value will be used to switch control to a case label matching the
value. If the value does not match any case label, control will go to the
default case. The break statement is often used to exit a switch statement.
For example,
40
int i, j, k
// do something to compute the value of i
switch(i) {
case 0:
j = 6
k = 36
break
case 5:
j = 7
k = 7
break
default:
j = 8
}
In this switch statement, the value of i is tested. When i is 0, the statement corresponding to case 0 is executed. When i is 5, the statement corresponding to case 5 is executed. When i is neither 0 nor 5, the default
case is executed. Notice the appearance of the break statement after each
case. Without it, every following statement in the switch statement would
be executed, until a break statement is encountered. There are occasions
when this is desired. A default choice is optional but is sometimes useful to
catch errors. For example,
char x
double y
// do something to compute the value of x
switch(x) {
case 'A':
//
case 'a':
//
y = 3.14
break
//
case 'B':
case 'b':
//
y = 2.17
break
//
default:
//
cout << "Impossible value
}
In general, the switch expression may be of any type for which integral
conversion is provided and the case expression must be of the same type.
A switch statement can always be replaced by an if-else statement, but a
switch statement can sometimes make the code more readable.
2.3 Statements
41
for Loops
The for loop is very versatile and convenient to use. Its general form is
for (initialize condition expression) statement
It will print out integers 3, 6, 12, 24, 48. The initial value of i is 3. Since
condition i < 50 is true, 3 is printed. Then i = 2 gives the second value
of i to be 6: Since i < 50 is still true, 6 is printed. This process repeats
until i = 2 gives i = 96 at which time the condition i < 50 is no longer
true.
The following loop will not print out anything since the initial condition
is false.
for (int i = 3 i > 5 i *= 2) {
cout << i << '\n'
}
When i = 3 the condition i > 5 is false. Thus the loop exits without
executing the statement inside the braces. The expression i = 2 is also
skipped.
A for loop denes a new scope. That is, the declaration int i = 3 in the
previous for loop is out of scope outside the loop:
for (int i = 3 i < 50 i *= 2) {
cout << i << '\n'
}
i = 20
42
The break statement can be used to stop a loop, and the continue statement stops the current iteration and jumps to the next iteration. For example,
for (int i = 3 i < 50 i *= 2) {
if (i == 6) continue
// jump to next iteration when i==6
cout << i << '\n'
if (i == 24) break
// break the loop when i is 24
}
It will print out integers 3, 12, 24. When i is 6, the value of i is not printed
out since the continue statement causes control to go to the next iteration
with i = 6 2 = 12: When i is 24 the loop is exited due to the break
statement. The break statement is often used to break an otherwise innite
loop. For example,
int n = 0
for ( ) {
cout << ++n << '\n'
if ( n == 100) break
}
// an infinite loop
// break infinite loop when n==100
It will print out 1 2 : : : 100: The loop breaks when n is 100: This is an
otherwise innite loop since it has no condition to check.
Comma expressions sometimes appear inside a for loop and the statement
of a for loop can be an empty statement. For example,
double sum
int i
for (sum = 0, i = 0 i < 100 sum += i, i++)
cout << "sum = " << sum << '\n'
// sum = 4950
The iteration variable in a for loop does not have to be integral. For
example,
for (double x = 0
A for loop can be nested just like any other compound statement. For
example,
double a = 0
for (int i = 0 i < 100 i++) {
for (int j = 200 j <= 500 j++)
}
a += i - j
2.3 Statements
long double b = 5
for (int i = 1 i < 5 i++)
for (int j = 27 j >= - 3 j--)
43
b *= i + j*j
i=0 j =200
where
(i ; j ) and 5
4 Y
27
Y
i=1 j =;3
(i + j 2 )
for product.
while Loops
The while loop statement has the form
while (expression) statement
The statement is executed until expression evaluates to false. If the initial
value of expression is false, the statement is never executed. For example,
int x = 0
while (x <= 100) {
cout << x << '\n'
x++
}
The statements inside the braces are executed until x <= 100 is no longer
true. It will print out 0 1 2 : : : 100: This loop is equivalent to the following
for loop.
int x
for (x = 0 x <= 100 x++) cout << x << '\n'
The following while loop will not print out anything since the initial condition is false.
int x = 10
while (x > 100) {
cout << x++ << '\n'
}
do-while Loops
The do-while statement resembles the while loop, but has the form
do statement while (expression)
The execution of statement continues until expression evaluates to false.
It is always executed at least once. For example,
44
int x = 10
do {
cout << x++ << "\n"
} while (x > 100)
It will stop after the rst iteration and thus print out only 10:
The continue statement can also be used in a while or do-while loop to
cause the current iteration to stop and the next iteration to begin immediately, and the break statement to exit the loop. Note that continue can
only be used in a loop while break can also be used in switch statements.
For example, the code segment
int x = 10
do {
cout << x++ << '\n'
if (x == 20) break
} while (true)
should print out all integers from 10 up to 19 and the code segment
int x = 10
do {
if (x++ == 15) continue
cout << x << '\n'
if (x == 20) break
} while (true)
should print out all integers from 11 up to 20 except for 16: However, the
code
int x = 10
while (true) {
if (x == 15) continue
cout << x++ << '\n'
if (x == 20) break
}
rst prints out integers 10 11 12 13 14 and then goes into an innite loop
(since x will stay equal to 15 forever).
f0 = 0
f1 = 1
45
qn = fn =fn;1
n = 2 3 4 : : :
p
converges to the golden mean, which is (1 + 5)=2
1:618:
The following program prints out the rst 40 (starting with n = 2) Fibonacci numbers and their quotients, using long int for Fibonacci numbers
and long double for Fibonacci quotients.
Making use of the for loop, the program can be written as
#include <iostream>
using namespace std
main() {
long fp = 1
long fc = 1
// temporary storage
// update Fib number
// store previous Fib number
}
}
Inside the for loop, the variable fc is used to store the updated Fibonacci
number and fp the previous one. Adding fp to fc gives the next Fibonacci
number that is also stored in fc: A temporary storage tmp is used to store
fc before the updating and assign the stored value to fp after updating,
getting ready for the next iteration. Notice that the integer fc is converted
into long double before the division fc=fp is performed, since otherwise
the result would be just an integer.
This program works ne except the output may look messy. To have a
formatted output, it may be modied to control the space in terms of the
number of characters each output value occupies:
main() {
long fp = 1
long fc = 1
cout.width(2)
cout << "n"
46
// temporary storage
// update Fib number
// store previous Fib number
}
}
Fibonacci number
Fibonacci quotient
2
3
4
5
6
7
8
9
10
1
2
3
5
8
13
21
34
55
1
2
1.5
1.6666666666666666666
1.6
1.625
1.6153846153846153846
1.6190476190476190476
1.6176470588235294118
20
6765
1.6180339631667065295
30
832040
1.6180339887482036213
39
40
63245986
102334155
1.6180339887498951409
1.6180339887498947364
The results show that the Fibonacci quotients converge very quickly to
the golden mean and the Fibonacci numbers grow extremely fast. Over
ow
will occur well before n reaches 100 see Exercise 2.5.14. A technique is
introduced in Exercise 3.14.21 to store a large integer into an array of
2.5 Exercises
47
2.5 Exercises
2.5.1. Add printing statements to the program at the end of x2.1.1 so that
the value of each variable in dierent scopes is output to the screen
right after it is dened. Check to see if the output values are the ones
that you have expected.
2.5.2. The function rand(), declared in the standard header <stdlib:h>,
generates a pseudorandom number between 0 and RAND MAX:
The macro RAND MAX is also dened in <stdlib:h>. Write and
run a program that outputs the value of RAND MAX on your system
and generates 100 pseudorandom numbers between 5 and 50, using a
for loop.
2.5.3. Rewrite the program in Exercise 2.5.2 using a while loop. Compare
the 100 numbers generated by this program and the program in Exercise 2.5.2.
2.5.4. Use a nested for loop to write a program to compute the sum:
100 X
300
X
i=0 j =5
cos(i2 + j ):
Check x3.11 for the cosine and square root functions in the math
library <math:h>.
2.5.5. Write a program that computes the number of (decimal) digits of a
given integer n. For example, when n = 145 the number of digits of
n is 3:
2.5.6. Write a program that contains the statements
j = i-- + ++i
k = j + j++
k *= k++
// unpredictable result
// unpredictable result
// unpredictable result
and outputs the values of j and k with i initialized to 5: Try to run the
program on dierent machines if possible. The result of such statements is machine-dependent and can cause dangerous side eects.
Experienced programmers can recognize such statements and do not
use them.
2.5.7. The two statements
48
are legal, but give dierent results on dierent machines. The value
of j is either 17 or 15 depending on whether the left subexpression
(i = 7) or the right subexpression (k = i + 3) is evaluated rst. The
value of k is either 10 or 8: Test what values j and k have under
your compiler. Again, such legal but machine-dependent statements
should be avoided.
2.5.8. Can division by zero ever occur in the following code segment?
int x = rand(), y = rand()
if ((x != 5) && (y/(x-5) < 30))
z = 2.17
Then how about this (just switch the order of the subexpressions):
int x = rand(), y = rand()
if ((y/(x-5) < 30) && (x != 5))
z = 2.17
2.5 Exercises
49
Test this loop on your computer. Equality expressions involving
oating point arithmetic must be used with great care. Replacing them
by relational expressions often leads to more robust code.
2.5.11. Write a program to test the dierence between the statement
while (i++ < n) { cout << i << '\n' }
where i and n are given integers, say i = 0 and n = 10: Is there any
dierence between the following two statements?
for (char i = 'a' i <= 'z' i++) cout << i << '\n'
and
for (int i = 'a' i <= 'z' ++i) cout << i << '\n'
2.5.12. Here is an easy way to know the largest unsigned int on a computer
with two's complement:
unsigned int largeui = - 1
Test to see if largeui stores the largest unsigned int on your computer. Explain the reason. Does your compiler warn you about assigning a negative int to an unsigned int? If it does, how about:
int i = - 1
unsigned int largeui = i
2.5.13. Rewrite the rst Fibonacci number program in x2.4 using a while loop
and the second using a do-while loop so that they produce exactly
the same output as the original programs.
2.5.14. Can you modify the program in x2.4 so that it will try to print out the
rst 100 Fibonacci numbers? Your output will probably have negative
numbers for large n: This is caused by integer overow since Fibonacci
numbers grow very quickly. Computation usually continues but produces incorrect results when overow occurs. It is the programmer's
responsibility to make sure that all variables (suspected to take on
large or small values) stay within range and stop the program or take
other actions when overow happens. Rewrite the program so that it
will print out a warning message before overow occurs.
50
Note that n & mask gives the leftmost bit of n followed by 31 '0's
in bit representation. When this bit is 1, character '1' is assigned to
cc and otherwise cc = '0'. The statement n <<= 1 shifts the bit
pattern of n to the left by 1 bit. When i = 1 the leftmost bit of n
is printed out and when i = 2 the second leftmost bit is printed out,
and so on. Run the program on your computer and print out the bit
representations of 55555 and ;55555: Modify the program to handle
integers with sizeof (int) = m, where m may be dierent on dierent
machines.
This is page 51
Printer: Opaque this
Derived Types
From basic types, other types can be derived. This chapter discusses derived
types such as enumeration types for representing a specic set of values,
pointers for manipulating addresses or locations of variables, arrays for
storing a number of elements having the same type, reference types for creating aliases, structures for storing elements of dierent types, and unions
for storing elements of dierent types when only one of them is present
at a time. Functions|a special type|that may take some values as input
and produce some other values as output are also discussed. Another derived type called class is discussed in Chapter 5. All C++ operators and
their precedences, and all mathematical functions in the standard library
<math:h> are summarized towards the end of the chapter. The chapter
ends with one section on polynomial evaluations and another on numeric
integration techniques.
52
3. Derived Types
pi = 3.1
const int size
// const expression
Note that const changes a type in that it restricts the way in which an
object can be used. Constant expressions are allowed in the denition of
a constant such as numelements above, since the compiler can evaluate
constant expressions.
Constants are typed and thus preferable to traditional C macros, which
are not type-checked and are error-prone, such as
#define PI 3.1415926535897932385
This says that clock may be changed by an agent external to the program
since it is volatile and may not be modied inside the program since it is
const: Thus two successive reads of the variable clock in the program may
give two dierent values. An extern const may not have to be initialized.
3.2 Enumerations
The enumeration type enum is for holding a set of integer values specied
by the user. For example,
enum {blue,yellow,pink =20,black,red =pink +5, green =20}
3.2 Enumerations
53
is equivalent to
const int blue = 0, yellow = 1, pink = 20, black = 21,
red = 25, green = 20
Neumann, Robin}
// a named enum
declare x, y to be of type bctype
assigning a value to variable x
illegal, an int can not be assigned
to a variable of type bctype
if (x != y) i = Robin
The rst statement above denes a new type called bctype: A variable of
this type may take on values of Dirichlet, Neumann and Robin:
An enum is a user-dened type and often used in conjunction with the
switch statement:
bctype x
// ...
switch(x) {
// do differently based on value of x
case Dirichlet:
// ... deal with Dirichlet boundary condition
break
case Neumann: case Robin:
// ... deal with Neumann and Robin conditions at same time
break
Default:
cout << "undefined boundary condition\n"
}
54
3. Derived Types
Although a programmer rarely needs to know the range of an enumeration type, the range is normally larger than the set of values of the enumerators. The range is dened to be the set of all integers in the closed interval
!0 2n ; 1] if the smallest enumerator is nonnegative and in the closed interval !;2n 2n ; 1] if the smallest enumerator is negative, where n is the
nearest larger binary exponent of the largest (in magnitude) enumerator.
This is the smallest bit-eld that can hold all the enumerator values. For
example,
enum flag { red = 1, blue = 0 }
// range: 0, 1
enum onenine { one = 1, nine = 9 } // range: 0, 1,..., 15
enum myminmax{ min = -90, max = 1000}
// range: - 2048,..., 2047
onenine m0 = onenine(5)
myminmax m1 = myminmax(- 1000)
myminmax m2 = myminmax(5000)
3.3 Arrays
For a type T , T !n] is the type \one-dimensional array of n elements of type
T" where n is a positive integer. The elements are indexed from 0 to n ; 1
and are stored contiguously one after another in memory. For example,
float vec%3]
int stg%30]
vec%0] = 1.0
vec%1] = 2.0
The rst two statements declare vec and stg to be one-dimensional arrays
with 3 and 30 elements of type float and int, respectively. A for loop is
often used to access all elements of a 1D array. A one-dimensional array
can be used to store elements of a (mathematical) vector.
Two-dimensional arrays having n rows and m columns (looking like a
matrix) can be declared as T !n]!m], for elements of type T: The row index
changes from 0 to n ; 1 and the column index from 0 to m ; 1: For example,
3.3 Arrays
double mt%2]%5]
mt%0]%0] = 5
mt%1]%4] = 5
double a = mt%0]%0]
//
//
//
//
55
5 columns
column 0
column 4
mt
In the declaration of v above, the compiler will count the number of elements in the initialization and allocate the correct amount of space for v
so that the number of elements can be omitted. In an initialization of a
multi-dimensional array such as u! ]!3], only the rst dimension (the number of rows for u) can be omitted. However, assignments can not be done
this way:
int vv%4]
vv = {1, 2, 4, 5}
// a declaration of a 1D array
// error, use: vv%0] =1, vv%1] =2, ...
Since multidimensional arrays are stored row by row, they can be initialized like one-dimensional arrays. For example, the following two initializations are equivalent.
56
3. Derived Types
Entries that are not explicitly initialized are set to 0: Thus they are also
equivalent to:
int ia%2]%3] = { {1, 2}, {4} }
0}
0}
8}
3}
},
},
},
}
//
//
//
//
ax%0]
ax%1]
ax%2]
ax%3]
The number of elements in a sequence often can not be known at compiletime, but rather is calculated at run-time. If we declare a large array, it
wastes space. If we declare a small one, it may not be big enough to hold
all the elements. For this reason arrays should not be used unless their dimensions are exactly or roughly known at compile-time. Instead, pointers
are often used and space can be allocated at run-time see x3.6. The standard libraries <valarray> and <vector> can also be conveniently used to
handle arrays whose sizes may not be known at compile-time see x7.5 and
x10.1.1.
3.4 Structures
57
3.4 Structures
Unlike an array that takes values of the same type for all elements, a struct
can contain values of dierent types. It can be declared as
struct point2d {
char nm
float x
float y
}
//
//
//
//
a structure of 2D points
name of the point
x-coordinate of point
y-coordinate of point
This denes a new type called point2d: Note the semicolon after the right
brace. This is one of the very few places where a semicolon is needed following a right brace. The structure point2d has three elds or members, one
char and two floats: Its size in memory is sizeof (point2d) bytes, which
may not be equal to sizeof (char) + 2 sizeof (float): Its members are
accessed by the : (dot) operator. For example,
point2d pt
pt.nm = 'f'
pt.x = 3.14
pt.y = - 3.14
//
//
//
//
double a = pt.x
char c = pt.nm
// accessing member x of pt
// accessing member nm of pt
// OK, initialization
point2d pt4
pt4 = {'A', 2.7, - 3.4}
// illegal, assignment
58
3. Derived Types
union val {
int i
double d
char c
}
The union val has three members: i, d and c, only one of which can exist
at a time. Thus sizeof (double) bytes of memory are enough for storing an
object of val: The exact size of val is sizeof (val): Members of a union are
also accessed by the : (dot) operator. It can be used as the following.
int n
cin >> n
// n is taken at run-time
val x
// x is a variable of type val
if (n == 1) x.i = 5
else if (n == 2) x.d = 3.14
else x.c = 'A'
double v = sin(x.d)
When x is declared, sizeof (double) bytes of memory are created for it.
This piece of memory can be used to store an int (member x:i), a double
(member x:d), or a char (member x:c). The last statement above is an
error (a compiler can not catch such an error and a programmer should be
responsible for this) since x:d may not be dened at all times. To see this,
look at the program:
main() {
union val {
int i
double d
char c
}
val x
x.i = 5
59
cout << "x.i = " << x.i << ", x.d = "
<< x.d << ", x.c = " << x.c << '\n'
}
Note that only the member that is dened can be correctly printed out.
Values of other members should not be used. When x:i is dened to be
5 its value can be printed out (and used) correctly. However, this same
piece of memory is interpreted to be a double and a char when x:d and
x:c are printed out, respectively. The outputs for x:d and x:c are of course
erroneous in the rst printing statement above.
Suppose that triangle and rectangle are two structures and a figure
can be either a triangle or a rectangle but not both then a structure for
figure can be declared as
struct figure2d {
char name
bool type
union {
triangle tria
rectangle rect
}
}
60
3. Derived Types
then the members tria and rect will have to be accessed as fig:torr:tria
and fig:torr:rect for a figure2d object fig:
The size of a C++ object is a multiple of the size of a char which is one
byte. That is, a char is the smallest object that can be represented on a
machine. There are occasions when it is necessary to put a few small objects
into one byte to squeeze more space. A member of a struct or union can be
declared to occupy a specied number of bits. Such a member is called a bit
eld and the number of bits it occupies is called its bit width. A bit eld must
be of an integral type (typically unsigned int), and it is not possible to take
its address. The bit width is at most the number of bits in a machine word
and is specied by a nonnegative constant integral expression following a
colon. Unnamed bit elds are also allowed for padding and alignment. An
example is
struct card {
unsigned int pips : 4
unsigned int suit : 2
unsigned int kq
: 2
// ...
}
//
//
//
//
A variable of card has a 4-bit eld pips that can be used to store small
nonnegative integers 0 to 15 a 2-bit eld suit for values 0 to 3 and another
2-bit eld kq: Thus the values of pips suit and kq can be compactly stored
in 8 bits. It can be used as
card c
c.pips = 13
c.suit = 3
c.kq
= 0
// c is of type card
The layout of the bits in left-to-right or right-to-left order is machinedependent. A compiler may not assign bit elds across word boundaries.
For example, sizeof (card) = 4 on some machines with 4-byte words. (It
still saves memory in this case since storing three unsigned int separately
would require 12 bytes.) Programs manipulating bits may not be portable.
Unless saving memory is really necessary for an application, bit elds (also
unions) should be avoided.
3.6 Pointers
For a type T , T is the pointer to T: A variable of type T can hold the
address or location in memory of an object of type T: For this reason, the
number of bytes of any pointer is sizeof (int) = sizeof (double) and so
on. For example, the declaration
3.6 Pointers
int* p
61
// p is a pointer to int
int j = *pi
double* d = &j
// illegal
*p = 5.5
double c = *p
double d = d2
62
3. Derived Types
int n = 100
// n can also be computed at run-time
double* a
// declare a to be a pointer to double
a = new double %n] // allocate space for n double objects
// a points to the initial object
The last two statements can also be combined into a more ecient and
compact declaration with an initialization:
double* a = new double %n]
In allocating space for new objects, the keyword new is followed by a type
name, which is followed by a positive integer in brackets representing the
number of objects to be created. The positive integer together with the
brackets can be omitted when it is 1: This statement obtains a piece of
memory from the system adequate to store n objects of type double and
assigns the address of the rst object to the pointer a: These objects can
be accessed using the array subscripting operator ! ] with index starting
from 0 ending at n ; 1: For example,
for (int i = 0 i < n i++) // a%0] refers to initial object
a%i] = i*i + 8
// a is used just like an array
double sum = 0
// find sum of all objects a%i]
for (int i = 0 i < n i++) sum += a%i]
The pictorial representation of the pointer a and the space allocated for it
can be drawn as
a
#
a!0]
a!1]
a!2]
a!n-1]
You can print out the values of a and a!0] to check that they are indeed the
same. This agrees with the fact that a points to the initial object a!0]: After
their use, these objects can be destroyed by using the operator delete :
delete%] a
3.6 Pointers
63
in use can be deleted from memory to make room to create other objects,
and the number of objects to be created can be computed at run-time.
Automatic variables represent objects that exist only in their scopes. In
contrast, an object created by operator new exists independently of the
scope in which it is created. Such objects are said to be on the dynamic
memory (the heap or the free store). They exist until being destroyed by
operator delete or to the end of the program.
Space can also be allocated for a pointer to create just one object in a
similar way:
char* pt
pt = new char
pt%0] = 'c'
*pt = 'd'
char x = pt%0]
char y = *pt
delete pt
//
//
//
//
//
//
//
pt is a pointer to character
allocate space of 1 char for pt
place 'c' at location pointed to by pt
place 'd' at location pointed to by pt
assign pt%0] to object x, x = 'd'
assign *pt to object y, y = 'd'
free space pointed to by pt
Note that when more than one object is freed, delete! ] should be used
otherwise use delete without brackets.
An object can also be initialized at the time of creation using new with
the initialized value in parentheses. For example,
double* y = new double (3.14) // *y = 3.14
int i = 5
int* j = new int (i) // *j = 5, but j does not point to i
a
are equivalent, as in
Notice that parentheses are needed for the second statement above, which
declares vp to be a pointer to an array of 10 integers. The rst statement
declares ap to be an array of 10 pointers, each of which points to an int.
64
3. Derived Types
// cc is a pointer to char
// allocate 3 char for cc
// cc points to initial char
cc%0] = 'a'
cc%1] = 'b'
cc%2] = 'c'
// or *(cc + 2) = 'c'
// (cc + 2) points to object 2
char* p = &cc%0]
char dd = *p
dd = *(cc + 2)
delete%] cc
The object pointed to by p is cc!0] and the value of cc!0] is 'a'. Thus the
value of the object pointed to by p that is, p is 'a', which is initialized to
dd: Similarly, cc + 2 points to object 2 in the sequence and thus (cc + 2) is
the value of object 2, which is also referred to by cc!2]: The term element
instead of object is often used when referring to an individual in a sequence,
such as an array.
Two-dimensional arrays and matrices can be achieved through double pointers (a pointer to a pointer is called a double pointer). For example,
int** mx
// double pointer:a pointer to a pointer
mx = new int* %n] // new space to hold n pointers to int
// mx points to initial element mx%0]
for (int i = 0 i < n i++) mx%i] = new int %m]
// create m objects for each of the n pointers
// mx%i] points to initial element mx%i]%0]
3.6 Pointers
65
mx!0]
mx!1]
mx!2]
The third statement above (the for loop) allocates m objects of type int for
each of the n pointers mx!i] i = 0 1 : : : n ; 1: A graphical representation
of the double pointer mx in the case of n = 3 and m = 5 can now be drawn
as
mx
#
introduces intptr as a synonym to pointer to int: A typedef does not introduce a new type so that intptr means the same as int : Then the double
pointer mx can be equivalently declared as
intptr* mx = new intptr %n]
for (int i = 0 i < n i++) mx%i] = new int %m]
66
3. Derived Types
tm!0] ! tm!0]!0]
tm!1] ! tm!1]!0] tm!1]!1]
tm!2] ! tm!2]!0] tm!2]!1] tm!2]!2]
Upper triangular matrices can also be declared using the techniques discussed here and in x3.6.3 see Exercise 3.14.12. Note that arrays can only
represent rectangular matrices. Using rectangular matrices to store triangular matrices or symmetric matrices would waste space.
3.6 Pointers
ar ; = k
ar
67
ar!0]
ar!1]
ar!n-1]
ar += k
FIGURE 3.1. Pointer osetting. Originally, ar points to initial object ar0]:
Pointer arithmetic ar ; = k osets ar to the left by k positions. Then,
ar += k changes ar back to point to the initial object. That is, after osetting, ark] = (ar + k) is the initial object.
See Exercise 3.14.14 for triple and quadruple pointers, which can be
used to dynamically allocate space for three- and four-dimensional arrays.
Higher-dimensional arrays may be created similarly, but are less often used.
Double, triple, and quadruple pointers, and the like, may be called multiple
pointers.
68
3. Derived Types
ar%i] = i*i + 1
Note that the objects are not moved at all in this process. Only the pointer
For example, to store the number of students in classes 1999 and 2000
in an array of 2 elements, the code can be written as
int* ns = new int %2]
ns -= 1999
ns%1999] = 36000
ns%2000] = 37000
ns += 1999
delete%] ns
//
//
//
//
//
//
If an array of 108 elements is needed, but the index must change from
to 7 it can be done as
;100
%108]
// offset
7 i++)
// access
// change
// delete
*q = n
int k = m
3.6 Pointers
69
Although q is a constant pointer that can only point to object m the value
of the object that q points to can be changed to the value of n, which is 5:
Thus k is initialized to 5:
A related concept is a pointer that points to a constant object that is,
if p is such a pointer, then the value of the object pointed to by p can not
be changed. It only says that p can not be changed explicitly by using it
as lvalue: However, the pointer p itself can be changed to hold the address
of another object. It can be declared and used as
const int* p = &m
*p = n
p = &n
//
//
//
//
//
//
p points to m, so *p becomes 1
*p = m = 1, so i = 1
m = 3, so *p becomes 3
*p = 3, so j = 3
ok, p itself can change, *p = 5
*p = n = 5, so k = 5
// i = 1, since *r = m = 1
// error, r is const pointer
// error, r points to const value
m = 3
int j = *r
70
3. Derived Types
// pv is a void pointer
// pi is int pointer
// implicit conversion from int* to void*
Its primary use is to dene functions that take arguments of arbitrary types
or return an untyped object. A typical example is the C quicksort function
qsort(), declared in <stdlib:h>, that can sort an array of elements of any
type. See Exercise 3.14.24 or the book !Str97] for more details. However,
templates (see Chapter 7) can be more easily and eciently used to achieve
the same goal.
The null pointer points to no object at all. It is a typed pointer (unlike
the void pointer) and has the integer value 0: If at the time of declaration, a
pointer can not be assigned to a meaningful initial value, it can be initialized
to be the null pointer. For example,
double* dp = 0
// *dp = 55.0
// it causes d = 0.0, dp still points to d
A pointer can point to a structure. In this case, its members are accessed
using the ;> operator. Space for structure objects can be allocated by the
operator new and freed by delete: For example,
point2d ab = {'F', 3, -5}
point2d* p = &ab
char name = p->nm
double xpos = p->x
//
//
//
//
ab is of type point2d
let p point to ab
assign nm field of p to name
assign x field of p to xpos
3.6 Pointers
double ypos = p->y
p->x = 15.0
p->y = 26.0
p->nm = 'h'
// ab.x = 15
// ab.y = 26
// ab.nm = 'h'
71
This is allowed because next is only a pointer and occupies a xed amount
of space (sizeof (int) bytes). Such a self-referential structure is called a
dynamic data structure elements can be allocated and attached to it (or
removed from it and deallocated) using the pointer next: C++ provides a
standard library on stacks see x10.1.4.
To dene two or more structure types that refer to each other, a forward
declaration is needed:
struct BB
struct AA {
char c
BB* b
}
struct BB {
AA* a
int i
}
// definition of structure BB
// b is a pointer to BB
72
3. Derived Types
The C++ standard library <string> can be conveniently used for dealing
with strings see x4.5. C-style strings are better avoided if possible.
As in C, pointers and arrays are closely related. The name of an array can
be used as a const pointer to its initial element. For example,
int v%5] = {6,9,4,5,7}
int* q = &v%0]
// point to initial element. *q = 6
int* p = v
// point to initial element. *p = 6
int* r = &v%5]
int* s = v + 5
3.7 References
A reference is an alternative name or alias for an object. For a type T , T &
denotes a reference to T: A reference to an object b of type T is dened as
T& r = b
3.8 Functions
73
3.8 Functions
3.8.1 Function Declarations and Denitions
A function is a procedure that may take some input, perform some instructions, and return some output. The input and output must be specied to
be of certain types. For example, one of the sqrt() functions in <math:h>
takes a double as its input, calculates its square root, and returns it as the
output. It is declared as
double sqrt(double)
When a function does not return an output, void is used to denote it.
When the output type is not explicitly stated, it is assumed to be int by
default. Not specifying the input of a function means that it takes no input.
Here are a few more examples of function declarations:
int square(int)
// take an int, return an int
double sum(double, double)
// take two double, return a double
int f()
// take no input and return an int
void g(double, double) // take two double, return nothing
h(int)
// take an int and return an int
74
3. Derived Types
All these declarations are called prototypes: It is only the types that matter
in a prototype.
Every function that is called in a program must be dened somewhere
and only once. The instructions that a function performs must be put inside
a pair of braces. The return statement is used for a function to produce an
output. A function denition also serves as its declaration. For example,
the square() function can be dened as
int square(int x) {
return x*x
}
// definition of a function
However, when a function is called before its denition, the function declaration must appear before the statement that calls the function. Such a
declaration is called a forward declaration. For example,
main() {
int square(int)
// forward declaration
int x = square(6) // call square() before its definition
}
int square(int x) {
return x*x
}
We can dene another square() function that takes a double and returns
a double:
3.8 Functions
double square(double x) {
return x*x
}
75
// definition of a function
So can we dene yet another taking a long double and returning a long
double. These versions of the square() function can coexist in a program.
The compiler can decide which version to use by comparing the type of the
argument with the type of the parameter. Using the same name for similar
operations on dierent types is called overloading.
Return type is not considered in overloading resolution. For example,
double a =
double b =
square(5)
square(5.0)
There are two argument passing mechanisms in C++: pass by value and
pass by reference. In pass by value, the argument is rst evaluated at the
time of the function call, and its value becomes the value of the parameter
during the execution of the function. The default is pass by value, as in the
following example.
int pass_val(int x) {
x = x*x
return x + 5
}
int i = 5
int j = pass_val(i)
// now i = 5, j = 30
In the function call j = pass val(i) the value of the argument i which is
5 is passed to the parameter x: The function pass val() starts with the
value x = 5 executes its block of statements, and returns value 30 to j:
Thus after the function call, i = 5 and j = 30:
In pass by reference, an argument must be a variable with allocated
location and the argument is passed to the function as a reference, so that
76
3. Derived Types
the parameter becomes an alias to the argument and any change made to
the parameter occurs to the argument as well. Pass by reference is signied
by the reference operator &: For example, consider an example of pass by
reference:
int pass_ref(int& x) {
x = x*x
return x + 5
}
// pass by reference
int i = 5
int j = pass_ref(i)
// now i = 25, j = 30
In the function call j = pass ref (i) the argument i is passed as a reference
to the parameter x: That is, parameter x and argument i refer to the same
object and x becomes an alias to i: Thus any change made to x inside the
function also occurs to i: The function pass ref () starts with the value
x = 5 executes its block of statements, and returns value 30 to j: However,
inside the function, x is updated to 25: This update occurs to i as well.
Thus after the function call, i = 25 and j = 30:
Pass by value for pointers can achieve the eect of pass by reference,
since two pointers point to the same object if they have the same value.
For example, compare the following three versions of the swap() function,
which is intended to swap two numbers.
void swap(int& p, int& q) {
int tmp = p
p = q
q = tmp
}
int i = 2, j = 3
swap(i, j)
// now i = 3, j = 2
// pass by reference
// a temporary variable
// swap values of p and q
// pass by value
3.8 Functions
77
p = q
q = tmp
}
i = 2, j = 3
swap2(i, j)
// now i = 2, j = 3
In the rst function call swap() the references of i and j are passed to
the function, and p and q become aliases to i and j respectively. Thus any
change made to p and q inside the function occur to i and j as well. In the
second swap() the values of &i and &j that is, the addresses of i and j are
passed to the parameters p and q. Thus p points to object i and q points to
object j and swapping p and q is equivalent to swapping i and j: In the
third version: swap2() the values of i and j are passed to the parameters p
and q respectively, and they can not be changed by the function call. This
version is useless since it does not swap the values of i and j at all.
Now look at another function that passes by value for its rst argument
and by reference for its second:
void f(int val, int& ref) {
val++
ref++
}
int i = 1, j = 1
f(i, j)
// now j = 2, i = 1.
Pass by value does not change the value of the arguments and thus is safe.
On the other hand, pass by reference or pass by value for pointers usually
implies that the values of the arguments are going to be changed through
the function call, unless they are explicitly told not to, using the keyword
const: For example,
int g(int val, const int& ref) {
// ref is not supposed to be changed
val++
return ref + val
}
Because of the const specier, the compiler will give a warning if ref is to
be changed inside the function. For example, it is illegal to write
void w(const int& ref) {
ref = 5
// WRONG, ref is not writable
}
Since the const specier implies not to change the parameter ref, it can be
substituted by a constant argument in a call:
78
3. Derived Types
g(5, 100)
// OK
f(5, 100)
int i = 100
f(5, i)
The name of an array is a const pointer that points to its initial element.
Thus array arguments are treated as pointers and have the same eect of
pass by reference. For example,
double h(double* const d, int n) { // d is a const pointer
double sum = 0
for (int i = 0 i < n i++) sum += d%i]
d%n - 1] = 1000
// but d%i] can change
return sum
}
double a%] = { 1, 2, 8, 20, -10, 30 }
double sum = h(a, 6)
// sum of elements in a
double d5 = a%5]
// d5 = 1000
3.8 Functions
79
A function that is not declared void must return a value, except for the
function main() see x3.8.10. For example,
int square(int& i) {
i *= i
// error, a value must be returned.
}
Each time a function is called, new copies of its arguments and automatic
variables (like tmp in the swap() functions) are created. Since automatic
variables will be destroyed upon the return of the function call, it is an error
to return a pointer or reference to an automatic variable. The compiler
normally gives a warning message so that such errors can be easily avoided.
For example,
int* fa() {
// need to return address of int
int local = 5
return &local // error
}
// can not return address of local variable
int& fr() {
// need to return reference to int
int local = 5
return local // error
}
// can not return a reference to local var
Observe that objects created by operator new exist on the dynamic memory
(heap, or free store). They still exist after the function in which they are
created is returned. These objects can be explicitly destroyed by calling
delete: The function f () returns the address of the object created by new
80
3. Derived Types
to the variable i. Thus i gives the value of the object and the operator
delete frees the object at address i: The function g() returns a reference to
a global variable v and thus g(3) refers to v!3]:
For a void function, it is an error to return a value. However, an empty
return statement can be used to terminate a function call, or a call to a
void function can be made in a return statement. For example,
void h(int)
void hh(int& i) {
int k = 0
for (int j = 0 j < 100 j++) {
// do something to modify k and i
if (k == 0) return
// fcn terminates if k is zero
if (k == 2) return h(i)
}
// call void function in return statement
}
3.8 Functions
for (int
delete%]
delete%]
delete%]
i = 0 i < n i++)
a
b
c
81
delete%] a%i]
// free matrix a
// free vector b
// free vector c
The function mxvrmy() returns (the base address of) the product of the
matrix mx with vector vr: Inside the main() program, some values are
assigned to matrix a and vector b and variable c is initialized to point to
the initial element of the product vector, which is allocated and computed
inside the function mxvrmy(): The summation of all elements in the product vector c is computed and stored in sum: Notice that the space for the
product vector is allocated using new inside the function mxvrmy(), but
is freed using delete in main():
// stopping statement
When a function is called, its arguments are copied and the program
branches out to a new location (on the run-time stack see x3.9) and comes
back after the instructions contained in the function are executed. To avoid
82
3. Derived Types
such function calling overhead, inline functions can be dened using the
keyword inline for small functions. Statements in an inline function are
substituted in the program calling it during compilation. For example, an
inline function can be dened as
inline int twice (int i) { return 2*i }
A function can have default arguments for its trailing arguments. A default
value for an argument can be specied in a function denition. For example,
void print(int v, int b = 10) { // b is defaulted to be 10
// define the function to print out integer v in base b
}
print(15,16)
print(15,2)
print(15,10)
print(15)
//
//
//
//
print
print
print
print
15
15
15
15
in
in
in
in
The last call is equivalent to print(15 10): That is, when a value is not
explicitly given for the second argument, 10 is assumed by default.
The default values for arguments can also be specied in a function
declaration. For example,
void print(int = 100, int = 10)
print(15,16)
print(15,2)
print(15)
// 2 default arguments
3.8 Functions
print()
83
Note that default arguments are possible only for trailing arguments.
The compiler will be confused at
void print(int b = 10, int)
// error
A typedef does not introduce a new type. For example, Uint and unsigned
int are the same type, and coef is the same type as a pointer to a function
that takes a double and returns a double: The last statement above declares
f to be a variable of type coef. That is, f can take on values that are pointers
to a function that takes a double and returns a double:
A pointer to a function can be passed as an argument to another function
like any other pointer. For example,
double integral(coef f, double a, double b){
return f((a+b)*0.5)*(b-a) // an inaccurate evaluation
}
// of a definite integral
double square(double d) { return d*d }
coef sfp = square
// initialize sfp with `square()'
double v = integral(sfp, 2, 2.01)
// call integral()
sfp = sqrt
// assign sqrt in <math.h> to sfp
v = integral(sfp, 2, 2.01)
// call integral()
84
3. Derived Types
The value of the variable first is initialized to true when the function
cal() is rst called. Then it is assigned to have a new value false inside the
function. This new value will be retained and the initialization statement
static bool first = true
3.8 Functions
85
When gcd() is rst called, callnum = 1 and callnum is then given a new
value 2: This value will be maintained when the second time gcd() is called
and the initialization int callnum = 1 will be ignored. In the second call,
callnum is incremented by 1 to a new value 3: With the static variable
callnum one can know how many times a function is called in a program.
For example, in calculating the greatest common divisor gcd(215 335) the
recursive function need be called eight times. However, look at the following
two calls of the function.
int i = gcd(215,335)
int j = gcd(215,335)
At the beginning of the second call j = gcd(215 335) the value of callnum
is 9 (from the rst call i = gcd(215 335)): The value of callnum will be
incremented from 9 to 16 during the second call, since the static variable
callnum persists throughout the program.
A static variable provides a function with \memory" without introducing
a global variable that might be accidentally accessed and corrupted by other
functions.
It takes no arguments and does nothing. Every C++ program must have
a function called main(): The program starts by executing this function.
The int value returned to the system indicates the program has completed
successfully or otherwise. If no value is explicitly returned, the system will
receive a zero value for successful completion and nonzero value for failure.
The function main() may take two arguments specifying the number of
arguments, usually called argc, and the array of arguments, usually called
argv for the program. The arguments are character strings having type
char !argc +1]: The name of the program is passed as argv!0] and the list
of arguments is zero-terminated that is, argv!argc] = 0.
For example, consider the simple program in x1.1. Instead of inputting
two integers after the program is run, we want to input them at the
UNIX/Linux command line as
86
3. Derived Types
add 1 1000
Here add is the name of the executable object code for the program. Then
add is stored in argv!0] and 1 and 1000 will be stored in argv!1] and argv!2],
respectively. The number of arguments is argc = 3: Note that argv!3] is 0
indicating the end of arguments. Now we need to modify the program to
support this input format:
#include <iostream>
#include <stdlib.h>
// library for function atoi()
int main(int argc, char* argv%]) {
if (argc != 3)
cout << "number of arguments is incorrect.\n"
int n = atoi(argv%1])
int m = atoi(argv%2])
if (n
int
n =
m =
}
//
//
//
//
> m) {
temp = n
m
temp
double sum = 0
// sum initialized to 0
for (int i = n i <= m i++) sum += i
cout << "Sum of integers from " << n << " to " << m
<< " is: " << sum << '\n'
// output sum to screen
}
Note that the function atoi() in the standard header <stdlib:h> converts
a character string into an integer. A related function atof () converts a
character string into a
oat.
..
.
.
text initialized uninitialized heap ..
..
.
data
data
87
..
.
..
. stack
..
.
tialized data. Uninitialized data contain items that are not initialized
by the program but are set to a default value (typically zero) before
the program starts execution. A program le consists of the program
text and initialized data. The advantage of providing an uninitialized data area is that the system does not have to allocate space in
the program le on the disk for uninitialized global variables. It also
reduces the time to read a program le from disk into memory.
The heap is used to allocate more data space dynamically for the
program while it is being executed.
The stack is used dynamically to contain the stack frames that include the return linkage for each function call and also the data elements required by the function.
A gap between the heap and the stack indicates that many operating
systems leave some room between these two portions so that they can grow
dynamically during a program execution. When there is no more room for
these two portions to grow, a run-time error will occur.
88
3. Derived Types
The variable param is stored in the initialized data area and myvalue
in uninitialized data. The constants 10000 555 and \result is: " (string
literal) can be stored as (read-only) initialized data. The variables a i and
sum are automatic variables and will be stored on the stack. When the
function f () is called in the statement myvalue = f () the system creates
the objects a i and sum on the stack and 10000 objects pointed to by a on
the heap. After the computation in the function call is nished, the objects
a i and sum are destroyed and the computed value of sum is returned
to the variable myvalue: However, the 10000 objects created on the heap
are not explicitly deleted and thus still exist throughout the rest of the
program execution. The automatic variable s is also stored on the stack
while the function main() is being executed. The machine instructions for
the functions main() f (), sqrt() and rand() are in the text segment.
{ /* do something */ }
Because of the higher precedence of == over the `bitwise and' operator &
the expression is interpreted as i & (mask == 0): In this case, parentheses
should be used to yield the desired result:
if ( (i & mask) == 0 )
{ /* do something */ }
89
90
3. Derived Types
91
which is equivalent to
if (0 > i || (i > 100 && abs(i) < 500))
{ /* ... */ }
where the function abs(i) gives the absolute value of an integer i (declared
in the standard header <stdlib:h>): Note that it is dierent from
if ((0 > i || i > 100) && abs(i) < 500)
{ /* ... */ }
// undefined results
92
3. Derived Types
and
and eq bitand bitor compl not
or eq xor xor eq
not eq or
are provided for some non-ASCII character sets, where characters such
as & and^are not available. They are equivalent to the following tokens,
respectively.
&&
!=
&=
jj
&
j=
~
^=
However, they can also be used even with ASCII character sets for clarity.
For example, not eq may be more readable than != to some people.
// cosine
// sine
// tangent
double
double
double
double
//
//
//
//
acos(double)
asin(double)
atan(double)
atan2(double x, double y)
arc cosine
arc sine
arc tangent
atan(x/y)
// hyperbolic cosine
// hyperbolic sine
// hyperbolic tangent
double acosh(double)
double asinh(double)
double atanh(double)
double exp(double)
double log(double d)
//
//
//
double log2(double d) //
93
In addition, <math:h> supplies these functions for float and long double
arguments. For complex arguments with real and imaginary parts in float
double and long double precisions, see x7.4. The angle returned by the
inverse trigonometric functions asin() acos(), atan() and atan2() are in
radians. The range of asin() and atan() is !;=2 =2]: The range of acos()
is !0 ]: The range of atan2() is !; ] whose principal use is to convert Cartesian coordinates into polar coordinates. For asin() and acos(), a
domain error occurs if the argument is not in !;1 1]:
The function frexp(double d int p) splits a value d into mantissa x and
exponent p such that d = x 2p and returns x where the magnitude of x is
in the interval !0:5 1) or x is zero. The function fmod(double d double m)
returns a
oating modulus d(mod m): If m is nonzero, the value d ; i m
is returned, where i is an integer such that the result is zero, or has the
same sign as d and magnitude less than the magnitude of m: If m is zero,
the return value is system-dependent but typically zero.
Errors are reported by setting the variable errno in the standard library
<errno:h> to EDOM for a domain error and to ERANGE for a range
error, where EDOM and ERANGE are macros dened in <errno:h>.
For example,
int main() {
errno = 0
// clear old error state, include <errno.h>
double x = - 50
// x can also be computed at run-time
94
3. Derived Types
double i = sqrt(x)
if (errno == EDOM)
cerr << "sqrt() not defined for negative numbers\n"
double p = pow(numeric_limits<double>::max(), 2)
if (errno == ERANGE)
cerr << "result of pow(double,int) overflows\n"
where cerr represents the standard error stream (usually the terminal
screen), dened in <iostream>. Thus, by checking the value of the variable
errno, the user can know if a domain or range error has occurred in calling
standard functions in <math:h>.
For historical reasons, a few mathematical functions are in <stdlib:h>
rather than <math:h>:
int abs(int)
long abs(long)
long labs(long)
// absolute value
// absolute value, not in C
// absolute value
// convert p to double
// convert p to int
// convert p to long int
In the denition of the structures div t and ldiv t the type of quot and rem
is implementation-dependent. The functions atof , atoi, and atol convert
strings representing numeric values into numeric values, with the leading
whitespace ignored. If the string does not represent a number, zero is returned. For example, atoi("seven") = 0 atoi("77") = 77, while atoi('s')
gives the numeric value representing the lowercase letter s.
95
needs to be evaluated for given values of x where the coecients an an;1
: : : a1 a0 are known. A C++ function can be written to do this job having
prototype
double eval(double* a, int n, double x)
96
3. Derived Types
return u:
It takes only n multiplications. Now Horner's algorithm can be coded into
C++ as
This program is not only shorter, but also more ecient than the straightforward approach eval(). Ecient algorithms require fewer operations and
may sometimes result in smaller accumulative roundo errors.
Now these two functions can be compared by evaluating an 8th degree
polynomial:
p8 (x) = x8 ; 8x7 + 28x6 ; 56x5 + 70x4 ; 56x3 + 28x2 ; 8x + 1
for x taking on 11 equally spaced values covering interval !0:99999 1:0001]:
Observe that p8 (x) = (x ; 1)8 : This expression will be compared to the
values returned by eval() and horner():
#include <iostream>
#include <math.h>
int main() {
double a%9] = {1, - 8, 28, - 56, 70, - 56, 28, - 8, 1}
for (double x = 0.99999 x <= 1.0001 x += 1.0e-5) {
cout.width(18)
cout << eval(a,9,x)
// straightforward evaluation
cout.width(18)
cout << horner(a,9,x) // Horner's algorithm
cout.width(18)
cout << pow(x-1,8) << '\n'
// direct evaluation
}
}
97
2.14564e-16
0
1.32321e-15
-1.59367e-15
-1.76064e-15
-6.9931e-17
-3.5203e-15
-3.23331e-15
-1.87686e-15
-2.31586e-16
5.89264e-16
1e-40
0
1e-40
2.56e-38
6.561e-37
6.5536e-36
3.90625e-35
1.67962e-34
5.7648e-34
1.67772e-33
4.30467e-33
The correct value for p8 (x) = (x ; 1)8 should be nonnegative and very
small since x is close to 1: The third column is the best since it is a direct
calculation of (x ; 1)8 and will be used as exact values in the comparison.
Comparing the rst column (produced by eval()) with the second column
(produced by horner()), the results by Horner's algorithm are more accurate (closer to the third column) for 8 out of 11 values. Both eval() and
horner() produce negative values due to the subtractions of nearly equal
quantities in their evaluations.
Let us now enlarge the errors by 1010 times in the evaluations of eval()
and horner() so that their accuracies can be more clearly seen. Modify the
main program as
int main() {
double a%9] = {1, - 8, 28, - 56, 70, - 56, 28, - 8, 1}
for (double x = 0.99999 x <= 1.0001 x += 1.0e-5) {
cout.width(20)
cout << 1.0e10*(pow(x-1,8) - eval(a,9,x))
cout.width(20)
cout << 1.0e10*(pow(x-1,8) - horner(a,9,x)) << '\n'
}
}
-2.14564e-06
0
-1.32321e-05
1.59367e-05
1.76064e-05
6.9931e-07
3.5203e-05
3.23331e-05
1.87686e-05
98
3. Derived Types
-8.88178e-05
-2.66454e-05
2.31586e-06
-5.89264e-06
For 9 out of 11 values, the second column is no larger than the rst.
In large-scale applications, it is not enough to compute correctly, but
also eciently and elegantly. The elegance of a program should include extendibility, maintainability, and readability, while correctness should also
include accuracy, robustness, and portability. Both horner() and eval() are
correct (horner() is slightly more accurate in certain cases), but the eciency of horner() is far better for polynomials of high degrees. In nance,
evaluation of high-degree polynomials is often encountered see Exercises
3.14.18 and 4.8.12.
y = f (x)
6
f (xi )
f (xi;1 )
99
xi;1
xi
FIGURE 3.2. The integral xxii;1 f (x)dx, representing the area of the region under
curve y = f (x) and above subinterval xi;1 xi ] is approximated by the area of
a trapezoid (with one edge drawn in dotted line), which is f (xi;1 ) + f (xi )]h=2:
<< result << '\n'
result = simpson(0, 5, sqrt, 100)
cout << "Integral using simpson with n = 100 is: "
<< result << '\n'
}
Thus,
n Z
b f (x)dx = X xi f (x)dx
a
i=1 xi;1
100
3. Derived Types
i=1
;1
f (xi;1 ) + f (xi ) h = f (x0 ) + nX
f (xn ) h
f
(
x
)
+
i
2
2
2
"
i=1
n Z
b f (x)dx = X xi f (x)dx
a
i=1 xi;1
n f (x ) + 4f (#x ) + f (x )
X
i;1
i
i h
6
i=1
n
n
X
X
= 31 f (xi;1 )2+ f (xi ) h + 32 f (#xi )h:
i=1
i=1
Notice that the rst summation in the formula is the same as the Trapezoidal Rule. Now it can be dened as
double simpson(double a, double b, pfn f, int n) {
double h = (b - a)/n
double sum = f(a)*0.5
for (int i = 1 i < n i++) sum += f(a + i*h)
sum += f(b)*0.5
double summid = 0.0
for (int i = 1 i <= n i++) summid += f(a + (i-0.5)*h)
return (sum + 2*summid)*h/3.0
}
3.14 Exercises
101
In x5.1, an abstract mechanism called class is used to present this example in a dierent style. Notice that this C-style denition of trapezoidal()
and simpson() passes a function pointer f for the integrand as an argument, which imposes function calling overhead for each call f () inside
trapezoidal() and simpson(): In x7.7 some ecient techniques are applied
to overcome the function calling overhead in passing a function pointer to
another function call, where integration functions are written with a type
parameter so that they can also handle arithmetics in dierent precisions
such as oat, double, and long double. For more details on mathematical
aspects of the Trapezoidal and Simpson's Rules, see !Ste99, KC96, VPR00].
3.14 Exercises
3.14.1. Write a program that denes a const integer n generates n random
numbers and stores them in an array, and computes and outputs
the average of these random numbers. Also compute and output the
maximum and minimum values of the random numbers generated.
3.14.2. The standard library <assert:h> contains a macro assert() that can
be used to ensure that the value of an expression is as expected. Here
is an example of how assert() can be used to ensure a variable n is
positive, but less than or equal to 100:
#include <assert.h>
void f(int n) {
assert(n > 0 && n <= 100)
}
If an assertion fails, the system will print out a message and abort the
program. When the macro NDEBUG is dened at the point where
<assert:h> is included, for example,
#define NDEBUG
#include <assert.h>
then all assertions are ignored. Thus assertions can be freely used during program development and later discarded (for run-time eciency)
by dening NDEBUG: Write a program to generate Fibonacci numbers (see x2.4) ensuring that they are positive using an assertion.
3.14.3. Write a function having prototype
int sumsq(int n, int m)
102
3.14.4.
3.14.5.
3.14.6.
3.14.7.
3. Derived Types
that returns the sum of squares n2 +(n +1)2 + +(m ; 1)2 + m2 for
n < m: The function should also be able to handle the case n > m:
Write a function that computes and returns the number of (decimal)
digits of a given integer n. For example, it should return 3 if n = 145:
Write a function
p outputs (e.g., the rst 1000 terms of) the sep that
quence dn = n ; n ; 1 for n = 1 2 3 : : :: Write another function
that computes dn in a mathematically equivalent but numerically
more robust way:
dn = p 1p
for n = 1 2 3 : : ::
n+ n;1
Compare the dierence in the outputs of the two functions. The second one should give more accurate results since it avoids subtracting
two nearly equal numbers and thus reduces cancellation errors.
An algorithm to nd a square root of a positive real number b is:
x0 = 1 xn+1 = 12 xn + xb n = 0 1 2 : : ::
n
Write a function that implements the algorithm.
A fast (quadratic)
p
convergence should be observed for xn ! b: Compare the accuracy
of this function with the built-in function sqrt() in <math:h>.
Write a function that takes three doubles a b and c as input and
computes the roots of the quadratic equation
ax2 + bx + c = 0:
When a = 0 and b 6= 0 the equation has only one root x = ;c=b:
When a 6= 0 its roots are given by
p
are not equivalent. The operand a!i ++] has a side eect of incrementing i: Except for possible implementation eciency, the rst
statement is equivalent to
3.14 Exercises
103
a%i] = a%i]*n
i = i + 1
or
a%i] = a%i+1]*n
i = i + 2
depending on whether the right-hand side of the assignment is evaluated before the left-hand side. The order of their evaluations is not
dened. Test what result your machine gives for the second statement
above. Such a statement is not portable and should be avoided.
3.14.9. Allocate space for a sequence, denoted by s of 2n + 1 doubles so that
the index changes from ;n to n where n is a given positive integer.
Assign s!i] = i for i = ;n ;(n ; 1) : : : ;1 0 1 : : : n ; 1 n and
print out the values of s!i] ;n i n for n = 10: Finally, delete
the space.
3.14.10. Explain the dierence between a two-dimensional array and a double
pointer. Consider further an example of a two-dimensional array:
double tda%5]%7]
The base address of the array tda is &tda!0]!0] or tda!0] but not tda:
The array name tda by itself is equivalent to &tda!0]: What can you
say for a double pointer and a one-dimensional array?
3.14.11. Write a function that takes an n by m matrix and a vector of m
components as input and computes the matrix-vector product such
that it has prototype:
void multiply(const double** const mx,
const double* const vr, int n,
int m, double* const pt)
104
3. Derived Types
// or computed at run-time
3.14 Exercises
105
double**** p4d
// b defaulted to be 10
S=
nX
;1
;i
;n
r
r
Pi 1 + 12 + Pn 1 + 12
i=1
assuming that each Pi is no smaller than his regular monthly payment
106
3. Derived Types
Bn =
XY
bj = b0 + b0 b1 + b0 b1 b2 + + b0 b1 bn
i=0 j =0
n Y
i 1
X
1 + 1 + 1 + +
1
Dn =
=
d0 d1 dn :
i=0 j =0 dj d0 d0 d1 d0 d1 d2
Em =
m 1
n=2 n!
for m = 100 500 and 1000: Hint: your answer should be closer to
0.71828182845904523536 as m gets larger.
3.14.20. The base of the natural algorithm e is
e = 2:7182818284590452353602874713526624977572 : : :
to 41 signicant digits. It is known that
n
x = 1 + 1 ! e as n ! 1:
n
Write a function that generates the sequence fxn g and prints out the
error e ; xn : A very slow (worse than linear) convergence should be
observed for xn ! e: Indeed it can be shown that je ; xn+1 j=je ;
xn j ! 1 as n ! 1: A computationally eective way to compute e to
arbitrary precision is to use the innite series:
1
X
e = n1! = 2 + 2!1 + 3!1 + 4!1 + :
n=0
To reduce roundo errors, smaller terms should be added rst in
calculating partial sums. It can be shown that
m 1
X
e :
<
e ;
n
!
(
m
+
1)!
n=0
3.14 Exercises
107
Write another function to generate the partial sums for approximating e and compare the convergence. Horner's algorithm may be used
to eciently compute this kind of partial sum.
3.14.21. When n is large, the factorial n! can easily cause overow. Write
a function to calculate n! by storing a large integer in an array of
integers, whose one element stores one digit of the large integer. To
illustrate the idea, consider the program:
#include <iostream>
// for input and output
#include <stdlib.h>
// for function exit()
const int mxdgs = 9500 // max # of digits in factorial
int main() {
int n
cout << "Enter a positive integer: \n"
cin >> n
// digits of n! will be stored in array dgs backwards,
// ie, highest digit will be stored in dgs%0], next
// highest in dgs%1], and so on.
int dgs%mxdgs]
dgs%0] = 1
for (int i = 1 i < mxdgs i++) dgs%i] = 0
for (int k = 2 k <= n k++) { // n! =2*3*4...(n-1)*n
int cary = 0
for (int j = 0 j < mxdgs j++) {
// multiply k with every digit of (k-1)! to get k!
dgs%j] = k*dgs%j] + cary
cary = dgs%j]/10
dgs%j] -= 10*cary
}
}
// counting the number of digits in n!
int count = mxdgs - 1
while (!dgs%count]) count--
// compute the number of digits in integer n
int numdgofn = 0
int m = n
while (m) {
m /= 10
numdgofn++
}
108
3. Derived Types
// check to see if size of dgs is enough
if (mxdgs - count < numdgofn + 1) {
cout << "factorial overflow, adjust parameter.\n"
exit(1)
// exit program
}
// printing n!: lowest digit dgs%count] first,
// highest digit dgs%0] last.
cout << "There are " << count + 1 << " digits in "
<< n << "!. They are:\n"
for (int i = count i >= 0 i--) cout << dgs%i]
cout << '\n'
}
3.14 Exercises
109
3.14.23. Compute the integral 12 ((sin x)=x)dx with ve digit accuracy after
the decimal point.
3.14.24. The standard library <stdlib:h> contains a sorting function qsort()
based on the quick sort algorithm. It can sort an array of any elements
in an order dened by the user through a pointer to a function. It
has prototype:
typedef int (*CMF)(const void* p, const void* q)
// a type for comparing p and q, it defines an order
void qsort(void* b, size_t n, size_t sz, CMF cmp)
// sort "n" elements of array "b" in an order
// defined by comparison function "cmp".
// Each element of "b" is of size "sz" (in bytes).
The operator static cast converts between related types such as from
one pointer type to another or from an enumeration to an integral type. Since r has type const void, it needs to be converted
to const point2d before accessing its member x.
Now arrays of point2d can be generated and sorted by qsort() in the
order dened by cmp2dx() :
int main() {
const int n = 10
point2d a%n]
for (int i = 0 i < n i++) {
a%i].x = i*(5 - i) a%i].y = 5/(i+2.3) - 1
}
110
3. Derived Types
qsort(a, n, sizeof(point2d), cmp2dx)
for (int i = 0 i < n i++)
cout << a%i].x << " " << a%i].y << '\n'
}
For people who are curious about how such a sorting function is
dened, here is a denition of a related (based on the shell sort algorithm see !Knu98] and !Str97]) function:
void shsort(void* b, size_t n, size_t sz, CMF cmp) {
for (int gap = n/2 gap > 0 gap /= 2)
for (int i = gap i < n i++)
for (int j = i - gap j >= 0 j -= gap) {
char* base = static_cast<char*>(b)
char* pj = base + j*sz
char* pjpg = base + (j + gap)*sz
if (cmp(pj, pjpg) > 0) { // swap b%j], b%j+gap]
for (int k = 0 k < sz k++) {
char temp = pj%k]
pj%k] = pjpg%k]
pjpg%k] = temp
}
}
}
}
3.14 Exercises
111
*(*a)%10]
(*a)%10]
112
3. Derived Types
C++ supports modular programming through a mechanism called namespace. A namespace is a logical unit that contains related declarations and
denitions. The idea of modular programming is to divide a large program
into small and logically related parts for easy management and information hiding. Dividing a large program into dierent parts and storing them
in dierent les can also help to achieve modular programming (this is
more so in C and FORTRAN 77). A few useful tools (some in UNIX and
Linux) are also presented for managing les, creating a library, proling a
program, debugging a program, and timing a program. Towards the end
of the chapter, two standard libraries on character strings and input and
output streams are given. Finally, iterative algorithms for solving nonlinear
equations are described.
4.1 Namespaces
The keyword namespace is used to group related declarations and denitions in C++. For example, we can group vector-related declarations in
a namespace called Vec and matrix-related declarations in a namespace
called Mat:
namespace Vec {
const int maxsize = 100000
double onenorm(const double* const, int)
double twonorm(const double* const, int)
// 1-norm
// 2-norm
114
// max norm
}
namespace Mat {
double onenorm(const double** const, int)
double twonorm(const double** const, int)
double maxnorm(const double** const, int)
double frobnorm(const double** const, int)
}
//
//
//
//
1-norm
2-norm
max norm
Frobenius
For a vector v = !v0 v1 : : : vn;1 ] its one, two, and maximum (also
denoted by 1) norms are dened as
nX
;1
kv k1
kv k2
n;
= jv0 j2 + jv1 j2 + + jvn;1 j = =
jvi j2
kv k1
i=0
jvi j
v
u
1
uX
t
2 1 2
i=0
kAk1
= max
kAk1
kAkF
n;
= max
jai0 j
i=0
n;1
X
j =0
nX
;1
jai1 j : : :
i=0
nX
;1
ja0j j
j =0
nX
;1
i=0
nX
;1
ja1j j : : :
jain;1 j
j =0
B3 = 4
= 0max
j<n
nX
;1
= 0max
i<n
v
u
1
1
uX X
t
j
2 1 2
1 ;2 0
;4 3 5 5
2 0 6
jaij j
i=0
nX
;1
jan;1j j
n; n;
i=0 j =0
j =0
jaij j
aij j2
4.1 Namespaces
115
The function fabs() declared in <math:h>, gives the absolute value of its
argument and max() declared in <algorithm>, returns the maximum of its
two arguments. The name maxsize can be used directly without using the
qualication operator :: inside the denitions of members of namespace Vec.
This is because maxsize is a member of Vec. Note that in the third function
denition above, the variable norm could cause over
ow while sqrt(norm)
116
is still within the range of the machine. A robust 2-norm formula is given
in Exercise 4.8.5 that will not cause over
ow if kvk2 is within range.
Functions for the matrix namespace Mat can be dened similarly. However, when members from another namespace are needed, the qualication
operator :: should be used. For example, use Vec::onenorm to refer to the
function onenorm() in the namespace Vec. In this way, name clashes can
be avoided. That is, members of dierent namespaces can have the same
names. For example,
double Mat::maxnorm(const double** const a, int size) {
double norm = Vec::onenorm(a%0],size)
// 1-norm of row 0
for (int i = 1 i < size i++)
norm = max(norm, Vec::onenorm(a%i],size)) //a%i]: row i
return norm
}
double Mat::onenorm(const double** const a, int size) {
double norm = 0
for (int j = 0 j < size j++) {
double temp = 0
// store column abs sum
for (int i = 0 i < size i++) temp += fabs(a%i]%j])
norm = max(norm, temp)
}
return norm
}
double Mat::frobnorm(const double** const a, int size) {
double norm = 0
for (int i = 0 i < size i++)
for (int j = 0 j < size j++) norm += a%i]%j]*a%i]%j]
return sqrt(norm)
}
See Exercise 4.8.6 for a robust denition of the Frobenius matrix norm.
However, for matrices whose entries are not large enough to cause over
ow
when summing the squares of all entries, this straightforward evaluation of
the Frobenius norm should be more ecient.
Now the namespaces Vec and Mat can be applied to evaluate norms of
matrices and vectors:
// declarations and definitions of Vec and Mat may be put here
int main() {
int n = 1000
double** a = new double* %n]
for (int i = 0 i < n i++) a%i] = new double %n]
4.1 Namespaces
117
When namespace Mat or other programs have to use members of namespace Vec intensively, repeated qualication using Vec:: can be tedious and
distracting, especially if a namespace has a long name. This qualication
can be eliminated by applying the keyword using:
double Mat::maxnorm(const double** const a, int size) {
using Vec::onenorm
// use onenorm from namespace Vec
double norm = onenorm(a%0],size) // Vec::onenorm is used
for (int i = 1 i < size i++)
norm = max(norm, onenorm(a%i],size))
return norm
}
The using declaration brings Vec::onenorm into the current scope and then
it can be used directly without qualifying by the name of its namespace.
Note that a function name overloading also happened here. Since Vec's
function onenorm() takes dierent arguments from Mat :: onenorm() the
compiler can gure out that Vec::onenorm() is called. If both Vec and Mat
118
had exactly the same members, the using-declaration would cause a name
clash.
If a member of Mat depends on several members of Vec, then the usingdeclaration has to be used for each member. This can be replaced by the
using-directive:
namespace Mat {
double onenorm(const double** const, int)
double twonorm(const double** const, int)
double maxnorm(const double** const, int)
double frobnorm(const double** const, int)
using namespace Vec
//
//
//
//
1-norm
2-norm
max norm
Frobenius
The using directive brings all members of namespace Vec into the scope of
namespace Mat: Then all names of Vec can be used directly in namespace
Mat and the denitions of its members.
By the using-directive for namespace Vec, the code for members of Mat
can then be simplied as if everything of Vec had the same scope. For
example, Mat :: maxnorm() can be dened as:
double Mat::maxnorm(const double** const a, int size) {
double norm = onenorm(a%0],size) // Vec::onenorm is used
for (int i = 1 i < size i++)
norm = max(norm, onenorm(a%i],size))
return norm
}
If two namespaces have two functions that have the same prototype and
both namespaces are used in a third namespace, it is possible to choose one
function over the other and to avoid name clash. For example,
namespace A {
int f(int)
}
namespace B {
int f(int)
}
namespace C {
using namespace A
using namespace B
using A::f
int g(int i) {
4.1 Namespaces
return i + f(i)
119
// A::f(i) is called
}
int h(int i) {
return i + B::f(i)
}
// B::f(i) is called
//
//
//
//
1-norm
2-norm
max norm
Frobenius
These two versions of the namespace Mat can coexist in a program so that
each can be used where it is most appropriate. For example, for a user
who just needs to evaluate matrix norms, the rst version is sucient to
bring the functions of Mat into her scope (including what she does not
need may be confusing). Minimal interfaces lead to systems that are easier
to understand, have better information hiding, are easier to modify, and
compile faster.
120
provided the rst version of Mat has been declared. That is, a namespace
can have multiple interfaces, but the compiler will combine all of them
together for the namespace. Another example is:
namespace A {
struct pt { double x double y }
int f(pt)
// A has two members pt and f()
}
namespace A {
void g()
}
Numerical_Integration::lowerbound = 1.0
The long name of this namespace takes time to type and is not quite
readable. A short name NI can be provided for the namespace:
namespace NI = Numerical_Integration
4.1 Namespaces
121
NI::lowerbound = 1.0
namespace my_module {
using NI::lowerbound
// .....
}
Its members are global names in the le (more precisely, translation unit
see x4.3) where it is dened. It is equivalent to
namespace anyname {
int a
void f(int& i) { i++ }
int g() { return 5 }
}
using namespace anyname
where anyname is some name unique to the scope in which the namespace
is dened. Unnamed namespaces in dierent translation units (les see
x4.3) are distinct. There is no way to refer to a member of an unnamed
namespace from another translation unit. Unnamed namespaces can be
used to avoid using one of the meanings of static in C programs (see x4.3.2).
122
// Matrix::maxnorm() is called
// Matrix::onenorm() is called
The way in which a C program includes its standard libraries' header les
is still valid in C++. For example,
#include <math.h>
// math library, inherited from C
#include <stdio.h>
// I/O library, from C
int main() {
printf("Hello World!\n")
// prints string to screen
printf("square root of pi is: %f", sqrt(atan(1)*4))
}
where the output function printf () has the prototype (it is an example of
a function that takes an arbitrary number of arguments, indicated by an
ellipsis):
int printf(const char* ...)
// from <stdio.h>
It prints out strings and values of variables to the screen according to some
specied format. Here %f means to print out the value of sqrt(atan(1) 4)
as a float and % is a format control character.
All C++ standard libraries, including those inherited from C, are in a
namespace called std: However, there is no namespace in C. The declarations of standard C I/O facilities from the C header le <stdio:h> are
wrapped up in the namespace std like this:
123
The using-directive above makes every declaration in stdio:h globally available in every le that includes the header stdio:h:
Similarly, the declarations in C's <math:h> le are put in the namespace
std, and made globally available. New C++ header les such as <cstdio>
and <cmath> (note: without the .h sux) are dened for users who do
not want the names globally available. For example, the C++ header le
cstdio looks like
// in header file cstdio
namespace std {
int printf(const char* ...)
// ..... declarations of C's other stuff in cstdio
}
That is, explicit namespace qualication such as std :: printf () and std ::
sqrt() must be used here. If we do not want the namespace qualier std ::
we can either use C-style include les with a sux :h or do this:
#include <cmath>
// C++ math library
#include <cstdio>
// C++ I/O (input/output) library
int main() {
using namespace std // make declarations in std available
printf("Hello World!\n")
// print string to screen
printf("square root of pi is: %f", sqrt(atan(1)*4))
}
124
<assert:h>
<ctype:h>
<errno:h>
<float:h>
<limits:h>
<locale:h>
<math:h>
<setjmp:h>
<signal:h>
<stdarg:h>
<stddef:h>
<stdio:h>
<stdlib:h>
<string:h>
<time:h>
Note that C++ has an alternative input and output library with headers
<iostream> and <fstream>: Details of the standard libraries <iostream>
and <fstream> are talked about in x4.6.
Table 4.1 lists standard C header les, while Tables 4.2 and 4.3 contain
standard C++ header les.
then this header le should be included in another le using double quotes:
// in file my.cc
#include "my.h"
<algorithm>
<bitset>
<cassert>
<cctype>
<cerrno>
<cfloat>
<climits>
<clocale>
<cmath>
<complex>
<csetjmp>
<csignal>
<cstdarg>
<cstddef>
<cstdio>
<cstdlib>
<cstring>
<ctime>
<cwchar>
<cwtype>
<deque>
<exception>
<fstream>
<functional>
<iomanip>
<ios>
<iosfwd>
<iostream>
<istream>
<iterator>
<limits>
<list>
<locale>
<map>
<memory>
<new>
<numeric>
<ostream>
<queue>
<set>
<sstream>
general algorithms
set of Booleans
diagnostics, denes assert() macro
C-style character handling
C-style error handling
C-style
oating point limits
C-style integer limits
C-style localization
mathematical functions (real numbers)
complex numbers and functions
nonlocal jumps
C-style signal handling
variable arguments
common denitions
C-style standard input and output
general utilities
C-style string handling
C-style date and time
C-style wide-character string functions
wide-character classication
double-ended queue
exception handling
streams to and from les
function objects
input and output stream manipulators
standard iostream bases
forward declarations of I/O facilities
standard iostream objects and operations
input stream
iterators and iterator support
numeric limits, dierent from <climits>
doubly linked list
represent cultural dierences
associative array
allocators for containers
dynamic memory management
numeric algorithms
output stream
queue
set
streams to and from strings
125
126
<stdexcept>
<stack>
<streambuf>
<string>
<typeinfo>
<utility>
<valarray>
<vector>
standard exceptions
stack
stream buers
string, dierent from <cstring>
run-time type identication
operators and pairs
numeric vectors and operations
one-dimensional array
int main() {
point2d A
{
using namespace std // cmath, iostream available here
A.x = sqrt(5.0)
A.y = log(5.0)
cout << "A.x = " << A.x << " " << "A.y = " << A.y
}
}
Spaces are signicant within " " or < > of an include directive. For
example,
#include " my.h"
#include <iostream >
named namespaces
type denitions
data declarations
constant denitions
enumerations
function declarations
inline function denitions
name declarations
include directives
macro denitions
template declarations
template denitions
condition compile directives
comments
127
checks if identifier has been and remains dened. It can be used to include
or skip some statements from it up to
#endif
depending on whether identifier has been and remains dened. For example, C and C++ can share the following header le.
#ifdef __cplusplus
namespace std {
#endif
128
checks whether the (constant) expression evaluates to true: Similar directives are #else and #elif (else if). They may be used as
#define DEBUG 1
// debugging information can be printed out.
#if DEBUG
cout << "debug: value of a is: " << a << '\n'
#endif
After the debugging process, DEBUG can be dened to be 0 so that unnecessary information will not be printed out. In fact, this printing statement
above will then be ignored by the preprocessor when the le is compiled.
This use of directives actually serves as comments that can be easily turned
on and o.
If a le includes a header le, the preprocessor simply copies the header
le into the le at the point indicated before compilation begins. There
are situations where several header les are necessary and one includes
another. Then there is a danger that some header les are included in a le
more than once. For example, if head1:h and head2:h both include head:h
and head1:h also includes head2:h, to avoid chaining, head:h may take the
form:
#ifndef _HEAD_H
#define _HEAD_H
void my_function(double)
// declare/define other things that need be
// declared/defined in head.h
#endif
129
If the le head:h has already been included by the preprocessor, the macro
HEAD H will have been dened and the body of the le will not be
included more than once. For safety, every header le may be designed like
this.
If somewhere the macro HEAD H need be undened, use the directive
#undef as
#undef _HEAD_H
130
// file2.cc
extern double x
// another file may define x
extern double f(double)
// another file may define f
void g(double z) { x = f(z) }
The variable x and function f () used in file2:cc are the ones dened in
file1:cc. Thus x and f () are said to have external linkage. External variables have external linkage. Note that x and f are both global and external
according to denitions in x2.1. A name that can be referred to only in
the translation unit in which it is dened is said to have internal linkage:
Thus the function g() in file2:cc has internal linkage and can not be used in
file1:cc. The keyword extern in front of a function such as double f (double)
on the second line in file2:cc can be omitted.
An object must be dened only once in a program, although it can be
declared many times as long as the declarations agree exactly with each
other. For example, consider the following two les.
// file3.cc
int i = 5
float a = 3.14
extern char c
// definition of c
There are two errors in these two les: the variable i is dened twice and
a is declared twice with dierent types. Note c is declared three times
but dened only once, which is not an error. These two errors can not
be caught by the compiler since each le is compiled separately. They are
linkage errors that should be detected by the linker.
At this moment, it may be a good idea to look at a few more examples
of declarations and denitions:
double d
int f(int)
extern int i
struct s
//
//
//
//
definition
declaration
declaration
declaration
131
// definition
// definition
// definition
//
//
//
//
internal
internal
internal
external
// file6.cc
#define DEBUG 0
const double d = 2.17
typedef unsigned long int Uint
extern const int i
//
//
//
//
internal linkage
internal linkage
internal linkage
declaration
linkage
linkage
linkage
linkage
The compiler needs to know its denition in order to make code substitution. An inline function can not be made extern to simplify the implementation of C++ compilers.
These specications may be very confusing and error-prone. The best
way to handle them is to put them in a common header le and include it
in dierent source les. An example is:
// file9.h
a header file
#include <iostream>
// include a standard library
#include <cmath>
// include a standard library
#define DEBUG 1
const double d = 2.17
// user's stuff
132
// definition of g
// file11.cc
a source file the main program
#include "file9.h"
int main() {
double a = g(pi, 5) + f(10) + d
// ...
std::cout << "a = " << a << '\n'
}
Here all common declarations and denitions are put in header le file9:h
and source les file10:cc and file11:cc both include this header. A struct,
enum, named namespace, inline function, and so on, must be dened exactly once in a program. Strictly speaking, this rule is violated when their
denitions are put in a header le and included in dierent source les.
For convenience, the language allows this since the denitions appear in
dierent translation units and are exactly the same. See x4.2.2 for what
should be and what should not be put in header les.
It is worthwhile to note that there are dierences in C and C++ in terms
of linkage. For example, consider these two les:
// file12.cc
float y
float f() { return 5 }
int main() { g() }
// file13.cc
133
float y
void g() { y = f() }
There are three errors as C++ programs: y is dened twice (the declarations
of y are also denitions since the compiler initializes y to a default value)
in the two les, and f () in file13:cc and g() in file12:cc are not declared.
However, they are correct C programs.
Sometimes a programmer has to deal with codes written in C++ and other
languages such as C and FORTRAN. It can be dicult in general, since
dierent languages may dier in the layout of function arguments put on
the run-time stack, in the use of machine registers, in the layout of builtin types such as integers and arrays, and so on. For example, the layout
of two-dimensional arrays is row by row in C++ and C, but column by
column in FORTRAN, and arguments to functions are passed exclusively
by reference in FORTRAN.
A user can specify a linkage convention in an extern declaration. For
example,
extern "C" char* strcpy(char*, const char*)
// C linkage convention
extern "C" {
int f(int)
int g(int)
double sqrt(double)
}
// C linkage convention
// group several declarations
// in one block
Note that the letter C in double quotes following extern means to link
these function calls according to the C linkage convention. Here C does
not refer to a language but to a linkage convention. It can also be used to
link FORTRAN and assembler codes that conform to C implementation
conventions.
In general, C code can be mixed with C++ code statement by statement,
provided the C code does not contain C++ keywords as user-dened names.
This enables many C programs to be called directly from and mixed with
C++ programs. Vaguely speaking, C is a subset of C++. However, dealing
with a mixture of C++ and FORTRAN programs is platform-dependent.
For example, a FORTRAN function name sum() could be compiled to a
symbol name sum, sum , sum , or SUM, depending on the compiler. For
examples on how to mix C/C++ and FORTRAN programs, see !LF93,
Arn98].
134
C++ inherits from C the time and date library with header in <ctime>
and <time:h>.
They have a function clock() that provides access to the underlying machine clock and returns an approximation to the number of CPU \clock
ticks" used by the program up to the current time. The returned value
can be divided by CLOCKS PER SEC to convert it into seconds. The
macro CLOCKS PER SEC is dened in <ctime> or <time:h>, but the
rate at which the clock time runs is machine-dependent. If the CPU clock
is not available, the value ;1 is returned. The function time() returns the
calendar time, normally expressed as the number of seconds elapsed since 1
January 1970. Other units and starting dates are possible. The prototypes
of these two functions and related declarations are given in <ctime> and
<time:h> and look like the following.
#define CLOCKS_PER_SEC
typedef long clock_t
typedef long time_t
clock_t clock(void)
time_t time(time_t* tp)
100
// machine dependent
When the variable tp in function time(tp) is not the null pointer, the returned value also gets stored in the object pointed to by tp: Note that the
calendar time (wall time) and CPU clock time may not be the same in a
time-shared operating system such as UNIX and Linux.
For example, the following program computes the wall times and CPU
times for a number of multiplications in data types of double and float.
#include <iostream>
#include <time.h>
// for output
// for measuring time
int main() {
int n = 100000000
double d, dpi = 3.1415926535897932385
135
3 15:56:45 2000
136
If ;o pgm is left out above, the executable object le will be created in
the default le a:out instead of pgm: These two commands can also be
combined into one:
c++ -o pgm main.cc pgm0.cc pgm1.cc pgm2.cc
Your compiler may not support all of these options and may provide others.
Also your compiler may not be named c ++:
A debugger allows the programmer to know the line number in a program where a run-time error has occurred, and to see the values of variables
and expressions at each step. This can be very helpful in nding out why
a program is not running successfully. The debuggers gdb and dbx are generally available on UNIX and Linux systems. The code must be compiled
with the ;g option. To use gdb on the object code pgm for example, at
the command line type
gdb
Then type run pgm (followed by the argument list if pgm has any) and
it will run the program. Errors, if existing, will be displayed. Then typing
where gives the exact line number and the function in which the error has
occurred. Below are some useful commands.
137
The UNIX/Linux manpage may give more information on how to use it.
See !LF93] for debuggers dbx and xdb, and other tools.
In UNIX, the ;p option used in compiling a program produces code that
counts the number of times a function is called. The proler prof then
generates an execution prole, which can be very useful when working to
improve execution time eciency. Look at the example:
#include
#include
#include
#include
<iostream>
<time.h>
<stdlib.h>
<algorithm>
//
//
//
//
for
for
for
for
output
measuring time
pseudorandom number
sort()
int main() {
int n = 100000
double* dp = new double %n]
srand(time(0))
// seed random number generator
for (int i = 0 i< n i++) dp%i] = rand()%1000
sort(dp, dp+n)
The function srand() seeds the random number generator and causes
the sequence generated by repeated calls to rand() to start in a dierent
place. The function sort() sorts a sequence in increasing order by default
and is discussed further in x10.2.1.
Suppose the program is called sortrand:cc. Compile it with the ;p option:
c++ -o sortrand -p sortrand.cc
The executable object code is written in le sortrand. Next run the program
by issuing the command
sortrand
138
prof sortrand
cumsecs #call
1.04
1
1.43
1.72 100000
1.77 100000
1.81 100000
1.81
1
1.81
1
1.81
1
1.81
1
1.81
1
1.81
1
1.81
3
1.81
2
1.81
1
1.81
1
1.81
1
1.81
1
ms/call
1040.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
0.00
name
_main
mcount
.rem
.mul
_rand
.udiv
.umul
_cfree
_exit
_free
_gettimeofday
_malloc
_on_exit
_profil
_sbrk
_srand
_time
This execution prole gives the number of times that a function is called,
average number of milliseconds per call, and cumulative times in seconds.
For example, the function rand() was invoked 100000 times, took 0:00
milliseconds per call, and took total 1:81 seconds, while the function main()
was called once, took 1040:00 milliseconds per call, and took total 1:04
seconds.
The key t will display the titles (names) of the les in the library.
A programmer may create his or her own library. Suppose there are
les matvec:cc mesh:cc and fem:cc that contain matrix-vector operation,
mesh generation, and nite element analysis codes. To create a library, rst
compile these source les and obtain the corresponding :o les. Then type
the two commands:
ar ruv libmvmf.a matvec.o mesh.o fem.o
139
ranlib libmvmf.a
The keys ruv in the rst command stand for replace, update, and verbose,
respectively. The library libmvmf:a is then created if it does not already
exist. If it exists, then the :o les replace those of the same name already
existing in the library. If any of these :o les are not already in the library,
they are added to it. The ranlib command simply randomizes the library
that is useful for the loader (linker).
If there is a program consisting of two les: main:cc and iofile:cc, and
these two les call functions in the library libmvmf:a then the library can
be made available to the compiler by issuing the following command.
c++ -o pgm main.cc iofile.cc libmvmf.a
The functions that are invoked in main:cc and iofile:cc but not dened
there will be searched for rst in libmvmf:a and then in C++ standard
libraries. Only those functions of libmvmf:a that are used in main:cc and
iofile:cc will be loaded into the nal executable le pgm: Typing all the
commands can be tedious when there are a lot of les and libraries. A
UNIX utility named make can achieve this by specifying all the commands
in a le called Makefile see x4.4.4.
4.4.4 Makele
The make utility (available always in UNIX and Linux and often in DOS
as well) provides easy management of compiling source les and linking
their object les, and convenient access to libraries and associated header
les. It is ecient to keep a moderate or large size program in several les
and compile them separately. If one of the source les needs to be changed,
only that le has to be recompiled. The use of the make utility can greatly
facilitate the maintenance of the source les in a program written in C++,
C, FORTRAN, Java, and others.
The make command reads a le with a default name makefile or Makele. The le contains dependency lines (e.g., how an object le depends
on a source le and some header les) and action lines (e.g., how a source
le is to be compiled with what options). As an example, let us write
a Makele for a program that contains a header le matvec:h and two
source les main:cc and matvec:cc all in the current directory. The le
main:cc contains the function main() that calls some functions dened in
matvec:cc: The header matvec:h is included in both main:cc and matvec:cc:
The Makefile for such a simple program can be just as
mat: main.o matvec.o
# mat depends on main.o,matvec.o
c++ -o mat main.o matvec.o
# link object code mat
main.o: main.cc matvec.h
cc -c -O main.cc
140
makes the rule mat in Makefile, which depends on two other rules main:o
and matvec:o. These two rules will be made rst according to their action
lines. That is, the source les main:cc and matvec:cc are to be compiled
with the ;O option. Then the rule mat will cause the object code main:o
and matve:o to be linked and the nal executable object code to be written
in le mat:
If the source le main:cc is to be changed, but matvec:h and matvec:cc
remain unchanged, then the command make mat will only cause the le
main:cc to be recompiled and leave the third rule matvec:o alone. However,
if the header le matvec:h is to be changed, then main:cc and matvec:cc
will be recompiled since main:o and matvec:o both depend on matvec:h
by the second and third rules.
Similarly, the command make main:o will cause the rule main:o to be
made in this case only main:cc will be compiled. By default, the command
make without an argument simply makes the rst rule in the Makefile.
For this Makefile, the commands make and make mat are equivalent.
Certain rules and macros are built into make: For example, each :o le
depends on the corresponding :c le and the second rule in the above
Makefile is equivalent to
main.o: main.cc matvec.h
cc -c -O $*.cc
where $ :cc expands to main:cc when main:o is being made. It stands for
all :cc les on the dependency line. The system also echoes a message on
the screen about the action. To suppress the message, the symbol @ can
be used:
main.o: main.cc matvec.h
@cc -c -O $*.cc
141
142
The les main:cc and fem:cc include the header le fem:h, all of which are
in the same directory =home=yang=c ++=fem as the Makefile: Furthermore, fem:h includes two header les matvec:h and mesh:h which reside,
respectively, in two dierent directories:
/home/yang/c++/matvec
/home/yang/c++/mesh
The macro INCLS means to search for header les in these directories.
When main:cc and fem:cc are compiled, these header les are searched
for by the system. When main:o and fem:o are linked to generate the
executable object le xx the libraries matveclib:a and meshlib:a are loaded
through the macro LIBS . Note that paths for C++ standard libraries
such as < cmath > and < complex > and their header les need not be
indicated since the system should nd them automatically. Only paths for
user-dened libraries and header les have to be indicated.
At UNIX or Linux command, type
make
It will compile main:cc and fem:cc and link main:o and fem:o into the
excutable object code xx: The last two lines in this Makefile indicate that
main:o depends also on main:cc besides fem:h and fem:o also on fem:cc:
Thus any change to main:cc will cause only main:o to be regenerated. The
command make clean will remove the les dened in macro OBJS and
the le xx:
UNIX also provides a utility called SCCS (Source Code Control System)
for managing dierent versions of source code (or any other text les) see
!LF93].
143
// empty string
// empty string
// initialization
s3 = 'B'
4.5.2 Operations
Certain arithmetic and operations are dened for string: For example,
addition of strings means concatenation. The operator += can be used to
append to the end of a string. Strings can be compared to each other and to
string literals. The function call s:empty() returns true if string s is empty
and false otherwise, and s:max size() gives the maximum size that a string
can have. The function calls s:size() and s:length() both give the size of
a string s: The size of a string must be strictly less than string :: npos
which is the largest possible value for string size (it is equal to 4294967295
on one machine). Elements in a string s can be accessed by the subscripting
operator ! ] (index changes from 0 up to s:size() ; 1). For example,
if (s2 == s1 ) {
// test for equality of s1, s2
s2 += '\n'
// append '\n' to end of s2
} else if (s2 == "world") {
s3 = s1 + ", " + s2 + "!\n" // s3 = "Hello, world!\n"
}
s3%12] = '?'
char h = s3%12]
int i = s3.length()
int j = s3.size()
// size of s3, i = 14
// size of s3, j = 14
144
A related function, rfind() searches for a substring from the end of a string.
The function s:erase(n m) erases m characters from s starting from s!n].
The usual comparison operators == != > < >= and <= are provided
based on lexicographical ordering. In addition, many standard algorithms
can be applied to strings see x10.2.
char* s6 = "Hello"
string s7 = s6
Strings can be read from and written to the screen or a le using the input
and output operators. For example,
string s10
cout << "Enter your city and state names\n"
cin >> s10
cout << "Hi, " << s10 << "!\n"
145
The function
getline(istream istm, string strg, char eol)
reads a line from an input stream istm terminated by character eol to the
string strg. If no eol argument is provided, a newline character 'nn' is used
as the delimiter.
including terminator
including terminator
int n)
into p
int n)
q into p
146
int
int
int
int
int
int
int toupper(int)
int tolower(int)
//
//
//
//
//
//
Is 'a'..'z' 'A'..'Z' ?
Is 'A'..'Z' ?
Is 'a'..'z' ?
Is digit '0'..'9' ?
Is space ' ' '\t' '\v' ?
isalpha() | isdigit()
// to uppercase
// to lowercase
The function setf () can set the format in which an integer is output. For
example,
int main() {
int i = 123456789
cout << i << " " << i << '\n'
// decimal
cout.setf(ios_base::oct, ios_base::basefield) // octal
cout << i << " " << i << '\n'
// print i in octal
147
The identiers ios base, ios base :: dec (decimal), ios base :: oct (octal, base
8), ios base :: hex (hexadecimal, base 16), and ios base :: basefield (which
base in the output?) are dened in <iostream>. Since the name ios instead
of ios base was used in prestandard C++, many standard C++ compilers
may continue to support ios: This program will output the value of i in
decimal, octal, hexadecimal, and decimal formats:
123456789 123456789
726746425 726746425
75bcd15 75bcd15
123456789 123456789
Setting showbase will enable one to see which base is used. By default, a
number is decimal. An octal number is preceded by 0, while a hexadecimal
number is preceded by 0x: For example, the program segment
int i = 123456789
cout.setf(ios_base::showbase)
// show base of output
cout << i << " " << i << '\n'
// decimal, default
cout.setf(ios_base::oct, ios_base::basefield)
cout << i << " " << i << '\n'
// octal, base 8
cout.setf(ios_base::hex, ios_base::basefield)
cout << i << " " << i << '\n'
// hex, base =16
cout.setf(0, ios_base::basefield) // reset to default
cout << i << " " << i << '\n'
Setting ios base :: basefield to 0 resets the base to default, that is, decimal. This technique can also print out an octal or hexadecimal integer
in decimal. For example, let i = 012345 or i = 0x12345 above. Setting
showbase shows the base of an integer. Similarly, setting showpoint prints
trailing zeros and setting showpos prints explicitly + for positive integers.
Once set, these format
ags remain valid until they are unset by calling
the function unsetf () as in cout:unsetf (ios base :: showbase): To set or
unset several
ags in one statement, the bitwise or operator can be used as
in cout:unsetf (ios base :: showbase j ios base :: showpos):
148
double d = 12345.6789
cout << d << " " << d << '\n'
// default
cout.setf(ios_base::scientific, ios_base::floatfield)
cout << d << " " << d << '\n'
// scientific
cout.setf(ios_base::fixed, ios_base::floatfield)
cout << d << " " << d << '\n'
// fixed
cout.setf(0, ios_base::floatfield) // reset
cout << d << " " << d << '\n'
// to default
outputs
12345.7 12345.7
1.234568e+04 1.234568e+04
12345.678900 12345.678900
12345.7 12345.7
The identiers ios base :: scientific (for scientic format), ios base :: fixed
(for xed format), and ios base :: floatfield are dened in <iostream>.
Setting ios base :: floatfield to 0 resets the format back to the default.
Note that the default precision of six digits is applied to the whole number
of the general format, while only to the fractional part of scientic and xed
formats. To increase the accuracy of an output, the precision() function
can be used. For example,
double d = 12345.67890123456789
cout.setf(ios_base::scientific, ios_base::floatfield)
cout.setf(ios_base::uppercase) // E in scientific format
cout.precision(15)
// precision = 15
cout << d << " " << 1000*d << '\n'
149
// precision = 10
Since the output is in the scientic format, the precision means the number
of digits after the decimal point. Notice that E instead of e appears in the
exponential part of the scientic format by setting ios base :: uppercase:
The function width() can control the number of characters that the next
output number or string occupies. If the number is not long enough, whitespace by default or some other \padding" character specied by the function
fill() will be used. For example,
double d = 12345.678987654321
cout.width(25)
// output width 25 chars
cout.precision(15)
cout << d << '\n'
cout.width(25)
// output width 25 chars
cout.precision(8)
cout.fill('#')
// use '#' for padding extra space
cout << d << '\n'
It will produce the following output, which is aligned to the right by default:
12345.6789876543
################12345.679
150
The C++ header <fstream> provides functions for inputting from and
outputting to les. In particular, they have types ifstream for input le
stream, ofstream for output le stream, and fstream for both input and
output. The open mode of a le can be
in : to open for reading.
out : to open for writing.
app : to append.
binary : to open in binary mode, rather than in text mode.
ate : to open and seek to end of le.
trunc : to truncate a le to 0-length.
For example, the declarations
ifstream infile("data", ios_base::in)
// open file for input
ofstream outfile(result, ios_base::out)
// open file for output
declare the variable inle to be an input le stream that opens the le data
for reading, and outle to be an output le stream that opens a le whose
name is stored in string result for writing.
The following is a complete program that reads an integer and a double
from a le called data, generates a string for an output le name, and writes
to the le.
#include <stdio.h>
#include <fstream>
// for sprintf()
// for input/output files
int main() {
ifstream infile("data", ios_base::in) // open file to read
int i
double d
infile >> i
// read from "data"
infile >> d
char result%20]
// output file name
sprintf(result, "%s%d%s%d%s", "ex",i,"-", int(1/d), "h")
ofstream outfile(result,ios_base::out) // open output file
outfile << "example number: " << i << '\n'
outfile << "grid size: " << d << '\n'
151
then the program rst reads the rst number 5 to variable i and then reads
the second number 0:01 to variable d. The function sprintf () causes the
variable result to have value ex5-99h. The output of the program is written
in a le called \ex5-99h":
example number: 5
grid size: 0.01
The bitwise or operator j can be used to choose a le to have more than
one open mode. For example, to open a le for both writing and appending,
or to open a le for both writing and reading, we can do:
ofstream wafile("data2", ios_base::out | ios_base::app)
// open file for write and append
fstream rwfile("data3", ios_base::in | ios_base::out)
// open file for read and write
The input le with comments is more readable since it indicates that 4
represents an example number and 0:01 a grid size. The program, which
must ignore the rst line and everything after the slash sign on other lines,
may be written as
152
#include <string>
#include <stdlib.h>
#include <fstream>
int main(int argc, char* argv%]) {
if (argc != 3)
cout << "input and output files must be specified\n"
ifstream infile(argv%1], ios_base::in)
if (!infile)
cout << "can not open file: " << argv%1] << " for read\n"
string s
int i
double d
infile.ignore(80,'\n')
getline(infile,s,'/')
i = atoi(s.c_str())
infile.ignore(80,'\n')
getline(infile,s,'/')
d = atof(s.c_str())
153
will read from le data4 and write the output into a le called result.
There are many operations dened for reading and writing characters. For
example, the function call
ism.read(Ch* p, streamsize n)
154
It also counts the number of commas and newlines in the input stream.
Suppose a le named readle contains the following.
The function read(p, n) reads at most n characters and
stores them in a character array p%0], p%1], up to
p%n -1]. The returned object will be converted to false
when end of file is encountered.
The functions read() and write() may read or write many characters at a
time. When only one character is read or written, the functions get(Ch&)
and put(Ch) are better suited. Using get() and put() the program above
can be rewritten as
main() {
ifstream infile("readfile", ios_base::in)
char c
int n = 0, m = 0
while ( infile.get(c) ) {
// read 1 char at a time
switch(c) {
case ',' : n++ break
case '\n': m++ break
}
c = toupper(c)
cout.put(c)
// write c on screen
}
cout << "There are " << n << " commas and "
<< m << " lines in the input file.\n"
}
The function get() is overloaded. Calling get() without any arguments returns the character just read.
Another version of get() is:
ism.get(Ch* p, streamsize n, Ch delimiter = '\n')
which reads at most n ; 1 characters from input stream ism and places
them into p!0] p!1] : : : p!n ; 2]: The third argument delimiter is a character stating that the reading of characters should be terminated when it
155
is very similar to get(), except that it discards the delimiter rather than
leaving it as the next character to be read in the input stream. The call to
gcount() returns the number of characters actually extracted by the last
call of get() or getline(). Here is an example using get() and gcount() to
read a le and count the number of characters on each line and the total
number of lines in the le:
void f(istream& ism) {
int n = 0
char line%1024]
while ( ism.get(line, 1024, '\n') ) {
n++
cout << "there are " << ism.gcount() << " characters "
<< "on line " << n << '\n'
ism.ignore()
// ignore terminate character: '\n'
}
cout << "There are " << n << " lines in input file.\n"
}
main() {
ifstream infe("readfile", ios::in)
f(infe)
}
Without ignoring the newline character on each line (through the function
call ism:ignore() see x4.6.4), this would cause an innite loop. The output
of this program on the input le readle above is:
there
there
there
there
There
are
are
are
are
are
56 characters on
54 characters on
57 characters on
34 characters on
4 lines in input
line 1
line 2
line 3
line 4
file.
Notice the newline character on each line is not read by get(): The function
f () above can also be alternatively written using getline():
void f(istream& ism) {
int n = 0
char line%1024]
156
while ( ism.getline(line,
n++
cout << "there are " <<
<< "on line " << n
}
cout << "There are " << n
1024, '\n') ) {
ism.gcount() << " characters "
<< '\n'
<< " lines in input file.\n"
Thus the function getline() is normally preferred over get() when one line
needs to be read at a time. Notice this function is dierent from getline()
discussed in x4.6.4. The output of this program on readle is:
there
there
there
there
There
are
are
are
are
are
57 characters on
55 characters on
58 characters on
35 characters on
4 lines in input
line 1
line 2
line 3
line 4
file.
The newline character on each line of the le readle is also read by
getline():
There are three other istream operators that puts back, ungets, or peeks
a character in an input stream:
ism.putback(Ch c)
ism.unget()
ism.peek()
//
//
//
//
157
The standard library <sstream> contains two types istringstream (for input string streams) and ostringstream (for output string streams), which
can be used to read from a string and write to a string using input and
output streams.
To illustrate how ostringstream can be used, consider a le named data
that contains only two lines:
5
0.01
A program can be written that reads the two numbers from this le, generates a string such as "ex5-99h" from the two numbers just read, opens a
le with this string as its name, and nally writes some information to this
le:
#include <string>
#include <fstream>
#include <sstream>
using namespace std
main() {
ifstream infe("data", ios_base::in)
int i
double d
infe >> i
infe >> d
// read from input file
ostringstream tmp
// declare variable tmp
tmp << "ex" << i << "-" << int(1/d) << "h"
string ofname = tmp.str()
// convert it into string
ofstream otfe(ofname.c_str(), ios_base::out)
otfe << "example number : " << i << '\n'
otfe << "grid size: " << d << '\n'
infe.close()
otfe.close()
}
158
ostringstream tmp
// declare variable tmp
tmp << "ex" << i << "-" << int(1/d) << "h"
This is exactly what was achieved in x4.6.4 using the C function sprintf ():
An istringstream is an input stream reading from a string. It can be
used to convert a string (with whitespace characters) into dierent parts,
some of which may have numeric values. For example,
main() {
string s = "ex 5 - 9.9 h"
istringstream ism(s)
// initialize ism from string s
int i
double d
string u, v, w
ism >> u >> i >> v >> d >> w
// i = 5, d = 9.9
This achieves more than what C functions atoi(), atof (), and atol() do.
The libraries <string> and <sstream> are very convenient to use when
manipulating strings. Compared to C-style strings (char), the user does
not need to do memory allocation and deallocation. However, they may
cause performance problems when used extensively, since they are built on
the top of C-style strings.
159
The bisection method assumes that f is continuous on !a b] and f (a)f (b) <
0: It rst computes the middle point c = (a + b)=2 and then tests if
f (a)f (c) < 0: If it is true, then f must have a root in the smaller interval !a c]: If it is false, then f must have a root in !c b]: In either case,
rename the smaller interval as !a b] which contains a root but whose size is
reduced by half. Repeat this process until jb ; aj or jf (c)j is small, and then
the middle point c is taken as an approximate root of f . This algorithm
can be written easily in a recursive program as
double bisctn0(double a, double b, double (*f)(double),
double delta, double epsn) {
double c = (a + b)*0.5
// middle point
if (fabs(b-a)*0.5 < delta || fabs(f(c)) < epsn) return c
(f(a)*f(c) < 0) ? b = c : a = c
return bisctn0(a, b, f, delta, epsn)
}
With the help of the function pointer pfn, this equivalent declaration may
be more readable to C++ beginners. Also the ternary operator ? : is used
to make the code look concise. It may also be equivalently written using
an if -statement:
if (f(a)*f(c) < 0)
b = c
else
a = c
160
(c) f (x) = (x3 + 4x2 + 3x + 5)=(2x3 ; 9x2 + 18x ; 2) !a b] = !0 4]:
Common declarations and denitions can be put in a header le called
ch4it.h:
// file ch4it.h
#include <iostream>
#include <math.h>
#include <stdlib.h>
161
exit(1)
}
}
The functions fa() and fc() may involve division by zero. When it occurs,
the program is terminated by calling the function exit() declared in header
<stdlib:h>.
A main program can be written to nd the roots of the functions:
// file mainch4it.cc
#include "ch4it.h"
int main() {
// find a root of fa()
double root = bisctn0(1.0e-2, 1, fa, delta, epsn)
cout << "Approximate root of fa() by bisctn0() is: "
<< root << '\n'
cout << "Fcn value at approx root (residual) is: "
<< fa(root) << '\n'
// find a root of fb()
root = bisctn0(1, 3, fb, delta, epsn)
cout << "\nApproximate root of fb() by bisctn0() is: "
<< root << '\n'
cout << "Fcn value at approx root (residual) is: "
<< fb(root) << '\n'
// find a root of fc()
root = bisctn0(0, 4, fc, delta, epsn)
cout << "\nApproximate root of fc() by bisctn0() is: "
<< root << '\n'
cout << "Fcn value at approximate root (residual) is:"
<< fc(root) << '\n'
}
Observe that the program nds approximate roots for functions fa() and
fb() with very small residuals. However, the residual for fc() is so large!
162
A closer look reveals that fc() does not have a root in the given interval,
although it changes sign at the endpoints (why?). It is a good idea to
check the correctness of computer output whenever possible. In the case of
nding a solution to an equation, a small residual normally implies a good
approximate answer.
The function bisctn0() resembles the algorithm closely and has worked
ne on three test problems. However, it is not very ecient and robust in
the following sense. First, it invokes three function evaluations: f (a) once
and f (c) twice, in each recursion. Second, the product f (a) f (c) may
cause over
ow or under
ow while f (a) and f (c) are within range. Third,
the middle point c can be updated in a more robust way as c = a +(b ; a)=2:
There are examples in which the middle point computed by c = (a + b)=2
moves outside the interval !a b] on a computer with limited nite precision.
It is numerically more robust to compute a quantity by adding a small
correction to a previous approximation. An improved version can be dened
as
double bisctn1(double a, double
double u, double
double e = (b - a)*0.5
double c = a + e
double w = f(c)
b, double (*f)(double),
delta, double epsn) {
// shrink interval size
// middle point
// fcn value at middle point
This version bisctn1() requires only one function evaluation f (c) per
recursion (u = f (a) is passed as an argument to the next recursion) and
avoids the unnecessary product f (a) f (c): However, these two functions
bisctn0() and bisctn1() have a common disadvantage. That is, both can
lead to an innite recursion (why?). A safeguard is to put a limit on the
number of recursions. From x3.8.9, a static local variable may be used to
count the number of recursions and lead to the next version:
double bisctn2(double a, double b, double (*f)(double),
double u, double delta, double epsn,
int maxit) {
static int itern = 1
double e = (b - a)*0.5
// shrink interval size
double c = a + e
// middle point
double w = f(c)
// fcn value at middle point
if (fabs(e)<delta || fabs(w)<epsn || itern++ > maxit)
return c
163
This new version bisctn2() achieves the goal that it stops when the number of recursions is larger than a prescribed number maxit, but has a side
eect. Since static local variable itern persists in all calls to bisctn2() it
may cause the function to terminate prematurely in subsequent calls. For
example,
double root = bisctn2(1.0e-2, 1, fa, fa(1.0e-2), 1.0e-8,
1.0e-9, 40)
root = bisctn2(1, 3, fb, fb(1), 1.0e-8, 1.0e-9, 40)
root = bisctn2(0, 4, fc, fc(0), 1.0e-8, 1.0e-9, 40)
The rst call on fa takes 27 recursions to stop. The static local variable
itern = 27 at the beginning of the second call on fb which is terminated
prematurely after 14 recursions when itern = 41. The third call on fc is
terminated after only the rst recursion.
To overcome this diculty, one more argument can be passed (by reference) to the function to count the number of iterations. It can be written
as
double bisectionr(double a, double b, double (*f)(double),
double u, double delta, double epsn,
int maxit, int& itern) {
/*********************************************************
bisection algm: recursive version, returns an approximate
root of a fcn in a given interval
a, b: endpoints of interval in which a root lies
f:
function is defined in interval %a,b] or %b,a].
u:
= f(a)
delta: root tolerance, return when updated interval is
narrower than it
epsn: residual tolerance, return when residual is
smaller than it
maxit: maximum number of iterations allowed
itern: iteration count, it must be initialized to 1.
********************************************************/
double e = (b - a)*0.5
double c = a + e
double w = f(c)
164
return bisectionr(a,b,f,u,delta,epsn,maxit,itern)
}
The reference variable itern must be initialized to 1 before each call and
stores the number of iterations after a call is nished. For example,
int itrn = 1
double root = bisectionr(1.0e-2, 1, fa, fa(1.0e-2),
1e-8, 1e-9, 500, itrn)
cout << "Number of iterations used = " << itrn << '\n'
itrn = 1
// itrn must be initialized to 1
root = bisectionr(1, 3, fb, fb(1), 1e-8, 1e-9, 500, itrn)
cout << "Number of iterations used = " << itrn << '\n'
Run this program to know that the rst call takes 27 iterations and the
second call takes 28 iterations to stop. They produce the same approximate
roots as bisctn0():
The procedure above is generally called an incremental approach to software development. It starts with a simple version representing the main
idea of an algorithm. From there, more ecient and robust versions can be
built.
A nonrecursive version of the bisection algorithm can now be easily coded
as
double bisection(double a, double b, double (*f)(double),
double delta, double epsn, int maxit) {
/**********************************************************
bisection algm: nonrecursive version, returns approximate
root of a fcn in a given interval
a, b: endpoints of interval in which a root lies
f:
function is defined in interval %a,b] or %b,a].
delta: root tolerance, return when updated interval is
narrower than it
epsn: residual tolerance, return when residual is
smaller than it
maxit: maximum number of iterations allowed
*********************************************************/
double u = f(a)
double e = b - a
double c
165
Let f (x) be a function of independent variable x f 0 (x) its rst-order derivative, and an initial approximation xp to a root r of f: Newton's algorithm
tries to nd a better approximation xn to this root r and repeat this process until convergence. The idea is to rst compute the root of its linear
approximation at xp and then let xn be this root. The linear approximation
of f (x) at xp is:
This is exactly the rst two terms of its Taylor's expansion at xp : Geometrically, the curve f (x) is approximated near the point (xp f (xp )) by its
tangent line at this point. To nd the root of this linear approximation, set
Lf (xp ) = 0 that is,
and solve for x to get x = xp ; f 0 (xp );1 f (xp ): Then let this x be the next
approximate root xn : That is,
166
// nested multiply
double fder(double x) {
return (3*x - 10)*x + 3
}
int main() {
double root = newton(5, f, fder, 1e-8, 1e-9, 500)
cout << "Approx root near 5 by newton method is: "
<< root << '\n'
cout << "Fcn value at approximate root (residual) is: "
<< f(root) << '\n'
}
4.8 Exercises
167
Thus Newton's method nds an approximate root with very small residual to this polynomial function. In general, the initial approximation xp
(also called initial guess) may not have to be very close to a root r: But a
good initial guess can speed up the convergence and even aect the convergence or divergence of the method. That is, Newton's method may converge
with one initial guess and diverge with another. Once a good initial guess
is obtained, Newton's method converges more rapidly than the bisection
method.
If f (r) = 0 and f 0 (r) 6= 0 then r is called a simple root of f: Newton's
method is designed for nding a simple root due to the following observation. If f 0 (r)
0 and xp is an approximation to r then the iterative
formula of Newton's method would involve a very small number or zero in
the denominator.
Let the second derivative f 00 be continuous and r a simple root of f:
Then there exists a neighborhood of r and a constant C such that if xp is
in the neighborhood, the next iterate xn is closer to r and
jr ; xn j C jr ; xp j2 :
4.8 Exercises
4.8.1. Implement matrix and vector namespaces as outlined in x4.1. Add
functions for vector addition, vector-scalar multiplication, matrix addition, and matrix-vector multiplication. Compute one, two, and max;1 and one, maximum, and Frobenius
imum norms of vector v = (vi )ni=0
n
;
1
norms of matrix m = (aij )ij=0 and the matrix-vector product mv
where vi = sin(i) and aij = cos(i2 + j ) for dierent matrix and vector sizes n = 100 500 and 1000: Manage the les by using a Makele
if you are on a UNIX/Linux system.
168
4.8 Exercises
169
Write a program that compares the time eciency of the three versions of the matrix Frobenius norm function, on an n n matrix
evaluated repeatedly m times. You may take n = 1000 and m = 100:
Adjust the values of n and m to t your computer.
170
4.8.7. Time the program computing the rst 40 Fibonacci numbers in x2.4
and the recursive program in Exercise 3.14.15. To clearly see the time
eciency (or ineciency) of recursive and nonrecursive function calls,
invoke each function inside a loop for m times, where m is a large
integer (e.g., m = 20000). Output the results directly into a le. Write
your own Makele for compiling and managing the programs if you
are using UNIX or Linux.
4.8.8. Write a function that takes a decimal int as argument and prints out
(to the screen and a le) the integer in hexadecimal, octal, and binary
formats.
4.8.9. Call all the functions (dened in x4.7.1) for the bisection method
inside a main() program to nd a root of
(a) f (x) = x;1 ; tan x !a b] = !0 2]
(b) f (x) = x ; e;x !a b] = !1 2]:
Comment on the advantages and disadvantages of these methods.
4.8.10. Apply Newton's method to nd a root of the function f (x) = 1 ; x ;
e;x near x = 1 and a root of g(x) = tan x ; x near x = 4:5 (test
what you get if starting from x = 5 instead).
4.8.11. Amounts A1 A2 : : : An are placed in an investment account (e.g., a
bank savings account) at the beginning of month 1 2 : : : n respectively. Let Bn be the balance in the account just after amount An
has been invested at the end of month n: The average annual return
rate r (assuming monthly interest compounding) of this investment
account is a root to the equation:
Bn =
i=1
n;i
r
:
Ai 1 + 12
4.8 Exercises
171
Bn =
i=1
Ai er(n;i)=12 :
executes the command date that causes the current date to be output
to the screen. Write a program on UNIX/Linux that lists all les with
a sux :cc using the following statements.
char command%30]
sprintf(command, "ls %s", "*.cc") // include <stdio.h>
system(command)
// include <stdlib.h>
Can you write a program that moves every :cc le into a corresponding
:cpp le?
A related function is asm() whose argument is a string literal representing a piece of assembly code that will be inserted into the generated code at the place where it is specied.
172
Classes
174
5. Classes
//
//
//
//
//
//
//
//
//
//
Notice a semicolon is needed after a class declaration. A class declaration is actually a class denition since it denes a new type. The members x and y are private and can only be accessed by member functions
pt2d::pt2d(), pt2d::move(), pt2d::draw(), and friend norm(), where the function pt2d::pt2d() is dened inside the class declaration and others are dened below. The member pt2d :: pt2d() whose name is identical to the name
of the class, is called a constructor and all objects of class pt2d must be
created by the constructor. For example, the statements
pt2d p(5.4, 6)
pt2d q(5, 0)
// call constructor
// call constructor
create an object called p of type pt2d with x-coordinate 5:4 and y-coordinate
6, and an object q with coordinates x = 5 and y = 0: A destructor is not
necessary for this class since the constructor does not allocate space for it
using the operator new. Many examples in later sections need destructors
see, for example, x5.2. Classes are di
erent from other types in that an
object of a class must be constructed through a constructor. In this sense,
objects of a class are di
erent from other objects.
The class scope operator :: must be used for denitions of members outside a class declaration. A member function such as pt2d::draw(), declared
using the const sux, can only read but can not modify members of the
class. The keyword const here is part of the type and must not be omitted
in the denition when dened outside the class declaration. In contrast,
nonconstant member functions imply that they modify members of the
175
// move point to p
// friend definition
// distance to origin
Notice the di
erence between the ways that members are accessed by a
function member such as move() and a friend such as norm(). In move()
data members x and y can be used directly (they refer to members of an
object for which the member function is called), while the class qualier
must be used in norm() such as p:x and p:y: Dening move() to be a
member function and norm() a friend here is only to show the di
erence
on how to dene member functions and friends. The function norm() may
also be alternatively dened as a member and move() as friends. A friend
does not belong to a class but has access to members of the class. More
details on friends are given in x5.3. Member functions dened inside a class
declaration are inline by default. They can also be alternatively made
inline outside the class declaration like pt2d :: move() above. Again, inline
is only a suggestion to the compiler for code substitution to avoid function
calling overhead. Short member functions and friends can be made inline
for possible run-time eciency. Long denitions should be put outside a
class declaration to make the interface look clean.
The following main program denes a few objects of class pt2d and calls
member functions and friend to show how this class can be used.
int main() {
pt2d a(0, 0)
pt2d b(5, 0)
pt2d c(3, -40)
a.move(23, -3.5)
b.move(c)
c.draw()
176
5. Classes
In the above, member functions move() and draw() are called using the
member selection operator : following a class object, while a friend norm()
is called in the same way as an ordinary function.
The following statements in f () are illegal.
void f(pt2d a) {
double ax = a.x
pt2d d
}
That is, private members of a class may only be accessed by its member functions and friends as member x can be used in member function
move() and friend norm() but not in other functions such as main() and
f () above to prevent them from accidental modication or misuse. Since
the constructor of pt2d requires two arguments, an object can not be constructed (for d in f () above) without specifying them. The protection of
private members in a class leads to a safer user-dened type that is also
easier to maintain. If the representation of a class is to be changed, only
its member functions and friends need be changed. User code depends only
on the public interface and need not be changed.
It is sometimes convenient to provide several constructors the compiler
can choose the correct one to use according to the type of its arguments
just as in function overloading. For example, the class pt2d can be dened
to have three constructors:
class pt2d {
public:
// in addition to other members
pt2d() { x = y = 0 }
// default constructor
pt2d(double p) { x = p y = 0 }
pt2d(double p, double q) { x = p y = q }
}
177
//
//
//
//
b = (5, 0)
c = (3, -40)
initialize d to b
assign d to c
the compiler creates an object p of type pt3d and initializes the members x,
y, and z to some default value. But there are classes for which the compiler
can not generate a default constructor see x5.6.
The keyword this refers to a constant pointer that points to the object for
which a member function is invoked. For a nonconstant member function
of class X , the pointer this is of type X const, and for a constant member
function, this has type const X const. For example, the member functions
move() and draw() can also be implemented as
inline void pt2d::move(double r, double s) {
this->x = r this->y = s
// move point to (r, s)
}
inline void pt2d::draw() const {
// draw coordinates
cout << '(' << this->x << ',' << this->y << ')'
}
Using this when referring to members is usually unnecessary. Its main use
is for dening member functions that manipulate pointers directly. Useful
examples are given in x5.2 and the next chapter.
178
5. Classes
class integral {
// members by default are private
double lower
// lower integral bound
double upper
// upper integral bound
pfn integrand
// integrand function
public:
// public members
integral(double a, double b, pfn f){
// a constructor
lower = a upper = b integrand = f
}
double lowbd() const { return lower } // const fcn member
double upbd() const { return upper }
// const fcn member
void changebd(double, double)
// nonconst member
double trapezoidal(int) const
// const fcn member
friend double simpson(integral, int)
// a friend
}
// note semicolon
The members lower upper, and integrand are private and can be accessed
only by its function members such as integral :: lowbd() and friends such as
simpson(): Thus public functions integral :: lowbd() and integral :: upbd()
are provided to return the lower and upper integral bounds. When a class
has a constructor, all objects of the class must be constructed by calling a
constructor. For example, the declaration
integral di(0, 5.5, sqrt)
// const member
179
// a friend
The
following main program can be used to evaluate integrals
R7
and 3 sin(x)dx.
R5
sin(x)dx
int main(){
integral di(0, 5, sin)
// sin from <math.h>
double result = di.trapezoidal(100) // call a fcn member
cout << "Integral from " <<di.lowbd() <<" to "<< di.upbd()
<< " is approximately = " << result << '\n'
di.changebd(3, 7)
// change bounds
result = di.trapezoidal(100)
cout << "Integral from " <<di.lowbd() <<" to "<< di.upbd()
<< " is approximately = " << result << '\n'
result = simpson(di,200)
// invoke a friend
180
5. Classes
then only the member functions and friends have to be changed and the
user code as in the main() function above does not have to be changed.
This slight modication of the representation for integral has hardly any
advantage, but there are many situations in which a change of class representation can improve eciency or portability. For example, a matrix class
can be changed to be represented by a valarray (see x7.5) to make use of
the high performance computing library.
// initialize d to b
// assign d to c
181
vertices1] = v1
vertices2] = v2
}
void f() {
pt2d x(1.0,2.0)
pt2d y(3.0)
pt2d z
pt2d z2(7.0)
triangle t1(x,y,z)
triangle t2(x,y,z2)
}
In this code segment, the copy initialization made the member vertices of
t2 equal to that of t1 (since they are pointers, being equal implies that
they point to the same object), and the copy assignment made the member
vertices of t3 equal to that of t1: Consequently, the member vertices of t1
t2 and t3 point to the same object. At the time t1, t2 and t3 are destroyed
when g() is returned, the same pointer is deleted three times (its behavior is
undened) and results in an error. In general, for a class with a destructor
that deletes space allocated by its constructor, the user must dene a copy
constructor and a copy assignment. For a class T , they have the form
T::T(const T &)
T& T::operator=(const T&)
// copy constructor
// copy assignment
182
5. Classes
These two member functions must also be declared in the class triangle:
Since this is a constant pointer that points to the object for which a member function is invoked, this is then the object. The user-dened copy
assignment returns a reference to the object. Notice that new space is allocated in the copy initialization and no new objects are created in the copy
assignment, which clearly reects the fact that initialization and assignment are two distinct operations. With the user-provided copy constructor
and assignment above, the function g() above should work correctly now.
In particular, at the time t1 t2 and t3 are destroyed, the three pointers,
for which spaces are allocated when they are constructed, are deleted. The
redenition of the assignment operator is called operator overloading for =.
Operator overloading is discussed in more detail in the next chapter.
As another example, a class called triple (consisting of three numbers)
can be declared as
class triple {
float* data
public:
triple(float a, float b, float c)
~triple() { delete] data }
triple(const triple& t)
triple& operator=(const triple& t)
// a triple of numbers
//
//
//
//
constructor
destructor
copy constructor
copy assignment
183
}
inline triple::triple(const triple& t) { // copy constructor
data = new float 3]
for (int i = 0 i < 3 i++) datai] = t.datai]
}
inline triple& triple::operator=(const triple& t) {
if (this != &t)
// beware of self assignment
for (int i = 0 i < 3 i++) datai] = t.datai]
return *this
}
inline triple add(const triple& t, const triple& s) {
return triple(t.data0] + s.data0], t.data1] + s.data1],
t.data2] + s.data2])
}
// create an object
// copy construction
// copy construction
// copy assignment
// self-copying is allowed
// print out members of ccc
184
5. Classes
duple& t) {
// copy assignment
// d1 contains 3 numbers
// d2 contains 8 numbers
// d1 is resized to have size 8
duple:
5.3 Friends
A friend can belong to many classes and have access to private members of
objects of these classes, while a function, data, or other member can only
belong to one class. A friend declaration can be put in either the private
or public part of a class it does not matter where. For example, a matrixvector multiply function can be declared to be a friend of classes Mat and
Vec:
class Mat
// forward declaration
class Vec {
// .... in addition to other members
friend Vec multiply(const Mat&, const Vec&)
}
185
class Mat {
// .... in addition to other members
friend Vec multiply(const Mat&, const Vec&)
}
Matrix and vector classes are discused in more detail in Chapters 6 and 7.
A friend is not a class member and thus does not have a this pointer. A
member function of one class can be a friend of another class. A class can
be a friend of another in this case all functions of the class become friends.
For example,
class X {
void f()
int g(int)
}
class Y {
// .... in addition to other members
friend void X::f()
// f() of X becomes friend of Y
}
class Z {
// .... in addition to other members
friend class X
// all functions of X become
}
// friends of class Z
186
5. Classes
In the constructor, the default vector size and entries are used when
it is not supplied with any arguments. The copy constructor and assignment can be dened accordingly. Static data and function members such
as Vec::defaultVec and Vec::setDefault() must be dened somewhere in the
program. Since static members belong to the class, are shared by all objects
of the class, and do not belong to a particular object, they are qualied by
the name of the class using the scope resolution operator. For example,
double ad] = {5, 6, 7, 8, 9}
Vec Vec::defaultVec(5, ad)
// defaultVec initialized by ad of size 5
void Vec::setDefault(int sz, double* p) {
Vec::defaultVec = Vec(sz, p)
// reset default vector
}
// note class qualification Vec::defaultVec
187
Since a and a2 are not supplied with arguments, default vectors (with default sizes and default entries) are constructed. Note the default vector is
reset by calling the static member Vec::setDefault() before the declaration
of a2: Thus a and a2 are constructed di
erently. Similarly, b and b2 are constructed di
erently although their declarations look exactly the same. In
contrast, c is constructed according to the arguments supplied to the constructor. From this example, it can be seen that it would be a waste of space
if each object such as b and c had a copy of static member Vec::defaultVec.
That is, although class Vec has member defaultVec, an object of Vec does
not have its own copy of this member. Instead, all objects of Vec share this
member. In other words, the member defaultVec belongs only to class Vec.
In contrast, every object of Vec has a copy of nonstatic members such as
size and entry:
188
5. Classes
From the user's point of view, the function trapezoidal() does not change
the state of an object of integral. This is why it is declared to be a const
function. The reason for declaring a mutable data member value for storing
an approximate value of the integral is that trapezoidal() might be an
expensive process and repeated invocation of it can just return the denite
integral value without repetitively calculating it. Now trapezoidal() and
other members of integral can be dened as
inline integral::integral(double a, double b,
double (*f)(double)) {
lower = a upper = b integrand = f value_valid = false
}
inline void integral::changebd(double a, double b) {
lower = a upper = b value_valid = false
}
double integral::trapezoidal(int n) const { // const member
if (value_valid == false || n != 100) {
double h = (upper - lower)/n
// compute integral
double sum = integrand(lower)*0.5
for (int i = 1 i < n i++) sum += integrand(lower+i*h)
sum += integrand(upper)*0.5
value = sum*h
value_valid = true
}
return value
}
// do the calculation
// just returns value
//
//
//
//
di2.changebd(5, 9)
di3.changebd(5, 9)
// OK
// error, const object di3
189
log() in <math.h>
a const object
const member modifies di2
OK, although di3 is const
The mutable member value of the const object di3 is modied by the
const member function trapezoidal(): Regular (non-mutable) members of
a const object can not be modied. Thus di3:changebd() is an illegal call
since changebd() modies regular members and di3 is a const object.
An example of a class with a const data member is given at the end of
x5.6. A class with a static const data member is presented in x9.1.
The rst constructor takes two points and a bool as arguments. Initialization for class object members (endpoints of the line segment) is done in
a member initializer list in the denition of the constructor. The member
initializers are preceded by a colon and di
erent member initializers are separated by commas. Initialization for ordinary members (such as direction
of line) is done inside the braces, which can also be done in the initializer
190
5. Classes
list. The rst constructor can create an object of class line with oneend
equal to class object a otherend equal to b, and direction equal to dir:
Constructors of the members are called before the body of the containing
class' own constructor is executed. The members' constructors are called
in the order in which they are declared in the class rather than in the order
in which they appear in the initializer list. The members' destructors are
called in the reverse order of construction. For example, the construction
for member oneend as an object of class pt2d is done before otherend when
constructing an object of class line.
In the denition of the second constructor above, the endpoint oneend of
line is taken to be the default point (the origin by the default constructor
of class pt2d see x5.1). Thus no argument is specied for it. In this case,
it can be equivalently written as one of the following.
line::line(pt2d b, bool dir): otherend(b) {
direction = dir
}
line::line(pt2d b, bool dir): otherend(b), direction(dir) { }
// less efficient
191
line& rn
AA(int j,pt2d p,bool d,line& c): i(j), n(p,d), rn(c) { }
}
Since i is a const member, n has a type of class line that does not have a
default constructor, and rn is a reference member, they must be initialized
in the initializer list, instead of inside the braces. Since constants and references must be initialized, a class with a const or reference member can
not be default-constructed:
struct BB {
const int i
const float f
double& d
}
BB b
// error, no default constructor is provided
struct CC {
int i
float f
double d
}
CC c
// OK, use compiler-generated default constructor
Objects of classes without constructors or destructors can also be members of a union. A named union is dened as a struct, where every member
has the same address. A union can have member functions but not static
members. In general, a compiler does not know which member of a union is
used. Thus a union may not have members with constructors or destructors.
denes an array of 100 two-dimensional points. Each of the points has been
initialized to the origin according to the default constructor of class pt2d
(see page 176).
However, if a class has constructors that do not provide default values,
an array of objects of such a class can not be declared. For example, if a
vector class is dened as
class Vec {
double* en
int size
192
5. Classes
public:
Vec(int, double* = 0)
void f(int)
}
Vec::Vec(int s, double*
en = new double size
if (d) {
for (int i = 0 i <
} else {
for (int i = 0 i <
}
}
// constructor
// a test function
d) {
= s]
// if d is not the null pointer
size i++) eni] = di]
// otherwise, use default
size i++) eni] = 0
//
//
//
//
193
when deallocating space for it. This technique can be very useful and may
also be used to dene a triangular matrix as an array of pointers see x11.3
for an application.
//
//
//
//
//
p is
call
call
call
call
//
//
//
//
//
//
m1 is a pointer to member
m2 is a pointer to member
call thru pointer to member
call thru pointer to member
call thru pointer to member
call thru pointer to member
194
5. Classes
(5.1)
(5.2)
dx = (1 ; et )x 0 < t 2
dt
1 + et
x(0) = 3
(5.3)
(5.4)
The exact solution x(t) can not be found in general. Instead, approximations to the solution x(t) are sought at certain points t0 < t1 < : : : <
tN = T for a positive integer N: Let the approximate solution at t = tk be
195
dt t=t
hk 0
hk
The smaller the grid size hk is, the more accuracy may be expected of
this approximation in the absence of roundo
errors and numeric instability.
That is, from (5.1),
xk+1 ; xk
hk
dx = f (t x(t )) f (t x )
k
k
k k
dt t=t
k
(5.5)
196
5. Classes
return x
}
Here data (tini for initial time, ison for initial solution, tend for ending
simulation time, and sfn for the source function) about the initial value
problem and method euler() are encapsulated into a class called ode: The
private data members can only be accessed by function member euler() and
others dened later. Copy construction and assignment and a destructor
need not be dened for such a class. The user needs to supply the number
of subintervals N to the function euler() that returns a pointer to the
approximate solution values x0 x1 : : : xN :
For the example (5.3)-(5.4), the exact solution happens to be found as
x(t) = 12et=(1 + et )2 . It can be used to check the accuracy of the approximate solution by Euler's method:
double f(double t, double x) {
// source function
return x*(1 - exp(t))/(1 + exp(t))
}
double exact(double t) {
// exact solution
return 12*exp(t)/pow(1 + exp(t), 2)
}
int main() {
ode exmp(0, 3, 2, f)
double* soln = exmp.euler(100)
// x(0) = 3, T = 2
// 100 subintervals
double norm = 0
double h = 2.0/100
// grid size
for (int k = 1 k <= 100 k++)
// compute error
norm = max(norm, fabs(exact(k*h) - solnk]))
cout << "max norm of error by euler's method = "
<< norm << '\n'
}
The maximum norm of the error between the exact solution and the approximate solution is 0:00918355: With 200 subintervals (h = 1=100), the
error is 0:0045747: When h = 1=200, the error is 0:00228309: It can be observed that the error decreases linearly with the grid size h: That is, when
the grid size h is halved, the error is reduced by a factor of two. Mathematically, it can be shown that the error is proportional to h. Such a method
is said to have rst-order. For some problems, the proportionality constant
is reasonably small and the explicit Euler's method gives good accuracy.
However, for some other problems (see Exercise 5.10.17), the proportionality constant is very large, which causes the method to be very inaccurate.
In other words, the explicit Euler's method is not very stable.
197
xk = xk + hk f (tk xk )
xk+1 = xk + hk f (tk+1 xk )
(5.7)
(5.8)
(5.9)
where
F1 = hk f (tk xk )
F2 = hk f (tk+1 xk + F1 ):
A fourth-order Runge{Kutta method is:
xk+1 = xk + (F1 + 2F2 + 2F3 + F4 )=6
where
F1
F2
F3
F4
(5.10)
= hk f (tk xk )
= hk f (tk + hk =2 xk + F1 =2)
= hk f (tk + hk =2 xk + F2 =2)
= hk f (tk+1 xk + F3 ):
It can be shown that the error between the exact solution and numeric
solution from (5.9) is proportional to h2 (second-order accuracy) while the
error for (5.10) is h4 (fourth-order accuracy) with the proportionality constant depending on high-order derivatives of the exact solution. In contrast,
198
5. Classes
// predictor
// corrector
See AP98, CK99, KC96] for more details on numeric methods for ordinary di
erential equations. Notice that a function pointer is passed to
the constructor of class ode, which may impose a lot of function calling
overhead on function evaluations of sfn() inside member functions euler()
eulerpc() and rk2(): In x7.7, ecient techniques for avoiding such function
calling overhead are discussed.
5.10 Exercises
199
5.10 Exercises
5.10.1. Extend the class pt2d dened in x5.1, to three-dimensional points.
The new class should have several constructors including a default
constructor, member functions move() draw() and a friend for calculating the distance from a three-dimensional point to the origin.
5.10.2. Use the class for denite integrals dened in x5.1 to evaluate the
integrals in Exercises 3.14.22 and 3.14.23.
5.10.3. Add printing statements in the constructors and destructor of the
class triangle declared in x5.2. For example,
triangle::triangle(pt2d v0, pt2d v1, pt2d v2) {
cout << "allocating space in triangle\n"
vertices = new pt2d 3]
// ...
}
triangle::~triangle() {
cout << "deleting space in triangle\n"
delete] vertices
}
Write a main program calling the function g() dened in x5.2, and
compare the outputs without and with user-dened copy constructor
and assignment. You should see space being allocated the same number of times as being deleted with user-dened copy constructor and
assignment. What do you think will happen without them?
5.10.4. Dene the member function triangle :: area() declared in x5.2, to
compute the area of a triangle. Note the area of a triangle in a twodimensional space with vertices A, B and C is 12 j(B:x ; A:x)(C:y ;
A:y) ; (B:y ; A:y)(C:x ; A:x)j: Then use the triangle class to nd
the area of the triangle passing through three two-dimensional points
a(5 5) b(7 8) and c(20 ;4):
5.10.5. Dene all the function and friend members in class duple as discussed
in x5.2. When dening add() for the addition of two duples, an error
message can be rst printed out and the program then exited if the
two duples have di
erent sizes. Test a few examples on all the functions, especially copy construction and copy assignment, and print
out the data to see if they work as expected.
5.10.6. Dene a vector class that contains a pointer for the entries, an integer
for the size of the vector, and one, two, and maximum norm functions.
Test your denitions on a few simple vectors.
200
5. Classes
5.10.7. Dene a matrix class that contains a double pointer for the entries,
two integers for the dimension (numbers of rows and columns) of the
matrix, and one, maximum, and the Frobenius norm functions. Test
your denitions on a few simple matrices.
5.10.8. Dene a friend function for both the matrix class and the vector class
in Exercises 5.10.6 and 5.10.7 for matrix-vector multiplication. Test
your denitions on a few simple matrices and vectors.
5.10.9. Dene a class for a triangle given three vertices, each of which is represented by an object of another class for three-dimensional points.
Using member initializer lists, dene two constructors for it, one with
three points as arguments for the three vertices and another with two
points (the third vertex is defaulted to be the origin). Also dene a
member function that moves a triangle to a new location and another
member function that prints out the coordinates of the three vertices
of a triangle. Create a few triangle objects, move them to new locations, and print out the coordinates of the triangles before and after
moving them.
5.10.10. Implement the root-nding problem as discussed in x4.7 using a class
with the bisection method as a member function and Newton's method
as a friend. Then apply it to Exercises 4.8.9 and 4.8.10.
5.10.11. A numeric quadrature has the form:
b
f (x)dx
nX1
i=0
wi f (xi )
5.10 Exercises
201
5.10.13. Test the class integral dened in x5.5 to see if repeated invocations
to function trapezoidal() with the default number of subintervals really do not cause recomputation of the denite integral. The member
value of integral is dened to be mutable so that it can be modied
by const function trapezoidal(). Alternatively, value may also be dened to be a regular (instead of mutable) member and trapezoidal()
a nonconstant member function. For what kind of classes does the
alternative way seem better?
5.10.14. Dene an n by n lower triangular matrix as a double pointer (or
say an array of n pointers) to Vec see x5.7 for the denition of Vec.
Each of the n pointers can be allocated space for a Vec of certain
number of elements representing a row of the matrix. Assign value
1=(i + j +1:0) to the entry at row i and column j for i = 0 1 : : : n ; 1
and j = 0 1 : : : i: Finally, deallocate space for the matrix.
5.10.15. Dene a type that is a pointer to a member of class integral as discussed in x5.1. Use pointers to members to call function trapezoidal()
and evaluate a few denite integrals this way.
5.10.16. Implement the implicit Euler's method and the fourth-order Runge{
Kutta method for the class ode discussed in x5.9. Test your implementation on the example (5.3)-(5.4).
5.10.17. Compare the accuracy of explicit, implicit, and predictor-corrector
Euler's methods, and the second- and fourth-order Runge{Kutta methods for the initial value problem: dx=dt = x 0 < t < 2 x(0) = 1
with di
erent values of = ;10 ;50 ;100 ;500 and di
erent grid
sizes h = 1=50 1=100 1=200 1=400 respectively. Its exact solution is
x(t) = et : The accuracy should decrease when gets smaller (negatively).
5.10.18. Suppose that Ms. Li deposits $10000 in an investment account that
pays an annual interest rate of 5% compounded continuously. She
then withdraws $1000 from the account each year in a continuous
way starting in year 10. Then A(t) the amount of money in the
account in year t, satises the di
erential equation
dA =
dt
0:05A
for t < 10
0:05A ; 1000 for t 10:
202
5. Classes
How long will the money last? That is, at what time t, will the account
balance A(t) = 0? (Answer: t = 44.79 years.)
To maintain a certain life standard, she decides to adjust the withdrawal amount to ination and withdraws $1000e0:03(t;10) in year
t for t 10 (assuming an annual ination rate of 3% compounded
continuously) then the di
erential equation becomes:
dA =
dt
0:05A
for t < 10
0:05A ; 1000e0:03(t;10) for t 10:
How long will the money last in this case? (Answer: 30.00 years.)
Operator Overloading
Suppose that v1 v2 and v3 are three vectors and m is a matrix. Mathematically we can write
v3 = v1 + v2
v2 = v1 + m v3
provided the dimensions of the matrix and vectors are compatible. In C++,
we can dene classes for vectors and matrices and redene the meanings of
+ and = such that vector addition and matrix-vector multiplication can
be written exactly in the same way as the mathematical expressions above.
This kind of extension of operators such as + and = from built-in types
to user-dened types is called operator overloading. Operator overloading
enables the C++ code of many mathematical methods to resemble their algorithms, which can make programming in C++ easier and C++ programs
more readable. In this chapter, various issues on operator overloading are
discussed. Examples are complex numbers, vectors, and matrices, which
are building blocks of many scientic programs. Note that standard C++
libraries include complex numbers (x7.4) and vectors (x7.5 and x10.1.1). A
simpler and easier-to-understand version is presented here to illustrate how
operators are overloaded. A deferred-evaluation technique is also presented
to improve the eciency of overloaded composite operators. In the nal
section, operator overloading is applied to solve systems of linear equations
using the conjugate gradient method.
204
6. Operator Overloading
operator+(Cmpx)
operator-(Cmpx)
operator+(Cmpx, Cmpx)
operator-(Cmpx, Cmpx)
//
//
//
//
unary +, eg z1
unary -, eg z1
binary +, eg z
binary -, eg z
=
=
=
=
+ z2
- z2
z1 + z2
z1 - z2
Note that the operators += ;=, +, ;, , <<, and >> are overloaded,
among which + and ; are overloaded as both unary and binary operators.
The operators + and ; are declared as ordinary functions since they do
not need direct access to private members of class Cmpx: The types ostream and istream are dened in <iostream>. The members, friends, and
ordinary functions can be dened as (the order in which they are dened
does not matter)
inline Cmpx operator+(Cmpx z) {
return z
}
// unary +, eg z1 = + z2
// unary -, eg z1 = - z2
// make use of binary // 0 converted to Cmpx(0)
205
}
inline Cmpx& Cmpx::operator-=(Cmpx z) { // eg y -= z
re -= z.re im -= z.im
// decrement real and imaginary
return *this
}
inline Cmpx operator+(Cmpx a, Cmpx b) { // binary +, z=z1+z2
return a += b
// make use of +=: a += b return a
}
inline Cmpx operator-(Cmpx a, Cmpx b) { // binary -, z=z1-z2
return a -= b
// equivalently: a -= b return a
}
inline Cmpx operator*(Cmpx a, Cmpx b) { // eg z = z1 * z2
return Cmpx(a.re*b.re - a.im*b.im, a.re*b.im + a.im*b.re)
}
ostream& operator<<(ostream& s, Cmpx z) {
s << "(" << z.re << ", " << z.im << ")"
return s
}
// output
// if z=Cmpx(2,5)
// output (2,5)
With the denition for Cmpx and the overloaded operators, we can now
write:
int main() {
Cmpx a(1,1)
//
Cmpx b = a
//
Cmpx c = a + b
//
c -= b
//
cout << c << '\n'
//
c = - b
//
cout << - c + a*b << '\n'
}
construction of an object
copy initialization
addition and copy initialization
subtract and assign
output the complex number c
unary operator and assignment
Note that operators + and ; do not directly manipulate the representation of an object but rely on operators += and ;= : This can be very useful
206
6. Operator Overloading
when dealing with user-dened types with a lot of data such as matrices
and vectors. The usual precedence rules still hold for overloaded operators.
For example, ;c + a b means ;c + (a b) instead of (;c + a) b: A few
more remarks are given below.
6.1.1 Initialization
construct a to be the complex number with real and imaginary parts zero
and construct b and f to be the complex number with real part 3 and
imaginary part 0. In the third statement above, 3 is rst constructed into
Cmpx(3) = Cmpx(3 0) and then copied to f by the compiler-generated
copy constructor.
207
return *this
}
}
Now adding a double to a Cmpx does not touch the imaginary part of the
complex number and thus is simpler and more ecient than adding two
Cmpx:
j
;=
<<
>=
;>
=
>>
&&
]
*
!
=
= = %=
>>= <<=
jj
++
()
new
^=
==
&=
!=
<
;;
new ]
>
;>
delete
&
+=
j=
<=
,
delete ]
208
6. Operator Overloading
::
209
This denition not only assigns a ; b to c but also changes the object a to
a ; b in a statement c = a ; b since a ; b simply means a:operator ; (b)
and this ;= bb in the denition of ; as a member function above also
changes the underlying object. A temporary object may need be created
even for member operator + when passing by reference is used for large
objects such as vectors and matrices see Exercise 6.7.2 and x6.3.
A unary operator, whether prex or postx, can be dened by either
a nonstatic member function taking no arguments or a nonmember function taking one argument. For any prex unary operator @, @aa can be
interpreted as either aa:operator@() or operator@(aa): For any postx
unary operator @, aa@ can be interpreted as either aa:operator@(int) or
operator@(aa int) where the int argument is used only to indicate that
the operator is postx, distinguishing it from prex. The unary operators
+ and ; are dened as nonmember functions for Cmpx in x6.1. They can
also be dened alternatively as member functions:
class Cmpx {
public:
Cmpx operator+()
Cmpx operator-()
}
Prex and postx operators ++ are now dened for Cmpx and their use
are illustrated:
class Cmpx {
public:
210
6. Operator Overloading
Cmpx operator++()
Cmpx operator++(int)
}
// prefix
// postfix
// bb = Cmpx(6,1), aa = Cmpx(6,1)
// cc = Cmpx(6,1), aa = Cmpx(7,2)
A user can also dene ++ to increment only the real part of a complex number, which reects the exibility of operator overloading on user-dened
types. If a is an integer, ++a means a += 1 which in turn means a = a +1:
For user-dened operators, this is not true unless they are dened this way.
For example, a compiler will not generate a denition of Z :: operator+=()
from the denitions of Z :: operator +() and Z :: operator =():
The operators = ] () and ;> must be dened as nonstatic member
functions to ensure that their rst operand is an lvalue. The subscripting
operator ] is used in x6.3, while the function call operator () is used in
x6.3, x7.6, x10.2.1, and x10.2.2. The dereferencing operator ;> can be used
to create \smart pointers" and is discussed in Str97, LL98].
The operators = (assignment), & (address-of), and , (sequencing) have
predened meanings when applied to class objects. They can be made inaccessible to general users by making them private:
class X {
private:
void operator=(const X&)
void operator&()
void operator,(const X&)
}
void f(X a, X b) {
a = b
&a
a, b
}
211
This is sometimes necessary to reduce possible errors or to improve eciency (see x7.5 for an example).
When an operator is overloaded for many operations with the same base
type (e.g., + can be overloaded to add two complex numbers, one complex
number and one double, etc.) or di
erent types (e.g., + can add complex
numbers, vectors, and matrices), overloading resolution (see x3.8.2, x6.4,
and x7.2.2) then comes in to determine which one to use or if there are
ambiguities. When the denition of an operator can not be found in the
scope in which it is used, then the namespaces of its arguments will be
looked up (see x4.1.5).
// number of entries
// entries of the vector
//
//
//
//
constructor
all entries equal d
copy constructor
destructor
212
6. Operator Overloading
// vec-scalar multiply
friend Vtr operator/(const Vtr&, double)
// vec-scalar divide
friend Vtr operator*(const Vtr&, const Vtr&)
// vector multiply
friend double dot(const Vtr&, const Vtr&)
// dot (inner) product
friend ostream& operator<<(ostream&, const Vtr&)
// output operator
}
Here are the denitions of the members and friends of class Vtr:
inline void error(char* v) {
cout << v << ". program exited\n"
exit(1)
}
// auxiliary fcn
// include <iostream>
// include <stdlib.h>
213
// usage: u = + v
// unary +
// usage: u = - v
// unary -
214
6. Operator Overloading
215
// resemble mathematics
Vtr v5 = - v1*v4
double a = dot(v1, v5)
// vector multiply
// dot product
// output vector
// number of rows
216
6. Operator Overloading
int ncols
double** ets
public:
Mtx(int n, int m, double**)
Mtx(int n, int m, double d = 0)
Mtx(const Mtx &)
~Mtx()
// number of columns
// entries of matrix
//
//
//
//
constructor (n by m)
all entries equal d
copy constructor
destructor
217
// usage: m1 = + m2
218
6. Operator Overloading
return *this
}
inline Mtx operator-(const Mtx & mat) {
return Mtx(mat.nrows, mat.ncols) - mat
}
// m1 = - m2
double* k]
< k i++) mti] = new double k]
< k i++)
j < k j++) mti]j] = 2*i*j + i + 10
m1(k, k, mt)
// construct m1 from mt
m2(k, k, 5)
// construct m2, all entries = 5
m3(k, k)
// construct m3, all entries = 0
(int i = 0 i < k i++)
// update entries of m3
219
// resemble mathematics
// very readable
Vtr vv(k)
for (int i = 0 i < k i++ ) vvi] = 5*i + 3
vv = m3*vv
// resemble mathematics
}
Since the function call operator () is overloaded, a matrix entry can also
be accessed using FORTRAN notation such as m3(5 7) = 10:
Note that pass by reference has been used when possible in the denition
of member functions and friends of classes Mtx and V tr: This is more
ecient when dealing with large objects than pass by value. In the latter
case operands are copied according to the copy constructor. Pass by value
is used in Cmpx, where only two doubles of a complex number are copied.
Since matrices and vectors often have many entries, copying their members
would impose signicant overhead. For example, if the binary plus operator
is dened through pass by value:
Mtx Mtx::operator+(Mtx mat) {
// usage: m1 + m2
Mtx sum = *this
// user-defined copy constructor
sum += mat
// is important here
return sum
// otherwise m1 would be changed
}
220
6. Operator Overloading
ai] = std::sin(i*i)
return a.twonorm()
Besides, it provides basic vector operations and the user can easily extend
it to meet specic needs. However, it may not be as ecient as the standard library <vector> (see x10.1.1) and <valarray> (see x7.5), which also
provide many more operations than Vtr.
Since C++ standard libraries do not include matrices, the matrix class
Mtx (especially its template form in Chapter 7) may be used in place of
two-dimensional built-in arrays (see x3.3). For example,
double h(int n, int m) {
Mtx mx(n, m)
for (int i = 0 i < n i++)
for (int j = 0 j < m j++)
mxi]j] = 1.2/(i + j + 1)
return mx.maxnorm()
}
// initialize z to Cmpx(2.2)
// call fcn f with argument Cmpx(5.5)
The constructor rst implicitly converts 2:2 into Cmpx(2:2) and then initializes z with the complex number Cmpx(2:2): This is called implicit conversion. For some types, this conversion may not be desirable. For example,
conversion from int to an enumeration type may cause truncation. Implicit
conversion can be suppressed by declaring a constructor to be explicit.
With an explicit constructor, conversion can not be done implicitly. For
example,
class Cmpx {
public:
//
//
//
//
//
221
// explicit constructor
// copy constructor
222
6. Operator Overloading
class tiny {
public:
// ... in addition to other members
int operator int() const { return v } // wrong !!!
}
// no return type needed
Now variables of tiny and int can be converted to each other freely:
tiny t = 12
int i = t
cout << int(t)
tiny tt = 16
//
//
//
//
Note that ambiguity can arise with user-dened conversions and userdened operators. For example,
tiny operator+(tiny t, tiny s) {
return tiny(int(t) + int(s))
}
//
//
//
//
//
//
223
version is legal. In the call h(5), the standard conversion h(double(5)) for
the argument from int to double is preferred over the user-dened conversion h(X (5)):
But this would sacrice readability and defeat the purpose of operator
overloading. That is, instead of writing
z = a*x + y
224
6. Operator Overloading
const Vtr& x
Sax(const double& d, const Vtr& v) : a(d), x(v) { }
}
inline Sax operator*(const double& d, const Vtr& v) {
return Sax(d,v)
// overload operator *
}
Then dene a class called Saxpy for vector saxpy operations (Again, to
avoid creating temporary objects, evaluations of suboperations are deferred).
struct Saxpy {
const double& a
// reference is used to avoid copying
const Vtr& x
const Vtr& y
Saxpy(const Sax& s,const Vtr& u) : a(s.a),x(s.x),y(u) { }
}
inline Saxpy operator+(const Sax& s, const Vtr& u) { //a*x+y
return Saxpy(s, u)
// overload +
}
inline Saxpy operator+(const Vtr& u, const Sax& s) { //x+a*y
return Saxpy(s, u)
// overload +
}
// number of entries
// entries of vector
//
//
//
//
//
constructor
constructor
copy constructor
constructor from Saxpy
destructor
225
With the new denition of class V tr the usual scalar-vector multiplication
operator (see x6.3) should not be dened, since otherwise ambiguity would
arise. Notice that V tr is used when dening Saxpy and Saxpy is used
when dening V tr. A forward declaration is actually needed to resolve this
chaining. With a forward declaration for V tr, the name of class V tr can be
used in Sax and Saxpy before V tr is dened, as long as its use does not
require the size of V tr or its members to be known.
Now a saxpy operation can be evaluated without creating any temporary
V tr objects and with only one loop:
int main() {
int k = 50000
double* p = new double k]
for (int i = 0 i < k i++ ) pi] = i
Vtr v(k, p)
// create a Vtr object using p
Vtr w(k, 5)
// create another Vtr object
Vtr u = 5*v + w
v = u + 3.14*w
226
6. Operator Overloading
on a SUN Ultra Workstation (167 MHz) running Solaris with a GNU C++
compiler. They take 67 14 and 19 seconds, respectively, on a Pentium
II PC (400 MHz) running Linux with a GNU C++ compiler. On an SGI
machine running UNIX, they take 78, 18, and 26 seconds, respectively. On
these four di
erent machines, the straightforward operator overloading is
three to ve times as slow as the deferred-evaluation operator overloading.
The latter is even faster than the traditional procedural function call on
three of the four machines, which is hard to explain.
aij = aji
for all i j = 0 1 : : : n ; 1
(6.1)
n
hA i > 0
for all 2 R and 6= 0:
(6.2)
Given an initial guess x0 , the conjugate gradient algorithm iteratively
constructs a sequence fxk g, which converges to the solution of the matrix
equation Ax = b: It can be stated as follows.
Algorithm 6.6.1 Taking an initial guess x0 2 Rn and setting r0 = b ;
Ax0 p0 = r0 construct the sequence xk , for k = 0 1 2 : : :
hrk rk i
k = hAp
(6.3)
k pk i
xk+1 = xk + k pk
(6.4)
rk+1 = rk ; k Apk
(6.5)
rk+1 i
k = hrkh+1
(6.6)
rk rk i
pk+1 = rk+1 +
k pk :
(6.7)
Equation (6.4) shows how a more accurate solution xk+1 is obtained from
an old iterate xk : The vectors pk are called search directions and the scalars
k search lengths. It can be shown easily that rk+1 = rk ; k Apk = b ;
Axk+1 : Thus rk+1 represents the residual corresponding to the approximate
solution xk+1 :
This algorithm can be implemented with only one matrix-vector multiplication and two vector dot products, two scalar-vector multiplications,
and one vector saxpy operation per iteration. The iterative process can
be stopped when a norm of the residual rk = b ; Axk is smaller than a
227
228
6. Operator Overloading
double alpha = zr/dot(mp,p)
x += alpha*p
r -= alpha*mp
if (r.twonorm() <= stp) break
double zrold = zr
zr = dot(r,r)
p = r + (zr/zrold)*p
//
//
//
//
// dot product
// zrold=0 only if r=0
}
eps = r.twonorm()
if (iter == maxiter) return 1
else return 0
}
// end CG()
With operator overloading, the main loop in the code resembles the algorithm very closely. Now the conjugate gradient algorithm can be tested
to solve a linear system with a 300 by 300 Hilbert coecient matrix (which
is symmetric and positive denite) and a known solution vector:
int main() {
int n = 300
Mtx a(n, n)
// n by n Hilbert matrix
for (int i = 0 i < n i++)
for (int j = 0 j < n j++) ai]j] = 1/(i + j + 1.0)
Vcr t(n)
// exact solution vector of size n
Vcr x(n)
// initial guess and solution vector
for (int i = 0 i < n i++)
// true solution
ti] = 1/(i + 3.14)
int iter = 300
double eps = 1.0e-9
int ret
if (ret
cout <<
cout <<
cout <<
<<
6.7 Exercises
229
The maximum number of iterations allowed can be taken to be the dimension of the matrix. In Chapter 11, this algorithm is used to solve matrix
equations with real and complex matrices in di
erent precisions and di
erent matrix storage formats.
6.7 Exercises
6.7.1. Test and run the class for complex numbers dened in x6.1. Add more
operations such as division, complex conjugate, complex equality operator == and inequality operator !=, prex and postx operators
;;, modulus, argument, and nth roots.
6.7.2. Modify the binary + and ; operator functions for Cmpx as dened
in x6.1 so that they have prototype:
Cmpx operator+(const Cmpx&, const Cmpx&)
Cmpx operator-(const Cmpx&, const Cmpx&)
6.7.3.
6.7.4.
6.7.5.
6.7.6.
The only di
erence is that pass by reference is now used in argument
passing. Dene these two functions and make sure your denitions
do not cause unwanted side e
ects.
Test and run the class for vectors dened in x6.3. Then comment out
the user-dened copy constructor and copy assignment and try to
add two vectors v = v1+ v2: What would happen to vector v1 in this
vector addition? Could any other vector operations also be a
ected
by depending on the compiler-generated copy constructor and copy
assignment?
Redene the class for vectors dened in x6.3 so that it provides range
checking for vector indexes. That is, if v is a vector, then accessing
vi] with i < 0 or i v:size() will give a run-time error and exit the
program.
Dene a new class called Cvec for vectors of complex numbers using
the simple denition in x6.1 and Exercise 6.7.1 for complex numbers.
Provide basic operations (similar to those in x6.3 for real vectors) for
complex vectors such as vector addition, vector multiplication, vectorscalar multiplication, and dot product. Note that the dot product
of two complex vectors includes complex conjugation of the second
vector.
Test and run the class for matrices dened in x6.3. Add more operations such as matrix-matrix multiplication, matrix-scalar multiplication, and one, maximum, and Frobenius matrix norms.
230
6. Operator Overloading
6.7.7. Implement the Gauss quadrature in Exercise 5.10.11 as the dot product of two vectors one vector represents the weights and the other
represents the function values at quadrature points. Evaluating denite integrals as dot products enables one to deal easily with integrals
with the integrand being the product or sum of several functions (see
x7.7).
6.7.8. Following the idea of Exercise 6.7.7, implement the Gauss{Lobatto
quadrature problem in Exercise 5.10.12 as evaluating dot products
on subintervals of a given interval a b].
6.7.9. Mimic the class tiny dened in x6.4 to dene a class for 6-bit nonnegative integers that can mix freely with integers in arithmetic operations. Provide range checking when assigning an int to a variable
of such a class and the conversion operator from it to int:
6.7.10. Test and run the deferred-evaluation operator overloading code for
vector saxpy operations as dened in x6.5. Compare the run-time on
your machine for the straightforward operator overloading as dened
in x6.3, the deferred-evaluation operator overloading as dened in
x6.5, and the traditional procedural-style function call saxpy () as
dened in x6.5.
6.7.11. Extend the vector class dened in x6.5 using deferred-evaluation operator overloading, so that it will include ecient vector addition and
scalar-vector multiplication (without any temporary vector objects
and with a minimum number of loops).
6.7.12. Dene a matrix class based on deferred-evaluation operator overloading as discussed in x6.5 to support ecient matrix-vector gaxpy (general A x plus y) operation z = A x + y where x y z are vectors
and A a matrix. The implementation should not use any temporary
vector objects and should apply only a minimum number of loops.
6.7.13. Apply the conjugate gradient algorithm to solve a system of linear
equations with a symmetric and positive denite coecient matrix,
for example, with a Hilbert matrix of dimension 500 and a given
right-hand side vector. Compute the two-norm of the residual of the
numeric solution to check its accuracy.
Templates
232
7. Templates
// number of entries
// entries of vector
//
//
//
//
constructor
constructor
copy constructor
destructor
// a template declaration
// vector of 10 doubles,
// empty vector of int
// a vector of 15 (float*)s
bi] = i
// a vector of 10 doubles
// add and assign
// dot product of two vectors
Note that the type parameter is replaced by specic type names bracketed by < > and is put after the template class name. The compiler automatically generates denitions for classes Vcr< double >, Vcr< int >,
and Vcr<float > from the class template Vcr<T >. In particular, the
compiler-generated class Vcr<double> is a class for vectors whose entries
are doubles and works exactly like the class Vtr dened in x6.3. The use
233
234
7. Templates
However, for friends and ordinary function templates, the qualication with
<T> is not redundant and must not be omitted.
235
Here val of type C and i of type int are also parameters of the template
class. Such parameters can be a constant expression, the address of an
object or function with external linkage, or a nonoverloaded pointer to a
member. It is an error if their values can not be determined at compile
time.
A close look at the template class Vcr<T> and the friend dot<T> () reveals
that it does not work correctly when the type parameter T is a complex
236
7. Templates
complex numbers
// use <complex>
constructor
constructor
copy constructor
destructor
Note that this specialization is itself a template class such a specialization is called a partial specialization. The template<class T> prex species that this template class has a type parameter T . The <complex<T>>
after the template class name means that this specialization is to be used
when the template argument in Vcr<T> is a complex number. Its members
and nonmembers must be redened to accommodate special situations of
vectors of complex numbers. They may be dened as
template<class T>
Vcr< complex<T> >::Vcr(int n, const complex<T>* const abd) {
vr = new complex<T> lenth = n]
for (int i = 0 i < lenth i++) vri] = *(abd +i)
}
template<class T>
Vcr< complex<T> >::Vcr(const Vcr & vec) { // copy constructor
237
// maximum norm
// use <algorithm>
template<class T>
// dot product
complex<T> dot(const Vcr<complex<T> >& v1,
const Vcr<complex<T> >& v2) {
if (v1.lenth != v2.lenth) cout << "bad vector sizes\n"
complex<T> tm = v10]*conj(v20])
// conjugate of v2
for (int i =1 i <v1.lenth i++) tm += v1i]*conj(v2i])
return tm
}
The prototypes and denitions for maxnorm() and dot() are very di
erent
now from the previous ones.
Now we can write the following function main() to test it.
int main(){
int n = 300
complex<double>* aa = new complex<double> n]
for (int j = 0 j < n j++) aaj] = complex<double>(5, j)
Vcr< complex<double> > v1(n, aa)
// vector v1
Vcr< complex<double> > v2(n)
// vector v2
for (int j = 0 j < n j++) v2j] =complex<double>(2, 3+j)
cout << "norm = " << v1.maxnorm() << '\n' // max norm
cout << "dot = " << dot(v1,v2)
<< '\n' // dot product
}
238
7. Templates
A complete specialization is one that does not take any type parameters.
For example, a complete specialization for Vcr<T> is:
template<> class Vcr< complex<double> >{
int lenth
//
complex<double>* vr
//
public:
Vcr(int, complex<double>*)
Vcr(int = 0, complex<double> = 0)
Vcr(const Vcr&)
//
~Vcr() {delete] vr }
//
number of entries
entries of vector
copy constructor
destructor
The template<> prex species that this class is a specialization for Vcr<
T> that does not take any type parameters. It is a complete specialization.
The same explanation holds for template function dot(). All members and
friends must be dened for a specialization. However, only two denitions
are presented here (the rest are omitted to save space):
Vcr< complex<double> >&
Vcr< complex<double> >::operator+=(const Vcr& vec) {
if (lenth != vec.lenth ) cout << "bad vector sizes\n"
for (int i = 0 i < lenth i++) vri] += veci]
return *this
}
template<>
complex<double> dot(const Vcr<complex<double> >& v1,
const Vcr<complex<double> >& v2) {
if (v1.lenth != v2.lenth ) cout << "bad vector sizes\n"
complex<double> tm = v10]*conj(v20])
// conjugation
for (int i = 1 i < v1.lenth i++) tm += v1i]*conj(v2i])
return tm
}
239
All specializations of a template must be declared in the same namespace as the template itself. If a specialization is explicitly declared such as
Vcr< complex<T > >, it (including all of its members) must be dened
somewhere. In other words, the compiler will not generate denitions for
it.
Similarly, a partial specialization for vectors of pointers can be dened
as
// ************* a specialization of Vcr<T> for pointers
template<class T> class Vcr<T*>{
int lenth
// number of entries
T** vr
// entries of vector
public:
Vcr(int, T**)
Vcr(const Vcr&)
// copy constructor
T* & operator](int i) const { return vri] }
Vcr& operator=(const Vcr&)
// ... other members and definitions
}
Specialization is a way of specifying alternative and often ecient implementations for specic type parameters. However, the user interface is not
changed in other words, with or without the specializations, the user still
uses a Vcr of complex numbers and a Vcr of pointers the same way. See Exercise 7.9.4 for an alternative approach to specializing vector maxnorm().
In the above, the second class is more specialized than the rst one, and
the third is more specialized than the second. The most specialized version
will be preferred over others in declarations of objects, pointers, and in
overload resolution. For example, in the declaration,
Vcr< complex<double> > cdv(100)
the second class is preferred over the rst and general class.
240
7. Templates
// function template
Note that the operator < is used for comparison. However, not every type
has such an operator. Improvements are given in x7.2.3 and x7.2.4. Another
way is to overload the operator <. For example, an array of point2d can
be sorted according to its x-coordinate as
struct point2d {
double x, y
}
inline bool operator<(const point2d& p, const point2d& q) {
241
// compare x coordinates
}
int main() {
const int n = 10000
point2d an]
for (int i = 0 i < n i++) {
ai].x = i*(5 - i) ai].y = 5/(i+2.3) - 1
}
sort(a, n)
// sort array a of n elements
}
A compiler can deduce the types of the parameters of a function template, provided the function argument list identies them uniquely. However, when a template parameter can not be deduced from the template
function arguments, it must be specied explicitly within a pair of angle
brackets:
template<class T> T* f(T)
int i = 5
double d = 3.4
f(i)
f(5)
f(d)
//
//
//
//
T = int, S = double
T = double, S = int
error: can not deduce T and S
T = double, S = double
242
7. Templates
// abs(double), version 3
// abs<int>(int), version 1
// abs<double>(complex<double>)
243
//
//
//
//
absmax<int>(1, 2)
absmax<double>(2.7,5.6)
ambiguous, absmax<double>(5,6.9)
or absmax<int>(5,6.9)?
The call absmax(5 6:9) is ambiguous and illegal, since no standard conversion is made for its arguments. This ambiguity can be resolved either by
explicit qualication:
absmax<double>(5, 6.9)
If a function template also takes basic types as its arguments, for example,
template<class T, class S, class R> R f(T& t, S s, double d)
7.2.3 Specializations
The sort program given early in this section does not sort arrays of char
correctly since it will compare the addresses of the rst char for each element of the array. Specialization can be used to dene a comparison function that compares di
erent types of elements correctly. We now use the
standard library <vector>, dened as a class template (see x10.1.1), for a
dynamic array of elements. For example,
244
7. Templates
With di
erent specializations of template less<T > (), a vector of strings,
a vector of complex numbers, or a vector of point2d can be sorted in increasing order or any other order dened by the specializations.
Another example of function template specialization is the useful vector
dot product, applied to real and complex arrays:
template<class T> T dot(T* a, T* b, int n) {
T init = 0
for (int i = 0 i < n i++) init += (*a++)*(*b++)
return init
}
245
246
7. Templates
Then the normal comparison criterion need not be specied each time it is
used:
void g(vector<char> v1, vector<char> v2) {
compare(v1, v2)
// use Ncomp<char>
compare<char, Nocase>(v1, v2)
// use Nocase
}
A class or class template can have function members that are parameterized
by general parameters. For example,
template<class S> class Cmpx {
S re, im
public:
template<class T> Cmpx(const Cmpx<T>& c)
: re(c.re), im(c.im) { }
}
247
248
7. Templates
The rst way is to include this template denition le in all les that use
the template. For example,
// file f1.cc
#include "error.cc"
int* a = new (nothrow) int 1000000]
// return 0 when out of memory
if (!a) error("no more space.")
// file f2.cc
#include "error.cc"
int* b = new (nothrow) int 1000000]
249
if (!b) error(*b)
That is, the denition of the template and all declarations that it depends
on are included in di
erent compilation units. The operator new throws
an exception bad alloc when there is no more memory available. However,
using (nothrow) causes it to return 0 instead see x9.5. The compiler will
generate appropriate specializations according to how the template is used.
This strategy treats templates the same way as inline functions.
The second way is to include only declarations of templates and explicitly export its denitions to di
erent compilation units. Split the original
error:cc le into two:
// file error2.h: only declarations of the template
template<class T> void error(const T & )
// file error2.cc: definition of template and export it
#include <cstdlib>
#include <iostream>
#include "error2.h"
export template<class T> void error(const T & t) {
std::cout << t << "\nProgram exited.\n"
std::exit(1)
}
This strategy treats templates the same way as noninline (ordinary) functions. The template denition in le error2:cc is compiled separately, and
it is up to the compiler to nd the denition of the template and generate
its specializations as needed. Note that the keyword export means accessible from another compilation unit. This can be done by adding export to
the denition or declaration of the template. Otherwise the denition must
be in scope wherever the template is used.
250
7. Templates
Notice the two template member functions above, which enable construction and assignment from other types of complex numbers, for example, from complex<float> to complex<double>. However, specializations will be dened to restrict implicit conversions, for example, from
complex<double> to complex<float>, in order to avoid truncation.
Other functions acting on complex numbers can be dened as
template<T> complex<T> operator+(const complex<T>&,
const complex<T>&)
template<T> complex<T> operator+(const T&,const complex<T>&)
template<T> complex<T> operator+(const complex<T>&,const T&)
// also binary operators: -, *, /, ==, !=
template<T> complex<T> operator+(const complex<T>&)
// also unary operator: template<T> T real(const complex<T>&)
// real part
template<T> T imag(const complex<T>&)
// imaginary part
template<T> complex<T> conj(const complex<T>&) // conjugate
template<T> T abs(const complex<T>&)
// absolute value
template<T> T arg(const complex<T>&)
// argument
template<T> complex<T> polar(const T& abs, const T& arg)
// polar form (abs,arg)
template<T> T norm(const complex<T>&)
// square of abs()
template<T> complex<T> sin(const complex<T>&)
// sine
251
complex<T>
complex<T>
complex<T>
complex<T>
pow(const
pow(const
pow(const
pow(const
const
complex<T>&, int)
complex<T>&, const T&)
T&, const complex<T>&)
complex<T>&,
complex<T>&)
The function polar() constructs a complex number given its absolute value
and argument. Note the misleading function name of norm(), which gives
the square of the absolute value of a complex number. The input and output
operators << and >> are also provided. Specializations for complex<float>,
complex<double>, and complex<long double> are dened to prevent certain conversions and to possibly optimize the implementations. For example,
class complex<float> {
float re, im
public:
complex(float r = 0.0, float i = 0.0)
complex(const complex<float>&)
explicit complex(const complex<double>&)
explicit complex(const complex<long double>&)
// ... other functions
}
252
7. Templates
{
valarray with size = 0
valarray with size = n
size =n, elements *p
size =n, all entries =v
copy constructor
destructor
//
//
//
//
copy assignment
all elements = v
subscript, rvalue
subscripting
// unary minus
cshift(int i) const
// cyclic shift of entries
shift(int i) const
// logical shift
apply(T f(T)) const
// apply f to each entry
apply(T f(const T&)) const
// apply f()
253
{1,2,3,4,5}
v(permu,5)
v2 = v.shift(2)
v3 = v.cshift(2)
v4 = v.shift(-2)
v4 = v.cshift(-2)
//
//
//
//
//
//
permutation
vector of 5 int
v2 = {3,4,5,0,0}
v2 = {3,4,5,1,2}, cyclic
right shift,v2={0,0,1,2,3}
right shift,v2={4,5,1,2,3}
// smallest value
// largest value
254
7. Templates
a00
6 a
A = 64 a10
20
a30
0
a01
a11
a21
a31
1
a02
a12 77
a22 5
a32
2
10 11
B = a00 a01 a02 a10 a11 a12 a20 a21 a22 a30 a31 a32 :
Then slice(0 3 1) of vector B describes row 0 of matrix A (consisting of 3
elements starting from 0 with stride 1 that is, B 0] = a00 , B 1] = a01 , and
B 2] = a02 ) and slice(3 3 1) describes row 1. Similarly, the slice(0 4 3)
of vector B describes column 0 of matrix A and slice(2 4 3) column 2.
That is, a slice(s z d) denes a mapping from three nonnegative integers
fs z dg into an index set I = fi : i = s + j d j = 0 1 : : : z ; 1g that
describes some elements vi] i 2 I , of a valarray v. In particular, a matrix
can be represented through a valarray and its rows and columns can be
described by slices. The idea is that a row or column of a matrix can be
accessed by using one index j for j = 0 1 : : : z ; 1, where z is the number
of elements in the row or column. Note that the row by row layout has
been used for representing a matrix through a valarray. FORTRAN-style
column by column layout can be used similarly. However, it may be better
to get used to the C++ style (row by row layout, array index starting from
0 instead of 1, and the symmetric notation mi]j ] instead of m(i j )) if one
wants to program in C++ extensively.
A type called slice array is dened in <valarray> to refer to elements
of a valarrary described by slices:
template <class T> class std::slice_array {
private:
slice s
valarray<T>* p
// implementation-dependent
slice_array()
// prevent construction
slice_array(const slice_array&) // prevent construction
slice_array& operator=(const slice_array&)
public:
void operator=(const valarray<T>&)
void operator=(const T& t)
// assign t to each entry
void operator*=(const T&)
// scalar-vector multiply
255
// also: +=, -=, /=, %=, ^=, &=, |=, <<=, >>=
~slice_array()
}
Since construction and copy operations are dened as private, a user can
not directly create a slice array. Instead, subscripting a valarray is dened
to be the way to create a slice array corresponding to a given slice:
template <class T> class std::valarray {
public:
// in addition to other members
valarray(const slice_array<T>&)
valarray operator](slice) const
slice_array<T> operator](slice)
valarray& operator=(const slice_array<T>&)
}
Note that it is illegal to copy slice arrays. The reason for this is to allow
alias-free optimizations and prevent side e
ects. For example,
slice_array<double> create2(valarray<double>& vd) {
slice_array<double> sad0 // error, no default constructor
slice s(1, vd.size()/2, 2)
// slice for odd index entries of vd
slice_array<double> sad1 = vds]
// illegal, no copy construction
sad1 = sad0
return sad0
}
256
7. Templates
A slice can be used to describe rows and columns of a matrix and multidimensional arrays represented through a valarray. In some applications,
submatrices need be manipulated. Generalized slices gslice and arrays
gslice array are introduced for this purpose.
An n-slice or generalized slice gslice contains n di
erent dimensions (n
slices) and is dened as
class std::gslice {
// ... representation, starting index, n sizes, n strides
public:
gslice()
gslice(size_t start, const valarray<size_t> nsize,
const valarray<size_t> nstride)
size_t start() const
// index of first entry
valarray<size_t> size() const
// number of elements in all dimensions
valarray<size_t> stride() const
// strides for all dimensions
}
a00
6 a
A = 64 a10
20
a30
0
a01
a11
a21
a31
1
a02
a12
a22
a32
2
7
7
5
4
10 11
B = a00 a01 a02 a10 a11 a12 a20 a21 a22 a30 a31 a32 :
Since every row of the submatrix A32 can be represented by a slice of size
2 and stride 1 and every column by a slice of size 3 and stride 3, a 2-slice
can be dened to describe the submatrix:
size_t z] = {2,3}
// slice(s,z0],d0]) for a row
size_t d] = {1,3}
// slice(s,z1],d1]) for a column
{\it valarray}<size_t> vz(z,2) // size vector
257
Thus gs2 denes a mapping from f(4 vz vd)g into the index set I = fi : i =
4+ j0 +3j1 j0 = 0 1 j1 = 0 1 2g for the submatrix A32 . In particular, the
subset fi : i = 4+j0 +3j1 j0 = 0 1 j1 = 0g = f4 5g describes row 0 of A32
(i.e, B 4] = a11 B 5] = a12 ), and fi : i = 4+ j0 +3j1 j0 = 0 j1 = 0 1 2g =
f4 7 10g describes column 0 of A32 (i.e., B 4] = a11 B 7] = a21 B 10] =
a31 ). The idea is that the submatrix A32 can now be conveniently accessed
using two indexes j0 and j1 for j0 = 0 1 : : : z0 ; 1 and j1 = 0 1 : : : z1 ; 1,
where z0 is the number of elements in each row and z1 the number of
elements in each column of the submatrix.
The template class gslice array is dened in the same way and o
ers
the same set of members. This implies that a gslice array can not be constructed directly and can not be copied by the user. Instead, subscripting
a valarray is dened to be the way to create a gslice array corresponding
to a given n-slice:
template <class T> class std::valarray {
public:
// in addition to other members
valarray(const gslice_array<T>&)
valarray operator](const gslice&) const
gslice_array<T> operator](const gslice&)
valarray& operator=(const gslice_array<T>&)
}
258
7. Templates
public:
// in addition to other members
valarray(const indirect_array<T>&)
valarray operator](const valarray<size_t>&) const
indirect_array<T> operator](const valarray<size_t>&)
valarray& operator=(const indirect_array<T>&)
}
The same set of members for mask array and indirect array are dened
as for slice array. In particular, mask array and indirect array can not
be directly constructed or copied by a user.
259
7.6.1 Accumulate
d)
sum by default, d = -5
0.0, SumAbs)
sum of absolute values, d = 5
The rst and second arguments of accumulate() must point to the rst and
the last-past-one elements of a sequence, respectively. With the operation
SumAbs(), it actually computes the one-norm of a sequence. It can also
be used to compute, for example, the sum of x-coordinates of an array of
point2d:
struct point2d { double x, y }
double Sumx(double a, point2d p) { return a + p.x }
260
7. Templates
Note that the second version of the function template accumulate() takes
a binary operation as its fourth argument. The fourth argument can be
either a binary function such as SumAbs() above, or a class object with
the function call operator () overloaded. For example,
template<class T> struct Multiply {
// overload operator()
T operator()(const T& a, const T& b) const { return a*b }
}
int main() {
double ai5]
for (int i = 0 i < 5 i++) aii] = -1
double d =
accumulate(ai, ai + 5, 1.0, Multiply<double>())
}
//d=-1
Notice an object rather than a type is needed for the fourth argument of
// #include <time.h>
261
On a Pentium PC running Linux, the running times are 24, 47, and 33
seconds, respectively, while on an SGI PowerChallenge they are 8, 16, and
14 seconds, and on a SUN Ultra2 workstation 10, 32, and 16 seconds. Thus
it can be more ecient to use a function object or a template argument
than passing a function pointer to another function. This implies that the
FORTRAN- and C-style passing functions as arguments to other functions
should be avoided if possible when a large number of such function calls
are needed. However, both of them can be much less ecient than plain
function calls. This suggests that a user may wish to dene his or her own
version of accumulate() and other functions when portability and readability are not as important as eciency. For example,
template<class In, class T>
// user-defined version
T accumulate_multiply(In first, In last, T init) {
while (first != last) init *= *first++
// multiply
return init
}
template<class In, class T>
// user-defined version
T accumulate_abs(In first, In last, T init) {
while (first != last) init += abs(*first++)
return init
// sum of absolute values
}
262
7. Templates
The function inner product() calculates the inner product or dot product
of two sequences of oating point numbers. It is dened as
template<class In, class In2, class T>
T inner_product(In first, In last, In2 first2, T init) {
while (first != last) init = init + *first++ * *first2++
return init
}
template<class In,class In2,class T,class BinOp,class BinOp2>
T inner_product(In first, In last, In2 first2, T init,
BinOp op, BinOp2, op2) {
while (first != last)
init = op(init, op2(*first++, *first2++))
return init
}
Note that only the beginning of the second sequence is passed as an argument and its size is implicitly assumed to be at least as long as the
rst sequence. Range checking is not provided here. The rst version of
inner product() does not apply to vectors of complex numbers since complex conjugation is needed for the second sequence. For inner products of
complex vectors, the user may wish to dene her own template instead of
using the second version for eciency reasons. For example,
void f(valarray<double>& v1,double* v2,vector<double>& v3){
double d = inner_product(&v10], &v1v1.size()], v2, 0.0)
double e = inner_product(v3.begin(),v3.end(),&v10], 0.0)
}
template<class In, class In2, class T>
// user-defined
T inner_product_cmpx(In first, In last, In2 first2, T init){
while (first != last)
init = init + *first++ * conj(*first2++)
return init
}
void g(valarray<complex<double> >& v1, complex<double>* v2){
complex<double> d = 0
d = inner_product_cmpx(&v10], &v1v1.size()], v2, d)
}
For a vector vr (x10.1.1), the functions vr:begin() and vr:end() point to the
rst and last-past-one elements of vr, respectively. However, a valarray va
does not have functions va:begin() and va:end(). Fortunately, it forms a
263
It can be used as
void f(valarray<double>& v1, vector<double> v2) {
partial_sum(&v10], &v1v1.size()], &v10])
partial_sum(v2.begin(), v2.end(), v2.begin())
}
If v1 has elements 1 2 5 14, then the rst call above changes it into the
partial sum sequence 1 3 8 22.
It can be used as
void f(valarray<double>& v1, vector<double> v2) {
adjacent_difference(&v10], &v1v1.size()], &v10])
adjacent_difference(v2.begin(), v2.end(), v2.begin())
}
264
7. Templates
If v1 has elements 1 3 8 22, then the rst call above changes it into the
adjacent di
erence sequence 1 2 5 14. It is easy to see that the algorithms
adjacent dierence() and partial sum() are inverse operations.
As pointed out in x7.2 and x7.6, using templates and function objects can
improve run-time eciency for programs that traditionally require passing
pointer-to-functions as arguments. Using a template andRa function object,
the function trapezoidal(), for numerically evaluating ab f (x)dx, can be
written and used as
template<class Fo>
double trapezoidal(double a, double b, Fo f, int n) {
double h = (b - a)/n
// size of each subinterval
double sum = f(a)*0.5
for (int i = 1 i < n i++) sum += f(a + i*h)
sum += f(b)*0.5
return sum*h
}
class integrand {
// define a class for my integrand
public:
double operator()(double x) { return exp(-x*x) }
}
int main() {
cout << trapezoidal(0, 1, integrand(), 100) << '\n'
}
265
Recall that an object of a class with the function call operator () overloaded is called a function object. Thus the object integrand(), instead
of the class name integrand, is passed to the function call trapezoidal().
The compiler will then instantiate trapezoidal(), substitute integrand for
the template parameter Fo, which results in integrand::operator() being inlined into the denition of trapezoidal(). Note that trapezoidal() also accepts
a pointer-to-function as its third argument, but this would be hard to be
inlined and may not improve the run-time.
This technique can be put in a more general form that also takes a
template parameter for the precision of integration:
template<class T, class Fo>
T trapezoidal(T a, T b, Fo f, int n) {
T h = (b - a)/n
// size of each subinterval
T sum = f(a)*0.5
for (int i = 1 i < n i++) sum += f(a + i*h)
sum += f(b)*0.5
return sum*h
}
template<class T> class integrand2 {
public:
// define a class for my integrand
T operator()(T x) { return exp(-x*x) }
}
int main() {
cout << trapezoidal(0.0, 1.0, integrand2<double>(), 100)
}
With this form, integrals such as 01 e;x2 dx can be integrated by the Trapezoidal Rule in double,
oat, long double, or other precisions.
R
266
7. Templates
}
double myintegrand(double x) {
return exp(-x*x)
}
int main() {
cout << trapezoidal<myintegrand>(0, 1, 100) << '\n'
}
For integrals such as ab f (x) + g(x)]dx and ab f (x)g(x)h(x)dx, the integrand is a product (or summation) of di
erent functions such as f , g, and h,
and the C++ programs presented in previous sections can not be directly
applied, since code such as
R
is illegal. One way is to write another function that returns the product or
sum of some functions:
double F(double x, double f(double), double g(double)) {
return f(x) + g(x)
}
267
f (x)dx
nX1
i=0
wi f (xi )
(7.1)
where x0 x1 , : : :, xn;1 ] are given Gauss points and w0 w1 , : : :, wn;1 ] are
the corresponding weights see Exercise 5.10.10. If vector wgts contains the
weights wi and vector fxi contains the function values at Gauss points, then
the dot product function can be called to give the approximate integral:
double dot(const Vtr& u, const Vtr& v) {
double dotprod = 0
int n = u.size()
for (int i = 0 i < n i++) dotprod += ui]*vi]
return dotprod
}
Vtr fxi(n)
for (int i = 0 i < n i++) fxii] = f(xi])
double d = dot(fxi, wgts) // dot() evaluates integrals
268
7. Templates
return w
}
Vtr fghxi = fxi*gxi*hxi
double d0 = dot(fghxi, wgts)
double d2 = dot(fxi, wgts)
where fxi, gxi, and hxi are the vectors consisting of the function values of
f , g, and h, respectively, evaluated at the Gauss points x0 x1 : : : xn;1 ]
269
Notice the function call exprn ] forces the operation Op :: apply() to evaluate elements of the left and right operands.
Next dene a class on how left and right operands are going to be evaluated:
// define multiply operation on elements of vectors
struct Multiply {
static double apply(double a, double b) {
return a*b
}
}
270
7. Templates
That is, the expression template technique eciently computes the product
of an arbitrary number of vectors with a single loop and without temporary
vector objects.
For references on expression templates, see Vel95, Fur97]. Some software libraries using expression templates to achieve run-time eciency are
PETE Pet], POOMA Poo], and Blitz++ Bli]. Besides, the Web address
http://www.oonumerics.org contains a lot of useful information on objectoriented numeric computing.
271
where the size of the vectors may not have to be known at compile-time.
For vectors of a small size (say 4), an alternative denition:
inline double dot4(const Vtr& u, const Vtr& v) {
return u0]*v0] + u1]*v0] + u2]*v2] + u3]*v3]
}
can boost performance, since it removes loop overhead and low-level parallelism can be easily done on the summation operation. Besides, it is easier
to make this small function to be inlined (reducing function-call overhead)
and its data to be registerized. All these factors can make dot4() much
more ecient than the more general dot() for vectors of size 4.
To improve performance, template metaprograms can be used so that
the dot products of small vectors are expanded by the compiler in the
form of dot4(). To illustrate the idea of template metaprograms, consider
the Fibonacci numbers dened by a recursive relation: f0 = 0 f1 = 1,
fn = fn;1 + fn;2 , for n = 2 3 : : :. They can be recursively computed by
the compiler:
template<int N> struct fib{
enum { value = fib<N-1>::value + fib<N-2>::value}
}
template<> struct fib<1>{
enum { value = 1 }
}
template<> struct fib<0>{
enum { value = 0 }
}
const int f3 = fib<3>::value
const int f9 = fib<9>::value
The compiler can generate that f 3 = 2 and f 9 = 34. The recursive template behaves like a recursive function call (but at compile-time) and the
specializations fib<1> and fib<0> stop the recursion.
This recursive template technique can be generalized to compute dot
products. First dene a class for small vectors:
template<int N, class T>
272
7. Templates
class smallVtr {
T vrN]
// array of size N and type T
public:
T& operator](int i) { return vri] }
}
273
n
X
i=0
yi L(in) (x)
(n)
L(in)(x) =
n
Y
=0
j =i
j
x ; xj
xi ; xj
i = 0 1 : : : n ; 1:
274
7. Templates
That is, each L(in) (x) is the product of all factors (x ; xj )=(xi ; xj ) for
j 6= i. They have the following property.
p2 (x) =
2
X
i=0
L(2)
i (x)yi
x0 )(x ; x2 ) y + (x ; x0 )(x ; x1 ) y :
; x1 )(x ; x2 )
y0 + (x(x ;
= (x(x ;
x
)(
x
;
x
)
;
x0 )(x1 ; x2 ) 1 (x2 ; x0 )(x2 ; x1 ) 2
0
1
0
2
1
Since some applications require more accuracy than others, a template
function can be written so that a user can conveniently choose single, double, long double, or other precisions:
template<class T>
T lagrange(const vector<T>& vx, const vector<T>& vy, T x) {
int n = vx.size() - 1
T y = 0
for (int i = 0 i <= n i++) {
T temp = 1
for (int j = 0 j <= n j++)
if (j != i) temp *= (x - vxj])/(vxi] - vxj])
y += temp*vyi]
}
return y
}
275
int main() {
const int n = 4
vector<float> px(n)
vector<float> py(n)
for (int i = 0 i < n i++) {
pxi] = 1 + i/4.0 pyi] = exp( pxi] )
}
float x = 1.4
float approximation = lagrange(px, py, x)
}
Compared to the exact function value e1:4, the error of this approximation
is 0:0003497 (on my machine).
p1 (x) = c0 + c1 (x ; x0 )
p2 (x) = c0 + c1 (x ; x0 ) + c2 (x ; x0 )(x ; x1 ):
Before presenting an algorithm on how to nd ci i = 0 1 : : : n, we talk
about how Newton's form can be evaluated eciently, assuming all the ci
are known. Write pn (x) into a nested multiplication form:
pn (x) = c0 + d0 (c1 + d1 (c2 + + dn;3 (cn;2 + dn;2 (cn;1 + dn;1 (cn ))) ))
where d0 = x ; x0 d1 = x ; x1 : : : dn;1 = x ; xn;1 , and introduce the
notation:
un = cn
un;1 = cn;1 + dn;1 un
un;2 = cn;2 + dn;2 un;1
..
.
u1 = c1 + d1 u2
u0 = c0 + d0 u1
u
cn
276
7. Templates
for (i = n ; 1 n ; 2 : : : 0) f
u
ci + di u
== that is: u
ci + (x ; xi ) u
return u
This is the so-called Horner's algorithm (see x3.12), which is very ecient
in evaluating polynomials.
Below is a procedure on how to nd the coecients ci . The requirement
pn (xi ) = f (xi ) leads to
f (x0 ) = pn (x0 ) = c0
f (x1 ) = pn (x1 ) = c0 + c1 (x1 ; x0 )
f (x2 ) = pn (x2 ) = c0 + c1 (x2 ; x0 ) + c2 (x2 ; x0 )(x2 ; x1 )
..
.
f xi ] = f (xi )
i = 0 1 : : : n
and of order 1 as
; f xi ]
f xi xj ] = f xxj ] ;
x
j
i j = 0 1 : : : n and i 6= j
and of order j as
x0
x1
x2
x3
277
x
3 1 5 6
f (x) 1 ;3 2 4
the table of divided di
erences can be computed according to Table 7.1 as
3 1 2
;3=8 7/40
1 ;3 5/4 3/20
5 2 2
6 4
Then the interpolation polynomial can be written as
7 (x ; 3)(x ; 1)(x ; 5):
p3 (x) = 1 + 2(x ; 3) ; 38 (x ; 3)(x ; 1) + 40
When n is large, calculating the divided di
erence table can not be done
by hand. To derive an ecient algorithm for doing this, introduce the notation cij = f xi xi+1 : : : xi+j ]. Then Table 7.1 of divided di
erences
becomes (in the case of n = 4):
278
7. Templates
x0
x1
x2
x3
x4
c00
c10
c20
c30
c40
for (j = 1 2 : : : n) f
for (i = 0 1 : : : n ; j ) f
cij
(ci+1j;1 ; cij;1 )=(xi+j ; xi )
g
for (j = 1 2 : : : n) f
for (i = n n ; 1 : : : j ) f
bi
(bi ; bi;1 )=(xi ; xi;j )
g
7.9 Exercises
279
Applying this program to the example presented at the end of x7.8.1 produces about the same result as lagrange().
The advantage of Newton's form is that it is very ecient and the coefcients ci i = 0 1 : : : n, can be used when later there are more interpolation points available. For example, if later one more point (xn+1 f (xn+1 ))
is given, then pn+1 (x) = pn (x) + cn+1 (x ; x0 ) (x ; xn ) gives cn+1 =
(f (xn+1 ) ; pn (xn+1 ))=((xn+1 ; x0 ) (xn+1 ; xn )).
7.9 Exercises
7.9.1. Test the class template for vectors Vcr<T> presented in x7.1 and
add more operations such as vector multiplication and addition, and
2-norm (see x6.3).
7.9.2. Test the class template for vectors Vcr<T> together with its specializations Vcr<complex<T> > and Vcr<complex<double> > presented in x7.1. Also add more operations such as vector multiplication
and addition, and 2-norm (see x6.3).
280
7. Templates
7.9.3. Turn the matrix class Mtx dened in x6.3 into a class template.
7.9.4. Traits can be used as an alternative to certain template specializations
or function overloading. For example, the maxnorm() function for
arrays of real or complex numbers can be dened as
template<class T> struct RTrait {
typedef T RType // for return type of maxnorm()
}
template<class T> struct RTrait< complex<T> > {
typedef T RType // return type for complex arrays
}
// a specialization of RTrait<T>
template<class T>
// definition of maxnorm
typename RTrait<T>::RType maxnorm(T u], int n) {
typename RTrait<T>::RType nm = 0
for (int i = 0 i < n i++) nm = max(nm, abs(ui]))
return nm
}
7.9.5.
7.9.6.
7.9.7.
7.9.8.
Here the trait class RTrait denes the return type for the function
maxnorm() but for complex vectors, the return value of a norm function must be a real number. The keyword typename must be used to
instruct the compiler that RTrait<T>:: RType is a type name. One
denition of the function handles real and complex vectors. Rewrite
the vector class Vcr<T > in x7.1 using this approach for the member function maxnorm(). This approach would be much better if
maxnorm() were a large function. Can it be applied to vector dot
products?
Apply the sort function template sort<T>() dened in x7.2 to sort
a vector of complex numbers in decreasing order according to their
absolute values, by overloading the operator < for complex numbers.
Apply the sort function template sort<T>() dened in x7.2.3 to sort
a vector of complex numbers in decreasing order according to their
absolute values, by using a specialization for the comparison template
less<T>().
Write a specialization for the template less<T>() so that the sort
function template sort<T>() in x7.2.3 will sort a vector of point2d
in decreasing order according to the y-coordinate.
Apply function template compare<T>(), dened in x7.2.4, to compare two vectors of char with case-sensitive and case-insensitive comparisons, respectively.
7.9 Exercises
281
x
5 7
6
6.6
f (x) 1 ;23 ;50 ;4
apply Lagrange and Newton forms of the interpolation polynomial to
approximate f (x) for x = 5:5 and x = 6:2.
282
7. Templates
Class Inheritance
A new class can be derived from an existing class. The new class, called
derived class, then inherits all members of the existing class, called base
class, and may provide additional data, functions, or other members. This
relationship is called inheritance. The derived class can then behave as a
subtype of the base class and an object of the derived class can be assigned
to a variable of the base class through pointers and references. This is the
essential part of what is commonly called object-oriented programming.
This chapter deals with various issues in class inheritance. The disadvantage
of object-oriented programming is that it may sometimes greatly a
ect runtime performance. The last section of the chapter presents techniques on
what can be done when performance is a big concern.
284
8. Class Inheritance
The function draw() is dened to simply print out the coordinate. Since
a 2D point has both x and y coordinates, it can be dened to inherit
the x coordinate from the class Pt and provide a new member for the y
coordinate:
class Pt2d: public Pt {
// class for 2D point
private:
// inherits x from Pt
double y
// y coordinate
public:
Pt2d(double a = 0, double b = 0): Pt(a), y(b) { }
void draw() const {
// draw x coordinate
Pt::draw()
// by Pt's draw()
cout << " " << y
// draw y coordinate
}
}
By putting :public Pt after the name of the class Pt2d when it is declared,
the new class Pt2d is dened to be a derived class from the base class Pt:
The derived class Pt2d is also called a subclass and the base class Pt a
superclass. Pt2d has members of class Pt in addition to its own members.
The derived class Pt2d is often said to inherit properties from its base
class Pt: This relationship is called inheritance: It can be represented
graphically by an arrow from the derived class to the base:
Pt
Pt2d
Now a Pt2d is also a Pt (a two-dimensional point can be regarded as
a one-dimensional point by just looking at the x coordinate), and Pt2d
(pointer to Pt2d) can be used as Pt : However, a Pt is not necessarily a
Pt2d and a Pt can not be used as a Pt2d : The keyword public in the
denition of Pt2d means that the derived class Pt2d has a public base Pt,
which in turn means that a Pt2d can be assigned to a variable of type
Pt by any function without an explicit type conversion. (Inheritance using
private and protected bases is discussed in x8.3.) The opposite conversion,
from a pointer to base class to a pointer to derived class, must be explicit.
For example,
void f(Pt p1, Pt2d p2) {
Pt* q1 = &p2
Pt2d* q2 = &p1
}
285
// x, y of p2 are printed
Pt2d* r2 = static_cast<Pt2d*>(&p1)
// explicit type conversion
r2->draw()
}
int main() {
Pt a(5)
Pt2d b(4, 9)
g(a, b)
During the second draw in calling g(), the 1D point a does not have a
y-coordinate and thus some garbage should be printed out. The rst draw
in g() on the 2D point b should print out both coordinates of b correctly.
An object of a derived class can be treated as an object of its base
class when manipulated through pointers and references. The opposite must
be through explicit type conversion using static cast (at compile-time) or
dynamic cast (at run-time see x8.5), although the result of such conversions can not be guaranteed in general. For example, the second cast in the
function g() above tries to convert a pointer to a base class to a pointer to
a derived class and may result in an object with an undened y coordinate.
The operator static cast converts between related types such as from one
pointer type to another or from an enumeration to an integral type. There
is another cast, called reinterpret cast, which converts between unrelated
types such as an integer to a pointer. These casts may not be portable, can
be even dangerous, but are sometimes necessary. They should be avoided
when possible.
Note that the function draw() may draw garbage for the y coordinate
in r2; >draw() above. A closer look reveals that r2 is cast from a onedimensional point. It would be nice if the system could detect the type of an
object and invoke the draw function Pt :: draw() for one-dimensional points
and invoke Pt2d :: draw() for two-dimensional points. In x8.1.5, virtual
functions are introduced so that for a 1D point, the function Pt :: draw() will
be invoked and for a 2D point, Pt2d :: draw() will be invoked automatically.
286
8. Class Inheritance
Member functions of a derived class can not access the private part of a
base class, although a derived class contains all members of the base class.
For example, the function Pt2d :: draw() can not be dened as
void Pt2d::draw() const { // incorrect
cout << x << " " << y // x is not accessible
}
The third denition is wrong since the construction of base class Pt is not
put in the initializer list.
A derived class constructor can specify initializers for its own members
and immediate bases only. The construction of an object of a derived class
starts from the base class, then the members, and then the derived class
itself. Its destruction is in the opposite order: rst the derived class itself,
287
then its members, and then the base class. Members and bases are constructed in order of declarations in the derived class and destructed in the
reverse order.
In the denition of the destructor of a derived class, only spaces allocated
in the derived class need be explicitly deallocated spaces allocated in the
base class are freed implicitly by the destructor of the base class.
8.1.3 Copying
Since the copy functions of Pt do not know anything about Pt2d only the
Pt part (x-coordinate) of a Pt2d object is copied and other parts are lost.
This is commonly called slicing: It can be avoided by passing pointers and
references see x8.1.5.
A derived class can be a base class of another derived class. For example,
class Pt3d: public Pt2d {
// point in 3D
private:
// inherits x,y from Pt2d
double z
// z coordinate
public:
Pt3d(double a = 0, double b = 0, double c = 0)
: Pt2d(a, b), z(c) { }
void draw() const {
// draw the point
Pt2d::draw()
// draw x,y by Pt2d's draw()
cout << " " << z
// draw z coordinate
}
}
288
8. Class Inheritance
// virtual fcn
The member Pt :: draw() is declared to be a virtual function. The declarations of Pt2d and Pt3d remain unchanged. The keyword virtual indicates
that draw() can act as an interface to the draw() function dened in this
class and the draw() functions dened in its derived classes Pt2d and Pt3d.
When draw() is called, the compiler will generate code that chooses the
right draw() for a given Pt object (Pt2d and Pt3d objects are also Pt
objects). For example, if a Pt object is actually a Pt3d object, the function
Pt3d :: draw() will be called automatically by the system.
A virtual function can be used even if no class is derived from the class,
and a derived class need not redene it if the base class version of the
virtual function works ne. Now functions can be dened to print out a set
of points (some may be 1D or 2D while others may be 3D):
void h(const vector<Pt*>& v) {
for (int i = 0 i < v.size() i++) {
vi]->draw()
cout << '\n'
}
}
int main() {
Pt a(5)
Pt2d b(4, 9)
// include <vector>
289
For library <vector> see x10.1.1. This will work even if the function h() was
written and compiled before the derived classes Pt2d and Pt3d were even
conceived of, and the points (1D, 2D, or 3D) in the argument of h() can
be generated dynamically at run-time by another function. Since draw() is
declared to be virtual in the base class, the system guarantees that in the
call vi];>draw() the function Pt :: draw() is invoked if vi] is a pointer to
Pt, Pt2d :: draw() is invoked if vi] is a pointer to Pt2d, and Pt3d :: draw() is
invoked if vi] is a pointer to Pt3d: This is a key aspect of class inheritance.
When used properly, it is a cornerstone of object-oriented design.
Getting the right correspondence between the function (e.g., among different versions of draw()) and the type (e.g., among Pt, Pt2d, or Pt3d)
of an object is called run-time polymorphism: A type with virtual functions is called a polymorphic type: Run-time polymorphism is also called
dynamic binding: To get polymorphic behavior, member functions called
must be virtual and objects must be manipulated through pointers or references. When manipulating objects directly (rather than through pointers
or references), their exact types must be known at compile-time, for which
run-time polymorphism is not needed and templates may be used. See x8.6
for examples that can be implemented using either templates or virtual
functions. In contrast, what templates provide is often called compile-time
polymorphism, or static polymorphism.
When run-time polymorphism is not needed, the scope resolution operator :: should be used, as in Pt :: draw() and Pt2d :: draw(): When a virtual
function is inline, function calls that do not need run-time polymorphism
can be specied by using the scope resolution operator :: so that inline substitution may be made. This is reected in the denitions of Pt2d :: draw()
and Pt3d :: draw():
Pointers to class members were discussed in x5.8. A function invoked
through a pointer to a member function can be a virtual function, and
polymorphic behavior can be achieved through pointers to virtual member
functions.
290
8. Class Inheritance
class and overriding it in derived classes. For example, consider the following
inheritance without a virtual destructor.
class B {
double* pd
public:
B() {
pd = new double 20]
cout << "20 doubles allocated\n"
}
~B() {
delete] pd
cout << "20 doubles deleted\n"
}
}
class D: public B {
int* pi
public:
D(): B() {
pi = new int 1000]
cout << "1000 ints allocated\n"
}
~D() {
delete] pi
cout << "1000 ints deleted\n"
}
}
// constructor of B
// destructor of B
// derive D from B
// constructor of D
// destructor of D
Proper cleanup is not achieved here. The reason is that the new operator
constructed an object of type D (allocated 20 doubles and 1000 ints when
D's constructor was called), but the delete operator cleaned up an object of
type B pointed to by p (freed 20 doubles when B's destructor was implicitly
291
called). In other words, run-time polymorphism did not apply to the destructor of class D. If it were applied, the system would have detected the
type of the object pointed to by p and called the destructor of the correct
type (class D).
This problem can be easily solved by declaring the destructor of the base
class B to be virtual :
class B {
double* pd
public:
B() {
pd = new double 20]
cout << "20 doubles allocated\n"
}
virtual ~B() {
// a virtual destructor
delete] pd
cout << "20 doubles deleted\n"
}
}
The denition of the derived class D is unchanged. Now the main program
above produces the following desired output.
20 doubles allocated
1000 ints allocated
1000 ints deleted
20 doubles deleted
292
8. Class Inheritance
virtual
virtual
virtual
virtual
}
void draw() = 0
void rotate(int i) = 0
bool is_closed() = 0
~Shape() { }
//
//
//
//
The member functions rotate() draw(), and is closed() are pure virtual
functions recognized by the initializer \= 0". Note the destructor~Shape()
is not a pure virtual function since it is dened, although it does nothing.
This is an example of a function that does nothing but is useful and necessary. The class Shape is meant to be a base class from which other classes
such as Triangle and Circle can be derived. Its member functions rotate()
draw() and is closed() can not be dened since there is not enough information to do so.
A class with one or more pure virtual functions is called an abstract class:
No objects of an abstract class can be created. For example, it is illegal to
dene:
Shape s
An abstract class can only be used as an interface and as a base for derived
classes. For example, a special shape: Circle can be derived from it as
class Circle: public Shape {
// derived class for circles
private:
Pt2d center
// center of circle
double radius
// radius of circle
public:
Circle(Pt2d, double)
// constructor
void rotate(int) { }
// override Shape::rotate()
void draw()
// override Shape::draw()
bool is_closed() { return true }
// a circle is closed
}
The member functions of Circle can be dened since there is enough information to do so. These functions override the corresponding functions
of Shape: The function Circle :: rotate() does nothing since a circle does
not change when being rotated, and Circle :: is closed() returns true since
a circle is a closed curve. Circle is not an abstract class since it does not
contain pure virtual functions, and consequently objects of Circle can be
created.
A pure virtual function that is not dened in a derived class remains a
pure virtual function. Such a derived class is also an abstract class. For
example,
class Polygon: public Shape {
// abstract class
public:
bool is_closed() { return true }
293
// override Shape::is_closed()
// but draw() & rotate() still remain undefined.
}
class Triangle: public Polygon { // not abstract any more
private:
Pt2d* vertices
public:
Triangle(Pt2d*)
~Triangle() { delete] vertices }
void rotate(int)
// override Shape::rotate()
void draw()
// override Shape::draw()
}
Class Polygon remains an abstract class since it has pure virtual functions draw() and rotate(), which are inherited from its base class Shape
but have not been dened. Thus objects of Polygon can not be created.
However, Triangle is no longer an abstract class since it does not contain
any pure virtual functions (denitions of its members rotate() and draw()
are omitted here) and objects of Triangle can be created.
Note that a virtual destructor is dened for the base class Shape. This
will ensure proper cleanup for the derived class Triangle which frees dynamic memory space in its destructor. In general, a class with a virtual
function should have a virtual destructor, although other base classes may
also need one see the example in x8.1.6.
Below is a design problem for iterative numeric methods on solving linear
systems of algebraic equations Ax = b where A is a square matrix, b is the
right-hand side vector, and x is the unknown vector. In x6.6 the conjugate
gradient (CG) method is implemented for a Hermitian and positive denite
matrix A and in x11.3 the generalized minimum residual (GMRES) method
is introduced for any nonsingular matrix A: These two iterative methods
require only matrix-vector multiplication and some vector operations in
order to solve the linear system Ax = b: Details of CG and GMRES are
not needed here. Instead, a class hierarchy is designed to apply CG and
GMRES to full, band, and sparse matrices A: In some applications, most
entries of the matrix A are not zero and all entries of A are stored. Such
a matrix storage format is called a full matrix. In some other applications,
the entries of A are zero outside a band along the main diagonal of A: Only
entries within the band (zero or nonzero) may be stored to save memory
and such a format is called a band matrix. Yet in other applications most
entries of A are zero and only nonzero entries are stored to further save
memory such a format is called a sparse matrix. Three di
erent classes
need be dened for these three matrix storage formats, of which the full
matrix format is given in x6.3 where every entry of a matrix is stored.
294
8. Class Inheritance
Details of these matrix storage formats are not needed here, although they
can be found in x11.1.
The objective of such a design is that the CG and GMRES methods
are dened only once, but are good for all three matrix storage formats,
instead of providing one denition for each storage format. This should
also be extendible possibly by other users later to other matrix formats
such as symmetric sparse, band, and full matrices (only half of the entries
need be stored for symmetric matrices to save memory). Since CG and
GMRES depend on a matrix-vector product, which must be done di
erently
for each matrix storage format, the correct matrix-vector multiplication
function has to be called for a particular matrix format. To provide only
one denition for CG and GMRES, they can be dened for a base class
and inherited for derived classes representing di
erent matrix formats. To
ensure the correct binding of matrix-vector multiplication to each matrix
storage format, a virtual function can be utilized. To be more specic,
dene a base class
class AbsMatrix {
public:
virtual ~AbsMatrix() { }
// base class
// a virtual destructor
295
The matrix-vector multiply operator can be dened (but omitted here) for
each of the derived classes and thus CG() and GMRES () can be invoked
for objects of FullMatrix, BandMatrix, and SparseMatrix: For example,
void f(FullMatrix&
fm.CG()
bm.CG()
sm.CG()
}
fm, BandMatrix&
// call CG() on
// call CG() on
// call CG() on
296
8. Class Inheritance
// a private member
// a protected member
// a public member
// f and b.f are accessible
{
5
3.14
2.71
//
//
//
//
by default constructor
error, can not access bb.i
error, can not access bb.f
bb.d is accessible freely
The speciers protected and private mean the same when inheritance is
not involved.
Protected members of a class are designed for use by derived classes and
are not intended for general use. They provide another layer of information
hiding, similar to private members. The protected part of a class usually
provides operations for use in the derived classes, and normally does not
contain data members since it can be more easily accessed or abused than
297
the private part. For this reason the data member x in the class Pt in x8.1
is better kept private than protected. Despite this, the derived classes Pt2d
and Pt3d can still print out the value of x.
B { /* ... */ }
X: public B { /* ... */ }
Y: protected B { /* ... */ }
Z: private B { /* ... */ }
//
//
//
//
a
B
B
B
class
is a public base
is a protected base
is a private base
Public derivation makes the derived class a subtype of its base class and
is the most common form of derivation. Protected and private derivations
are used to represent implementation details. No matter what form the
derivation is, a derived class contains all the members of a base class (some
of which may have been overridden), although members inherited from the
private part of the base class are not directly accessible even by members
and friends of the derived class (they are called invisible members of the
derived class in x8.1.1). The form of derivation controls access to the derived
class's members inherited from the base class and controls conversion of
pointers and references from the derived class to the base class. They are
dened as follows. Suppose that class D is derived from class B:
If B is a private base, its public and protected members become
private members of D: Only members and friends of D can convert
a D to a B :
If B is a protected base, its public and protected members become
protected members of D: Only members and friends of D and members and friends of classes derived from D can convert a D to a
B:
If B is a public base, its public members become public members of
D and its protected members become protected members of D: Any
function can convert a D to a B : In this case, D is called a subtype
of B .
The following example illustrates the use of protected members and public derivation.
class B {
private:
int i
protected:
float f
298
8. Class Inheritance
public:
double d
void g1(B b) { f = b.f }
}
class X: public B {
protected:
short s
public:
void g2(X b) { f = b.f }
}
Since X has a public base B it has three public members: d and g1()
(inherited from B ), and g2() (its own), two protected members: f (inherited
from B ) and s (its own), but no private members. X also inherits a member
i from base B but this member is invisible in X: The protected member
f of class B and member f of the object b of type B are accessible in the
member function B :: g1(B b): Similarly, the protected (inherited from base
class) member f of class X and member f of the object b of type X are
accessible in the member function X :: g2(X b):
However, it is illegal to dene a member function g3() of X this way:
class X: public B {
protected:
short s
public:
void g2(X b) { f = b.f }
void g3(B b) { f = b.f }
}
// public derivation
derived class are accessible by members and friends of the derived class.
However, the derived class can not access protected members (e.g., b:f in
the denition of X :: g3(B )) of an object of the base class, just as it can not
access protected members of objects of any other class.
No matter what type of inheritance it is, a private member of a base class
can not be used in derived classes to achieve a strong sense of information
hiding, since otherwise a private member of any class could be accessed
and possibly abused easily by dening a derived class from it.
299
// protected derivation
// private derivation
// this d hides B::d
The derivation specier for a base class can be left out. In this case, the
base defaults to a private base for a class and a public base for a struct:
class
XX: B { /* ... */ }
// B is a private base
300
8. Class Inheritance
// B is a public base
//
//
//
//
The derived class Circle in Triangle inherits properties from both Circle
and Triangle: It can be used as
void f(Circle_in_Triangle& ct) {
ct.dilate(5.0)
// call Circle::dilate()
ct.refine()
// call Triangle::refine()
ct.draw()
// call Circle_in_Triangle::draw()
301
}
double curvature(Circle*)
vector<double> angles(Triangle*)
// curvature of a curve
// angles of a triangle
Since public derivation is used for both base classes, any function can convert a Circle in Triangle to Circle or Triangle:
When two base classes have members with the same name, they can be
resolved by using the scope resolution operator. For example, both Circle
and Triangle have a function named area(): They must be referred to with
the class names:
void h(Circle_in_Triangle* pct) {
double ac = pct->Circle::area()
double at = pct->Triangle::area()
double aa = pct->area()
pct->draw()
}
//
//
//
//
OK
OK
ambiguous, error
OK
302
8. Class Inheritance
using A::g
using B::g
char g(char)
C g(C)
}
void h(C& c) {
c.g(c)
c.g(1)
c.g(1L)
c.g('E')
c.g(2.0)
}
//
//
//
//
//
C::g(C) is called
A::g(int) is called
B::g(long) is called
C::g(char) is called
A::g(float) is called
With the possibility of derivation from two bases, a class can be a base
twice. For example, if both Circle and Triangle are derived from a class
Shape then the base class Shape will be inherited twice in the class
Circle in Triangle:
class Shape {
protected:
int color
public:
// ...
virtual void draw() = 0
}
class Circle: public Shape {
// ...
public:
void draw()
}
class Triangle: public Shape {
// ...
public:
void draw()
}
// color of shape
//
303
and Triangle::draw()
Shape
Shape
Circle
Triangle
@
I
;
@@;;
Circle in Triangle
To refer to members of a replicated base class, the scope resolution operator must be used. For example,
void Circle_in_Triangle::draw() {
int cc = Circle::color
// or Circle::Shape::color
int ct = Triangle::color
// or Triangle::Shape::color
// ...
Circle::draw()
Triangle::draw()
}
Often a base class need not be replicated. That is, only one copy of a
replicated class need be inherited for a derived class object. This can be
done by specifying the base to be virtual: Every virtual base of a derived
class is represented by the same (shared) object. For example, if the circle
and triangle in Circle in Triangle must have the same color, then only
one copy of Shape is needed for storing the color information. The base
class Shape then should be declared to be virtual :
class Shape {
int color
public:
// ...
virtual void draw() = 0
}
// color of shape
304
8. Class Inheritance
Shape
;; @I@
;
@
Circle
Triangle
@I
@ ;;
@;
Circle in Triangle
Compare this diagram with the inheritance graph in x8.4.2 to see the
di
erence between ordinary inheritance and virtual inheritance. In an inheritance graph, every base class that is specied to be virtual will be
represented by a single object of that class. The language ensures that a
constructor of a virtual base class is called exactly once.
305
The member pt2d; >d is accessible publicly since D12 has a public base
D2 and d is a public member of D2. It would be inaccessible if both D1
and D2 were protected bases. However, ambiguities might arise when a
single entity was accessible through more than one path. Again, the scope
resolution operator can be used to resolve such ambiguities. For example,
class X1: public B { /* ... */ }
class X2: public B { /* ... */ }
class X12: protected X1, private X2 {
void f()
}
void X12::f() {
X12* p = new X12
// ... assign some value to *p
double i = p->d
// illegal, ambiguous
// X12::X1::B::d or X12::X2::B::d?
double j = p->X1::B::d
double k = p->X2::B::d
// OK
// OK
double n = p->sdm
// OK
// only one B::sdm in an X12 object
There are two copies of member d in an object of X 12, but there is only
one static member sdm in X 12:
306
8. Class Inheritance
system to examine the object to reveal its type. This section introduces two
mechanisms that can be used to recover the run-time type of an object.
The dynamic cast mechanism can be used to handle cases in which the
correctness of a conversion can not be determined by the compiler. Suppose
p is a pointer. Then
dynamic_cast<T*>(p)
// OK
// OK
B* p1 =p
// error, B is protected base
B* p2 =dynamic_cast<C*>(p) // OK, but 0 is assigned to p2
}
void g(B* pb) {
// assume A, B have virtual functions
if (A* pa = dynamic_cast<A*>(pb)) {
// if pb points to an object of type A, do something
} else {
// if pb does not point to an A object, do something else
}
}
307
// rb refers to an A object?
}
int main() {
try{
A* pa = new A
B* pb = new B
h(pa, *pa)
h(pb, *pb)
} catch (bad_cast) {
// exception handler
// dynamic_cast<A&> failed, do something.
}
}
308
8. Class Inheritance
The dynamic cast operator is enough for most problems that need to know
the type of an object at run-time. However, situations occasionally arise
when the exact type of an object needs to be known. The typeid operator
yields an object representing the type of its operand it returns a reference
to a standard library class called type info dened in header <typeinfo>.
The operator typeid() can take a type name or an expression as its operand
and returns a reference to a type info object representing the type name or
the type of the object denoted by the expression. If the value of a pointer
or a reference operand is 0, then typeid() throws a bad typeid exception.
For example,
void f(Circle* p, Triangle& r, Circle_in_Triangle& ct) {
typeid(r)
// type of object referred to by r
typeid(*p)
// type of object pointed to by p
if (typeid(ct) == typeid(Triangle) { /* ... */ }
if (typeid(r) != typeid(*p) { /* ... */ }
cout << typeid(r).name()
There can be more than one type info object for each type in a system.
Thus comparisons for equality and inequality should be applied to type info
objects instead of their pointers or references.
309
Using run-time type information (dynamic cast or typeid) involves overhead since the system examines the type of an object. It should and can
be avoided in many cases in which virtual functions suce. Use virtual
functions rather than dynamic cast or typeid if possible since virtual functions normally introduce less run-time overhead. Even a virtual function
can cause a lot of performance penalty when it is small (i.e., it does not
perform a lot of operations) and called frequently (e.g., inside a large loop).
When a virtual function contains a lot of code (i.e., performs a lot of computation) or is not called frequently, the overhead of virtual function dispatch
will be insignicant compared to the overall computation.
In large-scale computations, static (compile-time) checking may be preferred where applicable, since it is safer and imposes less overhead than
run-time polymorphism. On one hand, run-time polymorphism is the cornerstone of object-oriented programming. On the other hand, too much
run-time type checking can also be a disadvantage due to the run-time
overhead it imposes, especially in performance-sensitive computations. In
x8.6, some techniques are described to replace certain virtual functions by
using templates.
310
8. Class Inheritance
class Vtr {
/* a vector class */
}
311
}
}
void f(const Vtr& v) {
FullMatrix A
A.CG(v)
// Calling CG() on full matrix
BandMatrix B
B.CG(v)
SparseMatrix S
S.CG(v)
Although the code above compiles and runs, it has been greatly simplied and full denitions of CG() and matrix-vector multiply functions are
not given. The function CG() is dened for the base class Matrix which
depends on the matrix-vector multiply operator : The operator is syntactically dened for the base Matrix, but actually refers to the operator
belonging to the template parameter T: When T is instantiated by, for
example, FullMatrix in the denition of the class FullMatrix this operator now is the FullMatrix-vector multiply operator, which can be fully
dened according to its matrix structure.
In this approach, the type of an object is known at compile-time and thus
there is no need for virtual function dispatch. One version of the function
CG() can be called for di
erent classes, which is achieved without using
virtual functions.
A simple but more concrete and complete example using this technique
is now given. First dene a function sum() for a base template matrix that
adds all entries of a matrix. Then dene a derived class for full matrices
that stores all entries of a matrix and another derived class for symmetric
matrices that stores only the lower triangular part of a matrix to save
memory. The goal is to give only one version of sum() that can be called
for full and symmetric matrices, without using virtual functions. This can
be achieved as
template<class T> class Mtx {
// base matrix
private:
// refer to derived class
T& ReferToDerived() {
return static_cast<T&>(*this)
}
// entry() uses features of derived class
double& entry(int i, int j) {
return ReferToDerived()(i,j)
312
8. Class Inheritance
}
protected:
int dimn
// dimension of matrix
public:
// define common functionality in base class that can
// be called on derived classes
double sum() {
// sum all entries
double d = 0
for (int i = 0 i < dimn i++)
for (int j = 0 j < dimn j++) d += entry(i,j)
return d
}
}
class FullMtx: public Mtx<FullMtx> {
double** mx
public:
FullMtx(int n) {
dimn = n
mx = new double* dimn]
for (int i=0 i<dimn i++) mxi] = new double dimn]
for (int i=0 i<dimn i++)
for (int j=0 j<dimn j++)
mxi]j] = 0
// initialization
}
double& operator()(int i, int j) { return mxi]j] }
}
class SymmetricMtx: public Mtx<SymmetricMtx> {
// store only lower triangular part to save memory
double** mx
public:
SymmetricMtx(int n) {
dimn = n
mx = new double* dimn]
for (int i=0 i<dimn i++) mxi] = new double i+1]
for (int i=0 i<dimn i++)
for (int j = 0 j <= i j++)
mxi]j] = 0
// initialization
}
double& operator()(int i, int j) {
if (i >= j ) return mxi]j]
else return mxj]i]
// due to symmetry
}
}
313
void g() {
FullMtx A(2)
A(0,0) = 5 A(0,1) = 3 A(1,0) = 3 A(1,1) = 6
cout << "sum of full matrix A = " << A.sum() << '\n'
SymmetricMtx S(2)
// just assign lower triangular part
S(0,0) = 5 S(1,0) = 3 S(1,1) = 6
cout << "sum of symmetric matrix S = " << S.sum() << '\n'
}
/* a vector class */
}
template<class M>
// define CG as a global function
void CG(const M& m, const Vtr& v) {
// define CG here for matrix m and vector v
Vtr w = m*v
// call matrix vector multiply
cout << "calling CG\n"
}
class FullMatrix {
//
encapsulate storage information for full matrix
public:
Vtr operator*(const Vtr& v) const {
// define (full) matrix vector multiply here
cout << "calling full matrix vector multiply\n"
}
}
class BandMatrix {
//
encapsulate storage information for band matrix
public:
Vtr operator*(const Vtr& v) const {
// define (band) matrix vector multiply here
cout << "calling band matrix vector multiply\n"
}
314
8. Class Inheritance
}
class SparseMatrix {
//
encapsulate storage information for sparse matrix
public:
Vtr operator*(const Vtr& v) const {
// define (sparse) matrix vector multiply here
cout << "calling sparse matrix vector multiply\n"
}
}
void ff(const Vtr& v) {
FullMatrix A
CG(A, v)
// Calling CG() on full matrix
BandMatrix B
CG(B, v)
SparseMatrix S
CG(S, v)
315
316
8. Class Inheritance
class SymmetricMatrix to this class hierarchy, the code for the base class
AbsMatrix need not be recompiled. However, using the techniques here
without virtual functions, the complete type of all classes must be known at
compile-time. Thus, in this aspect, there may be some design disadvantage
to static polymorphism. Besides, not all virtual functions can be replaced
by templates.
8.7 Exercises
8.7.1. Implement the class hierarchy for points in one, two, and three dimensions, as outlined in x8.1. Add more functions such as moving a point,
nding the equation of the line passing through two points, and evaluating the distance between two points. Create a vector of (pointers
to) points and print them out using run-time polymorphism.
8.7.2. Implement the class hierarchy for points in one, two, and three dimensions, as outlined in x8.1, using pointers for their data members.
For example, declare a pointer to double for the x-coordinate in Pt
and for the y-coordinate in Pt2d: Consequently, user-dened copy
constructor and copy assignment need be dened for them, and a
virtual destructor is needed for the base class Pt:
8.7.3. When should a class have a virtual destructor?
8.7.4. Rewrite the base class B and derived class D in x8.1.6 so that B will
allocate space for d doubles in its constructor and D for i integers,
where d and i must be passed as arguments to the constructors.
8.7.5. Summarize all C++ language features that support polymorphism
(in compile- and run-times), and discuss their advantages and disadvantages.
8.7.6. Dene an abstract class called Shape containing a pure virtual function distance() that takes a two-dimensional point as argument and
is meant to nd the distance between the point and Shape. Derive
three classes for Triangle, Circle and Rectangle respectively, and
override the virtual function distance() in each of the derived classes.
The distance between a point and a gure is dened to be the minimum distance between the given point and any point in the gure.
Create a vector of pointers to Shape that actually point to objects of
Triangle, Circle or Rectangle and create a vector of points. Call
distance() to nd the distance between each point to each of the
Shape objects.
8.7.7. Test the class hierarchy for full, band, and sparse matrices as outlined
in x8.2 in which one denition of CG() and GMRES () is given for
8.7 Exercises
317
= (x(x ;
;
x0 )(y1 ; y0 ) ; (x1 ; x0 )(y2 ; y0 )
2
Now check to see if points p and p2 are on the same side of p0p1 by
checking if
> 0: Similarly, check to see if p and p1 are on the same
side of p0 p2 and if p and p0 are on the same side of p1 p2: These
three conditions are true if and only if p is inside the triangle.
8.7.9. Generalize Exercise 8.7.8 to three-dimensional Euclidean spaces with
three derived classes for tetrahedron, ball and cube, respectively.
The idea of checking to see if a point is inside a triangle given in
Exercise 8.7.8 applies to tetrahedra. Such techniques are often used
in computer-aided geometric design.
8.7.10. Dene an abstract class Shape, derive three classes Triangle, Circle
and Rectangle from it, and dene a function
bool intersect(Shape* s, Shape* t)
318
8. Class Inheritance
that determines if the two shapes s and t overlap. To achieve this, suitable virtual functions may need be declared for the base class Shape
and overridden in derived classes Triangle, Circle and Rectangle:
Hint: this is a so-called double dispatch problem. An answer to this
exercise can be found in Van98].
8.7.11. Run the codes in x8.6 to see if they work as expected. Rewrite the
codes into more general templates with a template parameter for the
type of the entries of the matrix, so that they can handle real and
complex matrices in single, double, and other precisions.