layout | title |
---|---|
presentation |
Week 1, session 1: Introduction |
class: title
5CCYB041
name: team
.center[.teamcols[
.col[
J-Donald Tournier ]
.col[
]
.col[
]
.col[
Michela Antonelli ]
] ]
.center[.teamcols[
.col[
Tobias Wood ]
.col[
]
.col[
]
.col[ Marc Modat ]
] ]
.center[.teamcols[ .col[
]
.col[
]
.col[
]
.col[
Abhijit Adhikary ]
] ]
.center[.teamcols[
.col[
Charel Junior Mangama Sindzi ]
.col[
]
.col[
]
.col[
Yovin Yahathugoda ]
] ]
.center[.teamcols[
.col[
Jakub Grzelak ]
.col[
]
.col[
]
.col[
Nashira Baena ]
] ]
.center[.teamcols[
.col[
Adriana Namour ]
.col[
]
.col[
]
.col[
Dewmini Hasara Wickremasinghe ]
] ]
name: course
- 10 week course, with 2-hour tutorials twice a week
- on week 6, peer-assessed exercise (formative only)
--
.columns[ .col[
-
first coursework (10%)
- instructions available 6 February
- submission due 25 February
-
second coursework (30%)
- instructions available 13 March
- submission due 3 April ]
.col[
- final exam (60%)
- this is a written exam
- please look through example questions provided ] ]
This course is an introduction to object-oriented programming using C++.
--
It will provide you with an understanding of:
- the Unix command-line --
- how to handle and manage files and commands in a Unix environments --
- the basics of programming using C++ --
- the basics of the object-oriented programming paradigm --
- how to manage and process complex data structures
--
Where possible, the course aims to provide you with transferable skills that can be applied in other situations
- for many of you, these skills will prove valuable for your final project
- even if you don't use C++!
C++ is a widely-used general-purpose programming language.
- ranked second on the TIOBE programming community index (after Python)
- used for high-performance and/or resource-constrained software
- can be used for a wide range of applications: embedded systems, operating systems, desktop applications, telecoms infrastructure, ...
--
C++ was first released in 1985 by Bjarne Stroustrup as an extension to the [C language](https://en.wikipedia.org/wiki/C_(programming_language%29)
- first standardised by the International Organization for Standardization (ISO) in 1998
- There have been many versions of the standard since (bold denotes
fundamental changes):
- C++98, C++03, C++11, C++14, C++17, C++20, C++23, C++26 (under development)
--
Specific features of C++:
- it supports Object-Oriented Programming (OOP), as well as generic and functional programming
- it is a compiled language (the code you write must be compiled to machine code before execution)
- it uses static typing (data types are known and checked at compile-time, not runtime)
--
On this course, we will be using the C++20 version of the standard
--
We will avoid concepts that many other C++ courses would consider fundamental, including:
--
- C-style arrays
- memory management
- pointers or smart pointers
- ...
--
Why are we avoiding these topics?
- They cause too much confusion too early. You can learn about these topics when you have become sufficiently familiar with the basics --
- Many of these features are discouraged in modern C++, and are best avoided altogether where possible
--
We will initially cover a lot of topics quickly and superficially
- the point is to appreciate how the language works as a whole to solve problems
- not everything will immediately make sense - but hang in there!
Learning to program can only be done through experience
- please make every effort to attend all the tutorials! --
- don't hesitate to ask if anything is unclear or you need any help --
- go through all the examples --
- search online for examples and explanations!
- but bear in mind that online sources may use concepts we have deliberately left out of the course, or rely on a different version of the C++ standard!
--
Good online resources include:
- learncpp: a great tutorial, accessible and up to date
- cppreference.com: very thorough C++ reference, up to date and correct, but not easy for beginners
- C++ FAQ: great resource with answers to general or specific questions
--
Other online sources (not as authoritative as the above):
- geeksforgeeks.org: great as a quick introduction to key concepts, but can often be too simplistic
- cplusplus.com: C++ tutorial and reference, a little out of date
name: cmdline
class: section
In this course, we will be using a Unix-like terminal environment and running all of our code within the terminal.
- This means we need to understand the Unix command-line.
--
On the KCL-managed Windows systems, we will rely on the MSYS2 project.
- Use the
MSYS2 MSYS
terminal (ignore the other variants) - This provides a Unix-like environment, preloaded with all the necessary software.
- Start it from the Start Menu (search for "MSYS")
--
To install this on your own Windows computer, follow the instructions on KEATS.
On macOS, you can immediately use the Terminal application.
To get started, click on the Start menu and search for the MSYS2 MSYS
application
.note[
ignore the other options, you need to use the MSYS
variant!]
--
The terminal is an application that allows you to enter commands and view their output
--
The commands are interpreted by another program called the command intrepreter
- typically referred to as 'the shell' --
- here, we are using the [Bourne-again shell (
bash
)](https://en.wikipedia.org/wiki/Bash_(Unix_shell%29)
--
.note[ On macOS, the standard shell is now the [Z shell](https://en.wikipedia.org/wiki/Z_shell) (`zsh`), which is an extension of the Bourne shell, and broadly compatible with `bash`.
On Windows, the standard shell used to be the DOS shell, though Microsoft has since introduced the more modern PowerShell – we won't be using either of them on this course!]
When started, the shell will show a prompt, and wait for you to enter commands.
--
The prompt will (typically) take the form:
user@hostname:folder $
--
where:
user
is your current username --hostname
is the name of the computer this session is currently running on --folder
is the folder you are currently operating in (the current working directory)
.note[
The ~
symbol is used as a shorthand for your home folder]
Try the following commands:
-
ls
list the files & folders in the current folderls -l
list in long format (permissions, ownership, file sizes, ...)ls Desktop
list the files in theDesktop
folder
-
pwd
print the current working directory -
cd
change directorycd
change to your home foldercd ..
change to the parent directory relative to the current foldercd Documents
change to theDocuments
foldercd ../Desktop
change to theDesktop
folder in the current folder's parent directory
.note[ In computing, the terms _folder_ and _directory_ are often used interchangeably]
You can see that the commands in the previous slide allow you to move around and inspect the filesystem, in much the same way as you would using your file manager.
Try using the Windows Explorer to verify that the listings provided on the command-line correspond to the folders in your account.
.note[ on macOS, you can use the _finder_ application instead]
Typing long commands over and over again can quickly get tiresome. Thankfully, modern command interpreters provide handy shortcuts to make life easier
- please get used to them as early as you can!
--
The up/down arrows allow you recall previously typed commands, which you can then edit and modify as required (using the left/right arrows)
- very useful when you've made a simple typo on a long command!
--
The TAB key asks the shell to complete the current word if it has enough
information to do so. For example, typing cd Doc
, then pressing TAB
will complete the
command to cd Documents
- provided there is a
Documents
folder at that location - and there are no other folders that start with
Doc
--
.note[You are **strongly** encouraged to learn more about how to use the shell. Please take a look through any of the many tutorials available online, in particular our own [Introduction to the Unix command-line](https://command-line-tutorial.readthedocs.io/)]
name: first_program
class: section
We need to create and edit a text file to hold our code.
To do this, we need to use a text editor
- There are many good editors available, but some are better suited for writing C++
--
--
- there are many other text editors you could use: Sublime Text, VS Code, BBEdit (macOS only), ...
--
.note[
We do not recommend the use of full-blown integrated development environments
(IDE) early on. While convenient, these hide the processes involved, making
it difficult for newcomers to understand where things might go wrong.
It is also very difficult to find an IDE that is both easy to install and works
flawlessly across all relevant platforms. We have
therefore decided to avoid the use of IDEs on this course.]
We need to start by creating a folder to store our code
- We can use the Windows File Explorer (or Finder on macOS), then navigate to this folder using the command-line
--
- ... or we can use the command-line straight away!
- navigate to the location where you want to create the folder, for example:
$ cd ~/"OneDrive - King's College London/Documents/"
--
- use the
mkdir
command to create the desired folder:$ mkdir OOP
--
- navigate to this folder, and create a subfolder for this first example:
$ cd OOP/ $ mkdir hello_world
--
- finally, change directory into this folder:
$ cd hello_world
Once in the right folder, we need to create a new text file called
main.cpp
with these contents:
#include <iostream>
int main ()
{
std::cout << "Hello World\n";
return 0;
}
--
Create a new text file called `main.cpp` using `micro` (or whichever editor you have decided to use), type in the contents, and save the file. ``` $ micro main.cpp ```
- If using a different editor, you can just as easily launch the editor application as you would any other app, type in the code, and save the file in the folder you just created
.columns[ .col[
-
Ctrl+Q
close current file -
Ctrl+S
save curent file -
Ctrl+O
open file -
Crtl+C
copy currently selected text -
Crtl+X
cut currently selected text -
Crtl+K
cut current line -
Ctrl+V
paste previously copied text -
Crtl+D
duplicate current line -
Crtl+Z
undo -
Crtl+Y
redo ]
.col[
-
Ctrl+T
new tab -
Ctrl+Alt+>
Next Tab -
Ctrl+Alt+<
Prev Tab -
Ctrl+F
search -
Ctrl+N
next match -
Ctrl+P
previous match -
Tab
autocomplete / indent selection -
Alt+Tab
un-indent selection -
Ctrl+G
help -
Ctrl+b
shell prompt ] ]
See the online documentation for the complete listing.
The source code we have written in the file main.cpp
cannot be executed as it is.
- it is human readable code, designed for us to more conveniently express what the program should do
--
To use the program, we need to compile it
- we need to translate our code into machine instructions that the computer can execute directly
--
We do this by running the appropriate
compiler on our code (g++
):
$ g++ -std=c++20 main.cpp
- the
-std=c++20
option instructs the compiler to use the C++20 version of the C++ standard
--
This should produce a new executable file in the current working directory:
$ ls
a.exe main.cpp
Now that we have an executable, we can run it.
--
By default, the shell looks for the command to execute by searching through a
predefined set of folders, as listed in the system PATH
.
- simply typing
a.exe
will not (normally) work – unlessa.exe
is in one of these folders.
.note[To know more about the PATH
, search online for more information, e.g.
[wikipedia](https://en.wikipedia.org/wiki/PATH_(variable%29)]
--
However, we can provide the explicit location of our executable when typing the
command – without modifying the system PATH
--
Our command is in the current folder, which we can refer to using the .
symbol.
We can therefore type:
$ ./a.exe
Hello, world!
--
Interpreted programming languages do not need to be compiled prior to execution
- examples include Python, Java, JavaScript, Perl, Ruby, and even our
bash
shell
--
Programs written using interpreted languages cannot be executed by themselves – they need to be run via another program called the interpreter
- the source code needs to be parsed by the interpreted at run-time
- if the code is deemed valid, the interpreter will perform the actions specified
- the interpreter must be installed and available on all target systems
--
In contrast, compiled languages are first translated into native machine instructions
- examples include [C](https://en.wikipedia.org/wiki/C_(programming_language%29), C++, Fortran, [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language%29), Rust, Go, ...
- this (in theory) provides the highest performance, avoiding the overhead of interpreting the instructions at runtime
- it also provides an opportunity to detect certain classes of errors at an earlier stage
- it can also produce more efficient code through various optimisation techniques that would be too time-consuming to perform at runtime --
- ... but the compile cycle can be lengthy, slowing down the development process
By default, the compiler will produce an executable called a.exe
- this is true for MSYS2 – on many other platforms, this will be
a.out
--
We can control the name of the executable using the -o
command-line
option:
g++ -std=c++20 main.cpp -o main
--
And invoke the resulting executable as before:
$ ./main
Hello, world!
--
.note[
On Windows, executables must have the .exe
file extension – there is no such requirement on
other operating systems. You will find that the executable produced is actually called
main.exe
.
Nevertheless, using the MSYS2 shell, you can type either ./main
or
./main.exe
: both will work equally well
]
name: hello_world
class: section
#include <iostream>
int main ()
{
std::cout << "Hello World\n";
return 0;
}
Let's look through our example code again and go through each line
*#include <iostream>
int main ()
{
std::cout << "Hello World\n";
return 0;
}
Lines that start with a #
symbol are so-called preprocessor
directives
--
The #include
directive requests inclusion of the contents of the iostream
header file
--
Header files are normal C++ files that declare functionality that we might need to use
--
The iostream
header declares the input/output stream functionality for
printing to the terminal via std::cout
– which is why we need to include it
#include <iostream>
*int main ()
{
std::cout << "Hello World\n";
return 0;
}
This is the declaration of the main()
function
--
The main()
function is the entry
point for any program
--
By convention, we declare it as returning an integer (int
)
--
We'll see more about this when we cover functions
#include <iostream>
int main ()
{
* std::cout << "Hello World\n";
return 0;
}
Next, we feed the
[string](https://en.wikipedia.org/wiki/String_(computer_science%29) "Hello World" to the standard output
stream, std::cout
- we enclose the string within inverted commas (
"
)
--
Note the use of the insertion operator, <<
- This line reads as: insert the string "Hello World" into the
std::cout
IO stream
--
Note also the trailing \n
escape sequence at the end of the string
- The backslash
\
character can be used to escape the normal interpretation of the next character - Here, the sequence
\n
translates into the newline character - See here for a full list of escape sequences
#include <iostream>
int main ()
{
std::cout << "Hello World\n";
* return 0;
}
Finally, we return from main()
, which marks the end of our program
--
We return the value 0
to indicate success (no errors)
--
This value is the exit code of our program. It can be queried by other programs, or by the shell, to detect any errors during execution
- by convention, any non-zero value signals that an error occurred
- different error codes can sometimes be used to signal different types of errors
#include <iostream>
int main ()
{
* std::cout << "Hello World\n";
* return 0;
}
Note the use of semicolons ;
to mark the end of each of these lines
--
These lines are individual statements
- in C++, the semicolon marks the end of the statement
- technically, these statements could both be on the same line – only the semicolon matters
#include <iostream>
int main ()
*{
std::cout << "Hello World\n";
return 0;
*}
Note also the use of curly brackets (or braces, or curly braces) to enclose the code for the main function
--
In C++, braces are used to group statements together. We will see more about this later
-
Modify
main.cpp
to remove the newline character, then compile and run the executable. What effect does this have? -
Modify
main.cpp
to remove the#include
line, then compile. What effect does this have? -
Modify
main.cpp
to remove the semicolon afterreturn 0;
, then compile. What effect does this have?
name: cmdline_args
class: section
To interact with our program, we need some way to pass information to it
- for example, the name of a file to process, or the value of some parameter
--
We can use command-line arguments
to do this. This requires a minor modification to the declaration of our main()
function:
int main (int argc, char* argv[])
--
Unfortunately, the arguments are provided in a slightly obscure C-style format:
argc
is the number of arguments (including the command name itself)argv
is a C-style array of pointers tochar
-- These are features that we are trying hard to avoid on this course!
- if interested, please have a look at other explanations online
--
For this reason, we recommend immediately converting these arguments into a more modern form: a vector of strings
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
// now we can access the command-line arguments:
std::cout << "first argument is " << args[1] << "\n";
return 0;
}
This is the way we recommend handling command-line arguments on this course
- Please use this structure for your own programs
--
Let's step through the program to understand each step
#include <iostream>
*#include <vector>
*#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
// now we can access the command-line arguments:
std::cout << "first argument is " << args[1] << "\n";
return 0;
}
We need to include two additional headers: <vector>
and <string>
- these provide the functionality required to form a vector of strings
#include <iostream>
#include <vector>
#include <string>
*int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
// now we can access the command-line arguments:
std::cout << "first argument is " << args[1] << "\n";
return 0;
}
We need to modify the declaration of the main()
function to accept additional arguments
- these provide the information required to access the command-line arguments
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
* std::vector<std::string> args (argv, argv+argc);
// now we can access the command-line arguments:
std::cout << "first argument is " << args[1] << "\n";
return 0;
}
This line declares a new variable called args
, of type std::vector<std::string>
(a vector of strings)
- It is initialised from the arguments passed to
main()
-- - Don't worry about the syntax for now – we will fill in the blanks in due course
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
// now we can access the command-line arguments:
* std::cout << "first argument is " << args[1] << "\n";
return 0;
}
Here, we simply display the value of the first argument by feeding it to standard output
- each argument can be accessed using the subscript operator
[]
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
* // now we can access the command-line arguments:
std::cout << "first argument is " << args[1] << "\n";
return 0;
}
A line starting with //
denotes a comment
- The compiler will ignore any text after this until the end of the line
--
.note[ You are strongly encouraged to use comments to explain anything that isn't immediately obvious in your own code!]
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
// now we can access the command-line arguments:
std::cout << "first argument is " << args[1] << "\n";
return 0;
}
Now modify your own code as shown above, compile it, and run it.
- What happens if you don't provide at least one command-line argument?
name: iteration
class: section
In your own code, you will often need to iterate over the contents of a vector
--
The easiest and safest way to do this is to use a range-based for loop:
for (auto item : vec)
statement;
In your own code, you will often need to iterate over the contents of a vector
The easiest and safest way to do this is to use a range-based for loop:
`for` (auto item : vec)
statement;
- the
for
keyword declares the start of the loop
In your own code, you will often need to iterate over the contents of a vector
The easiest and safest way to do this is to use a range-based for loop:
for (auto item : `vec`)
statement;
- the
for
keyword declares the start of the loop vec
is the container (e.g. astd::vector
) whose elements we wish to iterate over
In your own code, you will often need to iterate over the contents of a vector
The easiest and safest way to do this is to use a range-based for loop:
for (auto `item` : vec)
statement;
- the
for
keyword declares the start of the loop vec
is the container (e.g. astd::vector
) whose elements we wish to iterate overitem
is the variable that will contain a copy of each element for processing within the loop
In your own code, you will often need to iterate over the contents of a vector
The easiest and safest way to do this is to use a range-based for loop:
for (`auto` item : vec)
statement;
- the
for
keyword declares the start of the loop vec
is the container (e.g. astd::vector
) whose elements we wish to iterate overitem
is the variable that will contain a copy of each element for processing within the loopauto
is a special keyword, requesting that the compiler deduce the type ofitem
from the context
In your own code, you will often need to iterate over the contents of a vector
The easiest and safest way to do this is to use a range-based for loop:
for (auto item : vec)
`statement;`
- the
for
keyword declares the start of the loop vec
is the container (e.g. astd::vector
) whose elements we wish to iterate overitem
is the variable that will contain a copy of each element for processing within the loopauto
is a special keyword, requesting that the compiler deduce the type ofitem
from the contextstatement
is the code to execute for each iteration (for each element invec
)
Let's try using the range-based for loop to iterate over the command-line arguments:
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (auto a : args)
std::cout << "argument: " << a << "\n";
return 0;
}
Modify your own code to match and try it out.
Question: what is the type of a
in the code above?
The range-based for loop we just saw is a special case of the for
loop
- more convenient and safer when iterating over containers --
- it is also more modern (introduced in C++11) --
- if you can use a range-based for loop, please do so – this is why we introduced it first
--
The original for
loop is more general, and takes this form:
for (init-statement ; condition ; expression)
statement;
The range-based for loop we just saw is a special case of the for
loop
- more convenient and safer when iterating over containers
- it is also more modern (introduced in C++11)
- if you can use a range-based for loop, please do so – this is why we introduced it first
The original for
loop is more general, and takes this form:
for (`init-statement` ; condition ; expression)
statement;
init-statement
: (optional) a declaration and/or expression to be evaluated before the first iteration.
The range-based for loop we just saw is a special case of the for
loop
- more convenient and safer when iterating over containers
- it is also more modern (introduced in C++11)
- if you can use a range-based for loop, please do so – this is why we introduced it first
The original for
loop is more general, and takes this form:
for (init-statement ; `condition` ; expression)
statement;
init-statement
: (optional) a declaration and/or expression to be evaluated before the first iteration.condition
: a test to determine whether to perform the next iteration. Iffalse
, the loop will terminate.
The range-based for loop we just saw is a special case of the for
loop
- more convenient and safer when iterating over containers
- it is also more modern (introduced in C++11)
- if you can use a range-based for loop, please do so – this is why we introduced it first
The original for
loop is more general, and takes this form:
for (init-statement ; condition ; `expression`)
statement;
init-statement
: (optional) a declaration and/or expression to be evaluated before the first iteration.condition
: a test to determine whether to perform the next iteration. Iffalse
, the loop will terminate.expression
: an action to perform after completion of each iteration
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (int n = 0; n < args.size(); n++)
std::cout << "argument " << n << ": " << args[n] << "\n";
return 0;
}
Let's modify our program to use a regular for loop.
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (`int n = 0`; n < args.size(); n++)
std::cout << "argument " << n << ": " << args[n] << "\n";
return 0;
}
For the init-statement
: declare a variable of type int
(an integer) and initialise it to zero
- this will serve as our counter for the loop
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (int n = 0; `n < args.size()`; n++)
std::cout << "argument " << n << ": " << args[n] << "\n";
return 0;
}
For the condition
: keep iterating while the counter is less than the size of our container
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (int n = 0; n < args.size(); `n++`)
std::cout << "argument " << n << ": " << args[n] << "\n";
return 0;
}
For the expression
: increment the counter by one
--
- note the use of the postfix increment operator --
- this is equivalent to
n = n + 1
, orn += 1
(more on that later)
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (int n = 0; n < args.size(); n++)
* std::cout << "argument " << n << ": " << args[n] << "\n";
return 0;
}
For the statement
, we display the argument along with its position (n
) on
standard output
- with a regular
for
loop, we can also keep track of the index of the argument
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
for (int n = 0; n < args.size(); n++)
std::cout << "argument " << n << ": " << args[n] << "\n";
return 0;
}
Modify your own code to match, compile it, and run your program with different arguments to verify that everything works as expected.
The for
loop is by far the most commonly-used looping structure, but others exist and are sometimes more appropriate
--
The while
loop takes this form:
while (condition)
statement;
This will keep running statement
as long as condition
is true
--
The do ... while
loop takes this form:
do
statement;
while (condition);
This will also run statement
as long as condition
is true. The difference with the regular while
loop is that condition
is tested after running statement
name: fundamentals
class: section
In the previous example, you will have noticed the use of syntax of this form:
for (int n = 0; n < `args.size()`; n++)
...
--
We are using the dot operator, which provides direct access to members of an object. It takes this general form:
variable.member
variable
is an instance of an object (a class or struct – we'll cover these later)member
is a variable or function that is a member of that object
--
We will cover this in detail in future sessions. For now, it is sufficient to
think of the dot operator as a kind of possessive: variable
's member
(or
the member
of variable
)
- for example,
mesg.size()
is equivalent to:mesg
'ssize()
In the previous example, you may have noticed that the first entry in the
args
vector is at index 0
- this differs from Matlab, where the first element is at index
1
- starting at zero is how arrays are indexed in many programming languages
--
For a vector vec
of size N
:
- the first element is at
vec[0]
- the last element is at
vec[N-1]
(or alternatively:vec[vec.size()-1]
)
--
- there is no bounds checking (by default) in C++! --
- an out-of-bounds access leads to so-called undefined behaviour:
- the program may run fine with no visible side-effects
- the program may crash
- the program may run fine at that point, but crash later
--
.note[
An out-of-bounds access can be exploited by hackers, and is the biggest
security risk in
software!
This is why you should use a range-based for loop if you can: it automatically uses
the correct bounds]
C++ is a statically-typed language
- every variable needs to have its type defined before use
- once defined, a variable cannot change its type
--
The compiler will check the validity of operations at compile time
- this contrasts with dynamically-typed languages, where the validity of operations is determined at run-time, based on the type of the variables at that point in time
- this is more computationally efficient, and leads to fewer run-time errors
--
We have already come across a number of different data types:
int
std::string
std::vector<std::string>
--
Let's go over the basic types available in C++
type name | description | bits | range |
---|---|---|---|
bool |
true/false | ? | true or false |
char |
integer | 8 | -127 to 128 |
unsigned char |
integer | 8 | 0 to 256 |
short int |
integer | 16 | -32768 to 32767 |
short unsigned int |
integer | 16 | 0 to 65535 |
int |
integer | 32 | ±2.15×109 |
unsigned int |
integer | 32 | 0 to 4294967295 |
long int |
integer | 64 | ±9.22×1018 |
long unsigned int |
integer | 64 | 0 to 1.85×1019 |
float |
floating-point | 32 | ±3.4-38 to ±3.438 |
double |
floating-point | 64 | ±1.7-308 to ±1.7308 |
-- Note that the C/C++ standard does not mandate how many bits each type should be!
- an
int
may actually be 16 bits on a small embedded system - a
long int
may not be 64 bits on all platforms
Standard operators: +
, -
, *
, /
--
Modulus (remainder): %
--
combined with assignment: +=
, -=
, *=
, /=
- for example, always use
a += 5;
rather thana = a + 5;
--
Increment/decrement: ++x
, x++
, --x
, x--
- for example, always use
x++;
rather thanx += 1;
orx = x + 1;
-- - the difference between
++x
andx++
is the return valuex++
returns the original value ofx
, while++x
returns the updated value ofx
--- this only matters when the return value is used, for example:
int x = 5; std::cout << ++x << "\n"; // x is now 6, and the value printed will also be 6 std::cout << x++ << "\n"; // x is now 7, but the value printed will still be 6!
Operator precedence:
- First: increment, decrement
- Second: multiply, divide, modulo
- Third: addition, subtraction
--
- Answer: 14
--
Don't hesitate to use brackets to clarify!
Some operations only work for certain types
- e.g. you can add an
int
and adouble
- you can't use the modulus operator on a
float
ordouble
--
Be mindful of implicit type conversions when mixing data type
- you can add a
bool
and achar
– but they will first be converted to integer values
(true
=1,false
=0 forbool
, ASCII values forchar
) -- - you can add an
int
and afloat
, but theint
will first be converted to afloat
-- - the exact rules for type conversion are fairly complex – but the general principle is simply to use the type with greatest range and/or precision
--
Integer arithmetic can be different to floating point arithmetic!
- for example:
1 / 3
evaluates to0
→ two integer inputs will force an integer output -- - but
1.0 / 3
evaluates to0.33
→ theint
will be implicitly converted tofloat
thanks to the rules above
We have already encountered some data types that are more advanced:
-
std::string
: a class to hold an array of characters, used to represent text -
std::vector<...>
: a template class used to hold an array of any other type
-- We will cover classes and templates in more detail later in the course. For now, we will simply look at how to use them.
name: string
Declare a string called mesg
:
std::string mesg;
--
Declare a string called mesg
, initialised with the content "hello":
std::string mesg = "some text";
// or equivalently:
std::string mesg ("some text");
-- Retrieve the length of the string (returns an integer), or check whether it is empty (zero-length):
mesg.size();
// or equivalently:
mesg.length();
// check if empty:
if (mesg.empty()) ...
Retrieve a single character at index n
:
mesg[n];
--
Set the character at index n
to the letter a
:
mesg[n] = 'a';
-- Set the string to a different set of text, discarding previous contents:
mesg = "Hello";
--
.note[Note the use of single inverted commas ('
) to enclose single
characters, and double inverted commas ("
) for full strings.
This is important: single characters are of type char
, while full strings are
arrays of char
– they are fundamentally different types. ]
Append text to a string:
mesg += " World!" // mesg now contain "Hello World!"
--
Concatenate two strings together (this returns a new std::string
):
mesg + " How are you?";
// typical use case:
std::cout << mesg + " How are you?\n";
-- Clear the string (empty it):
mesg.clear();
Check whether two strings are identical:
if (mesg == "some string") ...
// or assuming there is another std::string called 'other':
if (mesg == other) ...
-- Check whether the string starts with (or ends with) a certain prefix or suffix:
if (mesg.starts_with ("prefix")) ...
if (mesg.ends_with ("suffix")) ...
-- Find the index of the first occurence of a given sub-string:
auto n = mesg.find ("World");
// note: this returns the special value 'std::string::npos' if no match found
The std::string
class offers a lot more functionality than presented here.
However, the information on the previous slides should already provide you with the necessary tools to tackle most tasks.
For full details, have a look online:
name: vector
The std::vector
class can be used in a similar way to the std::string
class. The main difference is the way the class is declared:
code | description |
---|---|
std::vector<int> ivec; |
Declare an empty vector of int |
std::vector<float> fvec; |
Declare an empty vector of float |
std::vector<std::string> svec (10); |
Declare a vector of std::string , with 10 elements |
std::vector<int> ivec (20, 0); |
Declare a vector of int of size 20, all initialised to zero |
--
Usage is otherwise similar to the std::string
class:
std::vector<float> vec (10, 0.0);
int n = vec.size();
if (vec.empty()) ...
float val = vec[3];
vec[2] = val;
vec.clear();
The std::vector
class is an example of a template class:
- It is a generic container, which can be used to contain other data types
- It is part of the C++ Standard Template Library (STL)
--
The contained data type is part of the type of a std::vector
:
std::vector
itself is not a type – you cannot create a variable of typestd::vector
std::vector<float>
is a proper data type
--
We will learn more about templates (much) later in the course
--
Look online for further details about the std::vector
class:
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
std::vector<int> vec;
for (int n = from; n <= to; n++)
vec.push_back (n);
for (auto x : vec)
std::cout << x << " ";
std::cout << "\n";
return 0;
}
]
.col[
Let's modify our code to demonstrate the use of the std::vector
class to hold
a different data type
This program will create a vector containing all integers from the first command-line argument provided to the second command-line argument provided.
Let's go through the code ] ]
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
* int from = std::stoi (args[1]);
* int to = std::stoi (args[2]);
std::vector<int> vec;
for (int n = from; n <= to; n++)
vec.push_back (n);
for (auto x : vec)
std::cout << x << " ";
std::cout << "\n";
return 0;
}
] .col[ First, we need to grab the two command-line arguments provided by the user to specify the first and last values
We use the std::stoi() function to convert text to integer
This take a std::string
and returns an int
If the string cannot be interpreted as an integer, this will cause an error
- an exception will be thrown (we will cover this later in the course)
] ]
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
* std::vector<int> vec;
for (int n = from; n <= to; n++)
vec.push_back (n);
for (auto x : vec)
std::cout << x << " ";
std::cout << "\n";
return 0;
}
]
.col[
We declare the variable vec
of type std::vector<int>
: a vector of integers
vec
will initially be empty
]
]
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
std::vector<int> vec;
* for (int n = from; n <= to; n++)
vec.push_back (n);
for (auto x : vec)
std::cout << x << " ";
std::cout << "\n";
return 0;
}
]
.col[
We then iterate over all values from the first argument provided (from
) to
the second argument provided (to
)
Note that in this case, we keep iterating while the value is less than or equal
to to
- This ensures the vector contains all values between
from
&to
, including bothfrom
&to
themselves
] ]
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
std::vector<int> vec;
for (int n = from; n <= to; n++)
* vec.push_back (n);
for (auto x : vec)
std::cout << x << " ";
std::cout << "\n";
return 0;
}
]
.col[
We then iterate over all values from the first argument provided (from
) to
the second argument provided (to
)
Note that in this case, we keep iterating while the value is less than or equal
to to
- This ensures the vector contains all values between
from
&to
, including bothfrom
&to
themselves
We use the push_back() method to append each value to the end of the vector
- the vector increases in size dynamically to accommodate each new value
] ]
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
std::vector<int> vec;
for (int n = from; n <= to; n++)
vec.push_back (n);
* for (auto x : vec)
* std::cout << x << " ";
* std::cout << "\n";
return 0;
}
] .col[ Finally, we iterate over the contents of our vector to print out its elements
- the values are separated by spaces, with a final newline at the end
Note the use of the range-based for loop here
- its use is possible here, and hence recommended
] ]
.columns[ .col[
#include <iostream>
#include <vector>
#include <string>
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
std::vector<int> vec;
for (int n = from; n <= to; n++)
vec.push_back (n);
for (auto x : vec)
std::cout << x << " ";
std::cout << "\n";
return 0;
}
] .col[ Modify your code as shown here, compile it and run it.
To run it, you will need to provide it with two arguments for the first and last values:
$ ./main 2 5
2 3 4 5
What happens if you don't provide both arguments?
What happens if the second argument is smaller than the first?
What happens if one or both of the arguments isn't a number? ] ]
name: error_handling
As you can see from the previous example, when errors are encountered at runtime, there is no useful feedback to the user
- failing to provide one or both arguments may cause a crash, or nothing at all
- providing a lower value in the second argument produces no output
-- In general, it is essential for code to validate its inputs and check its assumptions before running the relevant section of code
It is also important to report the reasons for any failures, along with any relevant information
- this will be very helpful to you and your users when trying to figure out what might have gone wrong!
--
Let's add a little bit of error checking to our program
...
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
if (args.size() < 3) {
std::cerr << "ERROR: expected min & max to be provided as arguments\n";
return 1;
}
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
if (from > to) {
std::cerr << "ERROR: max must be greater than min\n";
return 1;
}
std::vector<int> vec;
...
...
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
* if (args.size() < 3) {
* std::cerr << "ERROR: expected min & max to be provided as arguments\n";
* return 1;
* }
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
if (from > to) {
std::cerr << "ERROR: max must be greater than min\n";
return 1;
}
std::vector<int> vec;
...
.explain-bottom[
First, we check whether enough command-line arguments have been provided.
If we have fewer than 3 arguments, the command cannot run
- remember that the first argument (
args[0]
) corresponds to the command itself
We use an if
statement to do the checking
]
name: if
class: section
The if
statement allows us to execute some code if a condition is true
.
In its simplest form, it looks like this:
if (condition)
statement;
The if
statement allows us to execute some code if a condition is true
.
In its simplest form, it looks like this:
`if` (condition)
statement;
The if
keyword denotes the start of the conditional
The if
statement allows us to execute some code if a condition is true
.
In its simplest form, it looks like this:
if (`condition`)
statement;
The if
keyword denotes the start of the conditional
condition
is an expression that can evaluate to true
or false
The if
statement allows us to execute some code if a condition is true
.
In its simplest form, it looks like this:
if (condition)
`statement`;
The if
keyword denotes the start of the conditional
condition
is an expression that can evaluate to true
or false
statement
is the section of code to run if condition
is true
The complete form looks like this:
if (condition)
statement_if_true;
else
statement_if_false;
This form allows code to be executed if true
, and different code to be executed if false
--
If statements can also be nested or chained:
.columns[ .col[
if (condition) {
if (some_additional_condition)
statement_1;
else
statement_2;
}
] .col[
if (condition)
statement_1;
else if (other_condition)
statement_2;
else
statement_3;
] ]
Any expression that can be evaluated to true
or false
can be used as the condition
in an if
statement
- this includes any number: zero will evaluate to
false
, anything else totrue
--
Any of the following operators will return a bool
:
a == b // true if a is equal to b
a != b // true if a is NOT equal to b
a < b // true if a is less than b
a > b // true if a is greater than b
a <= b // true if a is less than or equal to b
a >= b // true if a is greater than or equal to b
--
These can also be combined using logical operators to form compound expressions:
cond1 && cond2 // true if cond1 and cond2 are both true
cond1 || cond2 // true if either cond1 or cond2 are true
!condition // true is condition is false
The form we gave for the if
statement only allows for a single statement to be executed:
if (condition)
statement;
What if we need to execute more than a single statement?
--
We can use curly brackets (braces) to group multiple statements into a single compound statement (also known as a block):
if (condition) `{`
statement 1;
statement 2;
...
statement N;
`}`
--
This also applies to the for
loop!
...
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
if (args.size() < 3) {
* std::cerr << "ERROR: expected min & max to be provided as arguments\n";
return 1;
}
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
if (from > to) {
std::cerr << "ERROR: max must be greater than min\n";
return 1;
}
std::vector<int> vec;
...
--
.explain-bottom[ If fewer than 3 arguments have been provided:
- feed an appropriate error message to the [standard error](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr%29) stream
- the standard error stream is similar to standard output, but is more appropriate for error messages
.note[ For information only: this is because the standard output stream can be [redirected](https://en.wikipedia.org/wiki/Redirection_(computing%29) for use in other applications. In such a situation, the error messages would not be displayed on the terminal, and would corrupt the normal expected output of the program. ] ]
...
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
if (args.size() < 3) {
std::cerr << "ERROR: expected min & max to be provided as arguments\n";
* return 1;
}
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
if (from > to) {
std::cerr << "ERROR: max must be greater than min\n";
return 1;
}
std::vector<int> vec;
...
.explain-bottom[
We then return from the main()
function with a non-zero exit code
]
...
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
if (args.size() < 3) {
std::cerr << "ERROR: expected min & max to be provided as arguments\n";
return 1;
}
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
* if (from > to) {
* std::cerr << "ERROR: max must be greater than min\n";
* return 1;
* }
std::vector<int> vec;
...
.explain-top[
We also check that the second value provided is greater than (or equal to) the first. Otherwise there will be no output from our program.
We do that in the same way as we did for the first if
statement.
]
...
int main (int argc, char* argv[])
{
std::vector<std::string> args (argv, argv+argc);
if (args.size() < 3) {
std::cerr << "ERROR: expected min & max to be provided as arguments\n";
return 1;
}
int from = std::stoi (args[1]);
int to = std::stoi (args[2]);
if (from > to) {
std::cerr << "ERROR: max must be greater than min\n";
return 1;
}
std::vector<int> vec;
...
.explain-bottom[ Exercise: modify your own code to add these checks, then compile and test it ]
name: switch
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
switch (variable) {
case value1:
statement;
...
statement;
break;
case value2:
statement;
break;
...
default:
statement;
}
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
`switch` (variable) {
case value1:
statement;
...
statement;
break;
case value2:
statement;
break;
...
default:
statement;
}
.explain-bottom[
the switch
keyword denotes the start of our conditional section of code
]
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
switch (`variable`) {
case value1:
statement;
...
statement;
break;
case value2:
statement;
break;
...
default:
statement;
}
.explain-bottom[
variable
is the variable whose value will determine the code to run
]
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
switch (variable) {
* case value1:
statement;
...
statement;
break;
case value2:
statement;
break;
...
default:
statement;
}
.explain-bottom[
for each value of variable
that we want to handle (e.g value1
), we use the case
keyword to label the matching section of code, using this syntax
]
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
switch (variable) {
case value1:
* statement;
* ...
* statement;
break;
case value2:
statement;
break;
...
default:
statement;
}
.explain-bottom[
immediately after the case
label, we insert the code to be run. This can consist of multiple lines – no need for braces here.
]
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
switch (variable) {
case value1:
statement;
...
statement;
* break;
case value2:
statement;
break;
...
default:
statement;
}
.explain-bottom[
... but we do need to close off the last statement of the code for that case with the break
keyword!
More on the break
statement shortly...
]
The if
statement is by far the most common structure for conditional execution, but other forms exist.
The switch
statement allows you to execute different section of code depending on the value of a variable. It takes this general form:
switch (variable) {
case value1:
statement;
...
statement;
break;
case value2:
statement;
break;
...
* default:
statement;
}
.explain-middle[
We can also have a catch-all default
label at the end, which will be executed in case none of the other labels matched
]
The break
statement can also be used with loops
In this case, it is used to terminate the current iteration and immediately return control to the code after the corresponding loop.
--
For example:
bool match_found = false;
for (int n = 0; n < 100; ++n) {
if (matches_criterion (n)) {
match_found = true;
break;
}
}
The continue
statement can also be used with loops
It also stops the current iteration, but in this case it immediately moves onto the next one
--
For example:
for (int n = 0; n < data.size(); ++n) {
if (!need_to_process (n))
continue;
perform_intense_processing (data[n]);
}
class: section name: exercises
Write a C++ program that calculates the following expressions and outputs the results. Why do you get each answer?
(7 * 3) / 5
(7.0 * 3) / 5
(7 * 3.0) / 5
(7 * 3) / 5.0
(7.0 * 3.0) / (5.0)
(7 * 3) % 5
(7 * 3.0) % 5
Write a program that takes three numbers a, b, c as input, calculates the quadratic formula and displays the output. Add a check to your program that the roots will be real-valued, i.e. that b² ≥ 4ac. A reminder that the quadratic formula is: $$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$
.note[
You will to include the maths header in order to access the square-root
function, std::sqrt()
.
In other words, you will need the following lines of code in the right place:
#include <cmath>
at which point you can compute the square root using e.g.:
float answer = std::sqrt(expression);
]
Write a program that takes three numbers a, b, c as input which represent
the lengths of the sides of a triangle. Use the formula below to calculate the
area of the triangle and display it. Your program should check that the side
lengths are valid. To be valid, the length of each side must be less than the
sum of the other two sides.
$$
\begin{align}
s &= (a + b + c)/2 \\
A &= \sqrt{s(s - a)(s - b)(s - c)}
\end{align}
$$