Course Notes
Course Notes
Course Notes
June 4, 2012
These notes provide a guideline for the practical sessions based on the book C++ Primer, Fourth
Edition By Stanley B. Lippman, Josée Lajoie, Barbara E. Moo Addison Wesley Professional. Each
chapter should be read in parallel with the practical session. Some technical parts which are less
likely to be relevant for the numerical applications we are usually interested in physics will be omitted
(or only briey commented). In each section we will provide a summary of the main points to keep
together with exercises. Each exercise will be provided in a zip le with a README le explaining
what you have to do and the source code solution that I produce.
In addition to this book, I will also extract some examples and exercises from C++ by Dissection
by Ira Pohl.
Contents
1 Getting started 2
1.1 Steps to create and compile a simple C++ program . . . . . . . . . . . . . . . . . . . . 2
1.2 Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Redirection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 Control structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4.1 The if statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4.2 while and for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Comment on class types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3 Library Types 15
3.1 using declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 Library string type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3 Library type vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.4 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1
4 Arrays and Pointers 23
4.1 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2.1 Pointers and Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.3 Dynamic memory allocation & multi-dimensional arrays . . . . . . . . . . . . . . . . . . 30
7 Functions 43
7.1 Function parameter list & argument passing . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.2 The return statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
7.3 Declaration & default arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
7.4 Inline functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
7.5 Overloaded functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.6 Pointers to functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
9 Classes 60
9.1 Recap and some further features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
9.2 The implicit this pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
9.3 Some scope rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
9.4 Friends . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
9.5 Static Class members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
9.6 Constructors, copy control and destructors . . . . . . . . . . . . . . . . . . . . . . . . . . 68
9.6.1 More on constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
9.6.2 Copy control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
9.7 Overloading operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
1 Getting started
This chapter of the book is supposed to: i) Explain the basic les structure of a basic C++ program
and explain how to compile simple programs; ii) Give you a avour of some of the basic language
words so that you can write very simple programs, and an idea about the data structures to be
learned later on. It is only to get you started and everything will be discussed in more detail later.
2
1. Create a source code le (that's just a text le), for example named program1.cpp (.cpp
means C++ source le). In that le you must have at least one function which is called
main and it is the function that the system calls when you execute the program.
int main(){
...
...
return 0;
}
Everything else your program does must be called from within this function directly or indirectly.
The body of the function is inside the block delimited by the braces {}. The dots denote your
source code. This function returns an integer as indicated in its prototype by the int (the rst
line) and it has no arguments (for now). The last line is the only explicit statement in this
schematic example. All statements must end with ;. The value 0 is usually a message of success
for the end of the run of the program. In fact, in C++, the last statement is assumed if omitted,
so strictly speaking, it does not have to be included.
2. From the terminal, go to the directory where your le has been saved and compile it by
invoking
$ ./program1.exe
Comment If you need to interrupt your program (because it gets stuck or otherwise...) usually you
have to hit Ctrl+c. However this might be system dependent.
The Standard Library can be thought of as a set of functions and data types that are
coded in C++ somewhere in library les in your system. You can use functions or data
types in the library by including the relevant les in your source code.
To use the Input/Output facilities in the library you need to include a line in your source
le:
#include<iostream>
int main(){
...
when you compile your code, the relevant iostream les will then be looked for in some default
directories.
The basic statements for I/O (input output) are:
• Input: This is done through the cin object which usually appears in the code as std::cin. The
:: is the scope operator which says that cin is in the namespace std. This is a space of names
3
for the objects in the standard library, to avoid clashes with variables dened by the user. A
schematic input statement in a program would be
#include<iostream>
int main(){
std::cin >> var1 >> var2 >> ...>> varN;
...
where >> is the input operator which reads from the standard input (usually what the user types
in the terminal, or a text le if we redirect the input we will see how to do so) into the variables
var1, var2,..., varN. We can chain as many of these reads from the input, streamed in this
way into std::cin.
Comment: Some times you may want to send an end-of-le signal when entering input, to
let the program know you are done with providing input. This may be system dependent, but
usually for UNIX it is Ctrl+d and for Windows Ctrl+z.
• Output: Similarly to the input stream there are 3 dierent output streams, which ordinarily
are all streamed into the terminal, but can be streamed to data les. These are std::cout,
std::clog and std::cerr. You can think of each of these streams' purpose to output data,
log information about the run of the program for the user and error information for the user
respectively. A schematic input statement can be illustrated with std::cout:
#include<iostream>
int main(){
std::cout << espression1 << expression2 << ...<< expresionN << std::endl;
...
where each expression will be printed in sequence. Similarly to the input operator, now we have
the output operator << which feeds into the stream the expressions to print out. In addition,
there is std::endl which is the end line manipulator. It's eect is to print out the stream
imediately for the user to see (it ushed the stream). If not present it may be delayed and
printed later! It is important to use this when debugging code to keep track exactly where at
the point of the run of the program we are .
1.2.1 Redirection
The input stream can be read directly from an input le (inputle.txt) by executing your program as
Do the Exercise ExampleRedirectIO.zip: Hint: You need to include the header for the math
facilities of the standard library #include<cmath> so that the log(x) function is known.
1.3 Comments
Lines of code are commented by placing a double slash at the begining of the line
4
For blocks of lines one can use
if(condition)
statement;
where if the condition is true then the statement is executed. If the code to be executed has several
statements then we must use a block in braces:
if(condition){
statement1;
statement2;
}
There is also a more general form where we can include an arbitrary number of elseif conditions to be
tested in case of failure and an optional else statement. The most general form is (schematically)
if(condition1){
statement1;
statement2;
}
else if(condition2){
statement3;
...
}
else if(condition3){
statement5;
statement6;
}
else{
statement7;
statement8;
}
5
while(condition){
statements
}
The for statement is similar except that there is an integer iteration variable of type int which
keeps being increased. The general form is
6
2.1 Primitive built-in types
In addititon to these types there is the void type which is used for example as the type returned
by a function which acts on some variables but does not return any value. Note that the size (in bits)
may depend on your machine achitecture. For example, in 64 bits machines usually a double holds
more digits.
The types int, bool, char (and the corresponding long or short versions) are known as integral
types and they hold integers, boolean (true or false) and characters (each character variable holds
a number corresponding to a character).
Types can be signed or unsigned. This is achieved by adding unsigned before the corresponding
type.
Note: You should be aware of the limits of the representation of the types above. If you enter
a value out of range (for example an integer that has too many digits), you will get a value that is
completely dierent. When coding you should be careful to make sure this does not happen otherwise
you will most denitely get wrong results and the code may still compile and run!
Regarding oating point number, the type float is usually not precise enough so it is standard
to use double for most numerical applications (though the longer version may be necessary in some
situations).
7
The action of each of the arithmetic operators depends on the type to which it is applied. For example
division of integers yields the integer part of the result.
2. Through an escape sequence which starts with a slash \. One can have special escape sequences
for special characters such as:
8
Or a generalised escape sequence for any character in the character set, which consistes of a slash
with the number of the corresponding character
For example for output (or later on when we introduce strings), it is useful to note how to write a
literal expression for an array of characters, i.e. a string. String literals are represented in double
quotes . Some examples are
All string literals have in addition a null character at the end of the string.
Long forms, both for characters and strings, are specied by adding L before the corresponding
literal. To concatenate string literals one just writes them adjacent to each other with white space in
between.
2.3 Variables
We have used already variables in some examples before. Variables are objects whose values can
change through the execution. They are declared in the program by specifying the data type (which
determines the amount of storage needed) and the name of the variable.
9
Another rule is that an identier cannot start with a digit. Underscores are also allowed. The following
tables show common conventions for identiers, together with bad practices and illegal names
10
2.3.2 Declaration and Initialisation
Some examples on how to declare variables are as follows:
where several variables of the same type are declared on the same line, separated by commas.
Objects can be initialised in the following ways:
For more complicated class types, we will see later that there may be more forms of initialising the
corresponding class objects. That is dened in the class itself by something called the constructor.
Initialisation may be done for several objects in one line, or with an arbitrarily complicated expres-
sion, including functions. For example
where in the second line a there is a function apply_discount with two arguments, which will return
the value to initialise sale_price.
There is a way of declaring a variable without dening it (i.e. without reserving space in memory
and initialising it). This just declares the presence of the variable somewhere in the program which
will be dened there. This is done by using the extern keyword.
where we note in the last line that if we asign it a value then if becomes a denition as well. This is
useful to declare variables which are common to dierent les.
1. Writes a formatted header paragraph of text with several lines informing the user about: the
author of the program, the year and version of the program, A short abstract of the program
describing what it does (look at the most suitable characters from the table in the previous
sections, to format the text).
11
2. Prompts the user for an input integer and then computes the factorial of that number, using a
for loop. The program should report an error if the input number is negative and print an error
message (try to use the bell character to see what happens).
3. Test the program for increasing values of the input. Can you explain what's happening. Repeat
the calculation using double and try again.
A constant is local to the scope where it is dened. To make a constant visible to several les, it
should be declared as external in all the les where it is used and dened in one of them. For example:
Exercise Write a program which computes the area under a parabola in a interval using a trapezium
rule. The program should:
1. Use a starting step of integration and compute a rst estimate, and then repeat with a while
loop with a smaller step while the error estimate (obtained by comparing to the previous value)
is below a threshold.
2. You should dene constants in your code which hold: the maximum number of subdivisions of
the integration domain, minimum number of subdivision (to start o with), and the goal for the
relative error.
3. The program prints the result with error estimate and error compared to the exact result.
4. Always comment your program with a lot of detail so that others reading it will quickly under-
stand it.
2.5 References
A reference is a compound type (i.e. dened in terms of another type) which works as an alias, by
referring to a object of the corresponding type. Operations on the reference act on the object it refers
to, so a reference is just another name for an object. Because it always has to refer to a specic object,
a reference must be initialised when it is dened.
A reference declaration and initialisation requires adding the character & attached to the name of
the alias:
12
For example if we add to refVal or assign it to a variable ival, it will be modied or used respectively.
The rule for declaration is to attach the & character to the reference name. For example in multiple
references
The header of the function can appear as a declaration of the function before it is dened, with a
; at the end
#include<iostream>
int main(){
printme();
}
void printme(){
std::cout "Print" std::endl;
}
will not compile because the function printme has not been declared before being used in main. The
correct form is to include its denition before, or the prototype:
13
#include<iostream>
void printme();
int main(){
printme();
}
void printme(){
std::cout "Print" std::endl;
}
This simple example also illustrates the point that a function may have no arguments and it may not
return anything (void).
The connection with references, appears when we consider functions with arguments. If the argu-
ments of a function is a data type which is not a reference, then the code wil create a copy of that
data type to be used in the function. This is called pass-by-value. However, at times one may want to
write functions which act on a variable and store the value of the computation directly in it without
having to create a copy (this is much more ecient for more complex data types which take up a lot
of memory space). This is known as call-by-reference.
An example of call-by-value:
2.6 Typedef
Another type of alias is typedef. This allows to dene a synonym for a certain data type which may be
convenient for readibility, or to simplify long types to make them easier to type. Examples of typedef
denitions are
14
and their usage later in the code
#include "headername.h"
This form in quotes searches for the header le in the current directory. The form we have used for
example for #include<iostream> searches in the system pre-dened paths. In the form with quotes,
you can also specify a full path.
The compilation of several source code les can be done as follows
Exercise
1. Adapt the program compute_sum.cpp so that n is called by reference. Check what happens now
to the value of n.
2. Re-write one (or both) of the programs which compute the factorial or integral of a parabola,
with a function for each of the operations (taking as arguments either the integer or integration
limits respectively). Place the function in a separate source le and write a header to be included
in the two source les. Try to compile.
3 Library Types
The C++ standard library denes convenient library types for strings and vectors, (the corresponding
built in types arrays and pointers will be seen later). These come with iterator types for an easy access
to elements. These library types are higher level in the sense that we only need to know the operations
they support without worrying about the way they are stored in memory.
using namespace::name;
For example
15
where a using declaration was written for each name. In some cases however, it may be convenient
to declare not only some names in the namespace, but the entire namespace. For the standard library
this is particularly useful because you end up using a lot of names which are dened in the library and
it may become cumbersome to write std:: before each name. This is done by declaring the entire
namespace
#include <string>
using std::string;
or alternatively to the last using declaration, the entire standard namespace.
The interface with cin, cout is such that each string read or write corresponds to a chain of
characters with no white space. A white space signals the end of the string. For example the following
code reads the rst string which is typed before a white space and returns it
16
One can read an unkown number of strings as follows
To read an entire line we can use the getline string operation which reads a line until the newline
character is introduced (which is not stored in the string)
Notes
• The type returned by .size() is actually not an integer but a companion type of string. This is
necessary because you can easily read an entire le into a string whose size cannot be stored in a
17
regular unsigned int. That special type is string::size_type and it should be used instead
of integers to avoid runtime errors.
• The + operator must have at least one string as argument (if all literals it does not work)
• The subscript of a string (when fetching an element) starts at 0. One can change the character
in position n by the character x for example through str1[n-1]='x'
There's a set of functions dened in the cctype header to process individual characters of a string:
Exercises:
1. Test the string input and output examples in this section
18
2. Do the following exercises from the C++ primer:
Examples
19
Operations
Just as for strings for each vector of a given type there is a size type vector<T>::size_type.
One can add elements with the push_back() operation:
Note: Using the size() function is good programming practice rather than remembering the size
of the vector (since they can grow).
Exercises
1. Write a program to declare several vectors of diferent built in types and the string type, in all
the ways on the table at the beginning of the section and print their values.
20
3.4 Iterators
They are built in library types which allow to navigate through the elements of a vector. These are
in general safer than using integers and subscripting, for example, because they prevent accessing
elements outside the bounds of the vector (these can be serious bugs which are hard to nd because
the code may compile and run apparently normally).
There is an iterator for each container type, for example for a vector of integers:
vector<int>::iterator iter;
Each vector container denes two functions which return a value with the type of the corresponding
iterator, they are the begin() function
*iter = 0;
This statement, for example, sets the current element that iter refers to, to zero.
• Increment: This moves the iterator to refer to the next element ++iter;
• Positive or negative shift by an integer: iter+n or iter-n returns an iterator of the same type
referring to a position ahead or behind the current element.
There is also a difference_type similar to size_type which holds the distance between two iterators
(and it is guarateed to be large enough for the largest distance between any two iterators) so
iter1-iter2;
21
returns a difference_type.
Here is an example of a comparison between subscripting
const_iterator type : This is another type dened by each container which is a read only iterator,
in the sense that it refers to the elements of a vector (and thus can equally be used to navigate through
the vector), but cannot be used to change the value of the elements of the vector. The dierence is
that when we dereference a const_iterator using *, we get a reference to a constant type. The use
of this type of iterator may be safer in some read-only situations.
Examples:
• Standard usage:
• An error:
• Possible confusion:
• More examples:
22
Exercises
4.1 Arrays
Just like vectors they are compound types consisting of a type specier and a dimension. The type
can be any built in data type or class type. With the exception of references (there are no arrays of
references), the element type can be compound as well.
To initialise the array we need to specify the dimention in brackets [], either with a literal constant
or an expression which yields a constant which must be known at compile time:
23
Elements can be initialised explicitly by providing a comma-separated list in braces, otherwise the ele-
menst are initialised as the corresponding types, or undened. When initialised explicitly the compiler
may infer the dimension of the array if not specied.
If the dimension is specied and the list is smaller, then the remaining elements are initialised to zero
or the default constructor if a class type
For arrays of characters, since string literals contain the null character at the end, one has to be careful
to take that into account in the dimension of the array (if specied). Examples:
Warning: There is no copy or asignment for arrays (it has to be done through a loop element by
element).
Operations on arrays We access elements by subscripting in the same way as for vectors. There
is an integer like type size_t which is the correct type to be used for the index. Similarly to vectors
the index runs from 0 to size-1. It is the responsibility of the programmer to make sure that the index
is within the bounds.
24
Do the following exercises from the C++ primer:
4.2 Pointers
These are the lower level equivalent of iterators, for arrays. They are compound objects. A pointer is
simply an object that points to the address (location) in memory, which it holds. A big dierence is
that, unlike iterators, pointers can point at a single object, not just the elements of a cointainer like
iterators do.
Initialisation
Pointers are declared by adding * together with the identier we want to declare as a pointer variable
(the * must come together with every pointer variable declared, not with the type specied for the
container!).
Note: Pointer declarations make more sense when read from left to right!
25
The values of a pointer can be: i) a memory address, ii) one past the end of an object, iii) or
zero (means no value and should be used instead of uninitialised pointers which will produce VERY
dangerous bugs),
There are only four kinds of values that may be used to initialize or assign to a pointer:
1. A constant expression with value 0 (e.g., a const integral object whose value is zero at compile
time or a literal constant 0)
The reason why types must match is because pointers provide indirect access to an object, and the
operations which are supported are determined by such type (hence the types must match).
void pointers
This is a special case when the pointer does not have a type specication and it simply holds an address
in memory. Thus this is used only to pass addresses around through functions or comparison to other
pointers.
26
Exercises
Operations on pointers
The basic operations are: Dereferencing (similar to iterators),
27
Note: Do not confuse pointers with references. Unlike the last line of the last example, references
cannot be re-bound, i.e. we cannot change a reference to refer to a dierent object by asigning it after
it is declared (and thus initialised), so for example
has changed the value of ival to be the same as ival2 and the references stayed the same.
Pointers to pointers
28
or if we want to point to another element
If a pointer points to an array, it is equivalent to use this arithmetic or to subscript as for the
corresponding array, i.e.
Important: Note that dereferrencing and pointer arithmetic do NOT commute (parenthesis are
essential!) so
Note: It is up to the user to be careful enough not to go beyond bounds to invalid addresses.
The computation of the pointer to one position past end is also possible (as for iterators) however
it has to be controled by the user!
Example
Print an array using pointers
29
Pointers to const, const pointers and const pointers to const
These are pointers which cannot change the value of the object they point to. They can be asigned
the address of an object which is not a const but they cannot change their value. They are usually
used in argument denition of functions if we want to ensure they are not changed
One can also have constant pointers (i.e. the address they point to, cannot be changed and must be
initialised when declared)
which however can be used to change the value of the object they point to
Finally, one can have const pointers to const where neither the pointer nor the object pointed at can
be changed
Exercises
1. Write code to change the value of a pointer (test various types). Write code to change the value
to which the pointer points (test various types).
2. Write a program that uses pointers to set the elements in an array of ints to zero.
int *pia = new int [10]; // The array is uninitialised for 10 integers
30
string *psa = new string [12]; // The array is value initialise for class type objects
by the default constructor
int *pia2 = new int [10](); // The array is value-initialised with 10 integers to zero.
This is done through the parenthesis for built-in types
where we have noted the dierence between uninitialised and value-initialised arrays. This distinction
is important, for example for dynamically allocated arrays of constants, which must be value initialised
using the parethesis for built in types, otherwise we get an error.
The examples above are for arrays of xed size determined when the code is written. However, the
big advantage is to be able to determine the array size at run time:
size_t n = get_size(); //Let's assume there is a function which determines the size of
the array while running...
int *p = new int [n]; // The array will be uninitialised with size n as determine while
running the code
//Then the rest of the program follows and uses the array...
We should note that if the size determined at run time is 0, the code still works.
The nal step is to free the memory after we are done with using the array. A good rule of thumb
is to think about the point in the code where the array will be freed, at the same time as we write its
declaration to allocate it, and write the delete statement imediatelly to avoid forgetting. For the last
example the statement is
delete [] pia;
For each new statement we must have a delete statement like this one to free the memory.
31
Multidimensional arrays
These are arrays of arrays and are initialised by adding another bracket [] with the dimension. Let's
look at some examples.
Note, in particular, the very dierent behaviour of the last two examples! Subscripting is done as
usual, with one pair of brackets for each dimension.
One can have multidimensional arrays with any number of dimensions. We should keep in mind
that multidimensional arrays are actually arrays of pointers.
Actually it is useful to think about multi-dimensional arrays in terms of pointers. This allows for
an easy dynamic allocation of multidimensional arrays.
For example, if we need to allocate a n by n matrix of doubleswhere n is determined at run time,
one would write
double ** matrix = new double * [n]; //Declare a pointer to pointer of type double, and
allocate n pointers to double one for each row of the array
for(size_t i = 0; i!=n;i++){
matrix[i]= new double [n]; //Looop over the dynamically allocated rows and allocate
space for the columns
for(size_t j = 0; j!=n;j++) //Looop over the dynamically allocated rows and columns
to initialise to the sum of their positions
matrix[i][j]=i+j; //Looop over the dynamically allocated rows and columns to initialise
to the sum of their positions
}
In the previous example one can have more nested indices i.e. pointers to pointers to pointers... It
is important to free the memory after it is used. The easiest way is to copy the declarations with the
32
new statement, invert the order and replace the new statements by delete.
for(size_t i = 0; i!=n;i++)
delete [] matrix[i];
double delete [] matrix;
The only dierence is that we do not specify the type of the pointer for the delete statement.
Exercises
1. Write a program which reads the elements of two matrices of doubles from the standard input,
determining the size of each matrix from the rst line, and then returns the elements of the
product and dierence of the two matrices.
2. Write a program to read n square matrices (number of matrices and dimension specied by the
user), compute all possible pairs of products and print the result for the user.
• An interface : Which consists on the operations/functions that the user is able to execute on
class types and it may also contain variables (data) that the user may manipulate directly.
• An implementation : Which are typically variables (data) and functions which are hidden when
the class type is used but are essential to make it work.
This paradigm of separating the interface from the implementation is useful, because we may change
the inner workings of the class (its implementation) without having to change the code that uses it.
There are two ways of dening a class using two dierent keywords. The most common one in
C++ is the class keyword, but one can also use the struct keyword which is inherited from C. The
only dierence between the two is the default behaviour. Let's present them and contrast.
A class is dened usually in a separate le. The general structure is
class ClassName{
public:
//Declarations which define the operations, the functions which are public for the user
to use, and the data types which the user might access directly
private:
//Hidden declarations of data types which hold the class data, and functions which are
used to make its operations and functions work.
};
The set of operations and data types dening the class are called the members of the class. The
keywords, public and private are access lables, which indicate whether the members can be accessed
directly in the code or not. The rule is that whenever an access lablel is specied, it is valid until the
next line with an access label (which changes the access again). The default behaviour for classes is
that when the rst access label is omitted (at the top of the denition), is that whathever is declares
there is set to be private. The struct keyword is exactly the same as class except for this default
behaviour which is opposite, i.e. if we don't specify the rst access label it is implicitly public.
One can see in this structure the separation from the interface and implementation, so the abstract
operations and functions that characterize the data type we want to create, are separate from how
those operations are implemented in the code which denes the class
33
The declaration of the data members are done exactly in the same way as in normal code, so in
a rst approach one can use a class simple as a way of encapsulating various data types to make a
convenient data sructure. In that case the use of the struct keyword may be more descriptive (since
there is only public data and no acces labels are necessary).
Let's look at a rst example which denes the coordinates of a point in 2D:
Now if we want to declare several 2D points in the main code we just declare it as a usual variable
p1.x=2.3;
p1.y=-4.2;
double distance = (p1.x)*(p1.x)+(p1.y)*(p1.y);
An equivalent denition using the keyword class would be (note how the keyword public is now
mandatory)
class point {
public:
double x,y;
};
Similarly to the built in data types, we can have pointers to structures. Then to access members we
have to dereference rst (with parethesis) and use the dot operator after. However there is an operator
(arrow operator ->) to avoid having to do so each time:
34
In addition to having data members, in C++ one can also have member functions. It is more usual to
use the class keyword in C++. So we will do so from now on and start by illustrating a class with
public members functions. Let's re-write the previous example:
35
Exercises:
1. Design a C++ structure to store a dairy product name, portion weight, calories, protein, fat,
and carbohydrates. Twenty-ve grams of American cheese have 375 calories, 5 grams of protein,
8 grams of fat, and 0 carbohydrates. Show how to assign these values to the member variables
of your structure. Write a function that, given a variable of type struct dairy and a weight in
grams (portion size), returns the number of calories for that weight.
2. Write a class point that has three coordinates x, y, and z, where the coordinates are private.
How can you access the individual members? Create a function which is external to the class
and computes the dot product between two point variables (pass the points by reference as we
have seen some weeks ago).
The ++ and operators One point to keep in mind about the increment (++) and decrement
() operators is that they come in two forms:
• Prex form : ++i or --i: This will rst increment the value of the variable and then it will
return the incremented value.
• Postx form : i++ or i--: This will return the value of the variable and then increment it.
If this behaviour does not make a dierence, then the rst form is preferred. This is because for the
second form, the original value of the variable has to be stored in addition to increment it, so that
such value can be returned.
36
and examples
Comma expressions These are series of statement which are executed sequentially from left o right.
The value returned by the expression is the one of the rightmost expression. Example:
Precedence It is very important to keep in mind the rules of precedence and how operators associate
with each other in a compound expression. The default precedence rules can be overriden by using
parenthesis, and such should be done whenever in doubt. For the usual arithmetic operators, the rules
are the usual ones. However, for logical, assignment operators, etc... what you think the behaviour
is, may be very dierent from what it actually is! A table of precedence rules is in section 5.10 of the
C++ primer which you can consult.
Another point to keep in mind is that the order in which operands of an operator are evaluated is
not always necessarily dened, for example
The new and delete statements We have already seen how these statements can be used to
dynamically allocate memory for arrays. These should be important statements, for class design so
let's dissect in more detail their properties:
1. The new statement, returns a pointer to a newly allocated object of the type that is specied.
Thus this can be used for any data type (not just arrays), including single objects.
One can initialise the newly allocated object using direct initialisation
otherwise, the default is used. However it is usually a bad idea to rely on this behaviour! If we
want to use explicit value initialisation we add ()
37
2. As we have seen before, as a general guideline, for every new statement we should have a delete
statement, to free the memory as soon as we do not need it anymore. This must always be
applied only to an object that has been dynamically allocated. Examples:
After deletion, the value of the object becomes undened, though a valid address is still held in
the pointer!
3. One can dynamically allocate constant objects and delete them. The rule is that the object must
be initialised when created, which means that for built in types we must initialise explicitly, and
for class types we may omit initialisation because the default constructor is applied:
Type conversion When a certain operation involves dierent types, one of the types may be con-
verted to the other one before the operation can be carried out. The main arithmetic conversions can
be understood from looking at some examples:
There is little point on memorising all the conversion rules in mind, so I refer to section 5.12 of the
C++ primer for other conversion rules involving other built in data types and class types.
Explicit conversion may be achieved which is called a cast. The general form is
cast-name<type>(expression);
where cast-name is one of the following cast operators:
38
• static_cast: This performs a conversion explicitly at compile time.
• const_cast: It removes the constancy property of the value returned by the variable. An
example is if we have a function which contains an argument which is not of const type which
it doesn't change, but we want to pass the value of a variable which is of const type, so we need
to remove the constancy property. Then form example
• dynamic_cast and reinterpret_cast: These will be covered later. The rst is the equivalent
of the static_cats operator for dynamically allocated objects.
Exercises
1. Write a program that writes the size of each of the built-in types. Also test other types we have
been using such as class types from the standard library.
2. Write a program that dynamically allocates a two dimensional array and try to obtain its size
information using sizeof.
39
An important behaviour to keep in mind is that execution continues to test all the cases unless the
break statements are present. This should always be present unless you really mean to do so, because
it may create unexpected errors.
An example of intentional absence of break is:
or equivalently
In addition, just as for the else statement, we may include a default line to be executed:
40
Some comments on for loops We have already seen for loops in some detail. Some further rules
to keep in mind are:
• Any part of the header of a for loop can be omitted by using a null statement. Null statements
should always be commented!
• Multiple denitions (of the same type though) and multiple expressions may be included through
Comma statements. For example:
The do while statement This is similar to the for loop except that the test statement is only
performed after each cycle in the loop so it is executed at least once. Because of such, loop variables
(or any variable that is tested in the statement) have to be dened before the loop. Also note that the
block always ends with a semi-colon ;
Example:
The break statement This stops the nearest enclosing while, for, do while or switch loop. A
if statement, only when the
break statement is only legal when it is inside a loop. It can be inside an
41
if itself is inside a loop.
Example:
The continue statement This terminates the current loop iteration skiping directly to the next
run around the loop. Example:
Exercises .
42
7 Functions
Functions are essential to any elaborate project. They can be thought of as a way of extending the
capabilities of the built in operators, such that they can take an arbitrary number of operands.
Parameter list Similarly to the local variables, the parameters of a function provide local storage.
The dierence is that they are initialised from the arguments that are passed when the function is
called. The number and type of arguments passed to a function must match the function denition or
must be given by an expression that can be implicitely converted to the correct type (we will see an
exception below). The types are checked at compile time, so if they do not match an error message
will be issued.
Example of errors :
int myreturn(int i1){ // Definition of a simple function
return i1;//which returns its integer argument
}
int main(){
myreturn("abcd"); //error... no rule to convert char* to int
int a=1;
double f=1.2;
myreturn(a,f); //error... wrong number of arguments
myreturn(); //error... arguments expected
myreturn(a); //ok
myreturn(a*2-1); //ok
myreturn(f); //ok
return 0;
}
We will see how to pass functions themselves as arguments, later on.
Argument passing As mentioned above, when the parameter of a function is not of reference type,
then the corresponding argument is copied to the function when it is called (so the original argument
cannot be accessed/changed by the function). Otherwise, if a reference, then the parameter is just a
name for the argument which is used directly without copy.
Non-reference parameters:
• Pointer parameters - What is passed to the function is a copy of the pointer variable which
points to a certain object. In this case the function can change the value of the object that the
argument pointer points to, but not the value of the pointer, since what is used in the function
is a local copy of the pointer:
43
so when we call ...
For example to avoid the value of the object the pointer points to, to be changed, we should
declare it instead as a pointer to const.
• const parameters - Because of the rule that a local copy is created, we can pass either a const
or non-const argument because a local copy will be created anyway (the reverse is also true).
Thus in the case of non-reference types, const can be seen just as a way of ensuring the value
of this argument is not changed as the function is executed.
Passing arguments as a copy is not enough if we want to: i) change the value of the argument, ii) pass
a large object, which may be inecient, iii) pass an object for which copy is not possible, etc... In
such case, pointers or non-reference paramaters will be necessary.
44
avoid copy but have the same behaviour. For example:
In general, a rule of good practice is to declare references which are not supposed to be changed as
const.
Another important rule for reference arguments, is that we cannot pass an expression or an
argument that can in principle be implicitely converted to the data type of the reference. Let's look
at some examples:
Passing a reference to a pointer In this case, we are able to change the value of the pointer,
and the obect it points to. For example, the swap function would become:
45
Exercises:
Vector and other container parameters It is usually good practice to pass an iterator to the
vector (or another container) to be processed, as an argument to a function, instead of the container
itseld. If the container must really be passed, then it should be done by reference. An example is:
Array parameters Because arrays cannot be copied, they are passed using pointers. Be aware that
whenever the parameter of a function is an array, a pointer is being passed, for example:
the two second syntaxes are usually not good practice because they may be misleading, in particular
the dimension in the third example is ignored by the compiler! The rst form is the one that is good
practice, because it corresponds explicitely to what is being passed.
Similarly to other data types, arrays are passed as reference or non-reference types, and const or
non-const.
Non-reference array parameters These are always converted to a pointer to the rst element
of the array which is then copied. As for other types, if we do not want to change the value of the
array elements such parameter should be dened as a pointer to const
46
Passing an array by reference In this special case, the size of the array is not ignored! This is
because a reference to the array is passed, so the array is not converted to a pointer. The compiler will
check the size of the parameter and the argument being passed at compile time (which must match).
For example:
Note that the parentheses are necessary in (& arr)[10] because the subscript operator has higher
precedence.
Multi-dimensional arrays These are actually arrays of pointers, so it is clear to pass them as
such:
Note: It is up to the programmer to manage arrays passed to functions. The most common way of
preventing errors such as going beyond bounds are:
• Put a marker in the last element of the array itself which denotes the end of the array (an
example are C-style character strings which contain the termination character).
• Use strategies similar to the standard library : Pass pointers to the begining of the array, and one
past the endo of the array
47
Exercises:
• Go back to the integrate parabola program. Create a function which takes as argument iterator
variables to run through a vector which contains several intervals of integration, and which uses
such intervals to repeatedly use IntegrateParabola and average out the result of all integrations.
A function of this type can also return a function which also has a void return type:
48
Return a value of the function return type: .
return statement;
This must be an expression which is either of the correct type, or can be implicitely converted. The
only exception is for the main() function, for which a return 0; statement may be omitted since the
compiler implicitely inserts that.
One can also return a reference, with the WARNING that this cannot be a local object!
The same rule applies for a function which returns a pointer, i.e. it should NEVER return a local
pointer.
An interesting property of returning references is that they are Lvalues:
Note that functions can recursively call themselves (except for main()):
49
Exercises .
Default arguments may be an expression which can be evaluated at run time, and may be specied
in the prototype or function denition. It is however best practice to place it in the prototype in
headers, because the default can be specied only once.
An example of some of the rules above:
50
then
Exercise: An important habit for any programmer is to be able to come up with test programs for
the features (s)he is trying to implement.
1. Come up with a test program that uses a function to perform a mathematical operation, contains
2 arguments which must always be suplied and 2 others that are optional and are related to
controlling the precision of your calculation. Write the main function such that it tests the case
when none, 1 or 2 default arguments are supplied.
2. Think about a repetitive short function that may be used a lot in a specic calculation and write
and test and inline function.
• If both return type and parameter list match then it is just a redeclaration.
• If only non-reference parameters dier by being of const type, then it is just a redeclaration.
One should be careful not to abuse overloading, because the extra information by using dierent
function names may be useful and make the program less obscure.
Let's look at some examples and the way the matching is done (this can become tricky):
51
7.6 Pointers to functions
This is an extremely useful construct that allows for example to dene functionals acting on functions.
For example, if we want to build an integrator based on a numerical integration rule, we would like to
be able to pass a function as an argument to be integrated.
A function pointer, points to a particular type in the same way as a pointer to a data type. It
points to a function with certain return type and paramater list types regardless of the function name:
the parenthesis are essential, otherwise the star * would refer to the return type which would be a
pointer. Because repeating this type of declarations may become cumbersome, creating a typedef by
adding the keword to the previous example is useful. The name of the dened type is the one assigned
to the pointer to function declaration in that case.
Notes:
• the name of a function is a pointer to that function, so if we use the name without calling the
value is a pointer to the function which returns the address of the function.
• a pointer to a function can only be asigned a pointer of the same type or an expression which
has a value 0, so there is no conversion between dierent type pointers to functions.
52
• One can also return a pointer to function. The explicit declaration is very confusing, so the best
strategy is to write a typedef
• One can asign an overloaded function to a pointer to function, as long as there is a denition
with matching argument type and return type.
Exercises
1. Write a function which performs the matrix multiplication of any two matrices for which the
product is dened and overload it for all possible built in types that can represent the matrix
entries. In your main le use both, arrays which are entered directly, and arrays that are entered
by the user, using dynamic memory allocation.
2. Re-Write the Integrate Parabola exercise, so that one of its arguments is a pointer to function,
that is the function to be integrated. Write several functions to be integrated using the simple
integration rule and write a main program which keeps calling the new Integrate routine and
returns the integral of the function over the interval.
53
8.1 Condition states
All streams have members (data types or functions) which hold/retrieve information about the state
of the stream. We have already seen such an example when testing cin for true or false. However,
it is possible to obtain more ne grained information:
54
Here is an example of interrogating a stream and managing errors:
1. endl is used; flush is used; the unitbuf manipulator is set, so that after each output line, it is
ushed.
55
4. If we tie the output stream to an input stream in which case whenever the input stream is used,
the output stream is ushed.
Examples:
These support all the operations in iostream with the addition of open and close functions, and their
own constructors.
To use a le stream, we need to dene/declare input objects (just like cin) and output objects
(like cout, cerr or clog).
One can either declare rst the stream objects we want to use as:
or use a constructor that does both (we are assuming ifile and ofile are strings, note that they
msut be converted to C-style strings!):
56
Rebinding (re-using) a le stream.
Neglecting the clear would cause only the rst le to be read, since the stream would be in an
error state or end-of-le. If we re-use a stream we must always clear it!!!!
where out, app, trunc are exclusive of ofstream, fstream, and in is exclusive of ifstream, fstream.
57
Example:
Finally an example of a standard function for opening a le, bind it to an input stream and checking
the state.
Note that only the functions which are common with iostream are allowed, not the ones that fstream
supports! The extra operations supported are:
58
Using stringstream.
Using stringstream for conversion/Formatting We can use string streams to convert numeric
values to a string:
EXERCISES:
59
9 Classes
We have already seen that classes allow us to dene our own data types, and member functions
associated with that abstract type we wish to dene. They also allow for a way to encapsulate the
details of the implementation, so that once we have dened a class, it behaves as an independent unit
which is easier to debug and optimise, so that it can be later used by any program. Another advantage
is that the implementation and the interface are done separately, so that if we have to change the
implementation due to bugs, performance, etc... the client code stays the same. In this section we will
look at some further features of class denition, and how to control further the data types created and
their behaviour.
There are several things in this class that we have already talked about, such as the keywords public
and private and the denition of member data and functions. Anoter thing to recall is that if a
function is const, it means that it will not change the values of the data members of the class.
The constructor One novelty of the example above is that there is a member function with the same
name as the class. This is called the constructor and it is a function which can take no arguments or a
number of arguments. Besides the special property of having the same name as the class, it contains a
60
colon and an initializer list before the function arguments, which denes the initial values of the data
of the class. Finally a constructor has no return type.
Constructor functions can be overloaded, and the denition that it used is determined by the
arguments provided to the constructor when an object of that class type is declared.
Example of overloading:
Note: Similarly to the constructor, other member functions can also be overloaded, provided a
suitable denition is provided. Another point to keep in mind is that member functions which are
dened inside the class denition are implicitely inline. It is also possible to declare them explicitly
inline as usual.
Besides being called in the forms above, we can also call the constructor to dynamically allocate
memory by using the class name
• Firstly it provides a better way to name types which have to be resolved for several scopes.
• Secondly, the user of the class may just use those internal types dened in the classs without
the worry of how they are implemented, and also the implementation itself may change later,
without the code using the class. For example:
Forward declarations It is possible to declare a class without deninig it. This is called a forward
declaration
class ClassName;
61
This can be used only in a limited number of ways, since their members are not yet dened. Forward
declarations can only be used to dene pointers or references to that class type.
Exercises .
62
and the following member functions:
The this pointer is const, so the object address to which this is bound cannot be changed, though
the object itself can be changed. If the member function is itself const, then neither can be changed.
Using the class denition above, now we can chain a serie of actions on the same screen:
63
and an example of when we have to be careful with const functions, if we dene the function display
as const
This can be solved by overloading the function:
Note that the return type is not after such scope resolution, so it may not be in class scope! For
example:
64
1. First denitions within the current class scope which are before the use of the name are
looked up.
2. If the previous step fails, types which are dened in the current class scope after, and types
which are dened in global scope, before the class denition are looked up.
Example:
2. Then check declarations for all members of the class to which the function belongs.
3. Finally if not found, declarations that appear before the member function are checked.
Exercise .
9.4 Friends
This is a mechanism which allows a class type, or function, which are not members of the class to
have access to the private members of the class. Such friends are declared anywhere in the class by
writing the keword friend followed by the class or the function name which are granted access to the
private members of the class.
Example: Consider the Screen class introduced before. We might want to have a window manager,
that manages several Screens on a display, which will have to have access to private members of the
Screen class
65
so that the window manager may use the private members as follows
A friend can be a non-member function, an entire class, or a function of a class that was dened
before. It is usually a good idea to restrict friend access, so the latter is sometimes useful. An example
of making a function which is member of another class, a friend would be in the previous example, to
give access only to a function of the window manager:
An important point to keep in mind in this example, is that we must be careful about the order
in which these denitions are provided. This is because a friend function, must know about the class
members with which it is friend. So in this example, we would need:
• rst Window_Mgr would have to be dened, together with a declaration of their members, so that
the Screen class can declare relocate as a friend
• Then Screen has to be dened, before relocate is dened, because the members of Screen will
have to be known for the denition of relocate
• Finally relocate can be dened.
Another important note regarding overloaded functions which are friends of a class. The rule is that
for every overloaded version, a friend declaration must be present. Example:
Exercises .
66
In some sense, these are members which are global to all class objects, while not being accessible
generically in the user code. Static objects can be data or member functions. static member functions
do not have a this pointer because they are not bound to any particular object.
The main advantages of using static members rather than globals are as follows:
1. The name of a static member is in the scope of the class, thereby avoiding name collisions with
members of other classes or global objects.
2. Encapsulation can be enforced. A static member can be a private member; a global object
cannot.
3. It is easy to see by reading the program that a static member is associated with a particular
class. This visibility claries the programmer's intentions.
Let us look at an example of static member declaration with public and private members, where the
class is bank account with an owner:
Note:
• Just like other members, static members can be accessed inside the class denitions without the
scope resolution operator.
• The static keyword appears only inside the denition in the class, where the member is declared,
and if the member is dened outside, the keyword is NOT repeated. In the example above:
• Static members must be dened exactly once, because unlike other members they are not ini-
tialised by a constructor.
• Static data members cannot be initialised in the class denition like other data members, with
the exception of integral static const, if a constant expression
67
• Another interesting property is that, since static members are not part of the object, but instead
are shared, they can be of the class type itself
68
• If the initialiser list is not provided, the class will initialise its data members using the default
constructor for each member.
• The order in which members are initialised follows the order of declaration in the class. This is
relevant when a member is initialised in terms of other members.
• As for any other function, the constructor can have default arguments. This can be particu-
larly useful to create a class with default constructor where one of the members has no default
constructor
The default constructor If there is no default constructor being dened, the compiler tries to
dene a synthesized default constructor. It is usually good practice to dene the constructor instead
of relying on this default behaviour whenever other constructors are dened. The default constructor
is not called with parethesis!!!, only the object name is used:
Implicit conversions Another useful behaviour to be aware of, is that a constructor with a single
parameter, denes an implicit conversion from the parameter type to the class type.
This behaviour can be avoided by declaring the corresponding constructor explicit, the only restric-
tion being that this keyword can appear only inside the class denition:
69
so that when used...
The explicit keyword means that we can use the constructor in an initialisation, as long as it is done
explicitely, i.e. by using the class name to call the constructor:
It can be used to :
• Explicitly or implicitly initialise an object from another of the same type. Some examples
contrasting copy-initialisation with direct initialisation
Note that copy initialisation is only ok when the class type suports copy. For example istream
variables do not allow copy:
70
and with an array, where copy initialisation is used
If we do not provide a copy constructor, the compiler will synthesize one for us, which simply copies
member by member. For example:
The assignment operator This is an overloaded operator (=) which is responsible for assigning
an object of class type to another. We will look at overloaded operators next, an example is:
Similarly to the copy construct, there is a synthesized assigment operator that assigns member by
member. For Sales_item an assignment operator could be
The Destructor This is another special member function which is responsible for deallocating an
object. This is called automatically whenever an object of the class type is destroyed
71
Note that the destructor is not run on a reference or pointer to a dynamically allocated object that
goes out of scope (a delete must be done explicitly). Similarly, for containers:
As a general rule, usually an explicit denition of a destructor is needed whenever copy or assignment
are needed. If not dened, the compiler always denes one for us which deletes each element in reverse
order. The object to which a pointer member points to is not destroyed, only the pointer itself.
The form of a destructor is similar to a constructor except that there is no return type or parameters
and a tilde ~ is added:
Exercises .
72
9.7 Overloading operators
This allows for a behaviour of our class types, which are similar to the built in types. This should
be used with care when there is an obvious an natural reason to overload an operator. For example,
overloading the + operator to represent division o members would clearly be a bad idea. The most
common overloaded operators are the shift operators. As an example of usefulness of overloading,
compare
Overloaded operators are functions which special names with the keyword operator and a parameter
list corresponding to the operands. For example, the declaration of an overloaded + operator in the
class Sales_item would be
73
The main rules for overloading operators are:
• At least one of the operands of the overloaded operator must be of class type, otherwise we would
be overloading a built in operator! Similarly we cannot dened new operators for built in types.
• Precedence and associativity cannot be changed (it is the same as for built in types)
• Class member operator seem to have one less parameter, which however corresponds to the this
pointer
• Often, overloaded operators which are non-member functions are made friends to classes. An
example of such is:
Exercise: Write a class which has a dynamically allocated pointer to hold a position in N-
dimensions. Include a constructor and default destructor and overloaded input and output operators.
74