IAP Notes
IAP Notes
Dr Steven James
Course Outline 9
1 Introduction 15
1.1 Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2 Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2 Programming Fundamentals 25
2.1 Code Snippets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.3 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.4 Rules of Precedence . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.5 Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6 Auto-marked Code* . . . . . . . . . . . . . . . . . . . . . . . . . 34
3 Conditionals 35
3.1 Logical Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.2 Logical Operations . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.3 Conditional Statements . . . . . . . . . . . . . . . . . . . . . . . 39
3.4 Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4 Iteration 43
4.1 While Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2 For loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.3 Break and Continue . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.4 Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5 Containers 53
5.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.2 Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.3 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6 Functions 63
3
4 CONTENTS
6.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.2 What are Functions? . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.3 Defining Python Functions . . . . . . . . . . . . . . . . . . . . . 65
6.4 Invoking Python Functions . . . . . . . . . . . . . . . . . . . . . 67
6.5 Function Returns . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.6 Function Properties . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.7 Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7 Introduction to C++ 73
7.1 Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.2 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.3 The Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
7.4 Compiling our First Program . . . . . . . . . . . . . . . . . . . . 75
7.5 The Compilation Cycle . . . . . . . . . . . . . . . . . . . . . . . . 78
7.6 Analysing our First Program . . . . . . . . . . . . . . . . . . . . 80
7.7 Summary Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
11 Functions 107
11.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
11.2 Pass-by-value and references . . . . . . . . . . . . . . . . . . . . . 113
11.3 Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
13 Recursion 135
13.1 Computing the factorial . . . . . . . . . . . . . . . . . . . . . . . 135
13.2 The stack frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
13.3 Summary lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
CONTENTS 5
Lecturer
Lecturer: Dr Steven James
Office: UG03, TWK Mathematical Sciences Building
Email: [email protected]
Code of Conduct
As a computer science student and member of the University, you understand
that fellow students, lecturers, coordinators and members of academic and ad-
ministrative staff should be treated with respect at all times. The School will
not tolerate any behaviour that runs counter to these ideals. You are required
to use language that is appropriate in all online discussion postings, chats and
emails related to your courses. You will not post harmful language or stereo-
types that target people of different genders, abilities, races, ages, ethnicities,
nationalities, languages, socioeconomic classes or other aspects of identity. You
may not engage in violent threats or language directed against another person,
nor post discriminatory jokes, offensive memes and language, and personal in-
sults (especially those using racist or sexist terms). Neither will you encourage,
or advocate for, any of the above behaviour. In general, please work to create a
welcoming environment for all.
Online Learning
Due to COVID-19, the lectures in this course will be presented entirely online.
This online book will be updated regularly with new text, videos, and other
resources. Every week, there will be a practical lab that must be submitted.
There will be a one-week deadline for each. These will be made available via
Moodle and all submissions should be done on Moodle.
A very important aspect of learning is interacting with peers/tutors/lecturers.
While labs will take place in-person, lectures will be online and so keeping this
social element alive may be challenging. To assist with this, we will be using
Discord to host a virtual “classroom”. You will be sent more information on
9
10 CONTENTS
how to join during the first week of class. I encourage everyone to post regularly
on Discord with questions, suggestions and programming-related resources that
you come across. There are desktop, web and mobile apps available for computer
and phone platforms, and the tutors and I will be online as often as possible.
Communication
All official communication will be posted to the announcements forum on
Moodle. Moodle will send you email digests daily, but it is still your respon-
sibility to ensure you’re receiving these emails. You can change your email
preferences on Moodle to receive a single email per post if you prefer that.
Consultations
More information about consultation sessions will follow as soon as venue allo-
cations have been finalised, so please stay tuned.
Course Description
This course provides an introduction to problem solving through algorithmic
thinking using the basic building blocks of programming: sequence, branching,
repetition and abstraction. Translation of algorithms into working programs, as
well as advanced programming features such as parameter passing mechanisms,
static and dynamic array allocation, and pointer arithmetic also fall into the
main scope of the course.
Objectives
The aim of this course is to develop the students’ skills in the design, imple-
mentation and debugging of computer programs. Learning to solve problems
through algorithmic thinking is an essential part in achieving this objective.
Students must therefore have an understanding of the basic building blocks of
algorithms, and possess the ability to express them using programming con-
structs. At the end of this course, students should feel comfortable with writing
basic programs — achieving this level of competence is important as it the basis
for many subsequent courses. After completion of this course, students should
be able to:
Textbook
There is no prescribed textbook for this course. However, the following
are recommended for those interested:
• John Zelle, Python Programming: An Introduction to Computer Science
(3rd Edition), Franklin, Beedle & Associates, 2016.
• Allen B. Downey, Think Python: How to Think Like a Computer Scientist
(2nd Edition), O’Reilly, 2015.
• Bjarne Stroustrup, Programming: Principles and Practice Using C++
(2nd Edition), Addison-Wesley, 2014
• Paul Deitel and Harvey Deitel, C++ How to Program (9th Edition), Pren-
tice Hall,
• Gary J. Bronson, A First Book of C++ (4th Edition), Cengage Learning,
2011
Resources
You are encouraged to make heavy use of a programmer’s best friends —
Google and StackOverflow. For Python, some useful online resources are
www.learnpython.org and www.w3schools.com/python/, while the official
documentation can be found here: docs.python.org/3/. For C++, both
www.cplusplus.com/doc/tutorial/ and www.learncpp.com/ offer good tutorials,
while en.cppreference.com/w/cpp provides the official reference guide.
If you find any other good resources, please share these resources with others in
the class as well using the forums.
Grading
The weighting for each component is given below
Activity Weight
Continuous Assessment 10%
Assignment 1 10%
Assignment 2 10%
Class Test 1 10%
Class Test 2 10%
Exam 50%
Dates for tests and assignments will be communicated through Moodle closer
to the time.
12 CONTENTS
Timetable
The table below lists the times allocated in the university’s timetable for this
course. Lectures will be presented in an asynchronous manner: prerecorded
videos, readings and demos will be the most common mode of teaching in this
course. Despite this flexibility, I recommend following the assigned timetable
for all your courses, including this one, to assist in staying up-to-date with all
your subjects.
Tentative Schedule
The following serves as a rough guide for the content we will be covering during
the course, but please note that it is subject to change.
Block 1
Week Topics
1 Course introduction & completing labs and quizzes with Moodle
2 Introduction to algorithms & Python
3 If statements & Boolean operators
4 Loops
5 Lists & strings
6 Associative containers
7 Functions
Block 2
Week Topics
1 Transitioning from Python to C++
2 Functions & scope
3 Arrays & vectors
4 Arrays & vectors
5 Memory management
6 Recursion
7 Classes & structs
CONTENTS 13
Satisfactory Performance
This outline extends the general undergraduate computer science course outline.
In order to attain Satisfactory Performance for this course, you must submit at
least 70% of your hand-ins (labs/quizzes/assignments). Otherwise, you may be
prevented from writing the final examination.
Academic Integrity
There is a zero-tolerance policy regarding plagiarism in the School. Refer to the
General Undergraduate Course Outline for Computer Science for more informa-
tion.
Communication during quizzes, sharing of answers or practicals, and all forms
of plagiarism are taken very seriously by the university and will result in failing
the course and/or being reported to the legal office. In most cases I encourage
you to help each other with problems, but be aware of the line between helping
someone and giving them the answer. A good rule of thumb: if I am struggling, I
can show someone a section of my code for assistance, but I cannot look at theirs.
We will be monitoring Moodle logs closely and will be checking all submissions
for plagiarism.
14 CONTENTS
Chapter 1
Introduction
1.1 Algorithms
The first question we might ask is: what exactly is an algorithm? Informally,
we can think of an algorithm as a solution to a problem. However, an algorithm
15
16 CHAPTER 1. INTRODUCTION
3443219011465754445417842402092461651572335077870774981712577246796292638635
6373289912154831438167899885040445364023527381951378636564391212010397122822120720357
Which problem do you think is harder? The first one may look like a tough
multiplication problem, but it is actually relatively straightforward given enough
patience. A ten year old with sufficient concentration could solve it! The second
problem, which seems very related to the first, is really hard. In fact, it is so
hard, that no man or computer has ever discovered the answer. It is so hard (and
provably so) that you would win ONE MILLION DOLLARS for coming up
with the solution! It is so hard that every sensitive piece of information ever
produced — your bank details, your passwords, nuclear missile launch codes —
relies on the fact that the problem cannot be solved in any reasonable amount of
time. And yet it seems so related to the first problem, that it should be simple.
So why isn’t it?! It’s these kinds of questions that computer science, at its core,
deals with.
2. Are there more real numbers between 0 and 2 than there are real numbers
between 0 and 1?
3. Are there more real numbers than there are real numbers between 0 and
1?
Having seen the properties of an algorithm, let’s look at an example and see
how we would read and follow it:
https://www.youtube.com/watch?v=oVe68i93W_c?vq=hd720
The above representation is known as pseudocode. As you can see, it is an
artificial informal language that mixes mathematics and English. There are no
standard rules for putting pseudocode together, but it can be used to help us
‘’think out” a solution to a problem! Another representation you may come
across is a flowchart, which is simply a graphical representation of an algorithm,
but we won’t go into those here.
1.2 Programming
You may have noticed that at this point we have made no mention of computers
or programming even once! So what’s the link between the two? Well, once
we’ve come up with a solution to a problem in the form of an algorithm, the
question is: who is going to execute the sequence of instructions? I mean, we
could do it, but that would be time-consuming (especially if there are millions
of instructions), and we have better things to do. So instead, we’re going to get
a computer to execute it for us! Computers don’t get tired, they don’t need to
eat, and they really have nothing else to do — the perfect candidates for the
job! Programming is nothing more than turning an algorithm into something a
computer can understand and execute. An algorithm that has been turned into
something executable by a computer is known as a program. Before we get into
programming, let’s talk about what computer are and are not.
ENIAC’s first task was to compute whether or not it was possible to build a
hydrogen bomb (the bomb had already been constructed at this point, so this
was just a test). The very first problem run on ENIAC required only 20 seconds
and was checked against an answer obtained after forty hours of work with a
mechanical calculator. The entire program was encoded on more than half a
million punch cards, and after six weeks, ENIAC produced output declaring the
hydrogen bomb feasible. This first ENIAC program remains classified to this
day
As you will see in BCO, one particularly important number system is binary,
consisting of only two symbols: 0 and 1. And we can use these to represent any
number we care about. For example:
1.2. PROGRAMMING 21
But very soon, even assembly became tiresome. You have to write a lot of
code to do some very simple things. Furthermore, it was architecture-dependent.
Assembly for Windows would not work on a Mac or on Ubuntu, for example.
The solution, of course, is simply to create a higher-level language, creating
a hierarchy of increasingly abstract programming languages. The programmer
would write their code in the high-level language, which would then be converted
into assembly, which would ultimately be converted into ones and zeros. We
can continue this trend infinitely, creating more and more languages that are
built on top of one another! Of course, programming languages are just tools to
get the job done, so if you need a high-level programming language, then you
should use one. But if you need to program a washing machine, for example,
then assembly is probably your best choice.
22 CHAPTER 1. INTRODUCTION
Figure 1.2: Our code is compiled and converted into machine code. When we
want to run the program, we just execute this machine code directly.
into the interpreter one line at a time! The first line is translated into machine
code and then executed on the computer, then the second line is translated, and
so on. Every time we execute the program again, this translation occurs!
Figure 1.3: Our code is translated and executed one line at a time. Every time
we want to run the code, we have to go through this translation process.
Which way is better? Well, the first seems much more realistic and faster!
Who would bother with the second way?! It turns out, both approaches have
merits and downsides. Clearly the first way is quicker: we only need to translate
the code once! However, the interpreted way also has some advantages – the
high-level language code is available (since it needs to be run) which promotes
openness, and the code can quickly be edited and changed on the fly. In the
compiled case, once the low-level language is produced, it’s almost impossible
to make changes to it!
Question! Head to our favourite place (Google) and make a list of some of the
pros and cons of using an interpreted or compiled language.
Programming
Fundamentals
In this chapter, we will look at the basics of programming using the Python
language. These basics include the ability to calculate mathematical expres-
sions, just as you would on a calculator. While the content here is specific to
Python, it’s important to remember that these concepts transfer to all other
programming languages as well.
Before we begin, however, we should address an obvious question that non-
computer scientists always ask. Why do you need to code in a special language?
Why can’t you just tell the computer what to do in plain words? Instead of
answering this question, let’s look at an example of what trying to instruct a
computer would look like if we used English instead.
User: Okay computer, please sum the numbers from 1 to 10.
Computer: What do you mean by "all the numbers"?
User: Okay computer, please sum the integers from 1 to 10.
Computer: Is that inclusive or exclusive?
User: Okay computer, please sum the integers from 1 to 10, inclusive of
both 1 and 10.
Computer: All the integers?
User: Okay computer, please sum every single integer from 1 to 10,
inclusive of both 1 and 10.
Computer: Okay sure! Why didn't you say so in the first place?
In our made-up example, the final command we issued to the computer was:
Sum every single integer from 1 to 10, inclusive of both 1 and 10. This was nec-
essary because a) a computer is just a big dumb calculator, and b) programming
is about implementing algorithms, which have precisely-defined instructions. In
our example, we took pity on the user, but you could imagine the computer
25
26 CHAPTER 2. PROGRAMMING FUNDAMENTALS
saying, *I know addition and subtraction, but what does “sum” mean?” The
user would then need to provide a precise definition of “sum”. In any case, you
can see how wordy it is to encode our instructions in English. By contrast, the
code to achieve this in Python is:
sum(range(1, 11))
So when asked why we need programming languages, the answer is: because it
is much, much easier!
2.2 Variables
The most fundamental aspect of programming is a variable, which is used to
store (“remember”) a particular value in the computer’s memory which can later
be used to perform a calculation. A variable is identified by its symbolic name
and the value associated with it. A variable is closely related to the mathemat-
ical notion of a variable, but differs in that the value associated with it over
time may change (hence the name “variable”). For example, in programming
we may start off with the variable x having the value 2, but by the end of it all,
x’s value has been changed to 3.
2.2.1 Names
Each variable in a program has a unique name. The rules determining what
constitutes a valid name vary from language to language, but in Python, names
may only consist of any number of letters, digits and underscores. Furthermore,
2.2. VARIABLES 27
a name may not start with a digit. In Python, variable names are case-sensitive,
so the variable name total is different from the variable name Total.
It is good programming practice to pick variable names that are readable and
easy to understand. This will help others read your code and understand what
is happening, but will also be helpful for you if you return weeks later to look
at your code. By convention (hence, it is only a suggestion, not a firm rule),
variables should be all lowercase, and if your variable name is composed of
multiple words, then each word should be separated by an underscore (e.g.,
total_amount).
Each language also has certain keywords that have special meaning. These
words are reserved, and so cannot be used as variable names. In Python, these
keywords are:
Question! Which of the following names are valid Python variable names? For
invalid names, why are they invalid?
1. x
2. number_of_people
3. total-amount
4. NAME
5. class
6. 2pac
7. pac2
8. FroggyFresh
9. return
10. FalsE
Music side quest! Learn about the rapper Froggy Fresh.
2.2.2 Types
A variable is used to store a particular value. But what kinds of values can be
stored? Every variable has an associated type, indicating the kind of value it can
28 CHAPTER 2. PROGRAMMING FUNDAMENTALS
If we were to execute this code using our Python interpreter (please go ahead
and try it), we would see the output is
<class 'int'>
<class 'float'>
This output shows that our variable was initially an integer (the type is int) but
then became a decimal later (in programming, decimals or real-valued numbers
are referred to as floating-point numbers or floats).
Python provides a set of pre-defined types. These are:
1. int (integer)
2. float (real number)
3. string (text)
4. bool (True or False values)
The table below lists these types, as well as literals, which are fixed constants
of the various types.
Note that in Python, we can use either single or double quotes to represent a
string (but we may not mix them).
2.2. VARIABLES 29
2.2.3 Assignment
So far we have spoken about variables that have names, types and associated
values. But how do we actually associate a variable with a value? In Python,
we do this using the assignment operator =. Let’s say we have a variable called
y. In order to assign y a value, we simply write:
y = 2.45824406892
The assignment operator here is not to be confused with the “equals” operator
you’re familiar with in mathematics. Rather, think of the assignment operator
as saying: “the left hand side is now the name for the value on the right hand
side.” Alternatively, we could think of it as “the variable on the left hand side
is now storing the result of the expression on the right hand side”.
You may wonder why we are trying to be very precise about the = here. To
illustrate where confusion might arise, consider the following assignments:
x = 42
y = 23
x = y # What happens here?
y = 17
The question here is: what is the value of x? Recall that programming languages
are executed one line at a time, in order. So to start, we execute the first line
and the value of x is 42. Then the next line sets the value of y to 23. Now it gets
a bit tricky. What happens on the third line? Remember that the assignment
operator is not mathematical equals. The third line does not say that x and y
are “tied together” and will always have the same value. Rather, it simply says
that the value of x is the value on the right hand side. The value on the right
hand side is 23, and so the third line is equivalent to saying x=23. Finally, the
30 CHAPTER 2. PROGRAMMING FUNDAMENTALS
last line sets y to 17. It is vital to note that this only sets the value of y; x
remains unchanged. Therefore, when all is said and done, x has a value of 23
and y has a value of 17.
https://www.youtube.com/watch?v=dgh8MG9qD7M?vq=hd720
Before moving on, make sure you understand the above and how assignment
operates.
Question! Consider the sequence of assignment operations below. What are the
final values of each variable mentioned? And what are their types?
a = 7
b = 9
c = "?"
x = 1.2
d = a
a = 1.2
s1 = "Hello world"
d = a + b
s2 = "1.2"
s1 = 91
As a final note, recall that Python statements are executed one at a time, start-
ing from the top down. It is therefore important that we declare a variable
(i.e. create it) before we actually use it. For example, the following code is
invalid:
x = y + 2 # What is y?!
y = 20 # Too late!
Notice that on the first line, x is supposed to take the value of the computation
y + 2. But the interpreter doesn’t know what y even is (it’s only declared on
the following line, but we haven’t got there yet)! The following error will result:
NameError: name 'y' is not defined
2.2.4 Casting
We have said that each variable holds a value with an associated type. If we
wish to, we can convert data types from one to another using casting. This
allows us to convert the value of a variable to a different type, but it doesn’t
affect the original variable’s type. To cast a variable, we use the type we wish
to convert it to, and then wrap the variable in round brackets. Some examples
of casting are below:
x = 1 # x is an integer with value 1
y = float(x) # x is cast to a decimal. y has a value of 1.0
a = "2" # note that a is a string i.e. it is the character 2, not the number!
z = int(a) # now z is the integer 2
2.3. OPERATORS 31
q = 2.6
r = int(q) # q is cast to an integer. r now has a value of 2 (the decimal part is dropped)
2.3 Operators
Operators in Python allow us to perform the standard arithmetic operations on
variables and literals (constants). These operators are:
1. Addition (+)
2. Subtraction (-)
3. Multiplication (*)
4. Division (/)
5. Exponentiation (**)
6. Modulus (%)
The only operator you may be unfamiliar with here is the modulus operator.
This operator computes the remainder when one number is divided by another.
So because 9/2 = 4 remainder 1, we would see that 9 % 2 (you would say this
as “9 modulo 2”) gives us 1.
Question! The modulus operator is a strange one! If this is your first time
programming, you probably haven’t thought about remainders in many years!
Can you think of some reasons why remainders would be useful?
Mathematics side quest! The statement that the modulus operator computes
the remainder was not quite correct. In fact, the modulus operator computes
the remainder only when both numbers have the same sign! What happens when
the numbers have different signs? What happens when one or both are zero?
A word of warning when it comes to division: certain programming languages
treat division differently, depending on the variable types. For example in
Python 3 (the language we are using) 9 / 2 produces 4.5. However, in Python
2 and C++, 9 / 2 produces 4. This is known as integer division: when the two
numbers are both integers, the result of the division is also an integer! If this
behaviour is desirable, then in Python 3 we can achieve this as follows: 9 // 2
which will produce 4.
Question! Predict the result of evaluating each of the following lines:
a = 7
b = 9
20 + 3
a - b
5 * b
b / a
b // a
14 % 2
2**3
32 CHAPTER 2. PROGRAMMING FUNDAMENTALS
given their student number, I want a program that can do that for any student,
not just one in particular. To achieve this, we need our programs to be able to
accept input from the user, and display the results (output). We will now see
how input and output are managed in Python.
2.5.1 Input
In Python, we can accept input from the user using the input() function. When
we do this, the user will be able to enter some input through the keyboard, hit
Enter, and our program will receive whatever they have typed. We can then
assign their input to a variable, as in the example below:
user_input = input()
In Python, the input() function always return a string (see Section 2.2.2 if
you’ve forgotten what a string is). But what if we don’t want a string? What
if we want an integer, for example? In that case, we must cast (Section 2.2.4)
the input to the type we want. Some examples of this are below:
points_total = int(input())
average_score = float(input())
2.5.2 Output
It is no good writing a program and doing all this computation if the user cannot
even see the final results! In order to display results to the user, in Python we use
the print() function. Despite its name, the function does not print something
out on paper — rather, it displays the result as text on the screen, and will
appear on your terminal when you run the program. We can print variables or
constant values of any type using this function, as in the example below.
print("Hello, world!") # prints a piece of text
x = 23
print(x) # prints an integer variable
print() # displays a blank, empty line
History side quest! The function that displays text on the screen is called print,
not just in Python, but also in many, many languages. This is a bit strange,
isn’t it? Surely a better name would be display or show? Try do some research
to find out why the name print has been used instead.
The default print() function will display whatever is in brackets, and then add
a new line. If we wish to display multiple things on the same line, we can do
this by separating them with a comma. For example:
print("The best number is", 42)
https://www.youtube.com/watch?v=ftj8yEkUaOo?vq=hd720
34 CHAPTER 2. PROGRAMMING FUNDAMENTALS
Conditionals
35
36 CHAPTER 3. CONDITIONALS
A B not A A and B A or B
False False True False False
False True True False True
True False False False True
True True False True True
Because we can represent them numerically as well, we can write the truth tables
using 1s and 0s instead:
3.2. LOGICAL OPERATIONS 37
A B not A A and B A or B
0 0 1 0 0
0 1 1 0 1
1 0 0 0 1
1 1 0 1 1
Question! Using the above table, can you see the mathematical operators that
produce the output of and, or, and not respectively? To start you off, the not
operation can be written as 𝑓(𝑥) = 1 − 𝑥.
Boolean algebra side quest! In the above question, we could almost say that the
or operation is like addition, but it’s not quite because 1 + 1 = 2 ≠ 1. Because
we are missing an addition operation, the above logical system is known as a
monoid. However, if we wanted to integrate the above into larger systems to
develop an entire algebra based on Boolean variables, then we really need an
addition operator. Is there a Boolean operator that would satisfy addition?
What is it?
Precedence Operator/Symbol
Operation Evaluation Direction
Level
0 (first) () parentheses Inner ones evaluated first.
Left-to-right
1 <, ≤, >, ≥ less than or equal, Left-to-right
, =, ≠ less than, greater
than,greater than
or equal, equal to,
not equal to
2 not negation (unary Left-to-right
NOT)
3 and logical AND Left-to-right
4 or logical OR Left-to-right
Question! Assume that the Boolean variable A is True, B is True and C is False.
Using the above precedence rules for the logical operators, evaluate whether the
38 CHAPTER 3. CONDITIONALS
2. C and A
3. C and A and B
4. C and A or B
5. B or C and A
print("Program terminating...)
Some other examples of if statements are below. Try predict what the output
of the code will be.
if 2 < 3:
print("2 is less than 3")
if 2 != 3:
print("2 is not equal to 3")
40 CHAPTER 3. CONDITIONALS
if 2 > 3:
print("2 is greater than 3")
Question! Write a program that accepts two marks as integers from input.
Display whether both marks are above 50, only one is above 50, or neither are.
print("Program terminating...)
print("C")
elif grade >= 50:
print("D")
elif grade < 50:
print("F")
print("Program terminating...)
Note also that we can integrate our else clause into the above if we wanted to.
The else acts as a “catch-all” for the case where we found no true expressions,
and essentially says: only if nothing above was true, execute these lines of code.
As such, the else is optional, and there can only be one else clause at
most! In the above example, we might wish to use an else clause to handle any
unexpected input, as follows:
grade = int(input())
if grade >= 75 and grade <= 100:
print("A")
elif grade >= 70:
print("B")
elif grade >= 60:
print("C")
elif grade >= 50:
print("D")
elif grade < 50 and grade >= 0:
print("F")
else:
# if none of the above were true, then we execute this line!
print("Invalid input! Expected value in range [0, 100])
print("Program terminating...)
if num % 3 == 0:
print ("Divisible by 3; not divisible by 2")
else:
print ("Not divisible by 2; not divisible by 3")
Notice in the above that there are multiple indentations. The indentations are
used to indicate which lines of code “belong” to which if and else clauses. Try
see if you can trace through the above example by hand, and then watch the
video below to validate that you were correct.
https://www.youtube.com/watch?v=WrmearGX62k?vq=hd720
Question! Rewrite the program above so that instead of using nested if state-
ments, you only make use of if, elif and else clauses. When you’ve done
this, try to run your code and input various numbers to verify that it’s correct.
Hint: you will need to make use of and and or operators.
Optimisation side quest! As you will learn in BCO, a computer is made up of
a central processing unit, and memory to store variables and results. However,
there are different levels of memory — main memory is large but slow, and cache
memory is fast but small. One problem with conditional statements is that the
computer needs to wait to determine whether they are true or false before they
know which lines of code to execute next. This means it cannot, for example,
preload data into cache memory, since it doesn’t know what data it will need.
Or does it?!
Use the internet to learn about branch prediction, where the computer will guess
the outcome before it even executes it! The CPU will make a guess and proceed
as though that guess was correct. If it turns out it was, then it has achieved
a speedup. But if not, then it must throw away those results! Now find out
about a computer virus called Spectre and how it takes advantage of this to steal
people’s passwords and private data!
Iteration
Up until this point, our programs have executed sequentially, proceeding one
line at a time. In the previous chapter, we saw how to use if statements to skip
or run certain lines, but even then, our program had just been “flowing forward”
until the end. But what if we don’t want this? In particular, what if we want
to execute something repeatedly? For example, imagine we go to the ATM to
draw money. There is a program running on the ATM that asks us for our card
and PIN, and then allows us to select options to withdraw money. Once we’re
done, what happens next? Does the program finish running? No! It goes back
to the beginning so that it’s ready for the next customer!
In this chapter, we’ll be looking at how to achieve this in a number of ways.
This concept is known as iteration, but more informally looping. In most pro-
gramming languages, there are three types of ways we can perform iteration.
These are called while loops, do-while loops and for loops. In Python, how-
ever, there are only two! Therefore, we will only discuss while and for loops
in this chapter, and leave the remaining one for later on.
43
44 CHAPTER 4. ITERATION
becomes false, at which point the program “escapes” from the loop and continues
on with the next line of code. The code indented inside the while loop is known
as the loop body.
Much like the if statement, we associate the lines of code that are to be executed
repeatedly by using indentation. Any code that is “inside” the while loop will
run repeatedly. In the example below, the statement_1 and statement_2 will
be run repeatedly, but statement_3 will only be run once the loop has finished,
since it is not inside loop body!
while logical_expression:
statement_1
statement_2
statement_3
As mentioned, the while loop will continue executing over and over again as
long as logical_expression remains true. This has two implications: 1. If
the logical expression never becomes false, then the loop will run forever! This
is known as an infinite loop, and is not desirable for obvious reasons. 2. If the
logical expression is immediately false, then the loop immediately exits, and the
loop body will never execute (not even once).
Humour side quest! There is a very old computer science joke that goes as
follows:
A programmer went to the grocery store. His wife said, “While you are out, buy
some milk.” He never came home.
Now, it’s not the funniest joke in the world, but before moving on, make sure
that you understand it!
https://www.youtube.com/watch?v=v83vF4qI3tQ?vq=hd720
History side quest! The above program is actually a very famous program. It
was written for the EDSAC computer by a man names David Wheeler, who
became the first person to be awarded a PhD in computer science! Use the
internet to find out more information about this program and other early com-
puter programs.
4.1. WHILE LOOPS 45
Note that because we started counting from 0, to count ten times we go from 0
to 9 (and so the logical expression is strictly less than). Now that we have our
pseudocode, we are ready to start programming!
Note that in general, the sentinel is just an arbitrary value. In this example, we
have chosen -1 since it is not a valid mark and so is a clear indication that the
user wants us to stop.
Question! Considering the above example, why would 0 be a poor choice for a
sentinel value?
Question! Is the above pseudocode actually correct?! Try find an edge case
where it produces wrong or unexpected output, and then modify the code to fix
it! (Hint: computing the average involves division. When is division danger-
ous?)
4.1. WHILE LOOPS 47
How should this bonus be decided? According to this problem, it’s based on
the number of people who have passed. So we must compute that number.
First refinement:
Input the 10 exam grades
Count passes and failures
Display a summary of results
Decide on bonus
Try break this pseudocode down even further, and then see if your solution lines
up with the pseudocode below. Remember, there is more than one way to arrive
at the correct answer.
Second refinement:
set the passes to 0
set failures to 0
set counter to 0
while counter < 10
input exam result
if result > 50
add 1 to passes
else
add 1 to failures
add 1 to counter
display passes
display failures
if more than 8 passes
display "Bonus!"
48 CHAPTER 4. ITERATION
Notice how similar the Python code is to the pseudocode. The takeaway here is
that once you have your pseudocode mapped out and in place, you’re basically
done! Converting to Python is a much easier job!
failures = failures + 1
print("Passes", passes)
print("Failures", failures)
if passes > 8:
print("Bonus!")
The counter_name is a variable name for our counter. We could choose any
name we want (common ones you will see are i and j). The range gives us a
sequence of integers from start to stop (exclusive of stop). step tells Python
how much to increment the counter by each time (e.g should we go in steps of
1 or 2?) In Python, we can leave out either or both of start and step values
if we wish. If we do that, they will take their default values, which are start =
0 and step = 1. Below are some examples of range definitions and the range
of integers they produce.
𝑁
Question! Write a program that accepts a positive integer 𝑁 and outputs ∑ 𝑖.
𝑖=1
Question! Write a program that, when run, produces the following output:
1
22
333
4444
55555
50 CHAPTER 4. ITERATION
666666
7777777
88888888
999999999
Question! A person invests R1000.00 in a savings account yielding 5% interest
annually. Assuming that all interest is left on deposit in the account, calculate
and print the amount of money in the account at the end of each year for 10
years. Use the following formula for determining these amounts:
𝑎 = 𝑝(1 + 𝑟)𝑛
where
𝑝 is the original amount invested (the principal)
𝑟 is the annual interest rate
𝑛 is the number of years
𝑎 is the amount on deposit at the end of the 𝑛th year
The continue statement, on the other hand, allows us to skip the statements
in the rest of the loop body, but continue with the loop otherwise. An example
of this is below.
for i in range(0, 10):
if i == 5:
continue # go to the start of the loop and continue
print(i)
If you run this code, you will see that it prints out every number from 0 to 9,
except 5. Make sure you understand why this happens.
It is important to note that, while break and continue help us modify loops
in various ways, we can often write code in such as way that we never need
to use them. It is good practice to keep the use of these statements to a bare
minimum.
Containers
Up until this point, we have considered individual variables and the operations
that can be applied to them. In this case, each variable is used to hold a single
value (such as an integer or a string). But what if we want to go beyond this?
What if we want our variables to store multiple things? To motivate why we
may want this, let’s consider the following problem:
Write a program to read in 10 integers. Then print them out in the reverse order
they were entered
Given what we have covered so far, how would we solve this problem? Well,
it seems like we would need to have 10 variables (e.g x1,x2, …, x10), read an
integer into each, and then print out the variables in the reverse order that the
input took place. This would give us the right answer, but now consider this:
what if there were 100 integers? 1000? 109 ?! Clearly this approach will not
work in the long run.
In this chapter, we will look at how to overcome this problem (and others) using
programming constructs called containers which can be used to hold multiple
values at once (lists), and associate values with one another (dictionaries).
5.1 Lists
A list is a type provided by Python that lets us store a collection of values. In
Python lists, we can store as many elements as we want, and each element can
be of any type (as we will see later, other languages are not as permissive and
only allow for the same type).
In Python, we can create an empty list (i.e. a list with no items) as follows:
my_list = list() # or my_list = []
53
54 CHAPTER 5. CONTAINERS
Some points to note on the above. In both cases, we have created a single
variable that is of type list. In other words, we have one variable, but it
is being used to refer to many values, and not just the one value we’ve seen
previously. In Python, lists are denoted with square brackets, and inside each
list is a comma-separated collection of items (or no items if the list is empty).
Try run the above code and observe the output. Notice that the order of the
items in the list is the same as the order in which they were added to the list!
We can also combine a list with another list — this will append the contents of
the second list to the end of the first. For example:
student_names = list()
student_names.append("Bruce Banner")
student_names.append("Bruce Wayne")
Again, try run the above code and see what the output is. Make sure you
understand what is happening here before moving on.
https://www.youtube.com/watch?v=Nr6QcoS-Ekk?vq=hd720
We can remove all the elements from a list using clear() and we can remove a
single element by specifying the value to remove, as in the example below
student_names = list()
student_names.append("Bruce Banner")
student_names.append("Bruce Wayne")
student_names.append("Peter Parker")
print(student_names)
5.1. LISTS 55
student_names.remove("Bruce Wayne")
print(student_names)
student_names.clear()
print(student_names)
Again, try running the above code and observing the output.
https://www.youtube.com/watch?v=A-VlfRLco0E?vq=hd720
Question! What happens if we try to remove an element from a list, but the
element does not exist? What happens if we try to remove an element, but
there are multiple elements with the same value?
https://www.youtube.com/watch?v=JWrxTKmYuJo?vq=hd720
What if we want to get the last element of the list though? In the above case,
we know that the list is of length 3, but in general we might not know the size
of the list up front. We can use Python’s len function to compute the length
of a given list. For example, len(colours) will return the value 3. We can use
this to extract the last element in the list, because we know that the last index
is len(colours) - 1.
colours = ['red', 'blue', 'green']
print(colours[len(colours) - 1]) # green
Getting the last elements of a list is fairly common, and so Python has provided
a convenient way for doing so by using negative indices. If an index is negative,
it refers to the elements in the list starting from the back. So an index of −1 is
the last element, −2 is the second last element, and so on.
colours = ['red', 'blue', 'green']
print(colours[-1]) # green
56 CHAPTER 5. CONTAINERS
https://www.youtube.com/watch?v=9UNOJUvS7MI?vq=hd720
In the above example, we directly accessed the value of each element in turn.
Alternatively, we could also loop through a list be using each index in turn, as
follows:
sum = 0
numbers = [5, 78, 23]
for i in range(len(numbers)):
sum = sum + numbers[i]
print(sum)
https://www.youtube.com/watch?v=5TFKHipBpr4?vq=hd720
The first approach is shorter and more convenient, but the second approach
has the added advantage in that we also have access to the index, in case we
need to do something with it. If we wish to have access to both the index and
the element at the same time, we can use the enumerate option provided by
Python:
names = ["Sansa", "Arya", "Bran", "Rickon"]
for i, name in enumerate(names):
# enumerate gives us index and value for each element
print("The name at position", i, "is", name)
Question! In the above use case, we could use in to determine whether the
list contains a particular value. But what if we wanted to know the index of a
particular position? Find out how to do this in Python? What is the resulting
index if the value is not contained in the list?
Question! In one of the twenty seven thousand, three hundred and seventy two
Marvel films, Thanos believes that overpopulation will destroy the universe. He
has managed to collect all the Infinity Stones (oh noes! �), and is about to use
their power to remove half of everyone from existence!
Write a program that reads a series of names into a list. I suggest using your
own name and your friends’! Loop through each name in the list and flip a coin.
If heads, the person survives. If tails, the person does not! Print out all the
people that survived Thanos’ plan! Did you? (Hint: You will need to look up
how to generate a random number/Boolean)
5.2 Dictionaries
Lists allow us to store a collection of values. But what if we want to associate
one value with another? For example, we may wish to associate a student
number with a student’s name. How would we maintain this link? These
associative containers are said to be a collection of key-value pairs, where each
key is associated with a particular value (in our example, a key would be a
student number and the value would be the name attached to that number).
These structures have many names depending on the programming language
(maps, dictionaries, symbol tables, etc), but they all offer the ability to
1. add a key-value pair to the collection,
58 CHAPTER 5. CONTAINERS
In Python, we use a dictionary, denoted by the type dict. Let’s look at how to
use dictionaries by considering the case of associating student names with their
marks. The names and associated marks are:
Vegeta: 99
Goku: 9001
Freeza: 80
In the above example, the key is the string/name, and the associated value
is the integer; however, the key can be many other data types as well. One
very important property is that dictionaries only allow for unique keys. It is
forbidden to have two entries in a dictionary with the same key.
5.2.1 Adding/Modifying
Let’s consider the following example below and walk through how entries are
added an modified in a dictionary
marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
marks["Vegeta"] = 40 # overwrites
5.2.2 Removing
Let’s consider the following example below and walk through how entries are
added an modified in a dictionary
marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
marks["Vegeta"] = 40
del marks["Vegeta]
https://www.youtube.com/watch?v=ioJrqn95lNs?vq=hd720
5.3. SETS 59
5.2.3 Querying
The main use of a dictionary is that we can look up an associated value given a
key quickly and efficiently. The example below illustrates this
marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
is_goku_a_key = "Goku" in marks
goku_mark = marks["Goku"]
https://www.youtube.com/watch?v=8ZjDbBFqBHM?vq=hd720
5.2.4 Iterating
If we wish to loop through every key-value pair in the dictionary, we can do so
by looping through the dictionary’s items() as follows:
marks = dict()
marks["Vegeta"] = 99
marks["Goku"] = 9001
for name, mark in marks.items():
print(name, ":", mark)
"""
Output:
Vegeta: 99
Goku: 9001
"""
5.3 Sets
A related structure is a set, which stores keys only. Unlike dictionaries, we
cannot associate keys with values, but we can use sets to store a collection of
data. Sets are therefore very similar to lists, but with a few major differences:
1. Each element in the set is unique. If a duplicate value is added to the set,
it will be ignored.
2. The entries in the set are not ordered.
3. Once a key has been added to a set, it cannot be modified. Sets only
support adding and removing elements.
60 CHAPTER 5. CONTAINERS
In Python, we can create an empty set (i.e. a set with no items) as follows:
my_dict = set()
5.3.1 Adding
Let’s consider the following example to see how elements are added to a set
names = set()
names.add("Vegeta")
names.add("Goku")
5.3.2 Removing
Let’s consider the following example to see how elements are removed from a
set
names = set()
names.add("Vegeta")
names.add("Goku")
names.remove("Vegeta")
5.3.3 Querying
The main use of a set is that we can determine if something is already in the
set. This is identical to how we would check a list, but is significantly faster!
names = set()
names.add("Vegeta")
names.add("Goku")
is_goku_a_key = "Goku" in names
https://www.youtube.com/watch?v=Fp8gKHcUnmY?vq=hd720
5.3.4 Iterating
We can loop through a set just as we would through a list. Remember that sets
do not support indexing, and so we would accomplish this as follows: If we wish
to loop through every key-value pair in the dictionary, we can do so by looping
through the dictionary’s items() as follows:
5.3. SETS 61
names = set()
names.add("Vegeta")
names.add("Goku")
for name in names:
print(name)
"""
Output:
Vegeta
Goku
"""
Also remember that sets have no ordering, so the order that elements are printed
out are not guaranteed to be the same as the order they were added.
62 CHAPTER 5. CONTAINERS
Chapter 6
Functions
In this chapter, we will look at the concepts of functions, why we need them
and how to create our own ones.
6.1 Motivation
In the above code, sqrt is a Python function that someone else has written for
us to use. To motivate why this is useful, let’s consider what would happen if
no such function existed, and we had to do it ourselves. If we were to write a
program to compute the square root of a given number, it might look something
like this:
n = float(input())
x = n
tolerance = 0.0000000001 # allowed error
while True:
root = 0.5 * (x + (n / x))
63
64 CHAPTER 6. FUNCTIONS
Numerics side quest! The code above uses the Newton-Raphson method to
compute the square root in a loop. However, there is a famous piece of code for
computing the inverse square root without any loops that uses the hexadecimal
number 5f3759df. Find out more about this magic number by researching the
fast inverse square root.
The above code will compute the square root of a given number within some
error, which is great! But there’s a problem: every single time we need to
compute the square root, we would need to rewrite this code! Who has time
for that?! Even worse, image we were asked to calculate the sum of two square
roots! Then we would have to duplicate the code as follows:
n = float(input())
x = n
tolerance = 0.0000000001 # allowed error
while True:
root = 0.5 * (x + (n / x))
if abs(root - x) < tolerance:
break
x = root
n2 = float(input())
x2 = n2
while True:
root2 = 0.5 * (x2 + (n2 / x2))
if abs(root2 - x2) < tolerance:
break
x2 = root2
print(root + root2)
Instead, the obvious thing to do is to write the code exactly once, package it
up somewhere, and then simply reuse the code as and when we need it. Since
we have the sqrt function provided to us by Python’s math library, the above
code can simply be written as:
import math
n = float(input())
n2 = float(input())
6.2. WHAT ARE FUNCTIONS? 65
print(math.sqrt(n) + math.sqrt(n2))
So much easier!
2. Next we have the function name. This is how we will refer to the function,
and follows the same naming conventions as regular variables. We can
assign a function any name that we want, but it should be as descriptive
as possible. Note also that we are not allowed to define two functions with
the same name. Each function should therefore be given its own name.
3. In round brackets, we have the parameter list. These indicate the variables
that the function accepts as input. As we will see in the examples below,
we can have as many or as few parameters as we want. We can even have
zero parameters, in which case we simply leave the parameter list blank
(but we must always have the round brackets). The parameters are simply
variables that will be used by the function, and we can given them any
name we wish.
4. Next is the colon. Just like if statements and loops, in Python the colon
indicates that the indented lines below are associated with the function.
5. The function body are the lines of code that should be executed when we
use the function. Just as before, the body is defined with indentation.
A Python function may also end in a return statement (but this is optional),
the general form of which is given below.
def <function_name>(<parameter_list>):
<function_body>
return <exp>
The return statement specifies the output of the function — that is, it specifies
the result of the computation performed by the function (<exp> is simply a
Python expression). It is very important to note that the output of
a function is NOT the same as print. When we print, we are simply
displaying something on the screen for a human to read. On the other hand,
the value that is returned (or output) by a function is its result, which can be
stored in a variable, or itself printed out if need be. For example, when we use
the sqrt function, the answer produced by that function is returned by the
function. The answer can the be stored in a variable or printed out, as below:
import math
x = math.sqrt(4) # inside the sqrt function, the value is returned and assigned to x h
Note that we can pass variables or constants to our functions, but the number
of arguments we send in must match the number of parameters. For example,
we cannot say add(1) because the add function expects two parameters, not
one!
https://www.youtube.com/watch?v=X8N72S02AHg?vq=hd720
One important aspect of using functions is that, just like variables, functions
must be defined before they are used. Let’s consider the following example:
m = int(input())
n = int(input())
show_message(m, n)
If we try run this code, we will receive the error message below.
NameError: name 'show_message' is not defined
This is because we have tried to use the show_message function on line 3, but
68 CHAPTER 6. FUNCTIONS
the function itself was only declared afterwards. And since Python will process
code line by line, it does not yet know of its existence!
To fix the problem, we must simply define the function before we use it, as
follows:
def show_message(x, y):
for i in range(x):
for j in range(y):
print("*", end="")
print()
m = int(input())
n = int(input())
show_message(m, n)
We may define functions anywhere before we use it in our code. They may exist
in the same file, but we could also put functions in different files. In this case,
we must import the functions so that we can use them. This is why we often
write import math. We wish to use the function that are defined in the math
file on our system.
Python side question! Try locate the math file somewhere on your system and
examine it. What’s with the funky code? Hint: it’s not actually a Python
file.
print("Hello, world!")
# returns here
print("Calculating...")
z = (x * x + y * y) / y
print("The answer is", z)
https://www.youtube.com/watch?v=1lEIHHz052k?vq=hd720
https://www.youtube.com/watch?v=DT4IjMWKpcI?vq=hd720
So what exactly can Python function return? In languages like C++ or Java,
we are only allowed to return one thing. But in Python, we can return as many
values as we want (hurray for Python!). However, it is good practice to return
one logical thing, because we don’t want our function doing too much work. If
our function is returning too many things, it’s often a good indication that we
should split it into two functions instead. An example of a good function that
returns multiple things (but one logical thing) is below:
def get_position_of_robot():
# get the xy position of a robot
# ... do calculations
x = ...
y = ...
return x, y
Finally, it is important to note that once the function returns, the variables in
the parameter list and any other variables that it declares are lost.
at all to us! We don’t care at all about how it works — all we care about is
that we can give it a number, and it will give us back the square root. This is
the idea behind encapsulation — the complexity of the function is hidden from
us. All we know is its interface (we give it a number, it gives us back another
number). One great thing about this concept is that, if someone decided to
write a better, faster sqrt function, they could go ahead and replace the code,
and it wouldn’t affect us at all! The diagram below encapsulates this idea:
Figure 6.1: Once we have written a function, we don’t care how it works inter-
nally. All we care about is what we need to give it, and what it gives us back
(if anything)
Functions are a powerful way for writing and reasoning about code for a number
of reasons. For one, it embodies the divide and conquer principle by allowing us
to chop up a program into manageable pieces, and then tackle them one by one.
It also makes programs much easier to read. For example, imagine we have a
bunch of functions and then want to use them as follows:
username = input()
password = input()
if is_valid(username, password):
option = show_menu()
process_input(option)
else:
display_warning()
We don’t need to look inside the functions to understand what’s roughly going
on here — the function names tell us everything we need to know!
On top of that, we’ve already mentioned the reusability aspect — we can reuse
a function if the thing it does comes up often. However, it also makes testing
code easier (since we can test functions independently of one another), helps
with the distribution of labour (I will work on function A, you work on function
B), and maintenance (if there’s a problem, we can often isolate with function is
causing it and fix things).
6.7. ADDITIONAL READING 71
Introduction to C++
7.1 Outline
The next few chapters cover the content we have already discussed in the first
half of the course. We’ll assume that you are familiar with basic programming
ideas in Python, and so we will briefly recap these, illustrating how C++ and
Python differ in these regards. The main takeaway here is that the underlying
concepts remain the same—regardless of the language, ideas like loops and vari-
ables are constant. The only difference is in the syntax—that is, the way we
actually write down an if-statement or for-loop, which differs from language to
language.
Once we have covered the basics, we will move on to new conceptual content.
This primarily involves the idea of memory allocation. In Python, for example,
memory allocation is automatically handled by the interpreter, and so is not the
concern of the programmer. However, C++ allows the programmer to manually
specify and control this—this is useful for programming in embedded systems
such as ATMs, washing machines and airplanes. As such, we will look at how
variables are allocated (and de-allocated) in memory.
However, it is important to note that these low-level concepts are often unnec-
essary. There are many modern techniques and advances to the C++ language
that means it is possible to write code without needing to ever manage memory
manually. However, as well-rounded computer scientists, it is our job to under-
stand how things work behind the scenes, even if we will never explicitly write
a real-life program in such a way.
As a final note, throughout these notes we will include either full and complete
C++ programs as examples, or (where appropriate) snippets of programs. An
example of a snippet is given below. These snippets are valid lines of C++ code,
but they are not full, self-contained programs. In other words, attempting to
73
74 CHAPTER 7. INTRODUCTION TO C++
execute the code will fail, since it is simply part of a large, complete C++
program. We will look at a full C++ program that can be executed in the next
sections.
cout << "Hello world" << endl;
7.2 Introduction
In this chapter, we will take a first look at the C++ programming language
and how it differs from Python. Before we do so, a reminder that programming
languages are nothing but tools—there is no one “best” language, and each
are designed with some use case in mind. For instance, the Java programming
language is designed to be very structured and solid, and so is a good choice
for enterprises or organisations with very large teams all working on the same
product. C++, on the other hand, generally provides greater performance when
compared to Python and Java, and so would be a good choice for, say, physics
simulators and video game engines, which require speedy computation.
The main difference between C++ and Python can be seen in the times when
they were created. The first version of Python was released by Guido Van
Rossum in 1991, while C++ was invented by Bjarne Stroustrup in 1979. Thus
C++ was designed to run on older hardware, which was slower and had less
memory. In particular, computers of that time had on the order of 10KB of
memory, which meant that memory had to be used and allocated very precisely.
Nowadays, memory is less of an issue with most machines have 8GB or more, and
so modern languages like Python do not burden the programmer with manual
memory allocation.
In general, the main differences between Python and C++ can be summarised
in that C++ is:
• more low-level. It exposes more of the inner workings of the program (such
as direct memory allocation and addressing) to the user.
• more dangerous. Since C++ gives the user more control and responsibility,
the programmer is ultimately responsible for managing more aspects of the
program and writing more code. Since we are imperfect, this means that
there’s a greater chance of making more mistakes.
• more complex. C++ is a difficult language, because it strives above all
else to be useful to everyone. Thus it has features that allow it to perform
extremely low-level tasks (such as determining CPU cache line sizes), as
well as be competitive with high-level modern languages like Python. It
has also resolved that it will always be compatible with previous versions
of itself. Thus if a bad idea was introduced in 1985, the 2020 version of
C++ is required to support it, no matter what.1
1 This is a very good reason for using pointers. For example, imagine you wrote a function
that searches an array and returns a pointer to the element in the array with a particular
value. What if there is no such element matching that value? What should we return in that
7.3. THE COMPILER 75
print("Hello, world!)
int main(){
cout << "Hello, world!" << endl;
return 0;
}
At this stage, do not worry about the exact details of the program, since we
will shortly analyse it in detail. Simply note that it requires many more lines
of code to achieve the same result as the Python program—with greater speed
and control, comes greater responsibility!
4. link,
5. load, and
6. execute.
The first four phases are responsible for actually creating the executable file,
and the remaining two are responsible for executing it.
7.5.1 Editing
In this phase, the programmer (ourselves) simply writes the C++ code and
saves it in a text file. This text file is known as the source file.
7.5.2 Pre-processing
This phase handles any directives, which are specified by a line beginning with
the # symbol. The most common use case here will be any code that we im-
port. In Python, you may have written import math in order to gain access to
Python’s mathematical functionality. C++ has a similar system, but instead
of writing import, we write #include. To import mathematical functions for
example, we would write
#include <cmath>
When we do this, the pre-processor will search for a file called cmath.h and
replace that line with the file’s entire contents. Note that the included file
may contain other #include statements, which themselves may contain other
#include statements, and so on. The contents of all these files are all included
(up to some limit).
7.5.3 Compiling
In this phase, the pre-processed file containing the written code and any included
code is converted into machine code that can run on the underlying computer.
The files output by the compiler are called object files. If there are any errors in
the code, the compiler will fail to create the object files and will instead print
the errors to the screen. You will then need to use this information to locate
the errors and fix them, before recompiling your program.
These errors are known as compile-time errors, since they are discovered during
the compilation step. These differ from run-time errors, which only occur when
the program is running. For example, imagine we have written perfectly good
code that computes the average class mark. Everything is correct, and so the
compiler detects no errors. Now imagine a user comes and decides not to enter
any marks, but wishes to compute the average. Our code will then likely have
a division by 0 error, because we had not budgeted for such an event. However,
the compiler cannot detect the error because it only happens once the program
is running and the user does something strange and unforeseen.
80 CHAPTER 7. INTRODUCTION TO C++
7.5.4 Linking
In this step, if the compiler has output multiple object files, then they are all
combined into one final executable file. In this course, we will only ever write
code in a single file. However, a C++ program may have multiple source files.
Each of them is converted to an object file during the previous phase, and the
linker is responsible for combining them all into one big file. Additionally, any
libraries that were required (libraries are object files that other people* have
created) are combined here. An example of a library file might be an object file
that allows for drawing and on-screen animations. Note that these library files
are object files, which differs from those we have #included in the pre-processing
phase (which were source files).
7.5.5 Loading
At this point, the linker has output an executable that can be run on the current
machine. In the loading phase, the executable is loaded from disk and placed
into RAM ready to be executed. At this stage, memory is set aside for the
program that only it will be able to access.
7.5.6 Executing
At this stage, the program is actually run. This simply involves transferring
the machine code, which was loaded into memory, to the central processing unit
(CPU) for execution. The CPU will then execute the program one line at a
time, following each instruction, until the program terminates.
An illustration of the full cycle is given below:
Figure 7.2: The compilation cycle given multiple source files. In this
course, we will generally only ever have one source file. Diagram courtesy
of [MIT OpenCourseWare](https://ocw.mit.edu/courses/electrical-engineering-
and-computer-science/6-096-introduction-to-c-january-iap-2011/lecture-notes).
//This is the "main" function. C++ will start executing code here
int main(){ //bracket signals the start of the main function
cout << "Hello, world!" << endl; //display with a new line
//main must return an integer. 0 means success, else fail
return 0;
} //end of the main function
7.6.1 Comments
We have added comments to the program. As in Python comments are anno-
tations that can be made to the code to explain exactly what’s going on, or to
help with readability. They are ignored by the compiler, and so do not affect
the functionality of the program in any way. There is no need to comment every
single line—use them only when they help in understanding the code. There
are two types of comments: C++ -style comments, which begin with // and
span a single line, and C-style comments, which wrap the comment in /* and
*/ and can span multiple lines. Both are perfectly valid in C++.
7.6.3 Namespace
This is a slightly advanced topic, and so we will only cover it briefly here. Do
not worry too much if you do not fully grasp the concept, just know that Line
6 is necessary to make everything work.
In C++, identifiers such as variables and functions can be defined within a
context called a namespace. This is mainly to prevent clashes between variables
and functions defined by ourselves and others. For example, if I were to write
a square-root function, I would like to be able to call it sqrt without having to
worry that C++ has its own function with the exact same name.
82 CHAPTER 7. INTRODUCTION TO C++
To overcome this, C++ uses namespaces to keep everything separate. All the
functionality provided by C++ by default is inside the std namespace. In our
example above, I am free to call my function sqrt, because C++ has placed
its sqrt function inside the standard namespace, and so its actual name is
std::sqrt. It is possible to define your own namespace to ensure that your
variables and definitions do not conflict with either the standard ones, or some-
one else’s, but we won’t need to do that in this course.
One downside is that it can get quite tiring writing std:: everywhere. Thus on
Line 6, we tell the compiler we’ll be using the standard namespace by default.
Both cout and endl reside in the standard namespace, but because we said we’ll
be using the standard namespace, we do not need to specify their namespace.
If we omit Line 6, we’d have to change Line 10 to read:
std::cout << "Hello, world!" << std::endl;
For our purposes, it is simply easier to use the standard namespace and not
have to worry about typing the std:: qualifier every time.
We will look later at how C++ defines and structures functions, but for now
it suffices to note that the int in front of main declares main to be a function
that is expected to return an integer. Contrast this to Python, where we did
not need to specify what type of data was being returned by a function.
The function body is defined by the opening and closing curly braces. Again
contrast this to Python whose function bodies were defined by indentation. It
is considered good practice to indent the text in the function body by a certain
amount of space, but unlike Python this is not necessary.
Line 10 is equivalent to Python’s print function, and will display the specified
string to the terminal screen. The endl term is a built-in function that will
insert a newline when printing (essentially equivalent to \n). There is a lot to
say about input and output in C++, but we will defer that discussion to later
chapters.
Finally, Line 12 specifies that the function returns the integer 0. The operating
system expects all programs to return an integer indicating whether they have
successfully completed, or whether they terminated with some error. 0 indicates
that the program completed successfully, while any other integer indicates some
error occurred. For example, if your program had prompted for user input and
the user typed in something that the program was not prepared to process, one
7.7. SUMMARY LECTURE 83
could signify this to the operating system by returning a 1. When this line is
encountered, processing in the CPU returns to the operating system.
Finally, note that all statements in C++ are terminated by a semi-colon. Pre-
processor directives and function definitions are not statements, and therefore
do not end with a semi-colon.
In this and subsequent chapters, we will use C++ snippets throughout. Recall
that snippets are segments of C++ programs, and so cannot be run on their
own. If you wish to execute them on your computer, you will need to write a full
C++ program, including the #incude statements, namespace declaration and
main function, and then add the snippet inside the main function. To see a full
C++ program, refer to the Hello, world! program in the previous chapter.
In this chapter, we will cover the concepts of variables, types and operators as
we did in Python. Because these concepts are essentially the same as Python’s
(aside from some of the specifics), we will not dwell too long on them here. We
will start with a discussion of variables and their types, and identify how they
differ from what we’ve seen in Python. We will quickly recap operators, for
which everything is roughly the same.
8.1 Variables
As in Python, C++ supports the concept of a variable. A variable is used to
store (“remember”) a particular value in memory, to which we can then refer to
perform computation. A variable is identified by its name, which consists of a
series of one or more of letters, digits or underscores. Variables should be given
meaningful names to aid in readability—short names can indeed be meaningful,
while overly long names can annoy. In C++, a name must start with a letter,
and it may contain letters, digits and underscores. Note that just like Python,
C++ is case-sensitive, and so a variable with name x is different from a variable
with name X. There are certain C++ keywords which cannot be used as variable
names, and are listed below.
85
86 CHAPTER 8. C++ VARIABLES, TYPES AND OPERATORS
Table 8.1: A list of keywords. Note that the list may vary depend-
ing on which version of is being used.
Question! Which of the following names are valid C++ variable names? For
invalid names, why are they invalid?
1. x
2. number_of_people
3. total-amount
4. NAME
5. class
6. 2pac
7. pac2
8. FroggyFresh
9. return
10. FalsE
11. false
Finally, though not required, C++ convention is to use camel-case for variable
names—camel-case means that if a variable name consists of multiple words,
then the first word starts with a small letter, and all other words begin with cap-
itals. For example, totalAmount and numberOfPeople are instances of camel-
case.
8.2 Types
In C++, each variable has an associated type, which tells the compiler what
kind of data the variable will store. This allows the compiler to reserve the
8.2. TYPES 87
If we were to try a similar thing in C++, the compiler would give us an error.
For example
int x = 0;
// to use typeid, you must include <typeinfo>
cout << typeid(x).name() << endl; // prints i(nt)
x = "Hello"; //not allowed to change from integer to string!
cout << typeid(x).name() << endl;
A couple of additional points about C++ data types. There are a number of
integer data types, but the one we will use most often is int. A variable of
88 CHAPTER 8. C++ VARIABLES, TYPES AND OPERATORS
type int is guaranteed to be able to store a 16-bit integer, but on most modern
machines will store a 32-bit one. There is also long long, which is guaranteed
to be at least 64 bits and should be used to store very large numbers. For decimal
numbers, we will always use double, which is normally a 64-bit number. C++
provides float, which is usually half the size of double and only takes up 32
bits, but because memory is no longer a concern, we will simply stick with
double everywhere.
Finally, we can convert data types from one to another using casting. Note
that this generally applies to numerical types—we cannot cast a string to an
integer, for example. Some examples are given below
int x = 1; // x is an integer with value 1
double y = (double) x; // x is cast to a double. y has a value of 1.0
double z = 2.5
x = (int) z; // z is cast to an int. x now has a value of 2
Note that by declaring a variable, we have not yet given it a value. We have
simply told the compiler that it must assign memory to hold such a variable.
Because we have not given the above variables values, they are said to be unini-
tialised, with unknown or undefined values.
8.3.2 Initialisation
If we wish to create a variable and give it a value, we can do so using initialisation.
Initialisation simply refers to the case where we assign a value to a variable at
the point of declaration. Note that initialisation is optional—we could declare
a variable, and then only much later in the program give it a value. Examples
of variable initialisation are below:
int x = 42;
double y = 2.45824406892;
string someMeaningfulName = "Pogba's Haircuts";
8.4. OPERATORS 89
8.3.3 Assignment
After having declared (and potentially initialised) a variable, we are able to later
set its value through assignment, which works in the exact same way as Python.
Here we simply use the = sign to assign the value of the right-hand side to the
variable on the left. Below are examples of variable assignment:
int x;
x = 42; // happens after declaration -> assignment
int y = 23;
x = y;
It is important to note in all cases that, just as in Python, we must declare a vari-
able before we use it. In the snippet below, we would encounter a compilation
error because we have tried to use y before it has been declared.
int x = y + 2; //What is y?!
int y = 20; //Too late! We needed it for the previous line
It is also very important that we do not work with uninitialised variables. This
is a major source of errors and they can take a long time to track down. In the
example below, the compiler would not issue an error because the code is valid,
but it likely would not work as we expect because y’s value is undefined.
int y; int x = y + 2; //y has an undefined value. What's undefined + 2?!
int y = 20; //Too late! We needed it for the previous line
8.4 Operators
The operators provided by C++ are exactly those offered by Python, and iden-
tical precedence rules apply. One very important thing to note is that we must
be very careful of integer division, which occurs when the division operator is
applied to two integers and produces an integer as a result. If this is undesirable,
at least one of the two operands must be a double or float. The following table
contains a list of built-in operators, and is followed by some examples of them
in practice.
8.4.3 Precedence
The rules of precedence for Python and C++ are identical, and we list them in
the table below for reference.
In this chapter, we will learn how to take in input from the user, and out-
put/print data to the screen. C++ handles this using the notion of streams.
In particular, we will focus on two standard streams: the input stream cin for
receiving input, and the output stream cout for displaying output. In order
to make use of these streams, we must include the iostream header file at the
start of our program. One thing to keep in mind while reading this section is
that C++ makes input and output very easy. In particular, we do not need to
worry about whether our input or output is an integer, a float or a string—C++
handles that for us automatically. By contrast, Python requires us to manually
ensure that the input is of the correct type.
We will be using the string data type in many examples in this chapter. Be-
cause string is not a built-in data type, we should in theory need to include
it by writing #include <string> at the top of our files. However, it turns out
that all C++ compilers have iostream libraries that themselves include string.
Thus when we include iostream, we are also conveniently including string as
well.
9.1 Output
The iostream library contains the cout stream, which we can use to send data
to be displayed as text in the terminal. Let’s look at our simple Hello, world!
program again and see what’s going on.
#include <iostream>
93
94 CHAPTER 9. INPUT AND OUTPUT
return 0;
}
First off, note that we include iostream at the very beginning so that we can
use the cout stream. Then on Line 6, we print some text to the console. We
do this by using the cout stream and the insertion operator (<<). Note how
the insertion operator is like two arrows pointing towards the stream. This
essentially tell the program to send what is on the right-hand side (in this case,
the text) to the stream, which then handles displaying it in the terminal.
Note that cout can print more than just text—it can print numbers and vari-
ables too, as in the example below:
int x = 10;
cout << 5;
cout << x; // prints the value of x to console
In order to print more than one thing on the same line, we can use the insertion
operator multiple times in a single statement to concatenate multiple pieces of
output:
string name = "David";
cout << "Hello, " << name << "!"; // prints Hello, David!
By default, C++ does not print a new line at the end of these output statements.
One way to achieve this is to use endl, which is also contained in the iostream
library, and forces the stream to print a new line character. For example:
string name = "David";
cout << "Hello, " << name << "!" << endl;
cout << "My name is HAL" << endl;
In the above example, everything would appear on the same line if we did not
include endl. The same effect can be had by including the newline character
\n in the text as so:
string name = "David";
cout << "Hello, " << name << "!\n";
cout << "My name is HAL\n";
9.2 Input
Having looked at output, input is very similar. In order to accept input from the
keyboard, we now use the cin stream, which again is defined in the iostream
header file. Whereas cout prints data to the terminal using the insertion opera-
tor,cin reads input from the keyboard using the extraction operator (>>). The
input must be stored in a variable to be used, as follows:
9.2. INPUT 95
To remember the extraction operator, notice how it looks like two arrows point-
ing towards the variable that will hold the input. A fantastic feature of C++
is that input works the same regardless of the type being read in. In Python,
if we wanted to read in an integer, we would need to write x = int(input()).
Similarly, a float is read in x = float(input()). By contrast, C++ handles
this automatically, as in the example below:
int x;
double y; //recall that double is for real numbers
string z;
cin >> x;
cin >> y;
cin >> z; // C++ handles the types for us!
We can also chain together input statements on a single line to read in multiple
values:
string name;
int age;
cin >> name >> age; //will read in a string, then an int
int main(){
string firstName;
cin >> firstName;
cout << "Hello, " << firstName << endl;
return 0;
}
If I were to try enter both my first and last name, separated by a space, only
my first name would be read and stored in the variable. My surname would be
waiting in the input stream for another read to occur. If we want to accept both
96 CHAPTER 9. INPUT AND OUTPUT
the first name and surname, we can achieve this by modifying our program to
look like this:
#include <iostream>
int main(){
string firstName;
string surname;
cout << "Please enter your first and surname" << endl;
cin >> firstName >> surname; //input first name and surname
string fullName = firstName + " " + surname; //concatenate
cout << "Hello, " << fullName << endl; //output message and name
return 0;
}
If we want to read in an entire line, including any whitespaces, we can use the
getline function like so:
#include <iostream>
int main(){
string name;
cout << "Please enter your full name" << endl;
getline(cin, name); //reads ENTIRE LINE into name
cout << "Hello, " << name << endl;
return 0;
}
Note however, that mixing getline and cin can cause subtle errors, so try
avoid this where possible.
Finally, we present code that asks the user to input their first name and year of
birth and then displays the user’s first name and age:
#include <iostream>
#include <string>
int main(){
string firstName;
cout << "Please enter your first name" << endl;
cin >> firstName; //input first name
int yearOfBirth;
9.3. SUMMARY LECTURE 97
In this chapter, we will look at how C++ implements if-statements and looping.
Broadly speaking, these are identical to Python’s implementations, although
there are some minor differences. We will start off with if statements, and then
continue on to loops, covering for loops, while loops and a new kind of loop:
the do-while loop.
10.1 If-statements
The standard if-statement in C++ is very similar to Python. To illustrate, we
first show the general form of an if-else snippet in Python:
if <exp>:
# do something
else:
# do something else
Not the main differences between the two. In Python, we use colons and inden-
tation to indicate which statements should be executed if the condition is true or
false. In C++ however, we use curly braces to indicate this (C++ does not care
about indentation, but we use it to make the code more readable to humans).
Additionally, the logical expression being evaluated by the if-statement must be
99
100 CHAPTER 10. BRANCHING AND LOOPING
inside round brackets. Other than those two aspects, everything remains the
same.
Note that the same rules apply to C++: as in Python the else condition is
optional, but if it is present it must be attached to an if-statement.
Let us now look at the situation where we have multiple cases to be checked.
One difference here is that C++ does not possess the elif. Instead, we must
write else if in full. In the Python program below, we read a mark from the
user, and display the corresponding symbol:
grade = int(input())
if grade >= 75:
print("A")
elif grade >= 70:
print("B")
elif grade >= 60:
print("C")
elif grade >= 50:
print("D")
else:
print("F")
The equivalent C++ program is as follows (note the use of else if):
#include <iostream>
using namespace std;
int main(){
int grade;
cin >> grade;
if (grade >= 75){
cout << "A" << endl;
}
else if (grade >= 70){
cout << "B" << endl;
}
else if (grade >= 60){
cout << "C" << endl;
}
else if (grade >= 50){
cout << "D" << endl;
}
else{
cout << "F" << endl;
}
return 0;
}
10.1. IF-STATEMENTS 101
https://www.youtube.com/watch?v=QAFZr_S-oac?vq=hd720
Finally, as in Python, we can also nest if-statements inside one another. The
following examples in Python and C++ illustrate this:
# Python program
num = int(input())
if num % 2 == 0:
if num % 3 == 0:
print("Div by 2 and 3")
else:
print("Div by 2; not by 3")
else:
if num % 3 == 0:
print ("Div by 3; not by 2")
else:
print ("Not div by 2 and 3")
//C++ program
#include <iostream>
using namespace std;
int main(){
int num;
cin >> num;
if (num % 2 == 0){
if (num % 3 == 0){
cout << "Div by 2 and 3" << endl;
}
else{
cout << "Div by 2; not by 3" << endl;
}
}
else{
if (num % 3 == 0){
cout << "Div by 3; not by 2" << endl;
}
else{
cout << "Not div by 2 and 3" << endl;
}
}
return 0;
}
Because C++ uses curly braces, there’s a chance you may have too many or too
few by mistake. You can reduce these errors by using proper indentation, so
that the closing brace lines up with the statement it belongs to (as in the above
102 CHAPTER 10. BRANCHING AND LOOPING
example). This makes it easy to see which brace belongs to which statement.
10.2 Loops
We have previously covered while-loops, for-loops and associated statements
such as break and continue in Python. As we shall see, C++ supports these
exact same features. As in the previous section, we will be using curly braces
instead of indentation and colons to indicate the loop-body (that is, the code
to be executed while the condition remains true).
10.2.1 While-loops
To begin, we contrast the while-loop in Python and then C++. In Python, we
have
while <exp>:
# statement 1
# statement 2
# etc
C++ looks almost identical but again note that we use curly braces and that
the loop’s logical expression must be inside round brackets.
while (<exp>){
// statement 1
// statement 2
// etc
}
A full example of a while loop in action is given below, where we read in a series
of 10 marks and determine the number of passes and failures: First, in Python:
passes = 0
failures = 0
n_students = 0
while n_students < 10:
mark = int(input())
if mark >= 50:
passes = passes + 1
else:
failures = failures + 1
n_students = n_students + 1
print("Passes", passes)
print("Failures", failures)
#include <iostream>
using namespace std;
int main(){
int passes = 0;
int failures = 0;
int nStudents = 0;
int mark;
while (nStudents < 10){
cin >> mark;
if (mark >= 50){
passes = passes + 1;
}
else{
failures = failures + 1;
}
nStudents = nStudents + 1;
}
cout << "Passes " << passes << endl;
cout << "Fails " << failures << endl;
return 0;
}
https://www.youtube.com/watch?v=YanOK8plYHA?vq=hd720
10.2.2 For-loops
Recall that in Python, a simple for-loop takes the following form:
for i in range(start, stop, step):
# statement 1
# statement 2
# etc
In C++, this looks slightly different, but has the same semantic meaning. The
equivalent C++ for-loop is as follows:
for (int i = start; i < stop; i = i + step){
// statement 1
// statement 2
// etc
}
The first thing to notice is that inside the round brackets there are three state-
ments, separated by a semi-colon. Note that we are introducing a new variable i,
and so we must specify its type the first time it is mentioned. These statements
are known as the initialisation expression, termination condition expression and
104 CHAPTER 10. BRANCHING AND LOOPING
variable update expression. A more general form of the C++ for-loop is as fol-
lows:
for (<exp_1>; <exp_2>; <exp_3>){
// loop body
}
print("Passes", passes)
print("Failures", failures)
Then, in C++:
#include <iostream>
using namespace std;
int main(){
int passes = 0;
int fails = 0;
int mark;
10.2. LOOPS 105
https://www.youtube.com/watch?v=Vj4-BAoj7kQ?vq=hd720
Note that we have used the increment operator and written ++nStudents. Re-
call from a previous chapter that this is equivalent to nStudents += 1 which
itself is equivalent to nStudents = nStudents + 1. It is a very convenient way
of adding one to a variable, and is frequently used in for-loops. C++ provides
two increment operators: x++ and ++x. They are subtly different, but for our
purposes are identical. Thus we can write nStudents++ or ++nStudents.
Programming language side quest! The increment operator ++ appears in the
language we’re currently using! This is because C++ was designed to be an
upgrade of the language C. But what was C an improvement upon? Find out
the language that C was based on, then download a compiler for it and try
reproduce some of the code examples here in that language!
https://www.youtube.com/watch?v=PDiRBsfXQms?vq=hd720
Note that unlike the while-loop, the condition appears after the loop body. Thus
the loop body is always executed at least once. In practice, the do-while loop
106 CHAPTER 10. BRANCHING AND LOOPING
Functions
In this chapter, we will discuss C++ functions, which once again closely parallel
Python’s versions. One main difference is in the way the functions are declared—
because C++ is a statically typed language, we will need to specify data types
in its declaration. After that, we will then discuss how variables are passed into
functions, as well as how long a variable exists in memory for.
11.1 Functions
Functions are groups of statements that, taken together, perform a task. Break-
ing a program up into these modular blocks makes it easier to understand,
maintain and test for errors. All C++ programs have at least one function
(main()), and even the most trivial programs can define additional functions.
The way code is divided into functions depends on the programmer and task
at hand, but the most logical course of action is to ensure that each function
performs only a single task.
We start by first showing the general form of a Python function:
def <function_name>(<params>):
# function body
C++ differs in that we must specify the parameter types, as well as the type of
object returned by the function. If the function does not return anything, then
its return type is void. The general form of the C++ function is thus:
<return_type> <function_name>(<params>){
// function body
}
Once again, notice that we use curly braces instead of indentation and colons to
define the body of the function. Here are some snippets of functions that have
107
108 CHAPTER 11. FUNCTIONS
11.1.1 Declaration
As with normal C++ variables, we can choose to declare a function without
defining what it does. This lets the compiler know that such a function exists,
and allows the programmer to define what it actually does later on. Declaring
a function informs the compiler about the function’s name, the data type it
11.1. FUNCTIONS 109
returns (if any), and the parameters it accepts (if any). A function must be
declared before it is used in another part of the program. The general form of
a function definition in C++ is as follows:
<return_type> <function_name>(<parameter_list>);
Note here that we have not specified the function body. The above is known as
a function prototype (or header) and consists of the following:
11.1.2 Definition
Declaring a function lists its name, inputs and outputs, but doesn’t actually
define what it does. A function definition in C++ specifies the function header,
as well as its actual functionality. We may choose to first declare a function, and
then define it, or we may simply define it immediately. If we define a function
without declaring it, it must again happen before the function is actually used.
Function definitions take the following form:
<return_type> <function_name>(<parameter_list>){
<function_body>
}
where the only additional concept is the function body, which contains the col-
lections of statements that describe what the function actually does.
1 The reason it is forbidden is that C++ was initially designed to be backward compatible
with its predecessor programming languages B and C. Since these languages do not allow for
one array to be assigned to another, neither does C++.
110 CHAPTER 11. FUNCTIONS
The two examples below illustrate a function that is first declared and the later
defined, and then the case where the function is simply defined.
#include <iostream>
using namespace std;
/*
We tell the compiler the function header,
but we don't actually provide implementation
*/
int add(int, int);
int main(){
int x = 1;
int y = 2;
cout << add(x,y) << endl;
return 0;
}
/*
The definition of add() The header here must
match the declaration
*/
int add(int a, int b){
return a + b;
}
#include <iostream>
using namespace std;
/*
We define the function header and its implementation
all at once
*/
int add(int a, int b){
return a + b;
}
int main(){
int x = 1;
int y = 2;
cout << add(x,y) << endl;
return 0;
}
11.1. FUNCTIONS 111
/*
We tell the compiler the function header,
but we don't actually provide implementation
*/
int add(int, int);
int main(){
int x = 1;
int y = 2;
cout << add(x,y) << endl;
return 0;
}
/*
The definition of add() The header here must
match the declaration
*/
int add(int a, int b){
return a + b;
}
#include <iostream>
using namespace std;
if (y == 0){
cout << "y can't be 0" << endl;
return; //returns here
}
112 CHAPTER 11. FUNCTIONS
int main(){
return 0;
}
int main(){
int x, y;
cin >> x >> y;
11.2. PASS-BY-VALUE AND REFERENCES 113
int m = min(x,y);
cout << "The min of " << x << " and " << y <<" is " << m << endl;
m = min(x,2); //we can pass literals or variables
cout << "The min of " << x << " and 2 is " << m << endl;
return 0;
}
https://www.youtube.com/watch?v=l7YH0F1S9iA?vq=hd720
11.2.1 Pass-by-value
By default, variables are passed into a function by value. This means that it is
not the variable itself being sent into the function, but rather a copy of its value.
To see what this means, let’s look at a small example:
#include <iostream>
using namespace std;
general, this means that the function cannot alter the arguments passed into
the function.
11.2.2 Pass-by-reference
Because it is the value of the variables that is passed to a function, the original
variables are unaffected by any operations that happen within that function.
But what if we don’t want that to happen? What if we actually want to modify
the original variables?2 Fortunately C++ provides a mechanism for doing so—
instead of passing variables to functions by value, we can instead do this by
reference. To understand how this works, we must first understand the concept
of variable addresses.
A variable address specifies the location of a variable in memory. The address
operator & can be used to obtain the address of any variable (which is displayed
as a hexadecimal number). For example:
double x;
cout << &x << endl; //prints the location of x
/*
x’s value: 2 x’s address: 0x7fffda1837d0
y’s value: 5 y’s address: 0x7fffda1837d4
z’s value: 7 z’s address: 0x7fffda1837d8
size of x: 4 bytes
2 This is a very good reason for using pointers. For example, imagine you wrote a function
that searches an array and returns a pointer to the element in the array with a particular
value. What if there is no such element matching that value? What should we return in that
case? A pointer that points to “null” is a good candidate in this case.
11.2. PASS-BY-VALUE AND REFERENCES 115
size of y: 4 bytes
size of z: 8 bytes
*/
In the above, we use the sizeof operator to determine how many bytes of
memory each variable takes up. The most important thing to note about the
above is the offsets (that is, the differences) between the memory locations of
the three variables. The memory location of z is 4 more than that of y, which
itself is 4 more than that of x. Given that both x and y are exactly 4 bytes large,
this implies that all the variables are stored next to each other in memory!
Now let us look at a more complex example by introducing a function:
#include <iostream>
using namespace std;
int square(int x) {
cout << "In function square(), x is located at " << &x << endl;
return (x * x);
}
int main() {
int x = 2;
int squared;
cout << "In main():\n"
<< " x is located at " << &x << endl
<< " squared is located at " << &squared << endl
<< "Before square() function call:\n"
<< " x = " << x << endl
<< " squared = " << squared << endl;
squared = square(x);
/*
In main():
x is located at 0x7fffe865e6f4
squared is located at 0x7fffe865e6f8
Before square() function call:
x = 2
squared = 0
In function square(), x is located at 0x7fffe865e6dc
116 CHAPTER 11. FUNCTIONS
The most important thing to notice here is that we invoke square with argument
x, which is at location 0x7fffe865e6f4. However, the parameter x belonging to
the function square is at a different location! Thus the two variables do not
refer to the same space in memory, and so modifying one does not modify the
other—the x of square is different to the x of main.
If we want in fact would like for the two variable to be the same (that is, refer
to the same memory location), we can achieve this using pass-by-reference as
opposed to by value. In the following example, the first parameter is passed by
value, while the second is passed by reference:
void func(int param1, int ¶m2){
// note the & symbol in front of param2
}
Let’s now look at how this works in practice. Imagine we have a function
that computes both the square and cube of a number. We cannot return both
values, since we can return at most one object. However, we can get around
this restriction using pass-by-reference as so:
#include <iostream>
using namespace std;
int main() {
int x = 2, squared, cubed;
cout << "In main():\n"
<< " x is located at " << &x << endl
<< " squared is located at " << &squared << endl
<< " cubed is located at " << &cubed << endl
<< "Before squareCube() function calls:\n"
<< " x = " << x << endl
<< " squared = " << squared << endl
11.2. PASS-BY-VALUE AND REFERENCES 117
return 0;
}
In this example, we have specified that y and z are passed by reference to the
function. Thus the original variables are in fact changed by the function. When
we compute the answers and store them in y and z, we are therefore also storing
them in squared and cubed (see Line 25).
If we investigate the output of the program, we find that squared and y share
the same memory location, as do cubed and z. This is because they were passed
by reference, and because they share the same memory location, any change to
y affects squared, and the same for the others!
In main():
x is located at 0x7fffc3f9cc44
squared is located at 0x7fffc3f9cc48
cubed is located at 0x7fffc3f9cc4c
Before squareCube() function calls:
x = 2
squared = 0
cubed = 0
In function squareCube():
x is located at 0x7fffc3f9cc2c
y is located at 0x7fffc3f9cc48
z is located at 0x7fffc3f9cc4c
After squareCube() function call:
x = 2
squared = 4
cubed = 8
As a final note, the idea of a reference is a general concept, and does not only
apply to passing variables to functions. We can think of a reference as an
alternative name for an object. In the example below, we create a reference to
a variable i.
int i = 0;
int &r = i;
r = 9; //i becomes 9 also!
118 CHAPTER 11. FUNCTIONS
11.3 Scope
A related concept to memory addresses is that of scope, which refers to the
visibility, accessibility and lifetime of objects and functions. In other words, the
scope of a variable or function determines when it is valid to use that variable.
The scope of a variable or function is ultimately determined by where that
variable or function was declared.
Figure 11.1: The coloured arrows indicate the scope or lifetime of the various
functions and variables. They can be used anywhere below the line they were
first declared.
11.3. SCOPE 119
Figure 11.2: The coloured arrows indicate the scope or lifetime of the local
variables. The rounded lines on the left indicate blocks of opening and closing
braces.
an inner block has the same name as one in an outer block, the outer variable
will be hidden, and only the inner variable can be referenced.
Figure 11.3: The coloured arrows indicate the scope or lifetime of the local
variables. Note that when ‘outerScope‘ is printed inside the inner loop, its
value is 5 and not 10, because it has hidden the outer variable with the same
name. Also note that we can create our own blocks if we wish, simply by using
opening and closing curly braces (as we have done for ‘p‘).
https://www.youtube.com/watch?v=0pdBr5JOzOw?vq=hd720
Chapter 12
121
122 CHAPTER 12. ARRAYS AND VECTORS
The above declares an array that will hold 9 integers. If each integer is 4 bytes,
then the compiler can determine that it must allocate 36 bytes of memory to
the array. The following is an example of using an array in practice. As with
Python lists, we can refer to particular elements in the array by specifying their
index and using the [] operator.
#include <iostream>
#include <array> //must use this!
using namespace std;
int main(){
array<int, 5> d; // d is an array of 5 int values
// initialize elements of array d to 0
for (int i = 0; i < d.size(); ++i){
d[i] = 0; // set element at location i to 0
}
/*
Output:
Element Value
0 0
1 0
2 0
3 0
4 0
*/
In the above, note that we use the size function to return the size of the array
(where Python uses len). We use a loop (Line 8) to initialise our array to
contain only 0. Without this loop, the values in our array would be undefined.
If we wish to declare and define the values in our array on a single line, we can
do so using an initializer list as follows:
12.1. C++11 ARRAYS 123
#include <iostream>
#include <array>
using namespace std;
int main(){
/*
Output:
Element Value
0 10
1 -1
2 2
3 4
4 100
*/
Note that if our initializer list contained less than 5 values, the remaining values
in the array would be set to zero. This a quick way of initializing an array to
be all zeroes is simply:
array<int, 5> d = {};
When specifying the array size, the compiler must be able to prove the size of
the array at compile-time (that is, before the program is even run). Thus we
cannot use variables to specify the size (because variables can vary in value).
Either we must use a literal value (such as a number), or we can use a constant
variable. A constant variable is a variable whose value is fixed and cannot
change. Good examples of constant variables are variables that store the value
of unchanging numbers, such as the number of days in the week, or 𝜋. As such,
the following are legal examples of array declarations:
array<int, 5> A; //valid because 5 is fixed
const int N = 5; // const indicates that it is fixed
array<int, N> B; //valid because N is constant
124 CHAPTER 12. ARRAYS AND VECTORS
The following are invalid declarations, and a compile error will occur:
int N;
cin >> N
array<int, N> A; // invalid because N is unknown at compile time
int M = 5; // normal variable
array<int, M> B; //invalid because M is a regular variable
Note here that we can loop through the array and access every element without
specifying an index. Since we have not specified an index, there is no chance of us
specifying an out-of-bounds index! C++11 provides similar functionality using
a range-based for-loop. You should always prefer this kind of loop, provided you
do not need access to the index value itself. The general form of the range-based
for-loop is:
for (<variable_declaration> : expression){
12.1. C++11 ARRAYS 125
//statements
}
If we were to rewrite the above Python example in C++, it would look very
similar to the Python version:
array<int, 5> values = {1, 2, 3, 4, 10};
// the type declaration below must be consistent with the array type
for (int x : values){ //we use a colon instead of in
cout << x << endl;
}
We can also combine references with range-based for-loops to loop over an array
safely while modifying the elements.
#include <iostream>
#include <array>
#include <cstdlib>
int main(){
array<int, 5> d = {1, 2, -1, 3, 5};
cout << "Items before modification: " << endl;
for (int item : d){
cout << item << " ";
}
//multiple elements of d by 3
for (int &itemRef : d){
itemRef *= 3;
}
cout << endl << "Items after modification: " << endl;
for (int item : d){
cout << item << " ";
}
cout << endl;
return 0;
}
/*
Items before modification:
1 2 -1 3 5
Items after modification:
3 6 -3 9 15
*/
In the above, we use an integer reference (Line 14) because d contain int values
and we want to modify each element’s value Because itemRef is declared as a
126 CHAPTER 12. ARRAYS AND VECTORS
reference, any change you make to itemRef changes the corresponding element
value in the array. If itemRef was not declared as a reference, then modifying
it within the loop would not change the array’s elements.
To summarise, we have looked at three different ways of looping over an array:
for (int i = 0; i < arr.size(); ++i){
//use if we explicitly need the value of i
cout << i << ":\t" << arr[i] << endl;
}
for (int element : arr){
//modifying element will not affect the array
cout << element << endl;
}
for (int &element : arr){
//modifying element will affect the array
cout << element << endl;
}
https://www.youtube.com/watch?v=Q3nEeSYg-P4?vq=hd720
In the above declaration, we have created an array called arr of size ROWS, where
each element is of type array<int, COLS>. Thus we have a table of size ROWS ×
COLS. In the image below, we can see how the elements of the 2D array are laid
out, and that we must use two indices if we wish to access any single element:
As with normal arrays, we can use initializer lists to initialise them with values
when they are declared. In the following program, we construct two 2D arrays,
and create a function that will print out their contents. Note that when looping
through a 2D array, we need a nested loop. The outer loop iterates over each
12.1. C++11 ARRAYS 127
Figure 12.1: A 2D array with the subscripts/indices of each element in the array.
row (Line 13), while the inner loop iterates through the columns of a given row
(Line 15).
#include <array>
#include <iostream>
//remember const!
const int ROWS = 2;
const int COLS = 3;
int main(){
array<array<int, COLS>, ROWS> matrix = {
1, 2, 3,
4, 5, 6
};
cout << "Matrix: " <<endl;
printMatrix(matrix);
array<array<int, COLS>, ROWS> matrix2 = {
7, 8, 9
};
cout << endl << "Matrix2: " <<endl;
128 CHAPTER 12. ARRAYS AND VECTORS
printMatrix(matrix2);
return 0;
}
/*Output:
Matrix:
1 2 3
4 5 6
Matrix2:
7 8 9
0 0 0
*/
In the above example, we have used a count-based for-loop to iterate over the
2D array. A quicker and safer way is to use the range-based for-loop to do so. In
addition, we can use the auto keyword, which was introduced in C++11. This
keyword informs the compiler that it should work out the variable type for itself.
Because our array is of type array<int, COLS>, we can save on this by simply
using auto and asking the compiler to fill this in for us. A cleaner version of
the printMatrix function is below:
void printMatrix(array<array<int, COLS>, ROWS> matrix){
for (auto row : matrix){
//auto infers that row is of type array<int, COLS>
for (auto element : row){
cout << element << ' ';
}
cout << endl;
}
}
https://www.youtube.com/watch?v=YPZck8WJow4?vq=hd720
12.1.4.1 Sorting
C++ provides functionality to sort arrays by ascending or descending order
using the sort function. An example of this is below:
12.1. C++11 ARRAYS 129
#include <iostream>
#include <array>
#include <string>
#include <algorithm>
int main(){
array<string, 4> colours = {"blue", "black", "red", "green"};
for (string colour : colours){
cout << colour << ' ';
}
cout << endl;
sort(colours.begin(), colours.end());
for (string colour : colours){
cout << colour << ' ';
}
return 0;
}
/*
Output:
blue black red green
black blue green red
*/
The begin() and end() functions refer to the start and end of the array respec-
tively (although end() is in fact one past the last element). The sort function
will thus know to sort all elements in the range [begin(), end()). In order to
sort in reverse order, we would replace begin() and end() with rbegin() and
rend(). In this instance, our array would then be sorted in reverse alphabetical
order.
12.1.4.2 Searching
C++ provides the binary_search function that can be used to determine
whether a given value is contained inside the array. This is an extremely ef-
ficient algorithm that can find a value in massive arrays very quickly. However,
the downside is that the array must first be sorted in order for it to work. An
example of searching for an element in an array is below:
#include <iostream>
#include <array>
#include <string>
#include <algorithm>
130 CHAPTER 12. ARRAYS AND VECTORS
int main(){
array<string, 4> colours = {"blue", "black", "red", "green"};
sort(colours.begin(), colours.end()); //must be sorted
string key = "black";
//look for black
bool found = binary_search(colours.begin(), colours.end(), key);
if (found){
cout << "We found the key 'black'" << endl;
}
return 0;
}
C++ also provides the count function, which is used to determine the number
of elements equal to a particular value. Unlike the previous function, the array
does not need to be sorted for this to work. An example of the count function
is below:
#include <iostream>
#include <array>
#include <algorithm>
int main(){
array<int, 5> nums = {1, 2, 3, 100, 2};
//counting number of twos
int numOccurrences = count(nums.begin(), nums.end(), 2);
cout << 2 << " appeared " << numOccurrences << " times" << endl;
return 0;
}
/*
2 appeared 2 times
*/
int main(){
array<int, 5> nums = {1, 2, 3, 100, 2};
//counting number of even numbers
int numOccurrences = count_if(nums.begin(), nums.end(), isEven);
cout << numOccurrences << " numbers are even" << endl;
return 0;
}
/*
3 numbers are even
*/
https://www.youtube.com/watch?v=MvFPCtLa5Ws?vq=hd720
12.2 Vectors
Arrays are great, but their main downside is the need to specify the size upfront.
If we do not wish to do this, we can use C++’s vector. Like arrays, a vector
represents a series of elements of the same type placed in contiguous memory
locations. However, vectors also provide additional flexibility in that they are
able to dynamically resize themselves! We therefore do not need to know be-
forehand how many elements will be stored in the vector—we can simply add
them to the vector as necessary, and the vector will take care of allocating more
space for them. In practice, there is often no need to use arrays at all—vectors
give us everything arrays can, and more!
12.2.1 Declaration
There are a few options when it comes to creating vectors. To begin, we must
first include the <vector> header. The following examples create a vector of
type integer, but the same applies to vectors of any type.
Example Comments
vector<int> vec; Creates an empty (size 0) vector
vector<int> vec(4); Creates a vector with 4 elements. Each element is initialised to zero. If this were a vector
We can also create a new vector that is a copy of an existing one, as in the
example bellow
132 CHAPTER 12. ARRAYS AND VECTORS
Note that unlike arrays, we need only specify the type of the elements of the
vector. Like arrays though, we can indeed have multidimensional vectors.
12.2.2 Initialisation
Like arrays, we can also provide the vector with initial values. We can do this
using either initialiser lists, or uniform initialisation. The following illustrates
how to initialise the values of a vector directly:
vector<int> myvector = {9, 7, 5, 3, 1}; // initialization list
vector<int> myvector2 {9, 7, 5, 3, 1}; // uniform initialization
12.2.3.1 Indexing
Accessing individual elements in the vector is done using the subscript operator.
Note that the subscript (or index) starts from 0 and runs up to one less than
the length of the vector.
vector<int> myvector = {9, 7, 5, 3, 1};
cout << myvector[1]; //get and display the 2nd element
myvector[2] = 6; //modify the third element
12.2.3.2 Iterating
There are primarily two ways of iterating over each element of a vector. We
could use a for-loop, with a counter that starts at 0 and runs over the whole
vector, as in the following example:
vector<int> myvector = {9, 7, 5, 3, 1};
for (int i = 0; i < myvector.size(); ++i){
myvector[i] = myvector[i] + i;
cout << myvector[i] << endl;
}
The advantage of this approach is that, within our loop, we have access to
the current index, which we can then use to get the element at that position.
12.2. VECTORS 133
Here we say “for each int e in the vector, print it out”. The advantage of this
is that we no longer need to worry about having to check bounds; however, we
lose access to the index.
As a final point, we could also use the range-based for-loop in conjunction with
references. This would allow us to change each element as we loop over them:
vector<int> myvector = { 9, 7, 5, 3, 1 };
for (int &e : myvector){
e = 0;
}
//after the loop, all the elements of the vector are zero!
//Clear all the elements from the vector and make it empty
vector<int> vec {1, 2, 3};
vec.clear(); //similar to Python!
//now vec = {}
// Changes the number of elements stored
vector<int> vec {1, 2, 3};
134 CHAPTER 12. ARRAYS AND VECTORS
vec.resize(5);
//now vec = {1,2,3,0,0}
vec.resize(1);
//now vec = {1}
// The opposite of push_back(). Removes the last element of the vector
vector<int> vec {1, 2, 3};
vec.pop_back();
//now vec = {1,2}
This illustrates that the sort function is independent of the data type its sorting.
It does not care whether its an array or vector—from its point of view, they’re
the same thing.
https://www.youtube.com/watch?v=agGlcToMO3M?vq=hd720
Chapter 13
Recursion
One of the most powerful concepts in computer science and programming is that
of recursion. Wikipedia has a great definition of recursion: recursion occurs
when a thing is defined in terms of itself. This is particularly useful to us when
the solution to a problem depends on smaller instances of the same problem.
For example, playing chess can be defined recursively: assume that we have a
function called playChess that is able to play a game of chess by picking good
moves. This function can be defined recursively as: play a move, then execute
playChess from the resulting position! Since playChess calls the function
playChess, it is said to be a recursive function.
𝑁 ! = 1 × 2 × … × (𝑁 − 1) × 𝑁 .
1 if 𝑁 = 0
𝑁! = {
𝑁 × (𝑁 − 1)! otherwise.
135
136 CHAPTER 13. RECURSION
Note that the above definition of the factorial is recursive, since the right-hand-
side of the definition itself contains a factorial function. Bot approaches are
equivalent to one another—they will both successfully compute the factorial of
a number. However, the first approach is iterative; it shows how to compute
the factorial by multiplying all the numbers from 1 up to 𝑁 . By contrast,
the second approach is recursive. As we mentioned, there is an equivalence
between iteration and recursion, and so the choice depends on whether it is
easier to model the problem recursively or iteratively. We now present the C++
functions for the two equations above:
int factorialIterative(int N){
int factorial = 1;
for (int i = 1; i <= N; ++i){
factorial *= i
}
return factorial;
}
Notice that the recursive definition has what we call a base case. This is also
known as an escape hatch, and is equivalent to a loop’s terminating condition.
It is a non-recursive statement that breaks the chain of recursion. Without it,
we would have infinite recursion (which is the equivalent of an infinite loop). To
see this, imagine we left out the base case:
int factorialBad(int N){
return N * factorialBad(N - 1)
}
1. The code section is where the entire program (with all its instructions
compiled to machine language) is stored.
2. Static data is where global variables are stored
1 This is a very good reason for using pointers. For example, imagine you wrote a function
that searches an array and returns a pointer to the element in the array with a particular
value. What if there is no such element matching that value? What should we return in that
case? A pointer that points to “null” is a good candidate in this case.
138 CHAPTER 13. RECURSION
Figure 13.2: A single stack frame, which holds the local variables, the return
address, the function’s parameters and space for the value being returned (if
any).
We can think of stack frames as the variables that the program is currently
working with. If a variable exists in a stack frame that is not on top, then it
cannot currently be referenced. This is how the scope of variables is managed.
We will first look at how this works with a single function call, before moving
on to the recursive case. The code below has two functions. We will show how
13.2. THE STACK FRAME 139
stack frames are added and removed as we proceed through the code one line at
a time. The arrows on the left indicate which line we are currently executing.
#include <iostream>
int main(){
int x = 2;
int z = square(x);
return 0;
}
Figure 13.3: We begin inside the ‘main‘ function, and so there is a single stack
frame on the stack belonging to ‘main‘. The function has two local variables, ‘x‘
and ‘z‘, which are stored in its stack frame. It also returns an integer, and so
its return space is 4 bytes wide.
with its predecessor programming languages B and C. Since these languages do not allow for
one array to be assigned to another, neither does C++.
140 CHAPTER 13. RECURSION
Figure 13.4: We then call the ‘square‘ function, which has a single parameter
‘x‘ and local variable ‘y‘. It also returns an integer and so have 4 bytes allocated
for this. Its stack frame is now placed on the stack on top of ‘main‘’s. Thus at
this point, the variables in ‘main‘’s stack frame cannot be accessed.
Figure 13.5: Finally, on Line 8, the ‘square‘ function returns. Its return value
is stored in an appropriate place, and its stack frame is discarded, leaving once
again only ‘main‘’s stack frame left.
13.3. SUMMARY LECTURE 141
Figure 13.6: We first invoke ‘factorial(3)‘. Hence there is a single frame on the
stack. The function will then proceed to compute 3𝑖𝑚𝑒𝑠‘factorial(2)‘
Now that the base case has been reached, the stack starts to unwind. The
frame for factorial(0) is removed, and the value 1 is returned. Then,
factorial(1) can compute its return value as 1 × 1. It returns 1, and the
frame for factorial(1) is removed. Similarly, factorial(2) can compute its
return value as 2 × 1. It returns 2, and the frame for factorial(2) is removed.
Finally, factorial(3) computes 3 × 2. The answer 6 is returned and the frame
for factorial(3) is removed.
Figure 13.7: ‘factorial(2)‘ has just been called, adding a stack frame on top of
the previous one. The function will then proceed to compute 2𝑖𝑚𝑒𝑠 ‘factorial(1)‘
Figure 13.8: ‘factorial(1)‘ has just been called, adding a third stack frame. The
function will then proceed to compute 1𝑖𝑚𝑒𝑠 ‘factorial(0)‘
13.3. SUMMARY LECTURE 143
Figure 13.9: ‘factorial(0)‘ has just been called, adding a fourth stack frame. The
function will then proceed to return 1. No more recursive calls are made, and
so the top stack frame will be removed.
144 CHAPTER 13. RECURSION
Chapter 14
We have reached the final chapter for this course! In this chapter, we will
seek to understand how C++ organises memory behind the scene (for instance,
understanding how vectors support dynamic resizing). We will also look at
concepts that are extremely important for low-level languages such as C. These
include built-in arrays and pointers. However, while it is important for us as
computer scientists to understand these low-level concepts, in practice we should
look to use modern C++11 constructs to avoid all this low-level unpleasantness.
We have already looked at one aspect of memory—the stack—previously. Here
we will discuss the other important aspects of memory.
14.1 Pointers
In this section, we’ll discuss a low-level concept known as raw pointers (or sim-
ply pointers). As mentioned, avoid these in your code if possible. Instead,
you should prefer to use modern C++11 smart pointers, which we will briefly
mention later on.
A pointer is a variable that holds the memory address of another variable.
Whereas a variable name refers to a particular value, a pointer indirectly refers
to a value by providing the address of the variable that stores the value. Access-
ing the underlying value through a pointer is known as indirection. We show
how this works in the diagram below, where x is a variable with value 7, and y
is a pointer that stores the memory address of x. In other words, y points to x.
Pointers are very closely related to the concept of references in C++. There
are, however, a few differences:
• Pointers can be declared without a value. References must be initialised.
• Pointers can be reassigned to point at different variables. References can-
not.
145
146 CHAPTER 14. MEMORY AND POINTERS
Figure 14.1: The variable ‘y‘ pointing to ‘x‘. It is easy to see how we can use
‘y‘ to get the value of ‘x‘ by simply looking up the variable at the location it
specifies.
The above declares a variable called ptr. This variable is of type integer pointer.
That is, it is a pointer that points to an integer variable. Note that any variable
declared as a pointer must be preceded by the * character. When the * appears
in a declaration, it is not an operator. Rather, it simply indicates that the
variable is a pointer.
Pointers can point to objects of any type. Recall that the const keyword can be
used to create constant, unchanging variables. It can also be used in conjunction
with pointers to ensure that the pointer cannot point to anything else. The
examples below show different kinds of pointers being declared. In order to
understand the exact type of the pointer, we can read the declaration from
right-to-left.
1 This is a very good reason for using pointers. For example, imagine you wrote a function
that searches an array and returns a pointer to the element in the array with a particular
value. What if there is no such element matching that value? What should we return in that
case? A pointer that points to “null” is a good candidate in this case.
14.1. POINTERS 147
Example Comments
double* ptr; A pointer to a double.
int** ptr; A pointer to an integer pointer.
const int* ptr; Read it from right-to-left! It is a pointer to an integer which is constant. Thus the integ
int* const ptr; Read it from right-to-left! It is a constant pointer to an integer. Thus the pointer is con
const int* const ptr; A constant pointer to a constant integer. Both the pointer and integer are constant.
Recall that the & operator can be used to get the memory address of a variable.
Thus an example of initialising a pointer to point to a variable is as follows:
int y = 20
int* xPtr = &y //points to y
int x = 10;
xPtr = &x //now points to x
Note that the * operator is only used during declaration. Otherwise, we simply
refer to the pointer by its name. If x is stored at location 0xffee, then the value
of the pointer will be 0xffee. Note that the address operator must be applied
to a variable. It cannot be applied to a constant or temporary expression, since
they have no valid memory location. For instance, the following is not allowed:
int *xPtr = &(12 * 2); //the value 24 has no memory location!
/* Output
Address of x: 0x7fffedda4f04
Value of xPtr: 0x7fffedda4f04
Value of x: 10
Value of *xPtr: 10
Value of *xPtr: 0
Value of x: 42
*/
Note that the memory location of x and the value of xPtr are the same. Thus
the pointer is references x and when dereferenced, the value of x is returned.
5 //by value
6 int cubeByVal(int x){
7 int t = x * x * x;
8 return t;
9 }
10
11 //by reference
12 void cubeByRef(int &x){
13 x = x * x * x;
14 }
15
16 //by pointer
17 void cubeByPtr(int *x){
18 *x = (*x) * (*x) * (*x);
19 }
20
14.2. BUILT-IN ARRAYS 149
21 int main(){
22 int number = 2;
23 cubeByVal(number);
24 cout << "After pass-by-value: " << number << endl;
25 cubeByRef(number);
26 cout << "After pass-by-reference: " << number << endl;
27 cubeByPtr(&number);
28 cout << "After pass-by-pointer-value: " << number << endl;
29 }
30
31 /**
32 After pass-by-value: 2
33 After pass-by-reference: 8
34 After pass-by-pointer-value: 512
35 **/
Note that on Line 27, we pass the address of the variable to the function, and on
Line 17, the function parameter is of type pointer. Thus the pointer parameter
is assigned the value of the memory address of number. Note also on Line 18
that we use the dereferencing operator to access the variable being pointed to.
This allows us to get and then set its value.
https://www.youtube.com/watch?v=MNvfy-78VUA?vq=hd720
Most of what we have discussed about C++11 arrays applied to built-in arrays,
such as initialisation:
int d[5] = {1, 2, -1, 3, 3};
and the ability to index elements with the [] operator. However, built-in arrays
do not possess functions such as size(). This means built-in arrays do not even
know their own size (more on this in a bit). Even worse, when built-in arrays
150 CHAPTER 14. MEMORY AND POINTERS
are passed to a function, they turn into pointers (this is called decaying)! This
means that the array is implicitly converted to a pointer that refers to the
memory address of its first element. In other words, a built-in array arr is
equivalent to &(arr[0]).
To illustrate, consider the two functions below. Their purpose is to loop through
a built-in array and print out all its elements. The first function accepts an array
as input along with its size, while the second accepts a pointer as input along
with the array’s size.
void printArray1(const int arr[], int size){
for (int i = 0; i < size; ++i){
cout << arr[i] << endl;
}
}
Both of these functions are perfectly valid and will work, But importantly, from
the compiler’s perspective, they are both identical. In other words, the compiler
does not differentiate between a function that receives a built-in array and one
that receives a pointer. To the C++ compiler, they are both pointers.
This is problematic, because as programmers, we now need to keep track and
remember whether a pointer is a just a regular pointer, or is in fact an array
(since they both look the same)! This is a common source of errors and is a
reason for why C++11 arrays were introduced.
An additional issue with built-in arrays is that they cannot be compared using
equality or relational operators. For example, if we wanted to know if two arrays
contain the same values, we could use the double equal sign with C++11 arrays.
Built-in arrays, however, are essentially pointers. Thus an equality check would
simply test whether both arrays point to the same memory location.
Finally, built-in arrays do not in general know their own size, and so a function
that accepts a built-in array must also take its size as an argument, which is
time-consuming and annoying. They can also not be assigned to one another,
since the C++ language forbids this.2
On the plus side, built-in arrays can still be made to work with the functions
provided by C++ such as sorting and searching. To do so, we must include the
<iterator> header file. Then sorting, for example, looks like this:
2 The reason it is forbidden is that C++ was initially designed to be backward compatible
with its predecessor programming languages B and C. Since these languages do not allow for
one array to be assigned to another, neither does C++.
14.2. BUILT-IN ARRAYS 151
sort(begin(arr), end(arr));
Ultimately though, there is no real good reason to use built-in arrays. Simply
use C++11 arrays and vectors instead!
The operator sizeof determines the size (in bytes) of an array or any other data
type, variable, or constant at compile time. When applied to a built-in array,
it returns the total number of bytes in the array. However, when applied to a
pointer, it returns the size of the pointer, not what the pointer references. In
the example below, we see that we can compute the number of bytes in the full
array, but as soon as the array is passed to the function, it decays to a pointer
and the array size is lost.
#include <iostream>
int main(){
double numbers[20];
cout << "The number of bytes in the array is "
<< sizeof(numbers) << endl;
cout << "The number of bytes according to getSize() is "
<< getSize(numbers) << endl;
return 0;
}
/*
Output:
The following code illustrates the effect of the sizeof operator on different data
types on a 64-bit computer. If you have a 32-bit machine, you may get different
results. Note that we only need to use round brackets with sizeof when we
want to compute the size of a type (e.g sizeof(char).
#include <iostream>
int main(){
char c; // variable of type char
short s; // variable of type short
int i; // variable of type int
long l; // variable of type long
long long ll; // variable of type long long
float f; // variable of type float
double d; // variable of type double
long double ld; // variable of type long double
int array[20]; // built-in array of int
int *ptr = array; // variable of type int *
cout<< "sizeof c = " << sizeof c << " "
<< "sizeof(char) = " << sizeof(char) << endl
<< "sizeof s = " << sizeof s << " "
<< "sizeof(short) = " << sizeof(short) << endl
<< "sizeof i = " << sizeof i << " "
<< "sizeof(int) = " << sizeof(int) << endl
<< "sizeof l = " << sizeof l << " "
<< "sizeof(long) = " << sizeof(long) << endl
<< "sizeof ll = " << sizeof ll << " "
<< "sizeof(long long) = " << sizeof(long long) << endl
<< "sizeof f = " << sizeof f << " "
<< "sizeof(float) = " << sizeof(float) << endl
<< "sizeof d = " << sizeof d << " "
<< "sizeof(double) = " << sizeof(double) << endl
<< "sizeof ld = " << sizeof ld << " "
14.2. BUILT-IN ARRAYS 153
/*
Output:
sizeof c = 1 sizeof(char) = 1
sizeof s = 2 sizeof(short) = 2
sizeof i = 4 sizeof(int) = 4
sizeof l = 8 sizeof(long) = 8
sizeof ll = 8 sizeof(long long) = 8
sizeof f = 4 sizeof(float) = 4
sizeof d = 8 sizeof(double) = 8
sizeof ld = 16 sizeof(long double) = 16
sizeof array = 80
sizeof ptr = 8
*/
https://www.youtube.com/watch?v=ktjaH8pkFB8?vq=hd720
Figure 14.2: The built-in array. Note that the first element is at location 3000,
the second at 3004 and so on. This is because each integer is 4 bytes wide.
Given that the value of arr is 3000, in normal arithmetic we would have arr +
2 = 3002. Pointer arithmetic is slightly different though. As arr is an array of
integers, we actually add the size of an integer times 2. So we get arr+(2×4) =
3008. Note that 3008 is the memory location at index 2, and so we have *(arr +
154 CHAPTER 14. MEMORY AND POINTERS
int main(){
int *arr = create(3, 3);
}
The problem here is that arr is a local variable that is stored in its function’s
stack frame on the stack. As soon as the function returns, its scope ends, but
the address of the array is returned anyway. We now have a pointer to some
invalid memory that may have been destroyed or reused!
stack where memory must be allocated upfront, the heap allows for allocation
to occur at any time. This is how a vector is able to resize itself whenever it
needs to.
In order to allocate memory on the heap, we must use the new keyword. When
we do so, memory is allocated on the heap and a pointer is returned to us.
When we want to free up or deallocate that memory, we must apply the delete
keyword to the pointer. For built-in arrays, we use the new[] and delete[]
keywords respectively. If we forget the square brackets, then only the first
element of the array will be deallocated.
You may encounter functions such as malloc, calloc and free when looking
at alternating resources. These are for C, not C++, and should never be used!
Figure 14.3: In the above example, we allocate a double variable on the heap.
The variable is stored in the heap, and we receive a pointer to this variable.
The pointer is stored on the stack. The variable on the heap will remain there
forever until we decide to ‘delete‘ it.
Figure 14.4: This example is identical to the previous one, except here we also
specify the value of the variable on the heap.
In all these cases, it is important to note that variables allocated on the heap will
remain there until we explicitly delete them. However, the pointer is allocated
on the stack and so is automatically destroyed by the compiler. Thus if we lose
the pointer, we can no longer access the memory on the heap, and if we have yet
to delete the memory, then it will never be deleted! This is known as a memory
leak.
156 CHAPTER 14. MEMORY AND POINTERS
Figure 14.5: Here we allocate a built-in array on the heap. 3 elements are
allocated on the heap, and a pointer to the first element is returned.
Figure 14.6: In the above example, we allocate a double variable on the heap.
We then use the pointer that references it to delete it, thus freeing up memory.
Figure 14.7: Here we have allocated a built-in array on the heap. We apply
the ‘delete[]‘ operator the pointer that references it to delete it, thus freeing up
memory.
14.3. DYNAMIC MEMORY ALLOCATION 157
int main(){
int *arr = create(3, 3);
//We now have a valid pointer to data on the heap
//don't forget to delete[] at some point
}
All in all, our main takeaway is that built-in arrays (whether declared on the
stack or heap) cause many issues that are ultimately unnecessary. C++11 ar-
rays are simply better built-in arrays, while vectors are better dynamic built-in
arrays, and so we should just use those instead.
However, if we try to assign multiple pointers to the same object, our program
will crash:
unique_ptr<int> ptr (new int(3));
unique_ptr<int> ptr2 = ptr; //invalid!
A shared pointer is similar to a unique pointer, but does allow for multiple
pointers to reference the same object. An example of creating a shared pointer:
14.5. SUMMARY 159
{
shared_ptr<int> ptr (new int(3));
// Going out of scope...
}
// I did not leak my integer here!
// The destruction of shared_ptr called delete
We can assign multiple shared pointers to the same object with no issue:
shared_ptr<int> ptr (new int(3));
shared_ptr<int> ptr2 = ptr; //no problem!
The following functions are useful for creating shared and unique pointers:
auto x = make_shared<string>("abc");
auto y = make_unique<int>(12);
14.5 Summary
To summarise, the heap can be used to store large amounts of data (and indeed
this is where vectors store their data). While you need to understand these
underlying concepts, try not to explicitly use them in your code—use smart
pointers instead of raw pointers, and vectors instead of dynamic arrays wherever
possible!