If you have spare time

Watch some CppCon β€˜Back to Basics’ videos:

Mechanics of a C++ program

  1. Write source code 1.1 Unix extensions: C,cc,cxx,c 1.2 GNU C++ extensions: C,cc,cxx,cpp,c++
  2. Compile the source code - Translate the code to machine code. The file containing the translation is the object code of the program.
  3. Link the object code with additional code - Combination of the source code with startup code and library object code to produce a runtime version. The final product is a file called the executable code, which contains a set of machine language instructions.
  4. Execute the program

Compilation and linking is done with:

Preprocessor

The preprocessor processes a source file before compilation. It allows the definition of macros, which are abbreviations for longer constructs.

Directives

Lines which begin with #:

  • #define IDENTIFIER [value] - Replaces the occurrences of IDENTIFIER in the code with value. Note that value is optional.
  • #undef IDENTIFIER - Removes the definition of IDENTIFIER.

Conditional directives allow the inclusion or exclusion of parts of the code if a certain condition is met:

  • #ifdef IDENTIFIER - If IDENTIFIER is defined, then the code that follows is included until #endif is reached.
  • #ifndef IDENTIFIER - If IDENTIFIER is not defined, then the code that follows is included until #endif is reached.

Conditional directives are used, for example, to include headers only once:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
  // header code
#endif // FOO_BAR_BAZ_H_

File inclusion:

  • #include <library> - The contents of the library file are sent along with the source code. In essence, the contents of the library replace the #include line. Note that the compiler tries to find library in the host system’s file system that holds the standard header files.
  • #include "library" - Same as above, but library is looked for in the current working directory.
  • #pragma - Specify diverse options to the compiler that are specific to the platform/compiler.

Program structure

Large programs can be split into multiple files, which can be compiled (if needed) and linked to generate an executable program. For example, a program can be split into three files:

  • A header file that contains structure declarations and prototypes for functions that use those structures.
  • A source code file that contains the code for the functions.
  • A source code file that uses those functions.

The following things are commonly found in headers:

  • function prototypes
  • symbolic constants defined with #define or const
  • structure, class, template declarations
  • inline functions
trunk
β”œβ”€β”€ bin     : for all executables (applications)
β”œβ”€β”€ lib     : for all other binaries (static and shared libraries (.so or .dll))
β”œβ”€β”€ include : for all project header files, 3rd party files not present in `/usr/local/include` should be here
β”œβ”€β”€ src     : for source files
β”œβ”€β”€ doc     : for documentation
β”œβ”€β”€ build   : for all the object files, removed by `clean`
└── test    : for testing

Example:

. project
β”œβ”€β”€ build
β”œβ”€β”€ include
β”‚   └── project
β”‚       └── Vector.hpp
β”‚   └── [third party library]
└── src
    β”œβ”€β”€ Vector.cpp
    └── main.cpp

Strings

C-style strings

The last character is the null character, \0.

char name[20];              // initialized with random data
char name[5] = {'j', 'h', 'o', 'n', '\0'};
char name[8] = {'j', 'h', 'o', 'n', '\0'};    // right padded with \0
char name[5] = "john";      // the \0 is understood
char name[8] = "john";      // right padded with \0
char name[] = "john";       // let the compiler count

Operations:

#include <cstring>
char source[] = "john";       // let the compiler count
char dest[10];

// size of the string
strlen(source);   // 4
strlen(dest);     // 10, 10 random characters, the 11th is \0

// copy `source` to `dest`
strcpy(dest, source);

// concat `dest` with `source`
strcat(dest, source);

Reading input:

char name[20];
cin >> name;            // read until space or newline
cin.getline(name, 20);  // read 20 characters or until newline
cin.get(name, 20);      // read 20 characters or until before newline

C++ strings

#include <string>

string str;           // ""
string name = "john";

cin >> name;          // reads until space or newline
cin.getline(name);   // reads until newline

Pointers

Given a variable, the address operator & is used to get its address or location in memory.

int oranges = 5;
int apples = 6;

// location in memory e.g. 0x0065fd40
&oranges;

// location in memory e.g. 0x0065fd44
&apples;

// NOTE: the difference between them is 4 bytes, the size of int

Pointers are variables that store the addresses of values rather than the values themselves. To declare a pointer, we use the form typeName * pointerName.

int oranges = 5;
int* p_oranges;        // declare pointer to an int
p_oranges = &oranges;  // assign address to pointer
sizeof(p_oranges);     // 4 bytes

The dereferencing operator, *, yields the value at the location.

int oranges = 5;
int* p_oranges = &oranges;
*p_oranges;                   // 5
*p_oranges = *p_oranges + 1;  // update the value
oranges;                      // 6
*p_oranges;                   // 6

Always initialize a pointer to a definite address before applying the dereferencing operator.

int* p_int;
*p_int = 3;     // value is lost forever

When a pointer is assigned to another pointer, the value stored is the address stored in the first pointer.

int oranges = 5;        // value: 5,     address: 0x000
int* p = &oranges;      // value: 0x000, address: 0x004
int* q = p;             // value: 0x000, address: 0x008
*q;                     // 5

If we want to create a pointer to a pointer, we use extra β€˜β€™. For the declaration, the number of β€˜β€™ must be equal to the length of pointers (including this one). In the same fashion, we must use the same number of β€˜*’ for dereferencing.

int oranges = 5;        // value: 5,     address: 0x000
int* p = &oranges;      // value: 0x000, address: 0x004
int** q = &p;           // value: 0x004, address: 0x008
*p;                     // 5
**q;                    // 5

Pointer and arrays

C++ handles arrays internally using pointers, which may seem equivalent. An ordinary array variable name is interpreted as the address of the first element of the array. The bracket notation, [], allows us to get/set elements of the array.

int numbers[] = {1, 2, 3};
numbers;      // address 0x0065fd40
numbers[0];   // 1, the value allocated in memory
// NOTE: numbers ~ &numbers[0]

// since a pointer is a reference to an address we can also do
int* p_numbers = numbers;
*p_numbers;   // 1, the value in memory accessed through pointer dereferencing

Adding one to a pointer variable increases its value by the number of bytes of the type to which it points.

int numbers[] = {1, 2, 3};
int* p_numbers = numbers;
p_numbers;      // points to the first element of the array
p_numbers + 1;  // points to the second element of the array
p_numbers + 2;  // points to the third element of the array

// NOTE:
//  numbers[0] == *(p_numbers)
//  numbers[1] == *(p_numbers + 1)
//  numbers[2] == *(p_numbers + 2)

The value &numbers is the address of a 3-int block of memory, so even though &numbers[0] == numbers == &numbers numerically, the value of &numbers + 1 != numbers + 1 because &numbers + 1 points to the next 3-int block of memory; however, numbers + 1 points to the second element of the initial 3-int block of memory.

  • numbers is type pointer-to-int or int*
  • &numbers is type pointer-to-array-of-3-int or (*int)[3]

The relationship between pointers and arrays also extends to C-style strings. For C++, a quoted string constant, strings in an array, and strings described by pointers are all handled equivalently.

char first[20] = "john";
const char* last = "smith";    // string literals are constant
cout << "I am the agent" << first << " " << last

Given a multidimensional array int a[][2] = { { 1, 2 } }, a is a pointer to the first element, which is a 2-element array (which is a pointer to the first of its elements). Therefore, a pointer to a has the form of a pointer-to-array-of-2-int.

int a[][2] = { { 1, 2 } };
int (*b)[2] = a;
(*b)[0];       // 1

Array of pointers

int a = 1, b = 2;
int* p[2] = {&a, &b};

Since p is a pointer to the first element, which is &a, and &a is another pointer, we can reference p with a pointer to a pointer.

int** q = p;

Runtime allocation: new

Pointers are a sort of alias for accessed memory, which could be accessed by named variables (memory allocated at compile time). However, we can allocate memory at runtime with the new operator. Runtime-allocated memory can be freed with the delete operator.

Advantages of runtime-allocated memory:

  • Memory is allocated only when needed.

Drawbacks of runtime-allocated memory:

  • Memory allocated by new must be freed using the delete operator; otherwise, we have a memory leak, which is memory that is allocated but unused. If it grows too large, it can halt the execution of the program.
  • An attempt to free a block of memory that has been previously freed results in undefined behavior, i.e., don’t use delete twice on the same block of memory in succession.

Additional notes regarding runtime-allocated memory:

  • Ordinary variables have their values stored in a memory region called the stack. Memory allocated with new has its values stored in a memory region called the heap.
// p_int address = 0x0065fd40
int* p_int = new int;
delete p_int;

int oranges = 5;
int* p_oranges = &oranges;
// INVALID since delete works only with memory allocated with new
delete p_oranges;

Dynamic arrays can be created with new typeName[count]. A pointer can be assigned to the location of the first element of the dynamic array.

// dynamic array
int* p_array = new int[10];

// p_array points to the first element of the array
// *p_array is the value of the first element using pointer dereferencing
// p_array[0] is also the value of the first element using array notation

delete [] p_array;

Dynamic structures can be created with new structName. When a pointer points to this block of memory, we can access the properties with the arrow membership operator, ->.

struct person {
  string name;
  int age;
};
person* p_person = new person;
p_person->name = "john smith";
p_person->age = 25;

Functions

Steps to build a function:

  • Provide a function prototype.
  • Provide a function definition.
  • Call the function.
// function prototype
double cube(double x);

int main() {
  // function call
  double q = cube(2.2);
}

// function definition
double cube(double x) {
  return x * x * x;
}

Writing prototypes has the following advantages:

  • The compiler correctly handles the function return value.
  • The compiler checks for the use of the correct number of arguments.
  • The compiler checks for the use of the correct type of arguments (performing conversion to the correct type if possible).

When a function is called with basic types for arguments, the function creates a new variable and initializes it with the same value, i.e., the function works with a copy of the basic types.

int main() {
  double x = 1.3;
  cube(x);
  // ..
}

double cube(double x) {
  // x is passed by value
  // x is private to this function
  return x * x * x;
}

However, we can instead pass the address of the basic type, which means that the function should be rewritten to use pointers.

int main() {
  double x = 1.3;
  cube(&x);
  // ..
}

double cube(double* x) {
  // x is passed by value
  // x is private to this function
  return (*x) * (*x) * (*x);
}

This is useful for complex structures if we want to save time/space by passing a reference to the structure instead of passing the entire structure.

struct person {
  string name;
  int age;
};

int main() {
  person john = { "john doe", 25 };
  greet(&john);
  // ..
}

double cube(person* someone) {
  // someone is private to this function
  // someone is a pointer to the original person
  someone->age;       // 25
}

When a function is called with an array, what’s actually sent is the name of the array, which is the address of the first element (a pointer-to-int, int *). This is different from basic types because the array is not copied; instead, the function works with the original array.

const int k_size = 3;

int main() {
  int a[k_size] = {1, 2, 3}
  sum(a, k_size);         // 6
  cout << *a << endl;     // 1
}

double sum(int* a, int k_size) {
  // a is another pointer to the original array
  // a is private to this function
  int sum = 0;
  for (int i = 0; i < k_size; ++i) {
    sum += *a;
    a++;
  }
  return sum;
}

Inline functions

When a program is executed and a function is about to be invoked, the following steps occur with the program:

  • store the memory address of the next instruction
  • copy function arguments to the stack
  • jump to the memory address the function is located
  • execute the function code
  • jump back to the instruction stored

A small enhancement to speed up the program is to make the function inline. That is, the program replaces the function call with the function code, avoiding the jumps.

When to use it:

  • the function is small and called very often
inline double cube(double x) { return x * x * x; }

Reference variables

A reference variable is a name that acts as an alias for a previously defined variable.

int p;
int& q = p;

In this context, & is not the address operator. Instead, it serves as part of the type identifier. Like int* is a pointer-to-int, int& is a reference-to-int.

  • A reference must be initialized to a defined variable when declared.
  • A reference is like a const pointer, e.g., int& r_n = n; is like int* const r_n = &n;.
int n = 5;
int* p_n = &n;
int& r_n = n;

// the following expressions can be used interchangeably
// - *p_n, r_n, n  to get the value
// - p_n, &r_n, &n to get the address

Example with a function:

int main() {
  int x = 2;
  pow2(x); // 4
  x;       // 2
}

int pow2(int& x) {
  // x is an alias to the x in main
  return x * x;
}

Note that any change to x in pow2() will actually change the original value. To avoid this behavior, use const, e.g., int pow2(const int &x).

Reference arguments should be used to:

  • allow the modification of data inside a function
  • speed the program by passing a reference instead of an entire data object

Classes

class Person {
  // var, functions declared here are private by default
private:
  // private vars and function prototypes
public:
  // public vars and function prototypes
  void sayHi();
};

Class member functions

  • Class member functions can access the private components of the class.
  • To identify which class a function definition belongs to, the :: operator is used.
void Person::sayHi() { /* ... */ }
  • If a class member function won’t modify the created instance, then use the const qualifier for the function.
// function prototype
class Person {
  // ..
  void show() const;
}

// function definition
void Person::show() const { /* ... */ }

All class methods have a this pointer set to the address of the object that invokes the method. Class members can be accessed through pointer dereferencing.

Class constructor/destructor

  • A class has the default constructor by default. It has the form Person() {}.
  • Custom constructors/destructors can be defined as follows:
class Person {
  string name;
  int age;
public:
  // implicit default constructor:
  //    Person() {}
  Person();                         // default constructor
  Person(string &name);             // operator overload
  Person(string &name, int &age);   // operator overload
  ~Person();                        // default destructor
}

// constructor definition
Person::Person() {
  // explicit default constructor
  // NOTE: constructor/destructor returns the class object (no need to add return)
}
Person::Person(string& name) { /* ... */ }
Person::Person(string& name, int& age) { /* ... */ }
Person::~Person() { /* ... */ }

Class objects

int main() {
  Person a;                               // default constructor
  Person b = Person("john", 25);          // with parameters
  Person c("john", 25);                   // alternative syntax
  Person* p_d = new Person("john", 25);   // pointer-to-Person

  b.show();
  p_d->show();
}

Operator overloading

class Time {
public:
  Time operator+(const Time& other) const;
}
// ..
Time Time::operator+(const Time& other) const {
  Tim total;
  // code for `total = other + *this`
  return total;
}
// ..
int main() {
  Time a, b;
  Time c = a + b;
  // translated to a.operator+(b)
}

Misc

Deciphering variable types

http://andybohn.com/deciphering-variable-types/

  1. Find the identifier and start there.
  2. Sweep to the right, translating the symbols you see. You should stop your sweep to the right when you get to the end of the type, or if you see a lone right parenthesis ). Seeing a left parenthesis ( is the start of a function symbol, so continue sweeping right.
  3. Sweep left of the identifier until you run out of symbols, or you hit a left parenthesis (. If you hit the left parenthesis now, you should go back to part 2, sweeping right, but now on the outside of the enclosing ), and continuing onto part 3 on the outside of the enclosing (.

Reading examples

Read a number and the next line as a string:

// input:
//   1234\n
//   a line of text
int year;
string name;
(cin >> year).get();
getline(cin, name);

Read until a char is found (note that cin >> ch omits spaces):

char ch;
cin.get(ch);            // or ch = cin.get();
while (ch != '#') {
  // do something with ch
  cin.get(ch);
}

Read until EOF:

int a, b;
// cin is an istream object that is casted to bool in this case
while (cin >> a >> b) { ... }

string str;
// same as before cin is casted to bool
while (getline(cin, str)) { ... }

char ch;
cin.get(ch);
// same as before cin is casted to bool
while (cin) { cin.get(ch); }

char ch;
while ((ch = cin.get()) != EOF) { ... }

Tokenize:

// example: split the following line by commas
// 1,2,hello
stringstream tokens(line);
string token;
string id, rank, description;

getline(tokens, id, ',')
getline(tokens, rank, ',')
getline(tokens, description)

Read/write files:

#include <fstream>

ifstream inFile;
inFile.open("input");
ofstream outFile;
outFile.open("output");

string line;
int n;

// reading input from file
getline(inFile, line);
inFile >> n;

// writing output to file
outFile << line;
outFile << n;

// close the stream
inFile.close();
outFile.close();

Read from a file, reusing the stdin stream, and write to a file, reusing the stdout stream. See freopen :

#include <cstdio>
freopen("input", "r", stdin);
freopen("output", "w", stdout);
// use cin here
// close the streams
fclose(stdin);
fclose(stdout);

Type casts

(long) value
long(value)
static_cast<long> (value)

// pointer cast
int* p_number = (int*) 0xB8000000;

Conversion between types

C++11

Declarations:

  • auto: automatic type deduction.
  • decltype: creates a variable of the type indicated by an expression.

Range-based for loop:

int numbers[] = {1, 2, 3, 4, 5};
for (int n : numbers) { ... }
for (int n : {1, 2, 3, 4}) { ... }
for (auto n : {1, 2, 3, 4}) { ... }

Multithreading

Threadpool