Essential C++ π·πΌββοΈπ·πΌββοΈ
(from LinkedIn Learning course)
Pointers
int *ip; // allocates space for a pointer to an int
int x = 42;
ip = &x; // & reference operator (or 'address of' operator) - address of x is placed in ip
int y = *ip; // copy value of x into y
References
int &y = x; // y is a reference of x
differences between pointers and references
- references don't need an operator to dereference them. (this can lead to side effects when calling functions with referenced params)
- references cannot be changed to point to a different variable
Primitive Array
int ia[5];
int *ip = ia;
*ip = 2; // ia[0] = 2
++ip;
*ip = 3; // ia[1] = 3
*(++ip) = 4; // ia[2] = 4
Can initialise an array with an initialiser list, e.g.
int ia[5] = {0, 1, 2, 3, 4}; // number in [] is optional
int ia[] = {0, 1, 2, 3}; // sizes the array automatically
int numbers[8] = {}; // creates an array filled with empty ints (i.e., set to 0)
int numbers[10]; // declares the array, but does not initialise it.
string animals[][3] = { // multidimension array
{"Fox", "Dog", "Jackal"},
{"Tiger","Lynx","Tabby"}
};
sizeof(string); => 8 // not dependent on how many chars in the string
sizeof(animals); => 48 // size of entire array
sizeof(animals[0]); => 24 // size of first row
sizeof(animals) / sizeof(animals[0]) = number of rows
sizeof(animals[0]) / sizeof(string) = number of elements in each row // possible to use animals[0][0] instead of string
Primitive String (or C-String)
(not the same as an STL String)
char name[] = "Rob"; // this string is zero terminated
const char * s = "Rob"; // another method of declaring a C String
must be a const. const refers to the character being pointed at - not the pointer. The pointer can change to point to another string. const char * const ptr (this pointer CANNOT change)
Can concatenate literal strings with a space, e.g. char day[] = "Sun" "day";
for(char *cp = name; *cp; ++cp) {
printf("%c\n", *cp);
}
Or with a range based loop (C++11 onwards)
for(char c:name) {
if(c==0) break;
printf("%c\n",c);
}
Outputting Text
printf()
and puts()
#include <cstdio>
printf(...);
puts(...);
cout
#include <iostream>
std::cout << "Hello World" << endl;
cout
has a larger footprint than puts & printf
Data Type Sizes
type | size |
---|---|
char | 8 bits (may or may not be signed) |
short int | 16 bits |
int | 32 bits |
long int | 32 or 64 bits |
long long int | 64 or 128 bits |
(ints are by default signed - unless otherwise stated)
Standard sizes
(if you need a specific size regardless of platform)
#include <cstdint>
int8_t uint8_t
int16_t uint16_t
int32_t uint32_t
int64_t uint64_t
Escape Sequences
\"
\'
\\
\x40
\n
\t
\u03bc
(unicode character)
Qualifiers
const
volatile
mutable
static
register
extern
volatile
-> may be changed by another process
statics
are NOT reinitialised once they are set
register
-> a suggestion that the variable should be stored in a register
Bitfields
struct prefs {
bool isTall : 1;
bool hasHair : 1;
bool isADude : 1;
bool canRockOut : 1;
unsigned int numberOfLegs : 4;
}
Number after the colon is the number of bits to assign to this datum. There is no guarantee about how much space is set aside - it's dependent on the compiler. This can be an issue in threading... all bitfields must be read to and written to at the same time.
Enum
Enums in C++ are essentially a collection of constants.
enum card_suit : **uint8_t** { SPADE, HEART, DIAMOND, CLUB};
enum card_rank {ACE = 1, JACK = 11, QUEEN, KING };
Can specify the size of the enum (e.g. uint8_t
in the above) instead of the default of int
.
Can specify the value of the enum, (e.g. ACE =1
, JACK =11
) and after that the count continues (so QUEEN will be 12 and KING will be 13)
struct Card {
int rank; int suit;
}
Card deck[52] = { {ACE, SPADE}, {2, SPADE}, β¦. }
Union
Using the same memory space for different types. e.g.
union ipv4 {
uint32_t i32;
struct {
uint8_t a; uint8_t b; uint8_t c; uint8_t d;
} octets;
}
union ipv4 hos;
host.octets = {192, 168, 0, 1};
Typedefs, Autotype and Nullptr
typedef unsigned char points_t;
points_t
is an alias for unsigned char
. Itβs common to add _t
to indicate itβs a typedef
.
autotype (C++11 onwards)
auto x= func();
Works like C# var
- can use it in place of complicated definitions (e.g. instead of vector<int>::iterator
)
nullptr (C++11 onwards)
A pointer of 0 that can be used for any pointer (and only pointers)
Operators
+=
-=
*=
/=
%=
++j
j++
(prefix is slightly more optimised than postfix)
Logical operators
&&
||
!
Bitwise operators
&
|
^
~
(invert)
<<
>>
Memory Operators
#include <new> // for bad_alloc
long int * ip;
try {
ip = new long int[300];
} catch(std::bad_alloc & ba) {
fprintf(stderr, β¦);
return 1;
}
//β¦
delete [] ip; // only use square brackets if created more than 1 object
Or without using exceptionsβ¦
ip = new (nothrow) long int[300];
if (ip==nullptr) {
// handle error here
}
size_t y = sizeof x; // returns the size of x in bytes
size_t z = sizeof(int); // brackets are required for types (not needed for vars)
β%zdβ
is the string format for size_t
#include <typeinfo>
typeid(β¦);
typeid(β¦).name();
Functions
call by reference
f(&i); // address of (or reference) operator
void f(int *p) {
++(*p); // * <- pointer dereference operator
}
void f(int &x) { // using ref instead of pointer
++x; // more dangerous - possible to change value of
} // original value without caller being aware of side effect
void f(const int *i) { // protects value from being changed
...
}
Functions must be declared before they are called.
Local variables are stored on the stack. Itβs a good idea not to declare large objects as locals.
Static variables are only initialised once (further calls to an initialiser do not update the value)
const string & func() {
const static string s= βHello World!β;
return s;
}
const string s = func();
Function Pointers
void func() { β¦ }
void (*pfunc)() = func; // declares a function pointer
pfunc(); // calls func() - also (*pfunc)(); is allowable - this is the C version
Use - Jumptable..
void fa() {β¦}
void fb() {β¦}
void fc() {β¦}
void (*funcs[])() = { fa, fb, fc }
funcs[1](); // calls fb - this is useful for menu options, etc.
Finding the size of an arrayβ¦
int array_size = sizeof(funcs) / sizeof(funcs[0]);
Variable number of parameters
#include <cstdarg>
double average(const int count, β¦) {
va_list ap;
double total = 0.0;
**va_start**(ap, count); // va_start needs a matching va_end
for(int i=0; i<count; ++i) {
total += va_arg(ap, double); // double is the type in this context
}
**va_end**(ap); // va_end needs a matching va_start
return total / count;
}
#include <cstdarg>
int message(const char *fmt, β¦) {
va_list ap;
va_start(ap, fmt);
int rc = vfprintf(stdout, **fmt, ap**); // passing a va list to another function
puts(ββ);
va_end(ap);
return rc;
}
Alternatively...
char buffer[2048];
va_list ap;
va_start(ap, fmt);
vsprintf(buffer,fmt,ap);
va_end(ap);
Objects & Classes
class Class1 {
int i; // private section (could have private on line above)
public:
Class1(); // ctor -> constructor
~Class1(); // dtor -> destructor
}
The class is essentially a struct with methods. Can replace a struct with a class and the code will still work. Only different is the default access modifier.
item | default access |
---|---|
struct | public |
class | private |
Considered best practice to separate implementation of methods (in a .cpp
file) from interface (in a .h
file)
Const Classes
When a class is declared as const, it needs const safe methods...
const C1 o1;
o1.getValue();
This will need the following functionβ¦
int C1::getValue() const; // cannot change C1βs members in this function
This is considered different to the (mutable) functionβ¦
int C1::getValue();
The compiler will call the relevant function depending on if the object is defined as a const or mutable. This is a form of overloading.
If you have a function in a class that doesnβt change member values in the object, itβs best to always declare it as const safe.
Constructors and Deconstructors
class Animal {
string _Type = "";
string _Name = "";
public:
Animal(); // default constructor
Animal(const string &type, const string &name);
Animal(const Animal &); // copy constructor (called by assignment, e.g. Animal b = a; )
~Animal();
}
Animal::Animal(const string & type, const string & name): _Type(type), _Name(name) {};
Operator Overloading - with Member Functions**
class Rational {
int _n;
int _d;
public:
Rational(int num = 0, int den = 1): _n(num), _d(den) {}; // compiler will use this to convert an int to a Rational
Rational(const Rational &rhs): _n(rhs._n), _d(rhs._d) {}; // copy constructor
Rational & operator = (const Rational &); // not const safe, assignment changes member values
Rational operator + (const Rational &) const; // this is const safe as it returns the result of the operation, doesnβt change "this"
}
Rational & Rational::operator = (const Rational &rhs) {
if (this!=rhs) {
_n = rhs._n;
_d = rhs._d;
}
return * this; // we return a reference to βthisβ - allows chaining of assignments: x = y = z;
}
Rational Rational::operator + (const Rational &rhs) { // returns a new object - canβt return a ref to a temp (stack based) object
return Rational(_n * rhs._d + _d * rhs._n, _d * rhs._d); // the new object is destroyed after the overloaded operator is evaluated
}
Operator Overloading - without Member Functions
Non member function overloading is useful for when the LHS of an expression is not the type of your class. e.g.
Rational = int + Rational <- we havenβt got an overloaded + operator for int -> Rational
Rational operator + (const Rational &lhs, const Rational &rhs) {
return Rational(lhs.numerator() * rhs.denominator() + lhs.denominator() * rhs.numerator(), lhs.denominator() * rhs.numerator() );
}
This is defined outside of the scope of the class. Now when Rational = int + Rational is called, the compile will use the implicit constructor to convert the int to a Rational and then call the + operator overload.
Note that this method (as it is outside of the class cannot access the private members of the class).
Overloading the c.out
<<
operator
std::ostream & operator << (std::stream &o, const Rational &r) {
if(r.denominator()==1)
return o << r.numerator();
else
return o << r.numerator() << β/β << r.denominator();
}
Templates
Template specialisation -> done at compile time for each set of data types.
Caveats - larger executables, longer compile times, confusing error messages
Template Functions**
template<typename T> // <typename T> is the template argument list
T maxof(T a, T b) {
return (a>b) ? a : b;
}
int m = maxof<int>(7, 9);
Cannot define a template within a block. Type safe as the compiler does the substitution at compile time (so if a type that doesnβt have a >
operator is used, the compiler will complain).
Template Classes
(Commonly used for containers)
template<typename T>
class bwstack {
private:
T * _stkptr;
public:
T & push(const T &);
T & pop();
}
template<typename T>
T & bwstack::push(const T &item) { β¦ }
Usage
bwstack<int> stack;
Standard Library
Common system level tasks
Covering
- File IO
- C-Strings
- Error Handling
- Date and Time
- Utilities
- Maths
- Localization
- Process control
- System services
uses namespace std
File IO - text
FILE * f = fopen(filename, "r"); // reading a text file
char buf[1024];
while(fgets(buf, 1024, f)) {
puts(buf);
}
FILE * fw = fopen(filename, "w"); // writing a text file
for(int i= 0; i<99; ++i) {
fputs("Hello World!", fw);
}
remove(filename); // removes the file
file modes
flag | mode |
---|---|
r | read from start (fail if doesn't exist) |
r+ | open for read/write, read from start - (fail if doesn't exist) |
w | create file for writing from start (destroy any existing file) |
w+ | create file for read/write from start (destroy any existing file) |
a | write from end (create if doesn't exist) |
a+ | read/write from end (create if doesn't exist) |
File IO - binary
FILE * fb = fopen(fn, "wb");
itemsWritten = fwrite(&buffer, sizeOfElement, numberOfElements, fb);
itemsRead = fread(&buffer, sizeOfElement, numberOfElements, fb);
File Management
#include <cstdio>
using namespace std;
Creating a file...
FILE * fh = fopen("newfile","w");
fclose(fh);
Renaming a fileβ¦
rename(filename1, filename2);
Unformatted Character IO
Output
fputs(βHello Worldβ, stdout); // unlike puts this doesnβt add an EOL at the end
Input
const int bufsize = 256;
static char buf[bufsize];
fputs("prompt: ", stdout);
flush(stdout); // flushes the stream IO so that itβs written before you read from stdin
fgets(buf, bufsize, stdin); // gets() is a variant of gets() And NEVER use it - it can be used to break computer security
Formatted Character IO
Output - uses printf
and fprintf
Format specifiers
token | type |
---|---|
%d | integer |
%ld | long integer |
%s | c-string |
%f | floating point |
%p | pointer |
%c | char |
%% | percent |
%zd | size_t |
String Functions
#include <cstring>
using namespace;
*Functions*
strncpy(*dest, *src, size of dest);
strncat(*dest, *src, size of dest);
strnlen(*src, max size);
strcmp(*str1, *str2) -> returns 0 if strings are the same
strchar(*str, char) -> returns pointer to char in string, or null if not found
strstr(*str, *substr) -> returns pointer to subset in str, or null if not found
Error Handling
#include <cerrno> <- provides access to **errno**
#include <cstring> <- required for the strerror function
If errno
is 0, then no error, otherwise errno
's value will indicate which error has occurred.
strerror(errno)
<- gets the string associated with the error number
(perror(str)
reports the error along with itβs string parameter straight to the stderr console)
STL (Standard Template Library)
Containers, etc..
- vector
- list (double linked list)
- set
- map
- stacks, queues and deques
- strings
- iostreams
vector
Essentially an OO-array with some cool extra features.
#include <vector>
vector<int> vints = {1,2,3,4,5,6,7,8,9};
vints.size() - returns size of vector array
vints.front() - returns first element
vints.last() - returns last element
Iterating
vector<int>::iterator it_begin = vints.begin();
vector<int>::iterator it_end = vints.end();
for( auto it = it_begin; it < it_end; ++it) { do stuff with *it }
for(int & i : vints) { do stuff with i }
Indexing
vints[5]
vints.at(5)
Inserting and Removing
vints.insert(vints.begin() + 5, <item to insert> ); // inserts item at 5th position
vints.remove(vints.begin() + 5); // removes item at 5th position
vints.push_back(<item to add); // adds item at back of
Initializing from a C-array
const static size_t size = 10;
int ia[size] = {1, 2, 3, 4,5 ,6, 7 ,8 ,9 ,10 };
vector<int> vints2 = {ia, ia + size};
Example with strings
vector<string> vstrings = { "one", "two", βthree" };
for( string & s : vstrings ) {
cout << s << endl;
}
string
string s1 = "This is a string";
s1.size() // size is used for all the other STL containers
s1.length() // length is an alias for size when working with strings
s1 + s2 // concatenated strings
s1 == s2 // comparison (can also use < and > )
s1 = s2 // assignment
for (char c : s1) { cout << c << " ": }
string::iterator it;
it = s1.begin() + 5;
s1.insert(it, βXβ);
s1.erase(it);
s1.replace(5, 2, "ainβt"); // replaces the two letters at position 5 & 6 with "ainβt")
s1.substr(5, 3); // returns a string of the 3 chars are positions 5 to 7
size_t pos = s1.find("s"); // find the first occurrence of "s"
size_t pos = s1.rfind("s"); // find the last occurrence of "s"
iostreams
cin
cin >> x;
cin
will only read the first word, to read the whole line you need...
char buf[128];
cin.getline(buf,sizeof(buf));
cout
cout << hex << endl;
note - endl
presents the end of line char and also flushes the buffer ( << flush
also flushes the buffer)
Other modifiers (integers):-
oct
(octal)dec
(decimal)hex
(hexadecimal)showbase
([precedeshex
with0x
and octal with0
])noshowbase
Other modifiers (floating point):-
fixed
(fixed point)scientific
notiationsetprecision(no. of points)
cout.unsetf(ios_base::floatfield); // sets floating point back to default
Other modifers (strings):-
setw(64)
- sets width to 64right
- right aligns textsetfill('-')
- fills leading spaces with '-'left
= left aligns text
file i/o
writing a file
ofstream ofile(filename); // open file for writing
ofile << "Hello World" << endl;
reading a file
char buf[128];
ifstream infile(filename);
while (infile.good()) {
infile.getline(buf, sizeof(buf));
cout << buf << endl;
}
infile.close();
Exception handling
#include <exception>
class E : public exception {
const char * msg;
E();
public:
E(const char * s) throw() : msg(s) {};
const char * what() const throw() { return msg;}
};
declaring set types of exceptions
const E e_ouch("ouch!");
const E e_bad("bad");
Throwing exceptions
throw E("this is an error messageβ);
Or
throw e_bad;
Catching exceptions
should look pretty familiar
try {
brokenFunction();
} catch(E & e) {
c.out << e.what() << endl;
}