C Plus Plus
C Plus Plus
Ronald Kriemann
MPI MIS Leipzig
2008
MIS
C++ for Scientic Computing 1/316
Chapters
1 Introduction
2 Variables and Datatypes
3 Arithmetic Operators
4 Type Casting
5 Blocks and Scope
6 Control Structures
7 Functions
8 Arrays and Dynamic Memory
9 Advanced Datatypes
10 Modules and Namespaces
11 Classes
12 Generic Programming
13 Error Handling
14 Standard Template Library
15 Class Inheritance
16 Appendix
C++ for Scientic Computing 2/316
Introduction
C++ for Scientic Computing 3/316
Why C++?
Why not Matlab?
Matlab is a high level language, e.g. provides many
functions/algorithms allowing rapid developement.
But Matlab is limited to dense and (to some degree) sparse
matrices, therefore not exible enough, especially for large
problems.
Why not Fortran?
Fortran is one of the main programming language in many
areas including numerics.
Many excellent software libraries are written in Fortran, e.g.
LAPACK.
Fortran 77 quite dated in terms of language features.
Recent updates of Fortran (90, 95, 2000) modernised the
language, but still somewhat dated.
C++ for Scientic Computing 4/316
Why C++?
So, Why C++ (and not C) ?
C is a subset of C++.
C++ provides many features which make programming easier.
C++ can be as fast as C (and sometimes faster).
C++ (like C) can use all software libraries written in Fortran or
C.
Many new software libraries are written in C++.
Why not C++?
C++ can be complicated as opposed to C.
If you do not follow strict programming rules, you can make
many errors (unlike Matlab, or Fortran).
C++ for Scientic Computing 5/316
Hello World
Like every programming course, we start with something simple:
#include <iostream>
using namespace std;
int
main ( int argc, char ** argv )
{
// print welcome message
cout << "Hello World" << endl;
return 0;
}
Even this small example contains:
modules and namespaces,
functions and blocks and
variables and datatypes
Remark
Comments in C++ begin with a // and span the rest of
the line.
C++ for Scientic Computing 6/316
Hello World
C++ is a compiler based language, i.e. one has to translate the
source code of the program into a machine executable format
using another program, called the compiler.
Source code les, or just source les, typically have a lename
sux like .cc, .C or .cpp.
There are many dierent C++ compilers available even for one
operating system. On Linux, the GNU Compiler Collection
provides the g++ compiler. Alternatively, Intel oers another
compiler named icpc.
As an example, to compile the source le hello.cc for Hello
World into an executable binary using the GCC compiler, youll
have to enter
g++ -o hello hello.cc
and afterwards run the program via ./hello.
C++ for Scientic Computing 7/316
Variables and Datatypes
C++ for Scientic Computing 8/316
Variables and Datatypes
In C++, all variables have to be of some specic datatype, which is,
once dened, xed and can not be changed, e.g. unlike in Matlab.
Integer Datatypes
characters :
char: c, /, \n, \\, (with Unicode)
also numbers: from 2
7
. . . 2
7
1, e.g. 0, 128, 127
signed integers :
short: from 2
15
. . . 2
15
1, e.g. 0, 32768, 1000
int: from 2
31
. . . 2
31
1, e.g. 0, 100, 2147483647
long: from 2
31
. . . 2
31
1, or 2
63
. . . 2
63
1
unsigned integers :
unsigned short: from 0 . . . 2
16
1
unsigned int or unsigned: from 0 . . . 2
32
1
unsigned long: from 0 . . . 2
32
1, or 0 . . . 2
64
1
C++ for Scientic Computing 9/316
Variables and Datatypes
Integer Datatypes: Overow and Underow
When doing arithmetic with integer types, the range of the types
has to be considered. If the result is bigger than the maximum
value, the result becomes negative, e.g. using short:
32760 + 100 = 32676
Here, an overow occured.
Similar behaviour can be observed if the result is less that the
minimum (underow):
32760 100 = 32676
C++ for Scientic Computing 10/316
Variables and Datatypes
Floating Point Datatypes
Floating point numbers x are represented as
x = s m 2
e
with the sign s, the mantissa m and the exponent e.
In C++, like in many other languages, we have two oating point
types
float: single precision, 23 bits for mantissa, 8 bits for
exponent, x [3 10
38
. . . 10
38
, 0, 10
38
. . . , 3 10
38
],
double: double precision, 52 bits for mantissa, 11 bits for
exponent, x [2 10
308
. . . 10
308
, 0, 10
308
. . . , 2 10
308
].
Floating point numbers are entered with a dot:
4.0 (double) or 4.0f (float) instead of just 4 (int)
Exponent is dened using scientic notation via E or e:
4.25 10
4
is 4.25E-4 or 4.25e-4
C++ for Scientic Computing 11/316
Variables and Datatypes
Floating Point Datatypes: Rounding
Since number of bits for representing numbers is limited, real
numbers are rounded, e.g. :
float: = 3.141592741,
double: = 3.141592653589793116
This might also lead to wrong results:
3.1415926 5.36 10
8
but in single precision one obtains
3.1415926 = 1.41 10
7
.
This eect is also known as cancellation
C++ for Scientic Computing 12/316
Variables and Datatypes
Floating Point Datatypes: Absorption
When adding two numbers with a large dierence in the exponent,
the result might be equal to the larger of the two addend, e.g. in
single precision for x :
x + 1 10
8
= x
For any oating point type, the smallest number , such that
1 + 6= 1 is known as the machine precision:
for float: 1.2 10
7
,
for double: 2.2 10
16
.
Coding Principle No. 1
Always check if the range and the precision of the
oating point type is enough for your application. If in
doubt: use double precision.
C++ for Scientic Computing 13/316
Variables and Datatypes
Boolean Datatype
Integer and oating point types are also available in C. In C++ one
also has a boolean type bool, which can be either true or false.
Missing Types
C++ does not have datatypes for strings or complex numbers.
Those have to be implemented by special ways.
C++ for Scientic Computing 14/316
Variables and Datatypes
Variables
Variables can be declared at (almost) any position in the source
le. The declaration follows the scheme:
htypenamei hvariablenamei;
or
htypenamei hvariablename1i, hvariablename2i, ...;
Remark
Every statement in C++ is nished with a semicolon.
A name for a variable can contain any alphanumerical character
plus and must not begin with a number. Also, they can not be
identical to a reserved name used by C++. Variable names are case
sensitive.
C++ for Scientic Computing 15/316
Variables and Datatypes
Variables
Examples:
int n;
int i, j, k;
float pi, Pi, PI;
double 1_over_pi; // ERROR
double _1_over_pi; // Ok
Coding Principle No. 2
Give your variables a reasonable name.
Variables: Initial Values
When declaring a variable, one can provide an initial value:
int n = 10;
int i, j, k;
float pi = 3.1415926;
double _1_minus_pi = 1.0 - pi;
double max = 1.8e308;
C++ for Scientic Computing 16/316
Variables and Datatypes
Variables: Initial Values
Coding Principle No. 3
Resource allocation is initialisation (RAII):
Whenever a resource, e.g. variable, is allocated/declared,
it should be initialised with some reasonable value.
Otherwise, strange things might happen, e.g. what is wrong with:
int n = 10;
int i, j, k;
float pi;
double _1_minus_pi = 1.0 - pi;
double max = 1.8e308;
The value of 1 minus pi is not dened, but only some compilers
will give you a warning about that.
C++ for Scientic Computing 17/316
Variables and Datatypes
Variables: Usage
Variables can only be used after they have been declared:
const double _1_minus_pi = 1.0 - pi; // ERROR: "pi" is unknown
const float pi = 3.1415926;
const double _2_plus_pi = 2.0 + pi; // Ok: "pi" is defined
Type Modiers
Each type can be modied with one of the following modiers:
const: the value of the variable is xed,
static: the variable is declared only once in the whole
program (well come back to that later),
The modiers can be combined:
const int n = 10;
int i, j, k;
const float pi = 3.1415926;
const double _1_minus_pi = 1.0 - pi;
stati c const double max = 1.8e308;
C++ for Scientic Computing 18/316
Variables and Datatypes
Type Modiers
Coding Principle No. 4
Use const as much as possible.
You avoid errors, e.g. by mistakenly changing the value and the
compiler can optimise more, e.g. replace every occurence by the
actual value.
C++ for Scientic Computing 19/316
Variables and Datatypes
Pointers and References
A pointer is a special datatype derived from a base type, where the
variable contains the memory position of another variable. The
syntax is:
hbase typei * hpointer namei;
The memory position, i.e. the address, of a standard variable is
obtained by the address operator &, e.g.
& hvariablei
To obtain the value of the memory position, the pointer directs to,
the (unary) dereference operator *, e.g.
* hvariablei
is available (not to be mistaken with the binary multiplication
operator!).
C++ for Scientic Computing 20/316
Variables and Datatypes
Pointers and References
Example for the usage of pointers:
int n = 5;
int * p = & n;
int m1, m2;
m1 = n + 3;
m2 = *p + 3; // m2 equal to m1
*p = 6; // this changes the value of n to 6!
m2 = n + 4; // evaluates to 10!
The value of a pointer, e.g. the address, can be assigned to
another pointer:
int * q = p; // q now points to the same position
// as p, hence, to n
n = 2;
m1 = *p + *q // is equivalent to n+n
C++ for Scientic Computing 21/316
Variables and Datatypes
Pointers and References
A special pointer value, NULL, exists, for the case, that no
standard variable is available to point to:
int * p = NULL;
Dereferencing a NULL pointer is illegal and leads to a program
abort.
Coding Principle No. 5
Always initialise a pointer with the address of an existing
variable or with NULL.
Remark
RAII is even more important when working with pointers
than with standard variables, since undened pointers
almost always lead to hard to nd errors.
C++ for Scientic Computing 22/316
Variables and Datatypes
Pointers and References
A reference is a special form of a pointer, which can only be
initialised with the address of an existing variable. The syntax is:
hbase typei & hpointer namei;
One does not need to dereference references:
int n = 5;
int & r = n;
int m;
m = r + 3; // m == n + 3
r = m; // r still points to n and n == m
m = 0; // r and n are unchanged
And you can not change the address where a reference points to:
int & s = m;
r = s; // r still points to n and n == m (== 0)
C++ for Scientic Computing 23/316
Variables and Datatypes
Pointers and References
Pointers and references can also be combined with const in various
forms:
int * pn1; // non-const pointer to non-const var.
const int * pn2; // non-const pointer to const var.
int * const pn3; // const pointer to non-const var.
const int * const pn4; // const pointer to const var.
If the pointer is constant, the address it is directing to can not be
changed. If the variable is constant, the value of it can not be
modied:
int n = 0;
const int m = 1;
int * pn1 = & n; // Ok
const int * pn2 = & n; // ERROR
int * const pn3 = & n; // Ok
C++ for Scientic Computing 24/316
Arithmetic Operators
C++ for Scientic Computing 25/316
Arithmetic and Assignment Operators
Arithmetic
For integer and oating point types:
x + y;
x - y;
x * y;
x / y;
For integer types, the modulus operator:
x % y;
Remark
No operator for power, like ^ in Matlab.
C++ for Scientic Computing 26/316
Arithmetic and Assignment Operators
Assignment
Standard assignment (as with initial values)
x = y;
Assignment can be combined with arithmetic, so
x = x + y; x = x - y;
x = x * y; x = x / y;
x = x % y; // for integers
is the same as
x += y; x -= y;
x *= y; x /= y;
x %= y; // for integers
C++ for Scientic Computing 27/316
Arithmetic and Assignment Operators
Increment and Decrement
In C++ (like in C) exist special versions for variable
increment/decrement:
int n = 0;
++n; // same as n = n + 1 or n += 1
n++;
--n; // same as n = n - 1 or n -= 1
n--;
Dierence between preincrement and postincrement, e.g.
int n = 5, m = 5 * n++;
results in n = 6 and m = 25, whereas
int n = 5, m = 5 * ++n;
results in n = 6 and m = 30.
C++ for Scientic Computing 28/316
Arithmetic and Assignment Operators
Examples
int n1 = 3, n2 = 4;
int n3;
n3 = 2 * n1 - n2 + n1 / 2;
n2 *= 6;
n1 -= 8;
const double approx_pi = 355.0 / 113.0;
double f1 = approx_pi / 2.0;
double f2;
f2 = approx_pi * approx_pi + 10.0 * f1;
f1 /= 5.0;
C++ for Scientic Computing 29/316
Arithmetic and Assignment Operators
Division by Zero and other undened Operations
With oating point types:
x/0.0 = INF
for x 6= 0.0. INF is a special oating point number for innity.
For x = 0.0:
x/0.0 = NAN
NAN (not-a-number) is another special oating point number for
invalid or not dened results, e.g. square root of negative numbers.
Both operations are (usually) performed without notication, i.e.
the program continues execution with these numbers. NANs often
occur with uninitialised variables, therefore RAII.
In integer arithmetic, x/0 leads to an exception, i.e. the program
(usually) aborts.
C++ for Scientic Computing 30/316
Relational and Logical Operators
Comparison
Standard comparison for integer and oating point types:
x > y; // bigger than
x < y; // less than
x >= y; // bigger or equal
x <= y; // less or equal
x == y; // equal
x != y; // unequal
Logic
Logical operators and, or and not for boolean values:
b1 && b2; // and
b1 || b2; // or
! b1; // not
C++ for Scientic Computing 31/316
Relational and Logical Operators
Minimal Evaluation
Logical expressions are only evaluated until the result is known.
This is important if the expression contains function calls (see
later), or a sub expression is only allowed if a previous sub
expression is true.
int x = 2;
int y = 4;
int z = 4;
bool b;
// z == 4 is not tested
b = ( x == 2 && y == 3 && z == 4 );
// only x == 2 is tested
b = ( x == 2 || y == 3 || z == 4 );
// correct, since x != 0 in "y/x"
b = ( x != 0 && y/x > 1 );
C++ for Scientic Computing 32/316
Relational and Logical Operators
Floating Point Comparison
Coding Principle No. 6
For oating point types, avoid equality/inequality checks
due to inexact arithmetic.
Better is interval test:
double f1 = sqrt( 2.0 );
double f2 = f1 * f1;
bool b;
b = ( f2 == 2.0 ); // unsafe
const double eps = 1e-14;
b = ( f2 > 2.0 - eps && f2 < 2.0 + eps ); // safe
b = ( abs( f2 - 2.0 ) < eps ); // even better
C++ for Scientic Computing 33/316
Precedence and Associativity of Operators
Precedence
When evaluating complex expressions, what is evaluated rst, and
in which order? Example:
int n1 = 2 + 3 * 4 % 2 - 5;
int n2 = 4;
bool b = n1 >= 4 && n2 != 3 || n1 < n2;
Precedence of arithmetics follows algebraic/logical precedence, e.g.
* before +, (&&) before (||). Increment (++) and decrement
(--) have higher, assignment (=, +=, . . .) has lower priority.
Parentheses have highest priority.
int n1 = (2 + ((3 * 4) % 2) - 5);
int n2 = 4;
bool b = (((n1 >= 4) && (n2 != 3)) || (n1 < n2));
C++ for Scientic Computing 34/316
Precedence and Associativity of Operators
Associativity
Arithmetic and logical operators are left associative, whereas
assignment operators are right associative:
int n1 = 1 + 2 + 3 + 4;
int n2 = 1 * 2 * 3 * 4;
int n3, n4 , n5;
n3 = n4 = n5 = 0;
is the same as
int n1 = (((1 + 2) + 3) + 4);
int n2 = (((1 * 2) * 3) * 4);
int n3, n4 , n5;
(n3 = (n4 = (n5 = 0)));
C++ for Scientic Computing 35/316
Precedence and Associativity of Operators
Summary
Priority Associativity Operators
highest left ()
right
++, --, - (unary), !, & (address),
* (dereference)
left * (multiplication), /, %
left +, -
left >, <, >=, <=
left ==, !=
left &&
left ||
lowest right =, +=, -=, *=, /=, %=
C++ for Scientic Computing 36/316
Precedence of Operators
Usage of Parentheses
Example:
int n1 = 4;
int n2 = 2 + 5 * n1 / 3 - 5;
bool b = n2 >= 4 && n1 != 3 || n2 < n1;
vs.
int n1 = 4;
int n2 = 2 + (5 * n1) / 3 - 5;
bool b = ( n2 >= 4 ) && (( n1 != 3 ) ||
( n2 < n1 ));
Coding Principle No. 7
When in doubt or to clarify the expression: use
parentheses.
C++ for Scientic Computing 37/316
Type Casting
C++ for Scientic Computing 38/316
Type Casting
Implicit Type Conversion
Up to now, we have only had expressions with either integer or
oating point types. Often, these types are mixed:
const double pi = 3.14159265358979323846;
double f = 1 / ( 2 * pi );
Here, implicit type conversion occurs: the int numbers 1 and 2
are automatically converted to the double numbers 1.0 and 2.0,
respectively.
const double pi = 3.14159265358979323846;
int n = 2 * pi;
is also allowed. Here, 2 is rst converted to double and the result
is nally converted back to int.
C++ for Scientic Computing 39/316
Type Casting
Explicit Type Conversion
Explicit conversion between data types is performed via
typename( value )
Examples:
const float pi = float(3.14159265358979323846);
int n = 2 * int(pi);
bool b = bool(n);
Remark
A non-zero value is interpreted as true, a zero value as
false.
The old C syntax (typename) value, e.g.
int n = 2 * (int) pi;
is also allowed, but not recommended.
C++ for Scientic Computing 40/316
Type Casting
Problems while Casting
Problem 1: Dierent ranges:
int n1 = 5 - 6;
unsigned int n2 = n1; // underflow
Problem 2: Dierent precision:
const double pi = 3.14159265358979323846;
float f1 = 2.1;
float f2 = 4 * f1 / pi;
The last expression is computed in double precision. Using
float(pi) instead, would result in pure single precision
arithmetic, which is (usually) faster.
Coding Principle No. 8
Avoid implicit type conversion.
C++ for Scientic Computing 41/316
Type Casting
Cast Operators
C++ denes four cast operators for various purposes:
const casthTi(v) : remove const qualier of a variable v,
static casthTi(v) : classic, compile time type conversion with
type checking
reinterpret casthTi(v) : type conversion with correct handling
of variable value and
dynamic casthTi(v) : runtime type conversion with type
checking
Since these operators are usually used in connection with classes,
we will come back to them later.
C++ for Scientic Computing 42/316
Blocks and Scope
C++ for Scientic Computing 43/316
Blocks and Scope
Blocks
Statements in a C++ program are part of a block, which is enclosed
by { and }:
{ // ...
}
Remark
The trivial block is dened by a single statement.
Blocks can be arbitrarily nested or otherwise put together:
{ // block 1
{ // subblock 1.1
}
{ // subblock 1.2
{ // sub subblock 1.2.1
}
}
}
C++ for Scientic Computing 44/316
Blocks and Scope
Variable Scope
Variables can be declared in each block individually, even with the
same name:
{ // block 1
int n1 = 1;
double f1 = 0.0;
}
{ // block 2
int n1 = 2; // n1 has value 2 in this block
}
but not twice in the same block:
{
int n1 = 1;
// ...
int n1 = 2; // ERROR
}
C++ for Scientic Computing 45/316
Blocks and Scope
Variable Scope
A variable is declared in the local block and all enclosed blocks:
{ // block 1: n1 declared
int n1 = 1;
{ // block 1.1: n1, n2 declared
int n2 = 2;
{ // block 1.1.1: n1, n2 declared
}
}
int n4 = 4;
{ // block 1.2: n1, n4, n3 declared
int n3 = 3;
}
}
C++ for Scientic Computing 46/316
Blocks and Scope
Variable Scope
Variables with the same name can also be declared in nested
blocks.
{ // block 1
int n1 = 1; // n1 == 1
{ // block 1.1
int n1 = 2; // n1 == 2
}
// n1 == 1
{ // block 1.2
int n1 = 3; // n1 == 3
}
{ // block 1.3
n1 = 4;
}
// n1 == 4 !!!
}
C++ for Scientic Computing 47/316
Blocks and Scope
Variable Scope
A reference to a variable will always be made to the rst
declaration found when going up the hierarchy of enclosing blocks.
{ // block 1
int m, n1 = 1;
{ // block 1.1
int n2 = 2;
{ // block 1.1.1
m = n1 + n2; // evaluates to m = 3
}
}
{ // block 1.2
int n2 = 3;
m = n1 + n2; // evaluates to m = 4
}
}
C++ for Scientic Computing 48/316
Blocks and Scope
Variable Scope
Remark
Using variables with same name in nested blocks is not
recommended, since that often leads to erroneous
programs.
C++ for Scientic Computing 49/316
Blocks and Scope
Variable Lifetime
The scope of a variable also denes their lifetime, e.g. the time
where resources are needed for the variable. For non-static
variables, memory is allocated if a declaration is encountered and
released, when the leaving the block holding the declaration:
{
int n = 0; // memory for an integer is allocated
{
double f = 1.2; // memory for a double is allocated
...
} // memory for "f" is released
} // memory for "n" is released
For static variables, the memory will never be released and only
allocated once per program run:
{
stati c int m = 10; // allocate memory once
} // memory for "m" is not released
C++ for Scientic Computing 50/316
Control Structures
C++ for Scientic Computing 51/316
Control Structures
Conditional Structure
A condition is dened by
if ( hconditioni ) hblocki
where hblocki is executed if hconditioni is true or
if ( hconditioni ) hif blocki
else helse blocki
where additionally helse blocki is executed if hconditioni is false,
e.g.
int n = 1;
i f ( n > 0 )
{
n = n / n;
}
i f ( n < 0 ) n += 5; // NOTE: trivial block!
el se n -= 6;
C++ for Scientic Computing 52/316
Control Structures
C++ supports three dierent loops: for loops, while loops and
do-while loops.
For Loops
for ( hstart stmt.i ; hloop conditioni ; hloop stmt.i )
hblocki
The start statement is executed before the loop is entered. Before
each iteration, the loop condition is evaluated. If it is false, the
loop is nished. After each iteration, the loop statement is
executed.
Example for factorial:
int n = 1;
for ( int i = 1; i < 10; ++i )
{
n = n * i;
}
C++ for Scientic Computing 53/316
Control Structures
While Loops
while ( hloop conditioni )
hblocki
The loop iterates until the loop condition is no longer true, i.e.
evaluates to false. The condition is checked before each iteration.
Example for factorial:
int n = 1;
int i = 1;
while ( i < 10 )
{
n *= i;
++i;
}
C++ for Scientic Computing 54/316
Control Structures
Do-While Loops
do
hblocki
while ( hloop conditioni );
The loop condition is tested after each iteration. Hence, the block
is at least executed once.
Example for factorial:
int n = 1;
int i = 1;
do
{
n *= i;
++i;
} while ( i < 10 );
C++ for Scientic Computing 55/316
Control Structures
Breaking out of Loops
To nish the current loop immediately, e.g. without rst testing
the loop condition, the keyword break can be used:
int n = 1;
for ( int i = 1; i < 20; ++i )
{
// avoid overflow
i f ( n > 21474836 )
break;
n = n * i;
}
C++ for Scientic Computing 56/316
Control Structures
Breaking out of Loops
Only the current loop is nished, not all enclosing loops:
for ( int j = 1; j < 20; ++j )
{
int n = 1;
for ( int i = 1; i < j; ++i )
{
i f ( n > 21474836 ) // break here
break;
n = n * i;
}
cout << n << endl; // and continue here
}
C++ for Scientic Computing 57/316
Control Structures
Finish current Iteration
To immediately nish the current iteration of a loop use the
keyword continue:
int n2 = 0;
for ( int i = 0; i < 1000; i++ )
{
// skip odd numbers
i f ( i % 2 == 1 )
continue;
n2 += i;
}
After continue, the loop condition of a loop is tested. In for-loops,
the loop statement is rst evaluated.
Remark
Again, continue eects only the current loop.
C++ for Scientic Computing 58/316
Control Structures
Selective Statement
To directly switch between several cases, the switch structure can
be used:
switch ( hvaluei ) {
case hCASE 1i : hstatementsi; break;
case hCASE 2i : hstatementsi; break;
.
.
.
case hCASE ni : hstatementsi; break;
default : hstatementsi
}
The type of hvaluei must be some integral typ, i.e. it can be
mapped to an integer type. Hence, oating point or more
advanced types (later) are not possible.
C++ for Scientic Computing 59/316
Control Structures
Selective Statement
Example for a switch statement:
unsigned int i = 3;
unsigned int n;
switch ( i )
{
case 0 : n = 1; break;
case 1 : n = 1; break;
case 2 : n = 2; break;
case 3 : n = 6; break;
case 4 : n = 24; break;
default : n = 0; break;
}
Coding Principle No. 9
Always implement the default case to avoid unhandled
cases.
C++ for Scientic Computing 60/316
Control Structures
Selective Statement
If break is missing after the statements for a specic case, the
statements of the next case are also executed:
unsigned int i = 3;
unsigned int n;
switch ( i )
{
case 0 :
case 1 : n = 1; break; // executed for i == 0 and i == 1
case 2 : n = 2; break;
case 3 : n = 6; break;
case 4 : n = 24; break;
default : n = 0; break;
}
C++ for Scientic Computing 61/316
Control Structures
Selective Statement
A switch statement can be implemented via if and else:
unsigned int i = 3;
unsigned int n;
i f ( i == 0 ) { n = 1; }
el se i f ( i == 1 ) { n = 1; }
el se i f ( i == 2 ) { n = 2; }
el se i f ( i == 3 ) { n = 6; }
el se i f ( i == 4 ) { n = 24; }
el se { n = 0; } // default statement
Remark
Using switch is faster than if-else, among other things
since less comparisons are performed!
C++ for Scientic Computing 62/316
Functions
C++ for Scientic Computing 63/316
Functions
Function Denition
The denition of a function in C++ follows
hreturn typei
hfunction namei ( hargument listi )
hblocki
When a function does not return a result, e.g. a procedure, then
the return type is void.
To return a value from a function, C++ provides the keyword
return.
double
square ( const double x )
{
return x*x;
}
C++ for Scientic Computing 64/316
Functions
Function Call
A function is called by
hfunction namei ( hargument1i, hargument2i, . . . );
Example:
double y = square( 4.3 );
One can not dene a function in the body of another function:
double cube ( const double x )
{
// ERROR
double square ( const double y ) { return y*y; }
return square( x ) * x;
}
C++ for Scientic Computing 65/316
Functions
Function Examples
Previous computation of factorial in functional form:
int
factorial ( const int n )
{
int f = 1;
for ( int i = 1; i <= n; i++ )
f *= i;
return f;
}
Coding Principle No. 10
Make all function arguments const, except when
changing value (see later).
C++ for Scientic Computing 66/316
Functions
Function Examples (Cont.)
Power function with positive integer exponents:
double
power ( const double x, const unsigned int n )
{
switch ( n )
{
case 0 : return 1;
case 1 : return x;
case 2 : return square( x );
default:
{
double f = x;
for ( int i = 0; i < n; i++ ) f *= x;
return f;
} } }
Coding Principle No. 11
Make sure, that a function has a call to return in every
execution path.
C++ for Scientic Computing 67/316
Functions
Main Function
The main function is the rst function called by the operating
system in your program. Every program must have exactly one
main function.
In principle, only code in main and functions called directly or
indirectly from main will be executed.
The main function may be implemented without arguments and
has a return type of int:
int
main ()
{
... // actual program code
return 0;
}
The value returned from main is supplied to the operating system.
As a standard, a value of 0 signals no error during program
execution.
C++ for Scientic Computing 68/316
Functions
Call by Value
In previous examples, only the value of a variable (or constant) is
used as the argument of a function, e.g. changing the value of the
argument does not change the value of the original variable:
int
f ( int m ) // non const argument!
{
m = 4; // explicitly changing the value of argument m
return m;
}
int m = 5;
int n = f( m ); // m is unchanged by f
This is known as call-by-value.
Remark
It is nevertheless advised to use const arguments.
C++ for Scientic Computing 69/316
Functions
Call by Reference
If the original variable should be changed in a function, a pointer
or reference to this variable has to be supplied:
int
f ( int & m ) // reference argument
{
m = 4; // changing m, changes the variable pointed to
return m;
}
int m = 5;
int n = f( m ); // m is changed by f to 4
This is known as call-by-reference.
C++ for Scientic Computing 70/316
Functions
Call by Reference
The same function with pointers:
int
f ( int * m ) // reference argument
{
*m = 4; // changing m, changes the variable pointed to
return *m;
}
int m = 5;
int n = f( & m ); // m is changed by f to 4
C++ for Scientic Computing 71/316
Functions
Call by Reference
When using references to constant variables, the value can not be
changed:
int
f ( const int & m )
{
m = 4; // ERROR: m is constant
return m;
}
Therefore, this is (almost) equivalent to call-by-value and needed
for advanced datatypes (see later).
For basic datatypes, using call-by-reference, even with const, is
usually not advisable, except when changing the original variable.
C++ for Scientic Computing 72/316
Functions
Call by Reference
Example for multiple return values
void
min_max ( const int n1, const int n2, const int n3,
int & min, int & max )
{
i f ( n1 < n2 )
i f ( n1 < n3 )
{
min = n1;
i f ( n2 < n3 ) max = n3;
el se max = n2;
}
el se
{
min = n3;
max = n2;
}
el se
...
}
C++ for Scientic Computing 73/316
Functions
Recursion
Calling the same function from inside the function body, e.g. a
recursive function call, is allowed in C++:
unsigned int
factorial ( const unsigned int n )
{
i f ( n <= 1 )
return 1;
el se
return n * factorial( n-1 );
}
Remark
The recursion depth, i.e. the number of recursive calls, is
limited by the size of the stack, a special part of the
memory. In practise however, this should be of no
concern.
C++ for Scientic Computing 74/316
Functions
Recursion
It is also possible to perform recursive calls multiple times in a
function:
unsigned int
fibonacci ( const unsigned int n )
{
unsigned int retval;
i f ( n <= 1 ) retval = n;
el se retval = fibonacci( n-1 ) +
fibonacci( n-2 );
return retval;
}
Remark
Remember, that variables belong to a specic block and
each function call has its own block. Therefore,
variables, e.g. retval, are specic to a specic function
call.
C++ for Scientic Computing 75/316
Functions
Function Naming
A function in C++ is identied by its name and the number and
type of its arguments. Hence, the same name can be used for
dierent argument types:
int square ( const int x ) { return x*x; }
float square ( const float x ) { return x*x; }
double square ( const double x ) { return x*x; }
Coding Principle No. 12
Functions implementing the same algorithm on dierent
types should be named equal.
This can signicantly reduce the number of dierent functions
youll have to remember und simplies programming.
C++ for Scientic Computing 76/316
Functions
Function Naming
If only the return type is dierent between functions, they are
identied as equal:
float f ( int x ) { ... }
double f ( int x ) { ... } // ERROR: "f" already defined
C++ for Scientic Computing 77/316
Functions
Default Arguments
Arguments for a function can have default arguments, which then
can be omitted at calling the function:
void
f ( int n, int m = 10 )
{
// ...
}
{
f( 5 ); // equivalent to f( 5, 10 )
f( 5, 8 );
}
Only limitation: after the rst default value, all arguments must
have default values:
void g1 ( int n, int m = 10, int k ); // ERROR
void g2 ( int n, int m = 10, int k = 20 ); // Ok
C++ for Scientic Computing 78/316
Functions
Default Arguments and Function Names
Two functions with the same name must dier by their arguments
without default values:
void f ( int n1, int n2, int n3 = 1 ) { ... }
void f ( int n1, int n2 ) { ... }
...
{
f( 1, 2, 3 ); // Ok: call to f(int, int, int)
f( 1, 2 ); // Error: call of "f(int, int)" is ambiguous
}
C++ for Scientic Computing 79/316
Functions
Function Name Scope
A function can only be called, if it was previously implemented:
void f ( int x )
{
g( x ); // ERROR: function "g" unknown
}
void g ( int y )
{
f( y ); // Ok: function "f" already defined
}
or declared, i.e. denition of function without function body:
void g ( int y ); // forward declaration
void f ( int x )
{
g(); // Ok: "g" is declared
}
This is known as forward declaration.
C++ for Scientic Computing 80/316
Functions
Function Name Scope
Of course, every function with a forward declaration has to be
implemented eventually:
void g ( int y ); // forward declaration
void f ( int x )
{
g();
}
...
void g ( int y ) // implementation
{
f( y );
}
C++ for Scientic Computing 81/316
Functions
Inline Functions
Calling a function involves some overhead. For small functions,
this overhead might exceed the actual computation:
double square ( const double x ) { return x*x; }
{
double f = 0;
for ( int i = 0; i < 100; i++ )
f += square( double(x) );
}
Here, simply calling square takes a signicant part of the runtime.
Some compilers automatically replace the function call by the
function body:
...
for ( int i = 0; i < 100; i++ )
f += double(x) * double(x);
...
C++ for Scientic Computing 82/316
Functions
Inline Functions
Replacing the function call by the function body is called inlining.
To help the compiler with such decisions, functions can be marked
to be inlined by the keyword inline:
i nl i ne double
square ( const double x )
{
return x*x;
}
Especially for small functions, this often dramatically increases
program performance.
Remark
If the function body is too large, inlining can blow up the
program since too much code is compiled, e.g. every
occurence of the function, and therefore decreases
performance!
C++ for Scientic Computing 83/316
Functions
Function Pointers
A function, like a variable, is stored somewhere in the memory and
therefore, also has an address. Hence, a pointer can be aquired for
it. For a function
hreturn typei hfunction namei ( hargument listi );
a pointer is dened by
hreturn typei ( * hvariable namei ) ( hargument listi );
Example:
int f ( const int n, int & r );
{
int ( * pf ) ( const int n, int & r ); // function ptr named "pf"
pf = f;
}
C++ for Scientic Computing 84/316
Functions
Function Pointers
A variable holding the address of a function can be used as a
function by itself:
int n = 0;
pf = f; // pf holds address to f
pf( 2, n ); // call to f
Since function pointers are normal variables, they can be supplied
as function arguments:
double f1 ( const double x ) { return x*x; }
double f2 ( double ( * func ) ( const double x ),
const double x ) { return func( x ); }
int main ()
{
f2( f1, 2.0 ); // returns f1( 2.0 )
}
C++ for Scientic Computing 85/316
Functions
Function Pointers
Example: apply Simpson rule to various functions
double
simpson_quad ( const double a, const double b,
double ( * func ) ( const double ) )
{
return (b-a) / 6.0 * ( func(a) +
4 * func( (a+b) / 2.0 ) +
func(b) );
}
double f1 ( const double x ) { return x*x; }
double f2 ( const double x ) { return x*x*x; }
int main ()
{
cout << simpson_quad( -1, 2, f1 ) << endl;
cout << simpson_quad( -1, 2, f2 ) << endl;
}
C++ for Scientic Computing 86/316
Functions
Functions and Minimal Evaluation
As discussed, C++ uses minimal evaluation when looking at logical
expressions, e.g. only evalutates until results is known. If functions
are used in the expressions, it can imply, that they are not called at
all:
double f ( const double x ) { ... }
...
// f is not called if x >= 0.0
i f (( x < 0.0 ) && ( f( x ) > 0.0 ))
{
...
}
For the programmer this means:
Coding Principle No. 13
Never rely on a function call in a logical expression.
C++ for Scientic Computing 87/316
Functions
Functions and static Variables
In contrast to standard variables in a function, which are specic
to a specic function call, for static variables in all function calls
the same instance, e.g. memory position, is referenced:
double
f ( const double x, long & cnt )
{
stati c long counter = 0; // allocated and initialised
// once per program
cnt = ++counter;
return 2.0*x*x - x;
}
int main ()
{
long cnt = 0;
for ( double x = -10; x <= 10.0; x += 0.1 )
f( x, cnt );
cout << cnt << endl; // print number of func. calls
}
C++ for Scientic Computing 88/316
Arrays and Dynamic
Memory
C++ for Scientic Computing 89/316
Arrays and Dynamic Memory
Array Denition
So far, we had only datatypes with one entry per variable. Arrays
with a xed number of entries are dened as:
hdatatypei hvariablenamei[hnumber of entriesi];
where the number of entries is a constant, e.g.
int n[ 5 ];
double f[ 10 ];
const int len = 32;
char str[ len ];
Arrays can also be preinitialised. In that case, the array size can be
omitted:
int n1[5] = { 0, 1, 2, 3, 4, 5 };
int n2[] = { 3, 2, 1, 0 }; // automatically size of 4
C++ for Scientic Computing 90/316
Arrays and Dynamic Memory
Array Access
A single entry in an arrays is accessed by the index operator []:
double f[5];
int i;
f[0] = -1.0;
f[1] = 3.0;
f[4] = f[1] * 42.0;
i = 3;
f[i] = f[0] + 8.0;
In C++, indices are counted from zero. The valid index range is
therefore:
[0, . . . , array size 1]
for ( int i = 0; i < 5; i++ )
f[i] = 2*i;
C++ for Scientic Computing 91/316
Arrays and Dynamic Memory
Array Access
There are normally no array boundary checks in C++, i.e. you can
specify arbitrary, even negative indices, resulting in an undened
program behaviour.
Typical error:
double f[5];
for ( int i = 0; i < 5; i++ ) // Ok
f[i] = 2*i;
for ( int i = 0; i <= 5; i++ ) // Bug
f[i] = 2*i;
Coding Principle No. 14
Always make sure, that you access arrays within the valid
index range.
C++ for Scientic Computing 92/316
Arrays and Dynamic Memory
Array Operations
Unfortunately, there are no operators for arrays, e.g. no
assignment, elementwise addition or multiplication like in other
languages. All of these have to be programmed by yourself:
void copy ( const double x[3], double y[3] )
{
for ( int i = 0; i < 3; i++ )
y[i] = x[i];
}
void add ( const double x[3], double y[3] )
{
for ( int i = 0; i < 3; i++ )
y[i] += x[i];
}
Remark
Arrays can be used as function arguments like all basic
datatypes. But not as function return types!
C++ for Scientic Computing 93/316
Arrays and Dynamic Memory
Multidimensional Arrays
So far, all arrays have been onedimensional. Multidimensional
arrays are dened analogously by appending the corresponding size
per dimension:
int M[3][3];
double T3[10][10][10];
long T4[100][20][50];
The access to array elements in multidimensional arrays follows the
same pattern:
M[0][0] = 1.0; M[0][1] = 0.0; M[0][2] = -2.0;
M[1][0] = 0.0; M[1][1] = 4.0; M[1][2] = 1.0;
M[2][0] = -2.0; M[2][1] = 1.0; M[2][2] = -1.5;
for ( int i = 0; i < 100; i++ )
for ( int j = 0; j < 20; j++ )
for ( int k = 0; k < 50; k++ )
T3[i][j][k] = double(i+j+k);
C++ for Scientic Computing 94/316
Arrays and Dynamic Memory
Multidimensional Arrays
Example: Matrix-Vector multiplication
void
mulvec ( const double M[3][3],
const double x[3],
double y[3] )
{
for ( int i = 0; i < 3; i++ )
{
y[i] = 0.0;
for ( int j = 0; j < 3; j++ )
y[i] += M[i][j] * x[j];
}
}
C++ for Scientic Computing 95/316
Arrays and Dynamic Memory
Arrays and Pointers
C++ does not support variable sized arrays as an intrinsic datatype.
Hence, arrays with an unknown size at compile time are not
possible with previous array types in C++.
But, in C++, there is no distinction between a pointer and an array.
A pointer not only directs to some memory address, it is also the
base point, e.g. index 0, of an array.
int n[5] = { 2, 3, 5, 7, 11 };
int * p = n;
cout << p[0] << endl; // yields n[0]
cout << p[1] << endl; // yields n[1]
cout << p[4] << endl; // yields n[4]
The index operator [i] of a pointer p gives access to the ith
element of the array starting at address p.
C++ for Scientic Computing 96/316
Arrays and Dynamic Memory
Dynamic Memory
Since pointers and arrays are equivalent, one needs to initialise a
pointer with the address of a memory block large enough to hold
the wanted array. This is accomplished by dynamic memory
management:
Memory of arbitrary size can be allocated and
deallocated at runtime.
In C++ this is done with the operators new and new[] to allocate
memory and delete and delete[] to deallocate memory.
For a single element:
hdatatypei * p = new hdatatypei;
delete p;
For more than one element:
hdatatypei * p = new hdatatypei[hsizei];
delete[] p;
C++ for Scientic Computing 97/316
Arrays and Dynamic Memory
Dynamic Memory
Examples:
char * s = new char[ 100 ];
int n = 1024;
double * v = new double[ n ];
float * f = new float;
for ( int i = 0; i < n; i++ )
v[i] = double( square( i ) );
*f = 1.41421356237; // dereference f
...
delete[] v; // new[] => delete[]
delete[] s;
delete f; // new => delete
Remark
The size parameter to new does not need to be a
constant.
C++ for Scientic Computing 98/316
Arrays and Dynamic Memory
Problems with Pointers
The corresponding array to a pointer has no information about the
array size. Remember, that C++ performs no boundary checks.
That opens the door to many errors (see Coding Principle No. 14).
double * v = new double[ 1000 ];
...
v[2000] = 1.0;
With the last instruction, you overwrite a memory position
corresponding to completely other data. The program will only
terminate, if the memory does not belong to the program
(segmentation fault).
C++ for Scientic Computing 99/316
Arrays and Dynamic Memory
Problems with Pointers
The programmer does not know if the memory was allocated or
deallocated, except if the pointer contains NULL (see Coding
Principle No. 5).
double * v = new double[ 1000 ];
...
delete[] v;
...
v[100] = 2.0; // Bug: memory for v is deallocated
Again, the last instruction will be executed and will only result in
an immediate error, if the memory is no longer part of the program.
Coding Principle No. 15
After calling delete, reset the pointer value to NULL.
C++ for Scientic Computing 100/316
Arrays and Dynamic Memory
Problems with Pointers
Memory addressed by forgotten pointers is lost for the program.
C++ does not automatically delete memory with no references to it
(garbage collection).
void f ()
{
double * v = new double[ 1000 ];
... // no delete[] v
}
// v is no longer accessible, memory is lost
This bug is not directly a problem, since no other data is
overwritten. But if a lot of memory is not deleted after use, the
program will have no available memory left.
Coding Principle No. 16
Always make sure, that allocated memory is deallocated
after using.
C++ for Scientic Computing 101/316
Arrays and Dynamic Memory
Problems with Pointers
Remark
The aftermath of a pointer related bug, e.g. array
boundary violation or accessing deleted memory, may
show up much later than the actual position of the error.
Summary: pointers are dangerous and require careful programming.
But we have no choice /.
Well, almost , (see later).
C++ for Scientic Computing 102/316
Arrays and Dynamic Memory
Problems with Pointers
Remark
The aftermath of a pointer related bug, e.g. array
boundary violation or accessing deleted memory, may
show up much later than the actual position of the error.
Summary: pointers are dangerous and require careful programming.
But we have no choice /. Well, almost , (see later).
C++ for Scientic Computing 102/316
Arrays and Dynamic Memory
Multidimensional Arrays with Pointers
The analog of multidimensional arrays are pointers of pointers, i.e.
pointers which direct to a memory address containing a pointer to
another memory address:
int n[5] = { 2, 3, 5, 7, 11 };
int * p1 = & n[2];
int ** p2 = & p1;
p2 p1
2 3 5 7 11
Memory (array)
This can be generalised to multiple dimensions:
int n1[4], n2[4], n3[4], n4[4];
int * p1 = n1;
int * p2 = n2;
int * p3 = n3;
int * p4 = n4;
int * p[4] = { p1, p2, p3, p4 };
cout << p[1][3] << endl; // yields 8
1 2 3 4
5 6 7 8
9 8 7 6
5 4 3 2
p4
p2
p1
= n1
= n2
= n3
= n4
p3
p
C++ for Scientic Computing 103/316
Arrays and Dynamic Memory
Multidimensional Arrays with Pointers
The same example with dynamic memory:
int * p1 = new int[4];
int * p2 = new int[4];
int * p3 = new int[4];
int * p4 = new int[4];
int ** p = new int*[4];
p[0] = p1;
p[1] = p2;
p[2] = p3;
p[3] = p4;
p[0][0] = 1;
p[0][1] = 2;
...
p[2][2] = 7;
p[2][3] = 6;
p[3][0] = 5;
...
1 2 3 4
5 6 7 8
9 8 7 6
5 4 3 2
p4
p2
p1
= n1
= n2
= n3
= n4
p3
p
C++ for Scientic Computing 104/316
Arrays and Dynamic Memory
Multidimensional Arrays with Mappings
Working with pointers to pointers is only one way to implement
multidimensional arrays. You can also map the multiple dimensions
to just one:
1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2
p
= n1 = n2 = n3 = n4
global Memory
int * p = new int[4*4];
p[ 2 * 4 + 1 ] = 8; // p[2][1]
p[ 0 * 4 + 2 ] = 3; // p[0][2]
C++ for Scientic Computing 105/316
Arrays and Dynamic Memory
Multidimensional Arrays with Mappings
In theory, one could use any mapping. In practise, two dierent
mappings are standard:
row-wise: standard in C, C++
column-wise: standard in
Fortran, Matlab
columnwise rowwise
For a two-dimensional array, e.g. a matrix, with dimensions n m,
the mappings are for index (i, j):
row-wise: i m+j,
column-wise: j n +i.
It is up to you, which mapping you prefer.
C++ for Scientic Computing 106/316
Arrays and Dynamic Memory
Example: n m Matrix (row-wise)
void
set_entry ( const double * M,
const int i, const int j,
const int m, const double f )
{
M[ i*m + j ] = f;
}
int
main ()
{
int n = 10;
int m = 20;
double * M = new double[ n * m ];
set_entry( M, 3, 1, m, 3.1415 );
set_entry( M, 2, 7, m, 2.7182 );
}
C++ for Scientic Computing 107/316
Arrays and Dynamic Memory
Comparison: Pointers vs. Mapping
Two approaches have been introduced for multidimensional arrays:
pointers of pointers and user dened mapping. Which is to be
preferred?
A user dened mapping is faster since only simple arithmetic is
performed for a memory access. The pointer based approach needs
to follow each pointer individually, resulting in many memory
accesses.
Pointers are more exible, e.g. for triangular matrices, whereas a
special mapping has to be dened for each shape.
My recommendation: use mappings, especially if you want fast
computations.
C++ for Scientic Computing 108/316
Arrays and Dynamic Memory
Application: BLAS
Properties and requirements:
vectors are onedimensional arrays, matrices implemented via
mapping (row-wise),
should provide functions for all standard operations, e.g.
creation, access, linear algebra
Initialisation:
i nl i ne double *
vector_init ( const unsigned i )
{
return new double[i];
}
i nl i ne double *
matrix_init ( const unsigned n, const unsigned m )
{
return new double[ n * m ];
}
C++ for Scientic Computing 109/316
Arrays and Dynamic Memory
Application: BLAS
Vector Arithmetic:
void fill ( const unsigned n, const double f, double * y );
void scale ( const unsigned n, const double f, double * y );
void add ( const unsigned n, const double f, const double * x,
double * y )
{ for ( unsigned i = 0; i < n; i++ ) y[i] += f * x[i]; }
double
dot ( const unsigned n, const double * x, const double * y )
{
double d = 0.0;
for ( unsigned i = 0; i < n; i++ ) d += x[i] * y[i];
return d;
}
i nl i ne double
norm2 ( const unsigned n, const double * x )
{ return sqrt( dot( n, x, x ) ); }
C++ for Scientic Computing 110/316
Arrays and Dynamic Memory
Application: BLAS
Matrix Arithmetic:
void
fill ( const unsigned n, const unsigned m,
const double f, double * M )
{ fill( n*m, f, M ); } // use vector based fill
void
scale ( const unsigned n, const unsigned m,
const double f, double * M );
void
add ( const unsigned n, const unsigned m,
const double f, const double * A, double * M );
i nl i ne double
normF ( const unsigned n, const unsigned m,
double * M )
{ return norm2( n*m, M ); } // use vector based norm2
C++ for Scientic Computing 111/316
Arrays and Dynamic Memory
Application: BLAS
Matrix-Vector Multiplication y := y +A x:
void
mul_vec ( const unsigned n, const unsigned m,
const double alpha, const double * M, const double * x,
double * y )
{
for ( unsigned i = 0; i < n; i++ )
{
double f = 0.0;
for ( unsigned j = 0; j < m; j++ )
f += get_entry( n, m, i, j, M ) * x[j];
// alternative: f = dot( m, & M[ i * m ], x );
y[i] += alpha * f;
}
}
Remark
Compute dot product in local variable to minimize
memory accesses.
C++ for Scientic Computing 112/316
Arrays and Dynamic Memory
Application: BLAS
Matrix-Matrix Multiplication C := C +A B:
void
mul_mat ( const unsigned n, const unsigned m, const unsigned k,
const double alpha, const double * A, const double * B,
double * C )
{
for ( unsigned i = 0; i < n; i++ )
for ( unsigned j = 0; j < m; j++ )
{
double f = 0.0;
for ( unsigned l = 0; l < k; l++ )
f += get_entry( n, k, i, l, A ) *
get_entry( k, m, l, j, B );
add_entry( n, m, i, j, f, M );
}
}
C++ for Scientic Computing 113/316
Arrays and Dynamic Memory
Application: BLAS
double * M = matrix_init( 10, 10 );
double * x = vector_init( 10 );
double * y = vector_init( 10 );
fill( 10, 1.0, x );
fill( 10, 0.0, y );
... // fill matrix M
cout << normF( 10, 10, M ) << endl;
mul_vec( 10, 10, -1.0, M, x, y );
C++ for Scientic Computing 114/316
Arrays and Dynamic Memory
Strings
One important datatype was not mentioned up to now: strings.
Strings are implemented in C++ as arrays of characters, e.g.
char str[] or char * str
As arrays have no size information, there is no information about
the length of a string stored. To signal the end of a string, by
convention the character 0 is used (as an integer, not the digit),
entered as \0:
char str[] = { S, t, r, i, n, g, \0 };
Constant strings can also be dened and used directly with
:
char str[] = "String"; // array initialisation
Here, \0 is automatically appended.
C++ for Scientic Computing 115/316
Arrays and Dynamic Memory
Strings
If a string is too long for one input line, it can be wrapped by a
backslash \:
const char * str = "This is a very long \
string";
C++ does not provide operators for string handling, e.g.
concatenation or searching for substrings. All of that has to be
implemented via functions:
char * concat ( const char * str1, const char * str2 )
{
const unsigned len1 = strlen( str1 );
const unsigned len2 = strlen( str2 );
char * res = new char[ len1 + len2 + 1 ];
int pos = 0, pos2 = 0;
while ( str1[pos] != \0 ) { res[pos] = str1[pos]; pos++; }
while ( str2[pos2] != \0 ) { res[pos++] = str2[pos2++]; }
res[pos] = \0;
return res;
}
C++ for Scientic Computing 116/316
Arrays and Dynamic Memory
Strings
Usage:
const char * str1 = "Hallo ";
char * str2 = concat( str1, "World" );
cout << str2 << endl;
delete[] str2; // dont forget to deallocate!
It can not be emphasised too much:
Coding Principle No. 17
Always ensure, that strings are terminated by \0.
Otherwise, operations on strings will fail due to array boundary
violations.
C++ for Scientic Computing 117/316
Arrays and Dynamic Memory
Strings
\0 is one example of a special character in C++ strings. Others are
Character Result
\n a new line
\t a tab
\r a carriage return
\b a backspace
\
0
single quote
0
\ double quote
\\ backslash \
Examples:
cout << "First \t Second" << endl;
cout << "line1 \n line2" << endl;
cout << "special \"word\"" << endl;
cout << "set1 \\ set2" << endl;
C++ for Scientic Computing 118/316
Advanced Datatypes
C++ for Scientic Computing 119/316
Advanced Datatypes
Type Denition
Often you do not always want to care about the actual datatype
used in your program, e.g. if it is float or double or if strings are
char *, but instead give the types more reasonable names, e.g.
real or string. In C++ you can do this via typedef:
typedef hdata typei hnamei;
Afterwards, hnamei can be used like any other datatype:
typedef double real_t;
typedef char * string_t;
typedef real_t ** matrix_t; // pointers of pointers
const string_t str = "String";
matrix_t A = new real_t*[ 10 ];
real_t f = real_t( 3.1415926 );
C++ for Scientic Computing 120/316
Advanced Datatypes
Type Denition
Remark
A real t datatype allows you to easily change between
float and double in your program.
To simplify the destinction between variables and datatypes, the
following is strongly advised:
Coding Principle No. 18
Follow a strict convention in naming new types, e.g. with
special prex or sux.
C++ for Scientic Computing 121/316
Advanced Datatypes
Predened Types
The C++ library and the operating system usually dene some
abbreviations for often used types, e.g.
uint: unsigned integer, sometimes special versions uint8,
uint16 and uint32 for 8, 16 and 32 bit respectively,
similar int8, int16 and int32 are dened for signed integers,
size t : unsigned integer type for holding size informations
best suited for the local hardware
ssize t : analog to size t but signed integer (not always
available)
C++ for Scientic Computing 122/316
Advanced Datatypes
Records
Working with vectors and matrices always involved several
variables, e.g. the size and the arrays. That results in many
arguments to functions and hence, to possible errors. It would be
much better to store all associated data together. That is done
with records:
struct hrecord namei {
hdatatype 1i hname 1i;
.
.
.
hdatatype ni hname ni;
};
By dening a struct, also a new type named hrecord namei is
dened.
C++ for Scientic Computing 123/316
Advanced Datatypes
Records
Example:
struct vector_t {
size_t size;
real_t * coeffs;
};
struct matrix_t {
size_t nrows, ncolumns;
real_t * coeffs;
};
void
mul_vec ( const real_t alpha,
const matrix_t & A,
const vector_t & x,
vector_t & y );
struct triangle_t {
int vtx_idx[3]; // indices to a vertex array
real_t normal[3];
real_t area;
};
C++ for Scientic Computing 124/316
Advanced Datatypes
Access Records
The individual variables in a record are accessed via ., e.g.:
vector_t x;
x.size = 10;
x.coeffs = new real_t[ x.size ];
If a pointer to a record is given, the access can be simplied.
Instead of * (dereference) and ., the operator > is
provided:
vector_t * x = new vector_t;
x->size = 10;
x->data = new real_t[ x->size ];
cout << (*x).size << endl; // alternative
C++ for Scientic Computing 125/316
Advanced Datatypes
Access Records
In case of a reference, e.g. vector t &, the standard access has to
be used, e.g. via .:
vector_t x;
x.size = 10;
x.coeffs = new real_t[ x.size ];
vector_t & y = x;
cout << y.size << endl;
cout << y.coeffs[5] << endl;
C++ for Scientic Computing 126/316
Advanced Datatypes
Records and Functions
Records can be supplied as normal function arguments, either as
call-by-value or call-by-reference:
double dot ( const vector_t x, const vector_t y );
void fill ( const real_t f, vector_t & y );
void add ( const real_t f, const vector_t & x,
vector_t & y );
When using call-by-value, a copy of the complete record is actually
created. For large records, this can be a signicant overhead:
struct quadrule_t {
real_t points[ 100 ];
real_t weights[ 100 ];
};
double quadrature ( const quadrule_t rule,
double ( * func ) ( const double x ) );
C++ for Scientic Computing 127/316
Advanced Datatypes
Records and Functions
In such cases, call-by-reference with a const argument is necessary
to avoid this overhead:
double quadrature ( const quadrule_t & rule,
double ( * func ) ( const double x ) );
Here, only a single pointer is supplied to the function instead of
200 real t values.
C++ for Scientic Computing 128/316
Advanced Datatypes
Application: BLAS (Version 2)
Modied BLAS function set using the previous record types for
vectors and matrices:
i nl i ne vector_t *
vector_init ( const unsigned i )
{
vector_t * v = new vector_t;
v->size = i;
v->coeffs = new real_t[ i ];
for ( unsigned i = 0; i < n; i++ ) // RAII
v->coeffs[i] = 0.0;
return v;
}
i nl i ne matrix_t *
matrix_init ( const unsigned n, const unsigned m )
{ ... }
C++ for Scientic Computing 129/316
Advanced Datatypes
Application: BLAS (Version 2)
// vector functions
void fill ( const double f, vector_t & x );
void scale ( const double f, vector_t & x );
void add ( const double f, const vector_t & x, vector_t & y )
{
for ( unsigned i = 0; i < n; i++ )
y.coeffs[i] += f * x.coeffs[i];
}
double dot ( const vector_t & x, const vector_t & y );
i nl i ne double norm2 ( const vector_t & x )
{ return sqrt( dot( x, x ) ); }
// matrix functions
void fill ( const double f, matrix_t & M );
void scale ( const double f, matrix_t & M );
void add ( const double f, const matrix_t & A, matrix_t & M );
i nl i ne double
normF ( double & M )
{ ... } // can not use vector based norm2!
C++ for Scientic Computing 130/316
Advanced Datatypes
Application: BLAS (Version 2)
void
mul_vec ( const double alpha,
const matrix_t & M,
const vector_t & x,
vector_t & y )
{
for ( unsigned i = 0; i < M.nrows; i++ )
{
double f = 0.0;
for ( unsigned j = 0; j < M.ncolumns; j++ )
f += get_entry( M, i, j ) * x.coeffs[j];
y.coeffs[i] += alpha * f;
}
}
void
mul_mat ( const double alpha,
const matrix_t & A,
const matrix_t & B,
matrix_t & C );
C++ for Scientic Computing 131/316
Advanced Datatypes
Application: BLAS (Version 2)
matrix_t * M = matrix_init( 10, 10 );
vector_t * x = vector_init( 10 );
vector_t * y = vector_init( 10 );
fill( 1.0, x );
fill( 0.0, y );
... // fill matrix M
cout << normF( M ) << endl;
mul_vec( -1.0, M, x, y );
C++ for Scientic Computing 132/316
Advanced Datatypes
Recursive Records
Records can have variables of its own type in the form of a pointer.
That way, recursive structures can be dened, e.g. a binary tree:
struct node_t {
int val;
node_t * son1, * son2;
};
node_t root;
node_t son1, son2;
node_t son11, son12, son21, son22;
root.son1 = & son1; root.son2 = & son2;
son1.son1 = & son11; son1.son2 = & son12;
son2.son1 = & son21; son2.son2 = & son22;
The above code yields: root
son1
son11 son12
son2
son21 son22
C++ for Scientic Computing 133/316
Advanced Datatypes
Recursive Records
Insert new value in binary tree:
void
insert ( const node_t & root, const int val )
{
i f ( val < root.val )
{
i f ( root.son1 != NULL )
insert( * root.son1, val );
el se
{
root.son1 = new node_t;
root.son1->val = val;
root.son1->son1 = NULL;
root.son1->son2 = NULL;
}
}
el se
{
i f ( root.son2 != NULL )
insert( * root.son2, val );
el se
...
} }
C++ for Scientic Computing 134/316
Advanced Datatypes
Recursive Records
Example for insertion:
int values[7] = { 5, 3, 1, 4, 8, 6, 9 };
node_t root;
root.son1 = root.son2 = NULL;
root.val = values[0];
for ( int i = 1; i < 7; i++ )
insert( root, values[i] );
yields:
5
3
1 4
8
6 9
C++ for Scientic Computing 135/316
Advanced Datatypes
Recursive Records
Looking for value in binary tree:
bool
is_in ( const node_t & root, const int val )
{
i f ( root.val == val )
return true;
return is_in( * root.son1, val ) ||
is_in( * root.son2, val );
}
...
cout << is_in( root, 6 ) endl; // yields true
cout << is_in( root, 7 ) endl; // yields false
C++ for Scientic Computing 136/316
Advanced Datatypes
Arrays of Records
Like any other datatype, records can also be allocated in the form
of an array:
struct coord_t {
real_t x, y, z;
};
coord_t coordinates[ 10 ];
for xed sized array or
coord_t * coordinates = new coord_t[ 10 ];
using dynamic memory management.
C++ for Scientic Computing 137/316
Advanced Datatypes
Arrays of Records
The access to record variables then comes after addressing the
array entry:
for ( unsigned i = 0; i < 10; i++ )
{
coordinates[i].x = cos( real_t(i) * 36.0 * pi / 180.0 );
coordinates[i].y = sin( real_t(i) * 36.0 * pi / 180.0 );
coordinates[i].z = real_t(i) / 10.0;
}
If instead, an array of pointers to a record is allocated:
coord_t ** coordinates = new coord_t*[ 10 ];
for ( int i = 0; i < 10; i++ )
coordinates[i] = new coord_t;
the access if performed with the arrow operator >:
coordinates[i]->x = cos( real_t(i) * 36.0 * pi / 180.0 );
C++ for Scientic Computing 138/316
Advanced Datatypes
Record Application: Sparse Matrices
We only want to store nonzero entries in a sparse matrix. For this,
each entry is stored in a record type, containing the column index
and the coecient:
struct entry_t {
unsigned column; // column of the entry
real_t coeff; // actual coefficient
entry_t * next; // next entry in row
};
All entries in a row are stored in a list, provided by the next
pointer in an entry type. A NULL value of next signals the end of
the list.
column
coeff
next
column
coeff
next
column
coeff
next
entry_t
NULL
C++ for Scientic Computing 139/316
Advanced Datatypes
Record Application: Sparse Matrices
A sparse matrix is then allocated as an array of entry lists per row:
struct sparsematrix_t {
unsigned nrows, ncolumns;
entry_t * entries;
};
sparsematrix_t
nrows
ncolumns
entries
entry_t
entry_t
entry_t
entry_t entry_t
entry_t entry_t
entry_t entry_t
C++ for Scientic Computing 140/316
Advanced Datatypes
Record Application: Sparse Matrices
As an example, consider the matrix
1 3
2 1
4 1 1
1 3
0
1
2
3
row
sparsematrix_t S;
entry_t * entry;
S.nrows = 4; S.ncolumns = 4;
S.entries = new entry_t[4];
// first row
entry = & S.entry[0];
entry->column = 0; entry->coeff = 1.0; entry->next = new entry_t;
entry = entry->next;
entry->column = 2; entry->coeff = 3.0; entry->next = NULL;
...
C++ for Scientic Computing 141/316
Advanced Datatypes
Record Application: Sparse Matrices
As an example, consider the matrix
1 3
2 1
4 1 1
1 3
0
1
2
3
1
row
0
sparsematrix_t S;
entry_t * entry;
S.nrows = 4; S.ncolumns = 4;
S.entries = new entry_t[4];
// first row
entry = & S.entry[0];
entry->column = 0; entry->coeff = 1.0; entry->next = new entry_t;
entry = entry->next;
entry->column = 2; entry->coeff = 3.0; entry->next = NULL;
...
C++ for Scientic Computing 141/316
Advanced Datatypes
Record Application: Sparse Matrices
As an example, consider the matrix
1 3
2 1
4 1 1
1 3
0
1
2
3
1 3
row
NULL
0 2
sparsematrix_t S;
entry_t * entry;
S.nrows = 4; S.ncolumns = 4;
S.entries = new entry_t[4];
// first row
entry = & S.entry[0];
entry->column = 0; entry->coeff = 1.0; entry->next = new entry_t;
entry = entry->next;
entry->column = 2; entry->coeff = 3.0; entry->next = NULL;
...
C++ for Scientic Computing 141/316
Advanced Datatypes
Record Application: Sparse Matrices
As an example, consider the matrix
1 3
2 1
4 1 1
1 3
0
1
2
3
1 3
2 1
4 1 1
1 4
row
NULL
NULL
NULL
NULL
0
1
0
0
3
1
3
2
2
sparsematrix_t S;
entry_t * entry;
S.nrows = 4; S.ncolumns = 4;
S.entries = new entry_t[4];
// first row
entry = & S.entry[0];
entry->column = 0; entry->coeff = 1.0; entry->next = new entry_t;
entry = entry->next;
entry->column = 2; entry->coeff = 3.0; entry->next = NULL;
...
C++ for Scientic Computing 141/316
Advanced Datatypes
Record Application: Sparse Matrices
Matrix-Vector multiplication:
void
mul_vec ( const real_t alpha, const sparsematrix_t & S,
const vector_t x, vector_t & y )
{
for ( unsigned i = 0; i < S.nrows; i++ )
{
real_t f = 0.0;
entry_t * entry = & S.entries[i];
while ( entry != NULL )
{
f += entry->coeff * x[ entry->column ];
entry = entry->next;
}
y[ i ] += alpha * f;
}
}
C++ for Scientic Computing 142/316
Advanced Datatypes
Record Application: Sparse Matrices (Version 2)
We can store sparse matrices even more memory ecient, without
pointers. For this, well use three arrays:
colind: stores column indices for all entries, sorted by row,
coeffs: stores all coecients in same order as in colind and
rowptr: stores at rowind[i] the position of the rst values
corresponding to row i in the arrays colind and coeffs. The
last eld, contains the number of nonzero entries.
This format is known as the compressed row storage format.
struct crsmatrix_t {
unsigned nrows, ncolumns;
unsigned * rowptr;
unsigned * colind;
real_t * coeffs;
};
C++ for Scientic Computing 143/316
Advanced Datatypes
Record Application: Sparse Matrices (Version 2)
For the matrix
1 3
2 1
4 1 1
1 3