0% found this document useful (0 votes)
16 views159 pages

IAP Notes

The document outlines the course 'Introduction to Algorithms & Programming' taught by Dr. Steven James in Semester 1, 2022, covering topics such as algorithms, programming fundamentals, conditionals, iteration, and functions. It emphasizes the importance of respect and appropriate conduct among students and faculty, and details the online learning format due to COVID-19, including the use of Moodle and Discord for communication and submissions. The course includes practical labs and encourages interaction among peers and instructors.

Uploaded by

paulamabelebele9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views159 pages

IAP Notes

The document outlines the course 'Introduction to Algorithms & Programming' taught by Dr. Steven James in Semester 1, 2022, covering topics such as algorithms, programming fundamentals, conditionals, iteration, and functions. It emphasizes the importance of respect and appropriate conduct among students and faculty, and details the online learning format due to COVID-19, including the use of Moodle and Discord for communication and submissions. The course includes practical labs and encourages interaction among peers and instructors.

Uploaded by

paulamabelebele9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 159

Introduction to Algorithms & Programming

COMS1018A & COMS1022A

Dr Steven James

Semester 1, 2022[Updated: 2022-06-02]


2
Contents

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

8 C++ Variables, Types and Operators 85


8.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.2 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
8.3 Creating Variables with Values . . . . . . . . . . . . . . . . . . . 88
8.4 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
8.5 Summary Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

9 Input and Output 93


9.1 Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9.2 Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.3 Summary Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

10 Branching and Looping 99


10.1 If-statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
10.2 Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
10.3 Summary Lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

11 Functions 107
11.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
11.2 Pass-by-value and references . . . . . . . . . . . . . . . . . . . . . 113
11.3 Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

12 Arrays and Vectors 121


12.1 C++11 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
12.2 Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

13 Recursion 135
13.1 Computing the factorial . . . . . . . . . . . . . . . . . . . . . . . 135
13.2 The stack frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
13.3 Summary lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
CONTENTS 5

14 Memory and Pointers 145


14.1 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
14.2 Built-in arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
14.3 Dynamic memory allocation . . . . . . . . . . . . . . . . . . . . . 154
14.4 Smart Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
14.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
6 CONTENTS
7
8 CONTENTS
Course Outline

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:

• apply the fundamental building blocks of algorithms (sequence, selection,


repetition, abstraction) for developing solutions
• recall some fundamental algorithms and transfer algorithmic methods and
skills to new problems
• analyse simple problems and construct algorithms for their solution
• translate simple algorithms into working programs
CONTENTS 11

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.

Course Activity Day Times


COMS1018A Lecture (online) Tuesday 10h15 — 12h00
COMS1018A Lab (in person) Thursday 14h15 — 17h00
COMS1022A Lecture & Lab (online) Wednesday 17h30 — 21h00

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

Welcome to this course on algorithms and programming! This course is about


programming, of course, but it is also about problem-solving. If there’s one
thing that you take away from this course, it should be that computer science
is about the science of solving problems — the fact that we use computers to
do this is efficient, but actually besides the point.
Throughout this book, you will see two kinds of question boxes from time to
time:
Question! This is a question box that will test you have understood the concepts
presented in the notes. I recommend tackling the questions to make sure you
have a good understanding before moving on.
Side quest! This is a “side quest” box. These are random questions, tasks and
digressions that you may wish to look into if you are curious. They are not
necessary, but are just fun activities that you may be interested in tackling.
Learning shouldn’t be about doing something because you have to.
We will be using multiple programming languages in this course. The key take-
away here is that languages are just tools to get the computer to do what we
want it to. The core fundamentals of programming (and problem-solving) are
the same, regardless of the language we use. That is why this course is not called
“Introduction to Programming with Python”. In this chapter we will discuss the
core elements of problem-solving (algorithms), and then move onto how we can
actually get the computer to do the work for us (programming).

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

is no ordinary solution — an algorithm is a solution that is so complete, it can


solve any instantiation of the given problem.
To illustrate what we mean by this, let’s consider the following example. We’re
walking down the street and a young kid approaches us The conversation goes
as follows:
Kid: Excuse me, but can you help me?
Us: Sure, what’s wrong?
Kid: We’re learning about even and odd numbers in school!
Can you please tell me all the even numbers from 1 to 10?
Us: Sure, the even numbers are 2, 4, 6, 8, 10!
Kid: Thanks! You’re so smart! Bye now!
Okay, so in this example we were given a problem and we able to provide a
solution. No problem! But now imagine the kid is a bit of a smarty-pants, and
instead asks the following question:
Kid: Can you please tell me all the even numbers from X to Y?
Us: Sure, but it depends. What is X and Y?
Kid: I don’t know. I haven’t decided yet.
Us: …
Well now we have a bit of a problem. We could claim that we can’t give an
answer until the kid tells us the values of 𝑋 and 𝑌 , but then we wouldn’t seem
very smart, and the kid would run home and tell his parents how the grown-ups
are all stupid! So what else can we do? How can we answer such an open
question?
Then we hit on an idea. Instead of giving the kid a direct answer (which is
impossible), we can give him instructions on how to find the correct answer
once he’s decided what the values of 𝑋 and 𝑌 are! Our conversation could go
something like:
Us: Sure, but it depends. What is X and Y?
Kid: I don’t know. I haven’t decided yet.
Us: Okay, well in that case, here’s what you
do when you eventually decide what X and Y are.
If X is divisible by 2 (let’s assume for now he
knows what divisible means), then the first even
number is X, the second number is X+2, and you
keep adding 2 until you get to a number bigger
than Y, at which point you stop. However, if X
is not divisible by 2, then the first even number
is X+1, the second even number is X+3, and then
you keep going up in steps of 2 until you get to
a number bigger than Y, at which point you stop.
Satisfied, the kid runs off and tell his parents how smart we are!
1.1. ALGORITHMS 17

What we have done here is provided an answer to the problem as a sequence of


instructions. Informally, this is what we would call an algorithm. Note that our
solution is completely general! We can plug in any values for 𝑋 and 𝑌 , and if
we follow our instructions, we’ll get the right answer!
This is the magical thing about algorithms and computer science in general —
it’s not about solving one particular problem, it’s about solving all instances of
a particular problem class! Think of this like making the step from arithmetic
to algebra in school, but instead of mathematics, we’re solving problems! For
example, maybe you enjoy doing crosswords? In that case, you could develop
an algorithm (with some difficulty) for solving not just the crossword you’re
currently working on, but any crossword! The same for chess: let’s not just
solve a particular position, let’s solve any position! Sudoku? Let’s solve all the
Sudokus! You get the point.

1.1.1 The Science of Problems


At its heart, theoretical computer science deals with issues like: What makes
one problem harder or more difficult than another? Can we quantify what we
mean by “difficult”? A natural response to these questions might be to wonder:
doesn’t it depend on the person solving the problem? For example, we might find
solving a quadratic equation straightforward, but someone who has never done
mathematics will find it impossible!
As it turns out, the question of difficulty has nothing to do with the solver — it
is all about the fundamental properties of the problem itself!
Philosophy side quest! Think about the implications of the fact that certain
problems are just intrinsically harder than others, no matter how smart you are
or how many people are working on it! For example, a major open question
is: is solving a hard problem more difficult that verify that the solution is in
fact correct? This is what is known as the classic 𝑃 ≠ 𝑁 𝑃 ? problem. Most
theoreticians believe this to be true, but we have yet to prove this. This would
be equivalent to saying: it is much harder to create a work of art than it is to
verify that the object is in fact art. What would it mean if this were not the
case?!
Let’s look at an example to make this concrete.
Problem 1: What is 𝑥×𝑦 where 𝑥 = 33478071698956898786044169848212690817704794983713768568912431388982883
and 𝑦 = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396
Problem 2: Find two positive integers 𝑥 and 𝑦, both greater than 1, so that 𝑥 ×
𝑦 = 2519590847565789349402718324004839857142928212620403202777713783604366202070
7595556264018525880784406918290641249515082189298559149176184502808489120072
8449926873928072877767359714183472702618963750149718246911650776133798590957
0009733045974880842840179742910064245869181719511874612151517265463228221686
9987549182422433637259085141865462043576798423387184774447920739934236584823
8242811981638150106748104516603773060562016196762561338441436038339044149526
18 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.

1.1.2 Properties of an Algorithm


We have so far only informally referred to an algorithm as a set of instructions
that forms a solution to a particular problem. But let’s be a bit more specific.
1. An algorithm is a set of well-defined, unambiguous steps or instructions
to be carried out in sequence to accomplish some task. Note here that
the instructions must be precise and exact. There must be no room for
interpretation or confusion!
2. An algorithm is made up of individual instructions.
3. There can only be a finite set of instructions – no infinity please!
4. Each instruction is well-defined and its outcome predictable if the instruc-
tion operates on valid input
5. An algorithm has a start and stop point. The start may require some
inputs, and the stop may provide some output.
6. There is a direction of logic flow or sequencing. Once an instruction is
executed, it passes control to another instruction.
7. When executed (with valid input) an algorithm is guaranteed to terminate
in a sensible way.
Question! Based on these properties, consider the statements below and decide
whether they are in fact algorithms. If not, why not?
1. Add some numbers from 1 to 10.
2. Add up all the natural numbers from 1 to 10 (inclusive).
3. Add up all the natural numbers.
4. Add up all the real numbers from 0 to 1.
Mathematics side quest!
1. Are there more real numbers between 0 and 1 than there are natural
numbers?
1.2. PROGRAMMING 19

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.

1.2.1 A Brief History of Modern Computers


The first computers were the women employees of the JPL lab in 1936. They
were known as the NASA human computers and their job was to perform calcu-
lations on pen and paper every day, the results of which were ultimately used
by NASA to launch their space shuttles!
History side quest! Watch the 2016 film Hidden Figures.
The very first instance of what we would now refer to as a computer was built
in 1945. It was called the ENIAC (Electronic Numerical Integrator and Com-
puter) — it weighed 30 000kg and took up 167𝑚2 of space. It could perform
an astounding three hundred and fifty seven multiplication operations per
second, clearly much faster than any human could ever hope to achieve. (For
reference, your cellphone can perform well over a billion operations per second).
20 CHAPTER 1. INTRODUCTION

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

1.2.2 Doing Arithmetic with Electricity


How a computer works and executes operations is the topic of another course,
but let’s briefly explain how we instruct computers. Computers are made up of
electronic circuits that perform addition and various other mathematical opera-
tors. The question then is: how do we represent numbers with electricity? Well,
we really have only two options. Either there is electricity present, or there isn’t.
We might think of these as representing the numbers 1 and 0. 1 for electricity
on. 0 for electricity off. But how does this help us represent numbers if we only
have 0 and 1?
To answer this, let’s think about the numbers we use — we work in decimal
numbers, which means we have the symbols 0–9 that can be combined to produce
any number we want. But why are there ten symbols? There’s no real reason
(probably because we have ten fingers). It’s completely arbitrary! We could
have 9 symbols, or 12 symbols or 32 symbols, or we could replace the digits
with hieroglyphics. It doesn’t really matter because…

Figure 1.1: Right on, Mr Incredible!

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

Decimal number Binary number


0 00000000
1 00000001
2 00000010
3 00000011
4 00000100
5 00000101
6 00000110
7 00000111
8 00001000

And so on (do you notice the pattern?).

1.2.3 Programming languages


So at the very lowest level, computers execute mathematical operations using
ones and zeros. Should we instruct computers this way? Well, at first we had
to. There was no other choice. However, we soon realised that this was really
tough and annoying. Typing ones and zeros all day is slow going, and it’s really
easy to make a mistake.

To overcome this, we developed a programming language. We would write some


text in human-readable manner, and this would be directly converted into 1s and
0s. This is known as assembly and is still used today for low-level programming.
We can think of assembly as looking something like this that maps text to binary
numbers:

LOAD PAY -> 01010001


ADD OVERTIME -> 10110101
STORE TOTAL -> 00000001

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

1.2.4 Compiled and Interpreted Languages


Broadly speaking, there are two classes of languages (although some will blur
the lines): compiled and interpreted. In the first half of this course we will
be using Python, an interpreted language, while in the second half we will use
C++, which is compiled.
The difference between the two can be illustrated by this example. Imagine we
are asked to give a speech to a group of politicians from Spain. Unfortunately,
we don’t speak Spanish, so what can we do? Fortunately, we don’t actually have
to present the speech ourselves, so we can hire someone who speaks English and
Spanish, give him our speech that we’ve written in English, and go from there.
Let’s look at the compiled vs interpreted way of doing this.
The Compiled Way: We write our speech in English. The day before, we
hand it to our employee who then translates the speech into Spanish and writes
it down. The next day, we send him to the presentation and he reads the speech
out. Success!
The Interpreted Way: We write our speech in English. On the day of the
presentation, we go with our employee to the meeting. He stands at the micro-
phone and we whisper the first line to him in English. He translates it on the
spot and announces the first line in Spanish. Then we whisper the second line.
He translates it for the audience. Then we do the third line, and so on!
In a programming context, a compiled language comes with a program known as
a compiler. This compiler is architecture specific, so a compiled language must
have a compiler for Windows, and another for MacOS, and so on. This compiler
accepts a high-level language as input and outputs a low-level (machine code)
language. This low-level language can then be directly run on the computer.
Every time we wish to run the program, we can just directly execute the low-
level code — no translation needs to occur.

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.

An interpreted language comes with a special program known as the interpreter.


The programmer writes the program in a high-level language, and this is fed
1.2. PROGRAMMING 23

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.

1.2.5 A Quick Look at Python


We will now briefly look at Python, the interpreted language we’ll be focusing
on. In particular, we wil be using Python 3, the third version of the language
(note that your Python version number might look like 3.X.X — as long as
it starts with a 3, you’re all good). When programming (with whatever the
language), there are two aspects to it. The first is writing the code – we do this
by typing the high-level language in a text file. The next step is executing the
code, which in the case of Python, we do using the interpreter. In the video
below, we show how to do this in Ubuntu.
https://www.youtube.com/watch?v=mn1a12TlvQw?vq=hd720
The video below shows how to execute Python code using the Windows operat-
ing system and IDLE.
https://www.youtube.com/watch?v=HKDmx8958ls?vq=hd720
24 CHAPTER 1. INTRODUCTION
Chapter 2

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.1 Code Snippets


Throughout this book, we will be using snippets of code to illustrate the concepts
we’re referring to. These snippets consist of a number of lines, and are executed
in order:
First line is executed
Then second line is executed
Then third line is executed
...
These code snippets may contain comments. Comments are annotations that
can be made to the code to explain exactly what’s going on, or to help with
readability. They are ignored when the code is executed, and so do not affect
the functionality of the program in any way. It is good practice to comment
your code, but there is no need to comment every single line — use them only
when they help in understanding the code. In Python, comments are indicated
by the hash symbol as follows
# This is a comment! :)

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:

False class from or


None continue global pass
True def if raise
and del import return
as elif in try
assert else is while
async except lambda with
await finally nonlocal yield
break for not

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

hold; examples of types include integers and real-valued numbers. Programming


languages can be divided into two categories: statically-typed and dynamically-
typed languages. In statically-typed languages, once a variable has a given type,
it can never be changed. So if variable x is used to store integers, it will only
ever be allowed to store integers. By contrast, dynamically-typed languages
have variables that can change their type whenever they like. So a variable x
might initially be used to store integers, but then later on change to storing
decimal numbers, and then back to integers. Python is a dynamically-typed
language, and so a single Python variable can hold any kind of data! To see
this in action, let’s look at the following code:
x = 0
print(type(x)) # display the type of x
x = 1.4
print(type(x)) # display the type of x

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.

Name Values Allowed Example


Literals
Integer Any integer of any size! 0, -1, 2145
Float Real-valued numbers within some range and to some 1.2, -0.6, .3,
precision (roughly 15 decimal places) 1.4e10
Boolean True or false values only True, False
String Any sequence of characters “T-Pain sucks”
, ‘Jeff’

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

Numbers side quest! Let’s think about large numbers!


1. What’s the largest number you can think of? (Infinity is not a number,
it’s a concept.)
2. What is the smallest strictly-positive real number that Python can repre-
sent?
3. What is the largest positive real number that Python can represent?
4. From your knowledge of basic arithmetic, you’ll know that we have oper-
ators such as addition, then multiplication (which is repeated addition),
then exponentiation (which is repeated multiplication). But what comes
next?!
5. Use the internet to learn about Graham’s number.

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

Other examples of assignment are as follows:


meaning_of_life = 42
some_name = "Pogba's Haircuts"

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

2.3.1 Mathematical Functions


Python also provides a set of pre-defined mathematical functions. These include
the absolute operator and a rounding operator:
abs(-10) # produces 10
round(2.543434) # produces 3 (rounds to the nearest whole)
round(2.543434, 1) # 2.5 (round to 1 decimal place)
max(10, 20) # returns the max, 20
max(10, 20) # returns the max, 20
min(10, 20, 1) # returns the min, 1

For more functions, see here.

2.4 Rules of Precedence


As with regular arithmetic, programming operators also follow rules of prece-
dence (e.g. BODMAS/PEDMAS). When there is more than one operator on a
line, the calculation is done according to the standard ordering: first, brackets
are evaluated. Then exponentiation, followed by unary negation (unary nega-
tion refers to a negative number like −3), then multiplication, division and
modulus, and finally addition and subtraction. This is summarised in the table
below. Note that if two operators have the same precedence level, then which
one is evaluated first depends on the “direction of evaluation” (which is normally
left-to-right).

Precedence Level Operator/Symbol Operation Evaluation Direction


0 (first) () parentheses Inner ones evaluated first. Left-to-r
1 ** exponentiation Groups right-to-left
2 (unary) - negation (unary minus) Right-to-left
3 *, /, % product, division,modulus Left-to-right
4 +, - addition, subtraction Left-to-right

Question! According to these rules of precedence, what is the value of the


following statement? Do it by hand and then use the Python interpreter to
double check your answer.
6 // 4+ -9 % 3 * 2 / -100 * -3 - -(17*5)

2.5 Input and Output


So far, our small programs we’ve used haven’t been very useful — we saw how
to perform mathematical operations and assign values to variables, but that’s
about it. What we really want is for our programs to be flexible and useful to
the person using them. For example, if I want to look up a student’s marks
2.5. INPUT AND OUTPUT 33

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

2.6 Auto-marked Code*


When submitting code in computer science modules, they will be automatically
marked. This is done by running your submitted code and providing it certain
input. The marker then evaluates the output, and if it matches what was
expected, marks it correctly. However, if the output does not match exactly, it
will be marked incorrectly. Please watch the short video below to clarify some
common issues where, even if everything is correct, you might still be marked
wrong.
https://www.youtube.com/watch?v=7CSeZioR9vU?vq=hd720
Chapter 3

Conditionals

In the previous chapter, we looked at some very simple Python programs. In


those programs, we observed how they were executed line-by-line sequentially:
the first line ran, then the second line, and so on, until we reached the end of the
program. However, we will often want a program that is more flexible than a
sequence of steps. In particular, we may want the program to execute different
lines of code depending on certain circumstances. For example, when we go to
the ATM, there is a program running on the machine that first asks us to enter
our details, and then asks what we want to do. Depending on our response,
the program will display different things. If we want to withdraw money, it will
ask how much to withdraw, but if we want to check our balance, it will simply
display the amount. The ability to control the order in which statements execute
is known by many names (conditionals, branching, if statements) and is what
we’ll cover in this chapter.

3.1 Logical Expressions


Before we get into that, let’s look at one of the fundamental data types we
mentioned in the previous chapter: the Boolean. As we briefly touched on,
a Boolean variable is a variable that can take one of two values: TRUE or
FALSE. Booleans are used to therefore determine whether an expression is true
or not. Such expressions are known as logical expressions and they differ from the
arithmetic expressions we have previously seen. For example, we would evaluate
the arithmetic expression 2 * 3 as 6, but how do we evaluate the expression 2
> 3? Clearly when we evaluate this, we do not get a number! Instead, we get
a statement about its truth, which in this case is false: two is not greater than
three.

35
36 CHAPTER 3. CONDITIONALS

3.2 Logical Operations


Just as arithmetic expressions contain terms with operators like addition and
multiplication, so too do logical expressions. These logical operators can be
applied to Boolean variables to modify their values, or can be applied to other
variables to produce Boolean results. For example, we can apply the greater (>)
operator to two numbers to produce a Boolean outcome.
There are three fundamental Boolean operators (from which other operators
can be derived). These are:
1. Negation: negating a Boolean variable flips its value, so true becomes
false and false becomes true
2. Or/Union/Disjunction: generally known as the OR operator, this is
applied to two Boolean variables. The result is true if at least one of the
variables is true, and false otherwise.
3. And/Intersection/Conjunction: generally known as the AND opera-
tor, this is applied to two Boolean variables. The result is true if both of
the variables are true. If either or both are false, the result is false.
Question! Using the above rules for logical operators, evaluate whether the
following statements are true or false:
1. 4 is a positive number
2. 4 is a positive number OR -1 is a positive number
3. 4 is a positive number AND 3 is a positive number
4. 4 is a positive number AND -1 is a positive number

Boolean variables can be represented numerically. By convention, False has a


numeric value of 0, and True has a numeric value of any non-zero integer (but
usually 1). The tables below are known as truth tables, and you will encounter
them in BCO. They are used to list all the possible truth values of a set of
variables, as well as the outcome of applying Boolean operators to them. If
we have two logical expressions A and B, then the truth table for the three
fundamental operators are as follows:

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?

3.2.1 Order of Precedence


Just as with our regular operators, Boolean operators are evaluated according
to their rules of precedence, described in the table below. Importantly, all the
Boolean operators (except brackets) have lower precedence than the arithmetic
operators. Hence, we evaluate all arithmetic operators first (e.g. addition and
multiplication) and only then do we evaluate Boolean operators (e.g. equality
and logical not).

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

following statements are true or false:


1. A and B or C
2. A or B and C
3. A and C or B
4. A or C and B
5. A or C or B
6. A and B and C
7. (A or C) and (B or C) or C
Note that in most programming languages, including Python, the equality and
inequality operators are written as follows (pay special attention to equality,
and notice how it is different from the assignment operator):

Mathematical Operator Python Symbol


< <
≤ <=
> >
≥ >=
= ==
≠ !=

3.2.2 Short-circuit operators


In many languages, including Python, the Boolean operators and and or are
short-circuit operators. What does this mean? Well, it means that if a Boolean
expression can be determined at some point, the rest of the expression is not
evaluated. As an example, look back at the truth tables above. You’ll notice
that if we’re evaluating A and B, and A is false, then the expression is false
regardless of B’s value! Therefore, Python will not even inspect B — it will just
say that the expression is false. Similarly, when computing A or B, if A is true,
then the expression is true regardless of B’s value! The ability to determine the
truth of a Boolean expression without inspecting all terms is what we mean by
short-circuit operators.
While this may not seem useful at first, it can actually save a lot of computation.
For example, imagine we have a mathematical expression that is extremely long
and takes a while to execute (e.g. pow(x, 4.2) * tan(x) + sqrt(x) - ...).
Now if we wish to compute A or pow(x, 4.2) * tan(x) + sqrt(x) - ... >
0 and we know that A is true, then we can just completely skip the long equation,
since we already know that the expression will be true!
Question! Assume that the Boolean variable A is True, B is True and C is False.
Using the precedence rules and the knowledge of short-circuit operators, which
terms are not evaluated in the expressions below?
1. A and C
3.3. CONDITIONAL STATEMENTS 39

2. C and A
3. C and A and B
4. C and A or B
5. B or C and A

3.3 Conditional Statements


Having looked at Boolean expressions and operators, we can now return to our
initial discussion of conditional statements. These statements allow us to run
certain lines of code, but only when certain conditions are met! For example, if
we want a program that outputs a list of students who have passed the course,
then we want student names to be printed only if their grade is greater or equal
to 50.
Informally, these are known as if-statements, and in pseudocode, they look some-
thing like this:
if logical_expression:
statement_1
statement_2
In the above, the logical_expression is a Boolean expression that is evaluated
as true or false. If that expression evaluates to true, the lines of code below
it are executed. Otherwise, those lines are ignored! The lines of code “inside”
the if statement (shown above with indentation) are known as the body of the
if statement. The code in the body is executed only if the condition is met; all
other lines of code not in the body are executed as normal. Let’s now look at
an example of a program that will accept a student’s mark and print out the
word “Passed”, but only if they have a sufficiently high mark.
https://www.youtube.com/watch?v=9F6JsmJqGw8?vq=hd720
For reference, the code in the above video is the following:
grade = int(input())
if grade >= 50:
print("Passed")

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.

3.3.1 Else Clauses


In the above example, we used if statements to execute lines of code if a condition
was met. Otherwise, those lines were skipped, and the program continued with
the next lines. But what if we also want to execute certain lines if the condition
is false as well?! In our example of passing, let’s say we want to output “Passed”
if the student has passed, but also now output “Failed” if they haven’t. To do
that, we need the if-else statement, which says: execute these lines of code if
true, but otherwise execute these other lines of code instead. An example of this
is below.
https://www.youtube.com/watch?v=CtHd2RLzk5Y?vq=hd720
grade = int(input())
if grade >= 50:
print("Passed")
else:
print("Failed")

print("Program terminating...)

3.3.2 Elif Clauses


We now have the ability to handle two cases: when the statement is true, and
when it is false. But what if we want to handle multiple cases? For example, if
we’re given a mark, perhaps we want to output a symbol (e.g. A, B, etc). How
do we do that?
In Python, this is done with the elif clause (elif is short for else if ). This allows
us to check multipe expressions to find the first one that is true, and execute
the corresponding lines of code. Once that true condition is found, the rest
of the statements are skipped. An example of how we would go about that is
below.
https://www.youtube.com/watch?v=avd5oozk-b4?vq=hd720
grade = int(input())
if grade >= 75:
print("A")
elif grade >= 70:
print("B")
elif grade >= 60:
3.3. CONDITIONAL STATEMENTS 41

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

3.3.3 Nested ifs


If statements (as well as elifs and elses) can be used to execute any number of
lines of code. Since we can put any code inside an if statement, one thing we
can do is put other if statements inside! We call these nested if statements, and
an example of them is below:
num = int(input())
if num % 2 == 0:
if num % 3 == 0:
print("Divisible by 2 and 3")
else:
print("Divisible by 2; not divisible by 3")
else:
42 CHAPTER 3. CONDITIONALS

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!

3.4 Additional Reading


• https://www.geeksforgeeks.org/python-if-else/
• https://www.w3schools.com/python/python_conditions.asp
Chapter 4

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.

4.1 While Loops


The while loop is the most fundamental looping construct in programming. In
fact, all other loops are just while loops dressed up in more convenient forms!
In psuedocode, the while loop looks like this:
while logical_expression:
statement_1
statement_2
The while loop can be thought of as “As long as…”. In other words, as long as
the logical expression is true, execute the following lines of code. Therefore, the
lines of code inside the while loop will be executed until the logical expression

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!

Let’s look at an example by considering the following program:


i = 0
while i < 100:
print(i, '\t', i * i)
i = i + 1
print("Done.")

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

4.1.1 Count-controlled repetition


Let’s look at a problem that could benefit from the use of while loops:
A class of ten students took a test. The grades (integers in the range 0 to 100)
for this test are available to you. Determine the class average.
As a programmer, when faced with a problem such as the above, the first step is
not to sit down and start writing code immediately! First, we must determine
the problem to be solved, and then break the problem into smaller and smaller
subproblems until we have a solution. This refinement process might happen on
paper, or if we are experienced enough, in our head. In any case, once we have
done that and have the solution to the problem, only then are we ready to start
programming. Let’s look at how we might develop a solution in pseudocde to
the problem.
Top-level pseudocode:
Determine the class average of the test.
Well that was straightforward, but now we need to ask how we’re going to do
that? This leads to our first refinement:
First refinement:
Input the ten test grades
Sum them up
Calculate the average
Output the average
Okay, so now we have a better idea of how to do this. The first two steps are
still tricky though. How do we achieve them? One way would be to just have
ten input statements! But we’re lazy, and we don’t want to write that much
code (plus, what if the question asked for 1000 students?!) So instead we can
use our new idea of iteration to loop ten times to get the input and sum it up.
Since we know we need to do it ten times, we need some kind of counter to let
us know when we’ve reached ten, so we can stop. Also, when summing numbers,
we can keep track of the sum seen so far (starting at zero), and every time we
get a new number we just add it to this sum. Therefore, our new pseudocode
might look like this.
Second refinement:
set the sum to 0
set the counter to 0
while the counter is less than 10
input next grade
add grade to sum
add 1 to counter
set average to sum / 10
output average
46 CHAPTER 4. ITERATION

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!

Question! Convert the above pseudocode into a Python program. Run it on


your computer and test it with some input to make sure it’s working.

4.1.2 Sentinel-controlled repetition


In the above example, we knew how many times to loop. But what if that’s not
the case. To handle this, we need some kind of flag or special case to tell us when
to stop. This flag is known as a sentinel value. The loop will continue to run
until the sentinel value is encountered, at which point it will stop looping. For
example, we might write a program that allows the user to enter as many marks
as they want, and tells them that they should enter a special value (e.g. -1)
to stop! Modifying the above psuedocode to support this, our new pseudocode
may look as follows:

set the sentinel to -1


set the sum to 0
set the counter to 0
input a grade
while the grade is not the sentinel
add grade to sum
add 1 to counter
input a grade

set average to sum / counter


output average

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! Convert the above pseudocode into a Python program. Run it on


your computer and test it with some input to make sure it’s working.

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

4.1.3 Nested control statements


Just like if statements, we can put any number of statements inside the body of
a loop. Therfore, we could have loops inside loops (nested loops), if statements
inside loops, loops inside if statements, etc. Let’s look at a quick example these
nested control statements. As previously, we’ll take the problem, break it down
into refined pseudocode, and then finally write the code in Python.
Write a program that would count and display the number of students that have
passed and the number of students that have failed from a list of exam results for
10 students. If more than 8 students have passed, display “Bonus to lecturer!”
Top-level pseudocode:
Accept exam results
Display results
Decide if lecturer should receive a bonus

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

The equivalent Python code is as follows:


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)
if passes > 8:
print("Bonus!")

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!

4.2 For loops


In the previous examples, we wanted to count to some number. This counting
procedure is a very common occurrent and requires three things:
1. The initialisation of a counter.
2. The termination condition (i.e. when to stop).
3. The modification of the counter (i.e. how the counter should change every
time).
While this can be achieved with while loops, it is slightly inconvenient. Because
this is such a common requirement in programming, for loops were developed as
a convenient way of doing so. Let’s look at the code above, and how we would
instead write the same thing using a for loop instead.
https://www.youtube.com/watch?v=KGs7JoH3VVY?vq=hd720
passes = 0
failures = 0
# the for loop is below
for n_students in range(0, 10, 1):
mark = int(input())
if mark >= 50:
passes = passes + 1
else:
4.2. FOR LOOPS 49

failures = failures + 1
print("Passes", passes)
print("Failures", failures)
if passes > 8:
print("Bonus!")

4.2.1 Python Ranges


In Python, for loops are particularly straightforward to write because of the
ability to define ranges. A range produces a sequence of integers between a
start and end point (much like a range or interval in mathematics). In Python,
the general form of a for loop is as follows:
for counter_name in range(start, stop, step):
statement_1
statement_2
...

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.

Python statement Range of integers


range(0, 3, 1) [0, 1, 2]
range(5) [0, 1, 2, 3, 4]
range(0, 10, 2) [0, 2, 4, 6, 8]
range(20, 25) [20, 21, 22, 23, 24]
range(5, 2, -1) [5, 4, 3]

𝑁
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

4.3 Break and Continue


A final related concept related to loops are the two statements break and
continue. These allow us to modify how the loop works from within the body
of the loop itself. The break statements forces the loop to quit, regardless of
whether it has in fact completed! We can think if this as a kind of “force escape”
statement — immediately exit the loop I am in!
In the example below, we have an example of an infinite loop, since the logical
expression is always true.
i = 0
while True:
print(i)
i = i + 1

We can use the break statement to exit the loop.


i = 0
while True:
print(i)
if i > 10:
# as soon as i > 10, let's get out of here!
break
i = i + 1
4.4. ADDITIONAL READING 51

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.

4.4 Additional Reading


• https://www.geeksforgeeks.org/loops-in-python/
52 CHAPTER 4. ITERATION
Chapter 5

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

We can also create a list with a set of items inside it as follows:


another_list = [27, 39, False, "Batman > Superman"]

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

5.1.1 Modifying Lists


The above examples showed us how to create lists. But we can do much more
than that! We can add, remove and modify elements of the list as we wish. One
of the most common operations is to add items to a list. In Python, this is done
as follows:
student_names = list() # creates an empty list
student_names.append("Bruce Banner") # adds string to end of list
student_names.append("Bruce Wayne") # adds string to end of list
print(student_names)

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

more_names = ["Nakia", "Okoye", "M'Baku"]


student_names.extend(more_names) # add more_names to list
# Alternative: student_names = student_names + more_names
print(student_names)

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?

5.1.2 Accessing List Elements


Now that we have a list of items, the next question is how to access the elements
from that list. We can access any particular element using its location, or index.
An element’s index is its position in the list: the first element is at index 0, the
second at index 1, and so on. If a list has 𝑁 elements, then the last element
will be at position 𝑁 − 1. An example of this is below:
colours = ['red', 'blue', 'green']
print(colours[0]) # red
print(colours[2]) # green

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

5.1.3 Iterating through Lists


We can combine the loops we looked at in the previous chapter with our lists
in very powerful ways. The for loop allows us to access every element of the
list one at a time and use it for something. The example below shows how to
achieve this in Python
sum = 0
numbers = [5, 78, 23]
for x in numbers:
# x will be each element of list
sum = sum + x
print(sum)

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)

5.1.4 Element Exists?


We have seen that we can use the in keyword to loop through a list or range.
However, we can also use it to determine whether a particular value is contained
in a list! This is achieved by simply writing x in my_list, where x is the value
we are searching for, and my_list is the name of the list we want to search.
This will return a Boolean with value True if there is at least one occurrent of
x in my_list, and False otherwise. An example of this is below
5.2. DICTIONARIES 57

concert_lineup = ["Cassper", "AKA", "Kwesta"]


if 'AKA' in concert_lineup:
print("I'm not going")

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!

Figure 5.1: Thanos adding a marble to his gold hand thingy

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

2. remove a key-value pair


3. modify an existing pair
4. lookup a value given a key.

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 Python, we can create an empty dictionary (i.e. a dictionary with no items)


as follows:
my_dict = dict() # or my_dict = {}

We can also create a dictionary with a set of items inside it as follows:


another_dict = {"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
"""

It is very important to note that in certain languages, ordering is not guaranteed


by dictionaries. For example, if I insert two entries one after the other, there is
no guarantee that they will be printed out in that same order. If you are using
Python with version 3.6 or greater, dictionaries will preserve the order in which
entries were added to the dictionary. For earlier versions of Python, there is no
guarantee!

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

4. Sets do not support indexing. Because there is no order to the elements,


we cannot ask what the 𝑛th element is.

In Python, we can create an empty set (i.e. a set with no items) as follows:
my_dict = set()

We can also create a dictionary with a set of items inside it as follows:


another_set = {"Vegeta", "Goku", "Freeza"}

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

Up to this point, we’ve written our programs as a sequence of instructions, one


after the other. We might say that the code we’ve written is one big mono-
lithic block. It gets the job done, but that’s about it — it can’t be reused or
repurposed by anyone else. However, we have (perhaps unknowingly) been us-
ing other people’s code in our own programs. For example, if at any point you
computed the square root of a number, you used someone else’s code to do it!
Some programmer had written functionality for computing the square root of
a number and placed it in Python’s math library. In doing so, it made it very
easy for us to just use it, like so:
import math
x = 5
print(math.sqrt(x))

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

if abs(root - x) < tolerance:


break
x = root
print(root)

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)

So now we’re really wasting our time!

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!

6.2 What are Functions?


Depending on the language you’re using, functions may also be called meth-
ods, procedures or subroutines. Whatever their names, functions are essentially
pieces of code that have been “packaged” as a single unit. They usually serve
a single purpose or complete a single task, such as the sqrt function we just
looked at. And just like the functions we’ve been using so far, we can call a
function as many times as we want.
Ultimately, functions are essentially identical to the concept of mathematical
function which you’ll be familiar with. For example, let’s consider the simple
case 𝑓(𝑥) = 𝑥2 . This is a function with the following properties:
1. The name of the function is 𝑓. This name is completely arbitrary, and
could be anything we wanted. We could call our function 𝑏𝑙𝑎ℎ, and it
wouldn’t change anything: 𝑏𝑙𝑎ℎ(𝑥) = 𝑥2
2. The function takes a single parameter 𝑥. Again, 𝑥 is an arbitrary name.
If we were modelling something to do with time, perhaps 𝑡 would be a
better variable name: 𝑓(𝑡) = 𝑡2 .
3. Functions can also take multiple parameters. For example, we could have
𝑔(𝑥, 𝑡) = 𝑥2 𝑡, which takes in two parameters.
4. We can call or invoke a mathematical function by providing it with an
argument. For example, 𝑓(2) is the case of us providing the argument 2
to function 𝑓, which will obviously produce 22 . Similarly, we could say
𝑔(1, 2) which provides two arguments to 𝑔 and produces an answer of 2.
Of course, if we wish to create a Python function, we must follow Python syntax
rules. Below, we see how the function 𝑓 above is translated into a Python
function that is functionally identical.
https://www.youtube.com/watch?v=b3hvq9YcUWI?vq=hd720

6.3 Defining Python Functions


In Python, functions take the following form:
def <function_name>(<parameter_list>):
<function_body>
Let’s analyse each of these components in turn.
1. First, we have def. This is a Python keyword that lets the interpreter
know that we are defining a function. All functions must start with this
keyword.
66 CHAPTER 6. FUNCTIONS

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

Let’s look at a few examples below to make things clearer


# a function called add that accepts two parameters and adds them up
def add(x ,y):
return x + y

# a function that has no parameters


def greet():
print("Hello") # this function does not return. Printing to the screen is not retu
6.4. INVOKING PYTHON FUNCTIONS 67

# A function that accepts a string and integer as parameters


def greet2(name, num_times):
for i in range(num_times):
print("Hello", name)

6.4 Invoking Python Functions


Having defined the above functions, the next question is how to use them?
Making use of a function is known as calling or invoking it and is done by
using the function name and passing arguments to the function (if it has any
parameters). The examples below show how to invoke the functions we just
defined:
# remember that add takes two parameters
x = 5
y = add(2, x) # the returned value is stored in y

# greet takes no parameters


greet()

# greet2 takes 2 parameters


greet("Bob", 10)

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)

def show_message(x, y):


for i in range(x):
for j in range(y):
print("*", end="")
print()

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.

6.5 Function Returns


When a function finishes executing its code, the function returns control to the
line where it was invoked. In other words, after the function has performed the
task, the program will continue execution from the point after the call
When a function terminates, it is said to return. There are three ways that a
function can terminate:
1. If it executes all the statements in its body.
2. If it runs into the statement return.
3. If it runs into the statement return <exp>, where <exp> is a Python
expression.
If a function does not return a value or variable (as in the first two cases), it is
said to be a void function. Otherwise, it is a non-void function.
Question! Earlier on, we defined three functions. Classify each of them as void
or non-void.
Let’s look at examples of each of these:
# we run into the end of the function and exit
def f():
6.6. FUNCTION PROPERTIES 69

print("Hello, world!")
# returns here

# early return from void function


def g(x, y):
if y == 0:
print("y can't be 0")
return # returns here

print("Calculating...")
z = (x * x + y * y) / y
print("The answer is", z)

# return from non-void


def h(firstName, surname):
f() # functions can invoke other functions!!
if firstName == "" or surname == "":
return "Default Name" # returns here
return firstName + " " + surname # returns here

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.

6.6 Function Properties


Functions embody two of the most important principles in computer science:
interfaces and encapsulation/abstraction. Again consider the sqrt function that
Python provides. How exactly does it work? The answer is: it doesn’t matter
70 CHAPTER 6. FUNCTIONS

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

6.7 Additional Reading


• https://www.w3schools.com/python/python_functions.asp
• https://www.learnpython.org/en/Functions
• https://realpython.com/defining-your-own-python-function/
72 CHAPTER 6. FUNCTIONS
Chapter 7

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

However, we should not be discouraged by the sheer complexity of C++, since


we will not be looking to write overly-complex or convoluted code. In fact, the
parts of C++ we will be using are in general very clean, simple, and straight-
forward to understand. In comparing C++ to Python, we will see that the
concepts of sequencing, branching, looping, functions and input/output are ex-
tremely similar, and so will hopefully be readily understandable.
The major differences between the two languages are in their syntaxes (how
we actually write down a for-loop or if-statement), their typing (Python is a
dynamically typed language, whereas C++ is statically typed), and their execu-
tion (Python is interpreted, whereas C++ is compiled). We will investigate each
of these differences in detail in the next few sections and subsequent chapters.

7.3 The Compiler


Recall that Python is an interpreted language. This means that our code is
executed by an interpreter, which is a special program written for each architec-
ture (such as Windows, Ubuntu, etc). The interpreter takes each line of Python
code, converts it to the appropriate machine code, and then executes it on the
underlying computer. This process is repeated for every line until the program
is finished. Importantly, this conversion happens every single time we run the
code.
By contrast, C++ is a compiled language. A compiled language uses a compiler,
which is a special program written for each architecture that accepts the C++
code and produces low-level (machine) code. This is quite similar to how the
interpreter converts Python code to machine code, but importantly here the
compiler does not execute the code. A separate process is used to execute
the resulting low-level code which was output by the compiler. This is the
most important difference because it means that the conversion from our high-
level code to machine code only happens once. After that, the machine code
is directly executed every time we run the code. Compare this to interpreted
languages, where the conversion happens every single time the code is executed.
As you can imagine, this conversion process adds additional overhead. As a
result, compiled languages are often significantly faster than interpreted ones.
The figure below gives a rough illustration of the difference between compiled
and interpreted languages.

7.4 Compiling our First Program


Before we look into the details of how compilation works, let’s see it in practice.
We’ll start by creating a simple C++ program that will display the text Hello,
world! to screen. Recall that to achieve this in Python, we would simply write

case? A pointer that points to “null” is a good candidate in this case.


76 CHAPTER 7. INTRODUCTION TO C++

Figure 7.1: The difference between compiled and interpreted languages. In


a compiled language, it is the output of the compiler (the machine language)
that is directly executed on the underlying computer every time the program is
run. By contrast, the interpreted language must pass through the interpreter
to be converted to machine code every time it is executed. One downside of the
compiled approach is that if the code changes in any way, it must be recompiled
before being executed.
7.4. COMPILING OUR FIRST PROGRAM 77

print("Hello, world!)

By contrast, the corresponding C++ program looks like this:


#include <iostream>

using namespace std;

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!

7.4.1 Compilation in Ubuntu


In this section, we will demonstrate how to compile and execute a C++ program
in Ubuntu (and which should work roughly the same on macOS). If you are using
a Windows PC, please watch the video at the end of this section. Importantly,
regardless of the operating system, the work going on behind the scenes remains
the same. So even if you have Windows, please read this section carefully.
First off, create a new text file called helloWorld.cpp and type the above code
into it. Be sure to save the file. Next, open the terminal in the same directory
and enter the following command to compile the program:
g++ -Wall -std=c++11 helloWorld.cpp -o helloWorld
The above command turns the text file into a program, and outputs an exe-
cutable file called helloWorld. If you receive any error messages in terminal,
you will need to correct the errors in your code, save the file, and rerun the
command. Note that if everything has gone well, the terminal will not display
anything.
You should now have a new file in the directory called helloWorld. This is the
file produced by the compiler that can be directly executed on our computer.
To run it, enter the following into the terminal:
./helloWorld
The following output should now be displayed in the terminal window: Hello,
world! Note that every time you make a change to the code, you will need to
save your file and run the above g++ command before executing it.
Before we move on, let us quickly have a look at the command we typed in order
to convert our C++ code into something that can be executed. For reference,
78 CHAPTER 7. INTRODUCTION TO C++

our command we used was


g++ -Wall -std=c++11 helloWorld.cpp -o helloWorld
1. g++ is the built-in compiler on Ubuntu. This is mandatory.
2. -Wall forces the compiler to print all warnings to the terminal. Warnings
are not errors, and so need not be fixed, but they are often a good indi-
cation that something may be wrong. Note that this is optional and
does not need to be present to compile.
3. -std=c++11 tells the compiler that we are using the 2011 version of C++.
In this course, this will be the standard version, but not that the latest
version is 2020. If you do not specify it, your compiler will use its default
version (which varies depending on your setup). If your compiler by default
uses an older version, then some of the code in these notes will not execute!
Thus be sure to specify the 2011 version to avoid these issues. Note
that this is optional and does not need to be present to compile.
However, the marker uses this version, and so if you leave it out,
there is a risk that your program will execute perfectly fine, and
then be marked incorrect by the marker. For this reason, we
encourage you to always specify the 2011 version.
4. helloWorld.cpp is the name of the text file you have written your code
in. You must specify this to let the compiler know which file you would
like compiled. This is mandatory.
5. -o helloWorld is a flag that specifies what the name of the file created
by the compiler should be called. In this case, the file will be called
helloWorld, but you could pick any name you wanted. Note that this
is optional. If you do not specify an output file name (that is,
if you leave off -o myFileName) then by default the compiler will
produce an output file with the name a.out.
The video below shows us compiling a C++ file on Ubuntu.
https://www.youtube.com/watch?v=b4djyVydydY?vq=hd720
The video below shows us compiling a C++ file on Windows.
https://www.youtube.com/watch?v=NamFbfp5cyg?vq=hd720

7.5 The Compilation Cycle


Before we look at the details of the above program, we first need to understand
what just happened. How did the compiler take our code and convert it into
something that can be directly executed on our machine? In C++, this is
achieved through the compilation cycle, which consists of the following steps:
1. edit,
2. pre-processing
3. compile,
7.5. THE COMPILATION CYCLE 79

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

7.6 Analysing our First Program


To conclude this chapter, let us take a moment to analyse the Hello, world!
program we looked at earlier. For convenience, we repeat the program below,
adding comments for greater clarity
/*These are comments. They are used to assist the programmer.
They do not affect the program in any way.
Write whatever you want*/
7.6. ANALYSING OUR FIRST PROGRAM 81

#include <iostream> //preprocessor directive (use input/output)

using namespace std; //use standard definitions

//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.2 The Include Directive


Line 4 is a preprocessor directive. Before compilation actually occurs, the di-
rective instructs the compiler to replace the line with the entire contents of the
specified file. In this case, the contents of the standard library file iostream
(which defines the standard C++ input and output functions) is added to the
file. 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). Note that in Python, there was
no need to import any input or output functionality, since it is always present
by default. In contrast, C++ does not make this assumption and so it is up to
us, the programmer, to #include it.

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.

7.6.4 The Main Function


Every C++ program must have a special function called main, which defines
the entry point of the program. That is, it defines where the code should start
being executed from. All C++ programs begin processing at the first executable
statement in main.

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.

7.7 Summary Lecture


https://www.youtube.com/watch?v=KNi-k8SX728?vq=hd720
84 CHAPTER 7. INTRODUCTION TO C++
Chapter 8

C++ Variables, Types and


Operators

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.

asm else new this


auto enum operator throw
bool explicit private true
break export protected try
case extern public typedef
catch false register typeid
char float reinterpret_cast typename
class for return union
const friend short unsigned
const_cast goto signed using
continue if sizeof virtual
default inline static void
delete int static_cast volatile
do long struct wchar_t
double mutable switch while
dynamic_cast namespace template

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

appropriate amount of memory. While variables in Python also had types, we


were free to change the types throughout the program. This is why we refer to
Python as a dynamically-typed language. By contrast, C++ is statically-typed,
which means that once a variable has a particular type, it can never change.
For example, the following Python code is perfectly legal:
x = 0
print(type(x)) # prints <class 'int'>
x = "Hello" # the variable type changes!
print(type(x)) # prints <class 'int'>

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;

produces the compilation error:


error: invalid conversion from ‘const char*’ to ‘int’
C++ provides a set of built-in types, and offers users the ability to define new
types themselves (user-defined types). Some of the more important built-in
types include int (integer), double (real number), char (a single character) and
bool (a boolean: either true or false). string, which stores text, is technically not
a built-in data type (i.e. it is not part of the core language), but is universally
supported and can almost be treated as such.
The table below lists the set of most-commonly used C++ types. Note that
C++ has different types for strings, and chars, which simply represent a single
character. For strings, we use double quotation marks, but for characters we use
single quotation marks.

Table 8.2: A table of common C++ types.

Name Keyword Kind Values allowed


Boolean bool Built-in True/False
Character char Built-in A single character
Integer int (also short/long) Built-in Integers within some range
Floating-point double (also float) Built-in Real-valued numbers in a range with some precision
String string Standard library Sequence of characters

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

8.3 Creating Variables with Values


8.3.1 Declaration
The very first time a variable is introduced into a C++ program, we must specify
its type. We only define the type this first type, and then afterwards simply
refer to the variable by its name. Creating a variable with a name and type is
called declaration. Some examples of variable declarations are below:
int x;
double y;
string someMeaningfulName;

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.

Table 8.3: C++ operators applied to different data types. If there


is no entry in the table, then the operator cannot be applied to
that type.

bool char int double string


assignment = = = = =
addition + +
concatenation +
subtraction - -
multiplication * *
90 CHAPTER 8. C++ VARIABLES, TYPES AND OPERATORS

bool char int double string


division / /
remainder (modulo) %
increment (add) by 1 ++ ++
decrement (subtract) by 1 – –
increment by n +=n +=n
add s to end +=s
decrement by n -=n -=n
multiply and assign *=n *=n
divide and assign /=n /=n
remainder and assign %=n
equals == == == == ==
not equal != != != != !=
greater than > > > > >
greater than or equal >= >= >= >= >=
less than < < < < <
less than or equal <= <= <= <= <=
negation !
unary minus - -
logical or ||
logical and &&

bool b = true; //b assigned to true


string s = "Hello" //s assigned to Hello
s = s + " world" //s assigned to Hello world (concat)
int x = 5;
x++; //increment, x now equals 6
int y = 7;
bool isEqual = (x == y); // isEqual contains false
int rem = y % x; //rem is the remainder of y divided by x
double z = y; //assign z the current value of y
z *= 3; //equivalent to z = z * 3;
double p = 9.0 / 2; //p is 4.5
double q = 9 / 2; // integer division!! q is 4

8.4.1 Logical Operators


The C++ logical operators are exactly those in Python. However, there are a
couple of differences in syntax. To perform the logical or operation in C++,
we must write ||, and to perform logical and we must write &&. As in Python,
both && and || are short-circuit operators, meaning that if the expression can
be determined after evaluating the first condition, the remaining conditions are
not evaluated.
One final difference between the two languages can be seen in the following
8.4. OPERATORS 91

example. Imagine we wanted to calculate whether a mark was between 50 and


74. In Python, we could write this as:
50 <= mark <= 74

However, in C++ we must explicitly write this as two separate conditions:


50 <= mark && mark <= 74

8.4.2 Mathematical Functions


In addition to the above, the C++ standard library offers some additional math-
ematical functions, such as square rooting and exponentiation. The following
code illustrates some basic usage
#include <iostream>
#include <cmath> //need this for math functions like sqrt, pow
using namespace std;
int main(){
int x = 4;
x = x + 3;
int y = x * x; //y is 49
y = sqrt(y); //take the square root of y
x = pow(x, 4); // x to the power of 4
double p = -x;
double q = abs(p); // the absolute value of p
return 0;
}

8.4.3 Precedence
The rules of precedence for Python and C++ are identical, and we list them in
the table below for reference.

Table 8.4: Rules of precedence in C++.

Precedence Level Operator/Symbol Operation


0 (first) () parentheses Inn
1 (unary)-, ! negation (unary minus/NOT)
2 *, /, % product, (integer) division, modulus
3 +, - addition and subtraction
4 <=, <, >, >= less than or equal, less than, greater than, greater than or equal
5 ==, != equal to, not equal to
6 && logical AND
7 || logical OR
8 = assignment
92 CHAPTER 8. C++ VARIABLES, TYPES AND OPERATORS

8.5 Summary Lecture


https://www.youtube.com/watch?v=IgYumDDDz8c?vq=hd720
Chapter 9

Input and Output

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>

using namespace std; //use standard definitions

int main(){ //bracket signals the start of the main function


cout << "Hello, world!";

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

int age; //we must create a variable to store input


cin >> age; // read in data and store in age
cout << "Wow, you are " << age <<"! You old!" << endl;

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

9.2.1 String Input


An important detail about input in C++ is that it will read all the characters up
to the first whitespace it encounters. This whitespace could be a regular space,
or a newline, but C++ will only accept input up to this point. For reading
strings, this means that C++ only reads one word at a time. Let’s look at this
using the example below:
#include <iostream>

using namespace std;

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>

using namespace std;

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>

using namespace std;

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>

using namespace std;

int main(){
string firstName;
cout << "Please enter your first name" << endl;
cin >> firstName; //input first name
int yearOfBirth;
9.3. SUMMARY LECTURE 97

cout << "Please enter your year of birth" << endl;


cin >> yearOfBirth;
int age = 2018 - yearOfBirth;
cout << "Hello, " << firstName << endl; //output message and name
cout << "Your age is " << age << endl; //output message and age
return 0;
}

9.3 Summary Lecture


https://www.youtube.com/watch?v=xjFPW3szp_o?vq=hd720
98 CHAPTER 9. INPUT AND OUTPUT
Chapter 10

Branching and Looping

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

The C++ equivalent of this is as follows


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)

Then, the equivalent program in C++


10.2. LOOPS 103

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

• <exp_1> initialises the control variable


• <exp_2> is the termination condition
• <exp_3> changes the control variable and is executed after the loop body
In more detail, the initialisation expression allows you to declare and initialise
a control variable before the loop begins (or assign a value to an existing vari-
able). This expression is executed only once before the loop, and never again.
The termination condition expression is a boolean test that determines whether
the body of the loop should be executed. Finally, the variable update expression
allows us to modify the control variable in some manner. After the first exe-
cution of the loop body, the variable is updated, and the condition is checked.
If the condition is still true, the loop body repeats, after which the variable is
updated, and so forth. Each of the above three expressions is optional and so
can be omitted, but the semicolons are mandatory. Note that if the condition
is empty, it is evaluated as true and the loop will repeat indefinitely (unless we
stop it some other way).
We now show the for-loop in action, using the same pass/fail calculator program
in the previous section. For reference, we first show the Python program:
passes = 0
failures = 0
for n_students in range(0, 10, 1):
mark = int(input())
if mark >= 50:
passes = passes + 1
else:
failures = failures + 1

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

for (int nStudents = 0; nStudents < 10; ++nStudents){


cin >> mark;
if (mark >= 50){
passes = passes + 1;
}
else{
failures = failures + 1;
}
}
cout << "Passes " << passes << endl;
cout << "Fails " << fails << endl;
return 0;
}

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!

10.2.3 Do-While loop


The do-while loop is similar to the while-loop, and does not have an equivalent
in Python. It is useful when we want to ensure that the loop runs at least once.
A good use-case for this is displaying a menu to a user. In this case, we would
always want the user to see the menu at least once, even if they then simply
quit the program. The general form of this loop looks like this:
do{
// statement 1
// statement 2
// etc
}
while (<exp>); //NB: semicolon!

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

is rarely used, and the while-loop is much more common.


Question! Can you think of other use cases where a do-while loop would be
useful?

10.2.4 Break and Continue


The break and continue statements are identical to their Python versions. Of
course, since we are using C++, the statements must end with a semi-colon.
An example of the continue statement is below:
for i in range(3, 20):
if i == 7:
continue
print(i)

for (int i = 3; i < 20; ++i){


if (i == 7){
continue;
}
cout << i << endl;
}

10.3 Summary Lecture


https://www.youtube.com/watch?v=1dmMoNmbzqg?vq=hd720
Chapter 11

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

different numbers of parameters and different return types:


// the main function returns an int, and has no params
int main(){
cout << "Hello, world!" << endl;
return 0;
}

// the function has no params, and no return


void greet(){
cout << "Hello" << endl;
}

// the function takes as input two doubles, and returns a double


double add(double x, double y){
return x + y;
}

// the function accepts a string and int, and returns nothing


void greet(string name, int numTimes){
for (int i = 0; i < numTimes; ++i){
cout << "Hello " << name << endl;
}
}

Some important properties of functions are as follows.


• Like variables, C++ functions must be declared before they are first used.
• Unlike Python, C++ functions may return at most one object via its
return statement. The object being returned must match the return type
that has been specified. So if the function is supposed to return a string,
it cannot return an integer, for example. Similarly, if the function has a
return type of void, then it cannot return anything at all.
• The parameters of the function are declared with their types.
• Once the function returns, the variables in the parameter list, and any
other variables declared inside the function, are lost.
• Functions can call other functions
• If a function has no parameters, then its round brackets are left empty.

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.1.1 Return type


A function may return a value. The return type specifies the data type of the
value the function returns. However, if the function does not return a value, the
return type is the keyword void.

11.1.1.2 Function name


This is the actual name of the function. The choice of name is completely up
to the programmer, but should explain what the function actually does. The
naming rules for functions follow that of variables.

11.1.1.3 Parameter list


This represents the values passed to the function when it is used (invoked).
These parameters are also known as formal arguments, and list the type, order
and number of parameters of a function. The parameter list, together with
the function name, constitute the function signature. If you declare multiple
functions, they must all have their own unique signature.1 They must differ
either by parameter list or function name (or both).

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

11.1.3 Function returns


Like Python, C++ functions can return in three ways. For void functions, a
function can return by either reaching its closing brace, or a return; statement.
Non-void functions return by encountering the statement return <exp>; where
<exp> should be of the correct type to be returned. Various examples of different
ways a function can return are below:
#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 run into the closing bracket and exit


void f(){
cout << "Hello, world!" << endl;
} //returns here

//early return from void function


void g(double x, double y){

if (y == 0){
cout << "y can't be 0" << endl;
return; //returns here
}
112 CHAPTER 11. FUNCTIONS

cout << "Calculating..." << endl;


double z = (x * x + y * y)/y;
cout << "The answer is " << z << endl;
}

//return from non-void


string h(string firstName, string surname){

if (firstName == "" || surname == ""){


return "Default Name"; //returns here
}

return firstName + " " + surname; //returns here


}

int main(){
return 0;
}

11.1.4 Invoking Functions


In order to actually use a function, we need to invoke or call it. When a
function is invoked, control is passed to the function which performs the defined
task. When the function terminates, control returns back to the line where
we first invoked the function. To invoke a function, we simply need to pass
the required parameters (of the correct type) along with the function name.
Additionally, if the function returns a value, we can store it in a variable. The
following example illustrates this for a function that calculates the minimum of
two numbers.
#include <iostream>
using namespace std;

int min(int a, int b){


if (a < b){
return a;
}
//why don't I need an else here?
return b;
}

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 Pass-by-value and references


When invoking a function and sending variables as input to the function, C++
provides two ways of doing so: pass-by-value and pass-by-reference. In this
section, we will discuss the difference between these two approaches, and when
to prefer one over the other.

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;

int increment(int x){


x = x + 1;
return x;
}
int main(){
int x = 0;
cout << increment(x) << endl; //prints 1
cout << x << endl; //prints 0; function didn't change x
int y = 7;
cout << increment(y) << endl; //prints 8
cout << y << endl; //prints 7; function didn't change y
return 0;
}

In this example, we have a function called increment that takes in parameter


x, adds 1, and then returns the answer. In our main function, we call this
increment function and pass in a variable x. Note that even though the vari-
ables have the same name, because it is being passed by value, it is not the
variable x, but rather its value 0 which is being sent to the function. Thus when
we print x on Line 11, its value has not changed. The same applies to y. In
114 CHAPTER 11. FUNCTIONS

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

In the following program, we declare a number of variables and investigate their


addresses:
#include <iostream>
using namespace std;
int main(){

int x = 2; // Declare and initialize an int


float y = 5.0f; // Declare and initialize a float
double z = 7.0; // Declare and initialize a double
cout << "x’s value: " << x << " x’s address: " << &x << "\n";
cout << "y’s value: " << y << " y’s address: " << &y << "\n";
cout << "z’s value: " << z << " z’s address: " << &z << "\n";
cout << "size of x: " << sizeof(x) << " bytes" << "\n";
cout << "size of y: " << sizeof(y) << " bytes" << "\n";
cout << "size of z: " << sizeof(z) << " bytes" << "\n";
return 0;
}

/*
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);

cout << "After square() function call:\n"


<< " x = " << x << endl
<< " squared = " << squared << endl;
return (0);
}

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

After square() function call:


x = 2
squared = 4
*/

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 &param2){
// 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;

void squareCube(int x, int &y, int &z) {


cout << "In function squareCube():\n"
<< " x is located at " << &x << "\n"
<< " y is located at " << &y << "\n"
<< " z is located at " << &z << "\n";
y = x * x;
z = x * x * x;
}

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

<< " cubed = " << cubed << endl;

squareCube(x, squared, cubed);

cout << "After squareCube() function call:\n"


<< " x = " << x << endl
<< " squared = " << squared << endl
<< " cubed = " << cubed << endl;

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

Any time r changes, so does i (and vice versa).


https://www.youtube.com/watch?v=T57_78JDuD0?vq=hd720

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.

11.3.1 File scope


A variable or function is said to have file scope if it is declared outside of a
function definition. Variables declared outside a function are often called global
variables. By definition, all functions have file scope, since they are not declared
inside another function. If a program consists of multiple files, then each file
has no knowledge of any variables declared outside itself.
If a variable or function has file scope, it can be referenced or used anywhere
in the file after where it was first declared. In the image below, we have three
functions and two global variables. Their scopes (that is, when they can be
used) are indicated by coloured arrows.

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

11.3.2 Block scope


In C++, a block is any segment of code within curly braces. If a variable is
declared inside a block, then it only exists within that block. As soon as the
closing brace is reached, the variable ceases to exist. Such variables are known
as local variables.
In the diagram below, the colour arrows and lines indicated the scope and
lifetime of local variables that have been declared within blocks. Notice that
each variable only exists and can be referenced within the block. As soon as the
closing brace is reached, that variable is lost. Note too that function parameters
are local variables, and exist only until the end of the function

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.

11.3.3 Nested scope


As we have seen with nested loops and if-statements, we can have nested blocks.
Variables defined outside a nested block carry into the block. If a variable inside
120 CHAPTER 11. FUNCTIONS

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

Arrays and Vectors

In this chapter, we will look at C++’s equivalents to Python’s lists. If you’ll


recall, Python lists allowed us to store a collection of value (of any type). In
C++, we have two options: array, which is of fixed size, and vector, which
is of variable size. In both cases, however, the type of data stored by these
structures must be of the same type (homogeneous). Thus we cannot store
strings and integers in the same vector, for instance. We will first look at
arrays in C++, before moving on to vectors, noting the differences between the
two.
If you are writing code, remember to use the -std=c++11 flag. In particular,
the arrays we will cover here were introduced in the 2011 standard, and so this
flag must be enabled in order to use them. If you are using a modern C++
compiler, you shouldn’t have to worry about this. However, for older versions
(and in particular Dev-C++), please make sure to use the flag.

12.1 C++11 Arrays


An array is a series of elements of the same type placed in contiguous memory
locations (next to one another) that all have the same type. The major char-
acteristic of an array is that it has a fixed size—while Python lists can grow
and shrink as we add and remove elements, C++ arrays remain of fixed length.
As a result, we must know the size of the array before the program runs. If we
do not know upfront how large an array should be, then it is likely not a good
situation to use an array!
When declaring an array, the compiler must allocated space in memory for it.
We must thus specify, upfront, the type and number of elements being stored in
the array. In this way, the compiler can compute how much memory is required.
In order to declare an array, we must first include the <array> header file. The

121
122 CHAPTER 12. ARRAYS AND VECTORS

general form of an array declaration array<type, arraySize> arrayName. For


example:
array<int, 9> myArray;

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
}

cout << "Element\tValue" << endl;

// output each array element’s value


for (int j = 0; j < d.size(); ++j){
cout << j << "\t\t" << d[j] << endl;
}
return 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(){

// Declare and use a list of initializers to initialise d


array<int, 5> d={ 10, -1, 2, 4, 100 };

cout << "Element\tValue" << endl;

// output each array element’s value


for (size_t j = 0; j < d.size(); ++j){
cout << j << "\t\t" << d[j] << endl;
}
return(0);
}

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

12.1.1 Accessing array elements


C++ arrays support indexing, which allows us to retrieve the 𝑛th element of
an array. However, C++ does not provide any bounds-checking to prevent you
from accessing an element that does not exist! Thus we may mistakenly try
to access the tenth element of an array of size 5, and C++ will not complain.
It is therefore important to ensure that the index we use is within the array’s
bounds—that is, greater than or equal to 0 and less than the number of array
elements.
Allowing programs to read from or write data to array elements outside the
array’s bounds is a common security flaw. Such an error is called a buffer
overflow, and can cause the program to crash, or even appear to execute correctly
while using bad data. It can also allow attackers to exploit a system and execute
their own code.
Hacking side quest! Learn about the “buffer overflow exploit”, where a hacker
can exploit the above error to take control of a program and run their own code!
This tutorial here walks you through how to do this with a C program (which
you will notice is very similar to C++).

12.1.2 Iterating over arrays


While we should ensure that we never make such an error, a better approach is
to write code so that we can never make such an error in the first place. Recall
that in Python, we could loop through lists in the following manner:
values = [1, 2, 3, 4, 10]
for x in values:
print(x)

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>

using namespace std;

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

12.1.3 Multidimensional arrays


Recall that to declare an array, we specify its size and type. Its type can be any
built-in type, such as integers or floats, but it can also be of type array. This
allows us to have arrays of arrays, also known as multidimensional arrays.
A multidimensional array is an array with 2 or more dimensions. We can think
of a standard array as a single row, whereas two-dimensional arrays can be
thought of as tables or matrices, with data arranged into rows and columns. If
we have a 2D array, then we must specify two indices: the first index is the row,
and the second is the column.
Let us now declare a 2D array with 3 rows and 4 columns:
const int ROWS = 3;
const int COLS = 4;
array<array<int, COLS>, ROWS> arr;

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>

using namespace std;

//remember const!
const int ROWS = 2;
const int COLS = 3;

void printMatrix(array<array<int, COLS>, ROWS> matrix){


//for each row
for (int row = 0; row < matrix.size(); ++row){
//for each element in the current row
for (int col = 0; col < matrix[row].size(); ++col){
cout << matrix[row][col] << ' ';
}
cout << endl;
}
}

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 Array operations


The standard C++ libraries provide very useful functions that can be applied to
arrays (and other containers such as vectors, which we will shortly encounter).
In order to access these, we must include the <algorithm> header file. There
are many functions that can be found at http://www.cplusplus.com/reference/
algorithm/, but we will outline a few useful ones here.

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>

using namespace std;

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

using namespace std;

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>

using namespace std;

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

Finally, the count_if function determines the number of elements satisfying


some condition. We can use this by specifying the array to search over, as well
as the name of a Boolean function. count_if will return the number of elements
that, when passed to the Boolean function, return true. An example of this is
below:
#include <iostream>
#include <array>
#include <algorithm>
12.2. VECTORS 131

using namespace std;

bool isEven(int x){


return x % 2 == 0;
}

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

vector<int> vec(4, 42);


vector<int> vec2(vec); // create a new vector and copy each element from vec into vec2.

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 Iteration and Indexing


Indexing and iteration function exactly the same for vectors as they do for
arrays. We briefly repeat what was said in the previous section here for ease of
reference.

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

The subscript operator does not do any bounds-checking. If an invalid index is


provided, bad things will probably happen.

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

However, we must be careful that our loop terminates appropriately, so that we


do not try access or modify an invalid element.
If we do not need the index, a safer way of iterating is to use the range-based
for-loop. For example:
array<int> myvector = { 9, 7, 5, 3, 1 };
for (int e : myvector){
cout << e << endl;
}

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!

12.2.4 Member Functions


Vectors have additional functionality above and beyond what arrays possess.
Unlike the algorithm functions we look at earlier (which can also be applied to
vectors), these functions exist in the <vector> file we include at the beginning
of the program. They are thus known as member functions, since they belong
to the vector class of objects.
These functions most commonly deal with operations that require some kind of
resizing—something arrays are not capable of. A full list of functions can be
found here http://en.cppreference.com/w/cpp/container/vector. We list some
of the more useful functions below:
// Add an element to the end of the vector
vector<int> vec {1, 2, 3};
vec.push_back(4); // push_back is equivalent to Python's append
//now vec = {1,2,3,4}

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

//Reserve memory in the background for the vector


vector<int> vec;
vec.reserve(100);
//now vec = {}, can add many
//ints before background resizing
//needed

12.2.5 Additional Functions


In class, we discussed some of the additional functions provided by the
algorithm header file. As we saw, these functions do not accept the object
itself, but rather the begin() and end() markers (known formally as iterators)
of the object. Thus as long as whatever data structure we’re working with
(whether it be arrays, vectors, or some other thing) provides a begin() and
end() function, they can be used with the algorithm header.
For instance, to sort a vector and array, we can simply do the following:
array<int, 4> arr = {2,1,3,4};
sort(arr.begin(), arr.end());

vector<int> vec = {2,1,3,4};


sort(vec.begin(), vec.end());

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.

13.1 Computing the factorial


There is an equivalence between iteration and recursion—anything that can be
done with a loop can be done with recursion, and vice versa. We can think of
iteration as counting up, and recursion as counting down. To see this, consider
the canonical example of the factorial function (!). Factorial is a mathematical
function where:

𝑁 ! = 1 × 2 × … × (𝑁 − 1) × 𝑁 .

By definition, we also have 0! = 1.


From the above, it is clear that we can write the factorial function as a recursive
function of any non-negative integer as follows:

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

int factorialRecursive(int N){


if (N == 0){
return 1; //this is the base case
}
return N * factorialRecursive(N - 1)
}

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

Without the base case, the above is equivalent to 𝑁 × (𝑁 − 1) × (𝑁 − 2) × (𝑁 −


3) × (𝑁 − 4) × … forever. The base case breaks this chain, so that when we
reach zero, it stops.
In order to understand exactly what happens behind the scene, we must first
discuss the concept of a stack frame. Before that, a final note on the difference
between recursion and iteration. In general, recursion is often more readable
and intuitive that iteration. Its main downside is that it is often slower and uses
more memory. However, one major advantage is that, unlike iteration, it does
not require mutable state. Mutable state refers to the fact that we have variables
that can change their value. For iteration, the loop counter is a mutable object.
In general, the more things that change, the more that can go wrong. Recursion
is a way to achieve the same result, without the need for mutable variables, and
13.2. THE STACK FRAME 137

so is a very powerful tool.1


Programming language side quest! The standard tradeoff between recursive
and iterative solutions is that iteration is faster, but harder to code and reason
about, whereas recursion is easier to think about, but also slower to execute and
can quickly run into memory issues. However, certain languages support the
concept of tail recursion, which is a form of recursion that has all the benefits
of iteration and recursion wrapped into one! First, use Google to learn about
tail recursion. Then, rewrite the factorial code above to take advantage of tail
call optimisation. Finally, if you have a relatively modern C++ compiler, try
follow this tutorial to see what’s happening behind the scenes!

13.2 The stack frame


C++ programs and the variables and functions they declare are all stored in
memory. The memory that the program is allocated is divided into the following
sections:

Figure 13.1: Memory allocated to the program divided into sections

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

3. The heap is a collection of extra memory that is assigned to us. We will


look at the heap in more detail in the next chapter
4. The stack is where local variables reside.
Variables in the stack have automatic allocation. This means that when we
declare a local variable, the compiler automatically finds an appropriate memory
location for it. Similarly, when the variables go out of scope, the compiler
automatically frees that memory.
The stack consists of a series of frames called stack frames. Stack frames are
stacked on top of the other (hence the name), and only the top-most one is
“visible” to the program. Every time a function is called, a new stack frame
is placed on the stack. Each stack frame stores the arguments passed to that
function, the local variables declared in the function, the state of the CPU at
that time, and the return address (that is, which line should be next executed
after the function returns?) When a function ends, its stack frame is discarded
from the stack. This “reveals” the stack frame underneath it.

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>

using namespace std;

int square(int x){


int y = x * x;
cout << y << endl;
return y;
}

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.

A similar procedure occurs when we use a recursive function, although the


number of stack frames added to the stack depends on how many recursive calls
occur.2 We will illustrate what happens when the factorial function below is
called with 𝑁 = 3.
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++.
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

int factorial(int N){


if (N == 0){
return 1;
}
return N * factorial(N - 1)
}

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.

13.3 Summary lecture


https://www.youtube.com/watch?v=3kB3_qAtkLU?vq=hd720
Programming side quest! Use the algorithm presented in the above video to
create a Sudoku solver program. Your program should be able to read in a
partially-completed Sudoku puzzle and output a solution, or an error message
if none exists. Bonus quest! Try extend your program to solve 16 × 16 versions!
142 CHAPTER 13. RECURSION

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

Memory and Pointers

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.

• C++ allows for pointers to pointers, but not references to references.


• Pointers can have a “null” value (i.e. pointing to “nothing” explicitly).1
References cannot.
• Pointers can be used for iteration.
In general, references are safer and cleaner than pointers, and so should be
preferred where possible.

14.1.1 Declaring pointers


We use the * operator in order to declare a pointer. We must also specify the
type of variable that is being pointed to. For example:
int* ptr; // space is irrelevant. Can have int * ptr; or int *ptr;

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.

14.1.2 Initialising pointers


Pointers should be given the memory address of the variable they point to, or
they may be set to “point to nothing”. The latter are known as null pointers.
To create a null pointer, use the C++11 keyword nullptr. Prior to C++11,
either 0 or NULL was used.
int* a = nullptr; //C++11
int* b = 0; //prior to C++11
int* c = NULL; //prior to C++11

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!

14.1.3 Dereferencing pointers


In what is a slightly confusing setup, if we want to retrieve the variable that a
pointer refers to, we must use the indirection or dereferencing operator *. This
is known as dereferencing a pointer. Note that * now plays two roles: when
used in a declaration, it specifies that the variable is a pointer. Outside of that,
it extracts the variable being pointed to. An example of this is below:
int x = 10;
int *xPtr = &x;
cout << "Address of x: " << &x << endl;
cout << "Value of xPtr: " << xPtr << endl;
cout << "Value of x: " << x << endl;
148 CHAPTER 14. MEMORY AND POINTERS

cout << "Value of *xPtr: " << *xPtr << endl;


x = 0; //change x's value
cout << "Value of *xPtr: " << *xPtr << endl;
*xPtr = 42; //change x through the pointer
cout << "Value of x: " << x << endl;

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

14.1.4 Passing pointers


Recall that we have previously covered pass-by-reference and pass-by-value.
Pointers can also be passed to functions (by value), but because it points to
an object, we can modify the original object in our function, just as in pass-
by-reference. In the following code, we illustrate how to achieve this, with a
comparison to passing by reference:
1 #include <iostream>
2

3 using namespace std;


4

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

14.2 Built-in arrays


A concept closely related to pointers are built-in (or C-style) arrays. These
arrays are fixed-size data structures, and much like C++11 arrays, we must
specify their type and constant size upfront.
Warning: in C++, arrays must have constant length. However, a number of
compilers have extended the C++ language to allow for variable-length arrays.
This is not valid C++ code, so do not use it. In particular, it might work
perfectly fine on your computer, but fail on the marker.
The declaration of a built-in array differs from C++11 arrays, as shown below:
array<int, 1000> arr1; //C++11 array
int arr2[1000]; //built-in array

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

void printArray2(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!

14.2.1 Getting the size of a built-in array


We can in fact compute the number of elements of a built-in array using the
sizeof operator. However, we can only do this provided that the array has not
yet decayed to a pointer. As soon as that happens, the array size cannot be
recovered.

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>

using namespace std;

int getSize(double arr[]){


return sizeof(arr);
}

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 number of bytes in the array is 160


The number of bytes according to getSize() is 8
*/

This occurs because the compiler treats the getSize function as


152 CHAPTER 14. MEMORY AND POINTERS

size_t getSize(double* arr){


return sizeof(arr);
}

and the size of a pointer is 8 bytes.

If we want to determine the number of elements in a built-in array, we must


ensure that it has not decayed to a pointer. Then we can use the following
expression, which will be evaluated during compilation.
sizeof(arr)/sizeof(arr[0])

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>

using namespace std;

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

<< "sizeof(long double) = " << sizeof(long double) << endl


<< "sizeof array = " << sizeof array << endl
<< "sizeof ptr = " << sizeof ptr << endl;
return 0;
}

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

14.2.2 Pointer arithmetic


Because pointers hold memory addresses (which are just hexadecimal numbers),
we can do a special kind of arithmetic with them. In particular, we can increment
and decrement a pointer, add or subtract an integer, and subtract one pointer
from another. To illustrate this, assume that an integer takes up 4 bytes, and
that we have a built-in integer array int arr[5] that starts at address 3000.
(We will use decimal numbers below just for readability.) Then we can visualise
the memory layout as follows:

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

2) = arr[2]. We can thus use pointer arithmetic to access elements at certain


locations.
If we have two pointers that point to the same built-in array, we can subtract
one from another. For instance, if ptr1 contains the address 3000 and ptr2
contains the address 3008, then ptr2 - ptr1 represents the number of built-in
array elements from ptr1 to ptr2, which in this case is (3008 − 3000)/4 = 2.
Thus pointer subtraction is equivalent to the “distance” between two pointers.

14.3 Dynamic memory allocation


In this section, we will look at the part of memory known as the heap. Before
that, recall our discussion of the stack, which is where local variables are stored.
Recall that the compiler automatically manages the stack for us. TO achieve
this, it must know how much memory to allocate beforehand (this is why array
sizes must be known upfront). Furthermore, the stack size is quite small. This
reduces overhead and makes everything very quick, but it means we may run
out of memory if we try allocate a large array (e.g. an array with 100 million
elements).
There is another problem with the stack, which can be seen in the example below.
In this program, we create a function that will create and return a built-in array
of size 𝑁 , where each element has value 𝑥. Can you spot the problem?
int* create(const int N, int x){
int arr[N];
for (int i = 0; i < N; ++i){
arr[i] = x;
}
return arr;
}

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!

14.3.1 The heap


The heap solves the problems associated with the stack. Unlike the stack, the
heap supports dynamic allocation. This means that we, the programmer, decide
when objects are to be destroyed or deallocated, and we must do so ourselves.
The heap is also much larger, although as a result a bit slower. Finally, unlike the
14.3. DYNAMIC MEMORY ALLOCATION 155

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!

14.3.1.1 Allocation examples

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

14.3.1.2 Deallocation examples


We can now go back an rectify the problem of creating a built-in array inside
a function. We achieve this by allocating the array on the heap, so that it is
not automatically deleted when the function returns. Note that the size of the
array N does not need to be constant, since we are declaring the array on the
heap.
int* create(int N, int x){
int* arr = new int[N];
//array on heap will survive end of scope
for (int i = 0; i < N; ++i){
arr[i] = x;
}
return arr;
}

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
}

14.3.1.3 Issues with dynamic allocation


While the use of the heap solves some stack-related problems, they come with
their own set of issues. Firstly, we must now remember to delete our data, or
risk a memory leak occurring. Additionally, it is unclear who is responsible for
deallocating the memory. For example, if we write a function that accepts a
pointer to memory on the heap, should we delete the pointer inside the function
or after it? A related issue is a double free error, which occurs when we try
delete memory twice, and results in undefined behaviour. Finally, at any point
in our program, it is hard to determine whether a given pointer is valid or has
already been deleted. A pointer that references memory that has been deleted
is known as a dangling pointer.
Dynamic arrays can be used to create multidimensional arrays, but creating and
deleting them is not straightforward. For instance, a 2D array can be created
and deallocated as follows:
int numRows, numCols;
cin >> numRows >> numCols;
int** matrix = new int*[numRows];
for (int i =0; i < numRows; ++i){
matrix[i] = new int[numCols];
}

//deleting is not straightforward


158 CHAPTER 14. MEMORY AND POINTERS

for (int i =0; i < numRows; ++i){


delete[] matrix[i];
}
delete[] matrix;

But look how much easier it is to simply use a vector!


int numRows, numCols;
cin >> numRows >> numCols;
vector<vector<int>> matrix(numRows, vector<int>(numCols));

//no need to clean up

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.

14.4 Smart Pointers


Our final section is that of smart pointers, which were introduced in C++11. To
use smart pointers, we must include the <memory> header file. Smart pointers
act exactly like the pointers we’ve seen so far, but they do not require us to
delete them. When a smart pointer is deallocated by the compiler, the memory
it references (whether on the stack or heap) is automatically freed. Thus smart
pointers prevent memory leaks from occurring. There are two types of smart
pointers in C++11: unique and shared.
A unique pointer ensures that only one pointer may point to a variable in mem-
ory. This prevents multiple pointers referencing the same object. For example,
we can create a unique pointer thusly:
{
unique_ptr<int> ptr (new int(3));
// Going out of scope...
}
// I did not leak my integer here!
// The destruction of unique_ptr called delete

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

However, the C++ committee forgot to include make_unique in C++11


(make_shared is present)! make_unique was finally introduced in C++14.
https://www.youtube.com/watch?v=zm2mJlPNbPQ?vq=hd720

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!

You might also like