Course Notes

Download as pdf or txt
Download as pdf or txt
You are on page 1of 74

C++ Summary notes and exercises

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

2 Variables and basic data types 6


2.1 Primitive built-in types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1.1 Arithmetic and logical operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Literal constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.1 Naming conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3.2 Declaration and Initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.3 Scope of a name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4 const qualier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.5.1 Quick introduction to functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.7 Header les . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

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

5 Classes as data structures 33


6 More on expressions and statements 36
6.1 Some types of behaviour to be aware of . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.2 Other types of expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.3 Further control structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

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

8 The I/O library 53


8.1 Condition states . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.2 Output buer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.3 File streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
8.3.1 File modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.4 String streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

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.

1.1 Steps to create and compile a simple C++ program


To create an executable program you need to follow the steps:

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

$ g++ program1.cpp -o program1.exe


Here, g++ is the compiler which uses the source le program1.cpp to create the executable
object le (the -o, is a ag which means object le) program1.exe
3. Finally you can run yout program by invoking it directly from the command line like this (./
means current directory  this may be system dependent)

$ ./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.

1.2 Input and Output


The C++ language does not include any facilities for input and output directly. However, in addition
to the language, there is the standard library which extends the language. Basically:

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

$ ./program1.exe < inputfile.txt


where < means redirection of the input to be read by the le in front, instead of the terminal. If the
program prompts the user to input any data, it will then be automatically read from the text le.
Similarly, one can redirect the output streams to les. For std::cout we use the > sign. For
std::cerr and std::clog one uses 2> and both are redirected to the same le, so for example

$ ./program1.exe > outfile.txt 2> infofile.txt


redirects the output of std::cout to outle.txt and the output of std::cerr and std::clog to
infole.txt.

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

// This is a commented line

4
For blocks of lines one can use

/* This is a commented block of lines


...
...
...*/

1.4 Control structures


The most basic control structures we are introducing for now allow you to: i) test conditions and ii)
perform recurssive tasks.

1.4.1 The if statement


This allows you to test a condition or a set of conditions sequentially. The simplest usage is (schemat-
ically)

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;
}

1.4.2 while and for


The while statement allows for iterative execution of a block of statements while a condition is true.
The general form is

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

for(int i=0; condition;i++){


statements
}
The rst statement in the argument initializes the iterator, the second is a condition that is tested
each time the block statements are repeated and the last one is an incrementation of the iterator. Here
we use the ++ operator, which increments by one the integer i.

1.5 Comment on class types


In the next chapter we will go through the built in data types and keywords of the language. C++
provides an extra feature which allows to dene classes. A class is a data type that can de dened by
the user and it can contain any structure of data. In addition, the operations on the data and how the
class type interacts with other class objects is dened in the class. For example it is usual to provide
an interface for a class type with std::cin, std::cout etc...
Introducing classes at this stage can become confusing. Instead we will skip to the next chapter to
learn rst the basics of the language.

2 Variables and basic data types


In this chapter the basic data types of the language and the operators they support are presented.

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

2.1.1 Arithmetic and logical operators


Though in the book this is only introduced later on. It makes more sense to introduce them right now
so that we can do some examples.

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.2 Literal constants


Literal integers and oating point numbers are entered in the usual way without or with a dot .. In
addition there are ways of specifying if integers are signed, unsigned, oat or long:

Other examples including scientic notation are

Printable characters literal expressions are written in several forms:

1. In single quotes with the character inside

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.

2.3.1 Naming conventions


Variable names are usually lower case, but this is not a strict rule. Some keywords are reserved for the
language so they cannot be used as variable names:

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.

2.3.3 Scope of a name


A name of a variable, function or even data type may be restricted to certain les, or certain blocks
of code and may not be known to other blocks of code. The part of the program where a certain
name applies is called its scope. Scopes in C++ are usually delimited by curly braces. We have seen
already this for some of the control structures such as if, while, for. Variables are usually dened
from their point of declaration and are not known outside the scope dened by the curly braces where
they are.
In C++ it is usually good practice to dene variables where they are used. Also you should avoid
using the same variable names in nested scopes, which may be confusing and create bugs.

Exercise Write a program which:

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.

2.4 const qualier


In many situations it is convenient to dene constants, for example if there is a xed dimension (which
you may want to change later and would be a pain to change all occurences in the program), or a
fundamental constant in your problem an you want an error to ocurr if there is some bug which changes
its value.
This is done by adding the keyword const to the data type and initialising the object. For example:

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

const type references are references to constants, i.e.

2.5.1 Quick introduction to functions


An essential feature of the language is to allow the denition of functions. Functions are essential to
produce structured code, because it allows to break a large project into smaller problems. A C++
program is essentially a set of functions which are called by the main function.
The basic structure of a function can be divided into a header and the body block of the function.
The header species the type of data to be returned, the name of the function, and list of arguments.
The body contains a list of statements and return statement at the end which returns the value of the
function.

returntype functionname(arg1, arg2, ...){


statements...;
return returntypeVariable;
}

The header of the function can appear as a declaration of the function before it is dened, with a
; at the end

returntype functionname(arg1, arg2, ...);


This is called a prototype and it is used by the compiler when it checks all the types declared in
the program. For a simple program with just one le it must appear before main. For example, the
following program

#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:

An example of a function which calls by reference is as follows

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

2.7 Header les


Header les are a way of having declarations which are common to dierent source code les. For
example if your program is large you may want to break it down into several les where sets of related
functions are all grouped in a le. A header le allows to include the declarations of functions and
data types in your program which are common to several source les, and can be include in all of them
through an #include preprocessor directive. Then each source code le can be compiled separately.
Headers usually have an extension .h and are included in the following way

#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

$ g++ -o executable source1.cpp source2.cpp source3.cpp ...

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.

3.1 using declarations


To avoid having to use the scope operator repeatedly, one can write a using declaration at the top of
the source code. The general form is

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

using namespace std;


Using declarations are not used in header les. In header les, the full qualied name of the library
type must be used.

3.2 Library string type


This type supports variable length strings of characters, manages the memory and provides operations.
To use it one must include

#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)

The most commonly used string operations are

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:

3.3 Library type vector


This is a class template which allows to dene variable size vectors of any type (including any user
dened type!), so it is a container (because it contains other objects). Without knowing anything
about the class we can just specify the type of the vector. We need to include #include<vector> and
a using statement either for the name vector or the whole standard namespace as before.
The generic ways of declaring are

Examples

If we do not specify a value, the vector is initialised for us.

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:

and access an element (which must already exist!!) by specifying a subscript:

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.

2. Do the following exercises from, the C++ primer

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

vector<int>::iterator iter = ivec.begin();


which returns a value referring to the rst element of the vector (same as ivec[0]); and the end()
function which returns a value beyond the last element, which is a sentinel indicating it refers to a
non-existing element, and cannot be increased!
The operations on iterators are as follows:
• Dereferencing: it allows to access the element an iterator refers to and it is done through the
dereference operator *:

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

• Comparison: Through the operations ==, !=

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

and using iterators

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

Consider the following way of locating the middle element of a vector


vector<int>::iterator mid = vi.begin() + vi.size() / 2;

4 Arrays and Pointers


These are lower level equivalents to vectors and iterators respectively, which are built into the language.
The main disadvantages are that arrays are xed size and these types do not provide simple operations
to: add elements; to make sure we don't exceed bounds; to check sizes, etc...
However they are still used in the implementations of class types, because they are more ecient.
For example, the implementation of vectors and strings in the standard library will most denitly have
arrays of the type of the corresponding types involved. As a rule of thumb, arrays and pointers should
be used mostly in class denitions, otherwise vectors and iterators should be used because they are
safer and will prevent bugs (or help debuging).

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)

2. An address of an object of an appropriate type

3. The address one past the end of another object

4. Another valid pointer of the same type

Some examples of these rules are as follows

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),

change value of object

and asign new address

Some examples with schematics

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

4.2.1 Pointers and Arrays


An array element can be accessed through a pointer. The name of an array is actually a pointer to
the rst element of the array:

28
or if we want to point to another element

Pointer arithmetic and dereferencing


Similarly to iterators one can advance or go back positions in memory or nd distances between point-
ers by using pointer arithmetic. To advance to a new element (or subtract to go backwards)

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

is not the same as

The dierence between pointers yields a type ptrdiff_t

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.

4.3 Dynamic memory allocation & multi-dimensional arrays


Arrays (as we have seen so far) have the limitation of having a xed size and to be available only to
the block where they are declared. Even though their size is xed, it can be determined at run time by
using some dynamical memory allocation facilities in the language. The main thing to be aware of is
that when we dynamically allocate an array, it continues to exist unless explicitely freed. When a C++
program runs it has a nite amout of memory availlable for allocation (called the free store or heap)
so it is important to free dynamically allocated arrays, after they are used. In C++, to dynamically
allocate memory one used the new and delete expressions.

Dening a Dynamic Array


This is done by declaring a pointer to the rst element of the array of a certain type, and then allocating
space with new some examples are as follows

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.

Initialising a vector from an array


There is a constructor for vectors which allows to do so, by specifying a rst argument which is a
pointer to the rst element of the array we want to pass and another pointer which points to one past
the last element of the array we want to pass (i.e. we can also pass a portion of an array as well):

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.

5 Classes as data structures


Classes allow us to dene our own data types and the operations they support. We have already seen
some examples of class types such as the library type string. We were able to learn how to use this
data type and its operations without having to go into the details of how it is implemented.
The denition of a give class involves:

• 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

point p1, p2, p3;


According to the default rules all member in this case are public and they can be accessed through
the dot . operator which is the member access operator. So for example, to asign values to the
coordinates and calculate the distance squared from the origin of point p1 we would write

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:

Some examples are as follows:

A more complete example:

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

6 More on expressions and statements


6.1 Some types of behaviour to be aware of
Logical statements We have used logical operators repeatedly in the examples of the previous
sections. A point that we haven't mentioned which one has to be careful with is that relational
operators are left associative. Thus, if we try to chain expessions, such as

if(1 < i < 3){


...
};
the result will be rst to check if 1<i and then the boolean result of that comparison will be compare
to 3.
Regarding bool variables, another point to keep in mind is that it is usually bad practice to test
equality of a bool variable to a bool literal (such as if(boolvar==true)), because it is an extra oper-
ation that is not needed since we can simply test the value of the bool variable itself (if(boolvar)).

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.

6.2 Other types of expressions


The sizeof operator This returns a value of type size_t which is the size in bytes of the object.
This value is a compile time constant. The possible forms are

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.

As a general rule, casting should be avoided unless strictly necessary.

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.

6.3 Further control structures


The switch statement This provides a way to avoid nested if statements, by running through
a list of cases. This statement compares the value of an arbitrary expression (which must yield an
integral result) to the value of each of the cases (which must be an integral result). Let's look at an
example:

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.

7.1 Function parameter list & argument passing


We have already seen the basic structure of a function denition in previous chapters. We have also
seen that any built in data-type can be an argument (including an empty list) or a return type of a
function. However there are some restrictions to this rule in the way such types are passed or returned.

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.

Reference Parameters We have already seen an example of parameters passed by reference.


The swap function is a typical example for which a copy would not work, because the local copies of
the object would be swaped instead of the original objects.
Another usage of reference parameters is to return additional information from the function. Let's
look at an example where a function is supposed to nd whether a certain integer occurs in an vector,
return an iterator which refers to the rst occurrence, and also we want to return the number of
occurrences of such integer:

which we would call for example as


iterfirst = find_val(ivec.begin(),ivec.end(),42,counter);
We may also want to pass a large object as a reference, just because it is innecient to copy it, but
we may at the same time want to prevent it is changed. In that case we can use a const reference to

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:

// first parameter is an array whose elements are arrays of 10 ints


void printValues(int (* matrix)[10], int rowSize); though it is possible to do it in the
following way (where the rst dimension has been omitted on purpose because it is not used)

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

• Explicitely pass a size parameter

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.

• Re-write the previous program passing an array by reference.

7.2 The return statement


There are two forms of this statement:

No value returned: return;


This is used for void functions which do not return anything:

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 .

7.3 Declaration & default arguments


As we have seen in previous chapters, a function must be declared before it is used. Usually the
prototype goes into a header le that is included at the top of the source code le.
Another property of functions is that there may be a list of arguments (they must always be the
last ones in the prototype) which have default values. As such, they may be omitted when the function
is called. If the rst default is omitted, all have to be omitted. For example:

which can be called as:

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:

7.4 Inline functions


Functions have several advantages as seen above. However, there is the drawback that calling a function
is slower than evaluating the corresponding expression. If one wants to avoid this, one can dene an
inline function (by adding the label inline before the data type), which is expanded at each point of
the program where it is called. The compiler will usually do so, however it should be kept in mind
that this is only a request. Inline function denitions should always appear in headers, and whenever
they are changed, the source les where they are included must be recompiled.
Let's look at some examples. Assume that you have the following inline function dened in a
header:

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.

7.5 Overloaded functions


These are functions (within a common scope) which have the same name, but dierent parameter list.
Typical examples are the arithmetic operations which act on diferent types. Similarly, one may dene
our own overloaded functions. Example:

The main restrictions are:

• If only the return type diers, then, that's an error

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

• The previous point does not apply to reference parameters

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.

• a function can be called through a pointer to function.

• A function parameter can be a pointer to function!

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.

8 The I/O library


In this section we will see some further features of input/output streams, many of which are common
with the istream type object cin and the ostream type object cout. We will introduce lestreams
and stringstreams to read/write to les and strings. An important property of these new streams is
that they are derived from the a base class dening istream and ostream. This is related to the
concept of inheritance which we will not cover. The main idea is that all the operations from the base
class are supported by the derived class, so any code we have written for the base class already works
for the derived class.
The streams we will now address are summarised in the following table, and inheritance hierarchy

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:

An example of how to access the condition state:

8.2 Output buer


In the begining of the course we have mentioned that endl forces the output buer holding the output
that should be written to the terminal through cout to be ushed. The same applies for other output
streams, they are held in a buer which get's ushed when:

1. endl is used; flush is used; the unitbuf manipulator is set, so that after each output line, it is
ushed.

2. The program ends normally

3. If the buer gets full and then it happens automatically

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:

One can tie an output stream to an input stream

8.3 File streams

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:

and then bind them to les with a given name

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!):

Let's look at some common operations:

Checking if open was successful.

56
Rebinding (re-using) a le stream.

Clearing a stream before re-using.

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!!!!

8.3.1 File modes


The le stream constructor has a default argument to set the les in one of the following modes:

where out, app, trunc are exclusive of ofstream, fstream, and in is exclusive of ifstream, fstream.

57
Example:

Note that a mode is an aribute of a le not a stream, i.e.

Here are some valid combinations of modes:

Finally an example of a standard function for opening a le, bind it to an input stream and checking
the state.

8.4 String streams


The sstream header denes:

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:

And to extract back:

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.

9.1 Recap and some further features


Let us look at the Sales_item class that was mentioned in the begining of the course.

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:

and its use

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

Typedefs Using typedefs is useful for two reasons:

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

9.2 The implicit this pointer


Member functions have an extra implicit parameter which is a pointer to the class type object to which
they are bound. It is called the this pointer, and it may be thought of as a pointer to the class type
object which calls the member function.
Let's look at an example of a Screen class with the following data:

62
and the following member functions:

Let's assume we add the following to the class denition

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:

9.3 Some scope rules


Since class member functions are often dened outside the class denition, it is important to note some
scope rules. We have already seen that members are accessed through the dot operator.
When dening a member function one needs to specify the scope of the function in its name, and
then everything following is in the same scope. For example:

Note that the return type is not after such scope resolution, so it may not be in class scope! For
example:

Further rules for name lookup are as follow:


• Declarations in the class containing other dened type members (other class types 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:

• Name lookup in Class member function denitions:

1. First check the local scope of the function.

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 .

9.5 Static Class members


Static Class members, are members which are shared among all class objects that are dened. This
might be useful for example to keep a count of objects of a certain class type that have been dened.

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:

and its usage:

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

9.6 Constructors, copy control and destructors


9.6.1 More on constructors
We have seen that constructors are member functions which have no return type and are executed
automatically when a class type object is declared. A special feature is the initialiser list which is run
to initialise the class objects. Some properties are as follows:

68
• If the initialiser list is not provided, the class will initialise its data members using the default
constructor for each member.

• If there is no default constructor for a member, then an initialiser is required.

• The initialiser expression can be any valid expression

• 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:

9.6.2 Copy control


The copy constructor This is a constructor with a single parameter which is usually a const
reference to an object of the same type as the class.

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:

• Copy a non-reference object to be returned by a function, or similarly, a non-reference parameter


of a function

• Initialise elements of a container or a list of elements in an array: An example for a vector:

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

The operators that are allowed to be overloaded are:

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)

• Short circuit evaluation is not preserved

• 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

You might also like