Skip to content

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 ([precedes hex with 0x and octal with 0])
  • noshowbase
Other modifiers (floating point):-
  • fixed (fixed point)
  • scientific notiation
  • setprecision(no. of points)
cout.unsetf(ios_base::floatfield);    // sets floating point back to default
Other modifers (strings):-
  • setw(64) - sets width to 64
  • right - right aligns text
  • setfill('-') - 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;
}