C++ Handbook Published - 01 Oct 2022

These notes originated from my time at the University where all of the coding courses were in c++. They make a nice refresher and reference guide for getting into c++.

Contents


Fundamentals

  • C++ is a compiled language, unlike Python or Javascript. It needs to be compiled before it can be run.
  • C++ files are split into Header and CPP files. One for declaring, and the other for defining.
  • C++ gives you access to memory management and "low-level" system functions.
    • Great for optimizing programs to conserve memory and CPU cycles!
    • Extra work to manage it all and it opens up more bug opportunities.
    • C++ is free to allow you to program almost anything.

Compilation Process

  1. Compile — Each CPP file is compiled one at a time on their own.
    1. First the Preprocessor goes through the whole file and executes preprocessor commands like #include.
    2. Included files are processed, then copy-pasted into the include section.
    3. Then, the compiler runs through the file and makes a compiled file.
    4. The compiler and preprocessor goes one line at a time, top to bottom. So you have to declare things at the top of the file.
  2. Link — Each compiled file is joined together alongside additional library files.
  3. These linked files are grouped up into an executable.

Define versus Declare?? Why Bother?

Declare
This means you're telling the compiler that this thing exists. A variable, a class name, a function, whatever.
You declare a variable via int x;
Declare a function via int MyFunction();
Define
This tells the compiler to assign a value or it gives the actual code to run the function.
Define a variable via x = 3;
Define a function via int MyFunction() { return 5; };

Declare lets the compiler know that a function or variable exists and it can trust that it will be safe to use it and this is what the function takes for parameters and here's what it returns. We declare functions and classes in the Header file because other CPP files can include this header file and know how to use those classes or functions. C++ compiles files separately, so when it's compiling one file it won't know about any other compiled files. A Header file lets the compiler know about your custom classes. C++ doesn't let you compile a class more than once, so you can't define a class multiple times. Separating out custom classes into their own files makes a project managable to work on. All of these reasons are why C++ uses Header and CPP files and why there's a Declare versus Define separation.

C++ Errors

Because of how C++ has 2 compilation steps (compiler and linker), there are unique errors that can occur in those steps. Errors also pop up in execution, just like any other language.

Compiler Errors
These are usually syntax related, like forgetting a semicolon or a mispelled variable name.
The compiler couldn't finish its job because it couldn't translate a CPP file into a compiled file.
Linker Errors
These errors are hard to fix.
The Linker failed to link all of the files into an executable for some reason. Maybe a missing library? Good luck!
Execution Errors
Your standard logic bugs like assigning a variable when you meant to test it for equality (= versus ==).
The infamous Segmentation Fault. This means your program tried to access a memory location on the computer that it doesn't have access to.

A small aside on how programs access memory. It's up to the Operating System to figure out which segments of memory belong to which program and to force a program to shutdown if it tries to go outside of its assigned memory block. So, how do some programs (that you can find online) end up indeed reading the memory of other programs?? The operating system probably offers a special command to read this memory, but it will demand special clearance from the computer user (adminstrator or super-user rights).

Pointers

C++ has 2 kinds of variables — Normal and Pointer. You declare a normal variable like you would in any other language int x = 3;. You can declare a pointer by using the asterisk keyword *, as in int* y;.

Pointer variables don't accept typical values, they can only be assigned memory addresses. Specifically, the memory address of a normal variable of a matching type. You can get the memory address of a normal variable by using the ampersand keyword &. For example, int* y = &x;. That line of code can be written in English as "I'm creating a variable named y, that's a pointer to an integer, and I'm assigning it the memory address of x (which is a normal integer)."

You can access variables through a pointer by using the aseterisk keyword *. For example, *y = 5;. In C++, this code will first dereference y and then store the value 5 at that memory address.

  • To create a pointer, put the asterisk after the variable type – int* y;.
  • To dereference a pointer, put the asterisk before the variable name – *y = 5;

You can also make pointers of objects, classes, and structs. C++ uses a unique dereference keyword for these ->. For example, MyClass* a = new MyClass(); a->CallMyFunction();. The arrow keyword is used as a shorthand for this, which does the same thing (*a).CallMyFunction();. Dereference a, then read one of its properties

Why use Pointers?

  • When you pass a variable to a function, the entire thing is duplicated and that duplication is handed to the function, which is a big waste of memory for custom classes which can hold dozens of properties and take up a significant amount of memory. It's so much more managable to duplicate a single memory address and pass that to the function.
  • It allows you loop over an array more easily
  • It allows you to create more interesting data structures like linked-lists

Arrays and Lists

  • Create an array — int array[100];
  • C++ arrays can never change size after they've been created.
  • Dynamically sized arrays are called Lists.
  • When you create an array of objects, the constructor is called for every slot in the array!
  • 2D arrays — int aray[10][10];

Arrays are guaranteed to have a contiguous block of memory, so you can create a pointer to the start of an array and increment the pointer to traverse through the whole array! Very old-school. This will actually adjust the memory address of the pointer manually to access other indicies in the array. Of course, there are better ways to do this like with index access — myArray[5] = 3;.

List

#include <list>
using namespace std
list<int> myList = {0, 16, 42,39};
for (int x : myList)
    out << x << '\n';

Vector is similar to an array and a list. It has a contiguous block of memory but it can aso change its size. Just avoid using them. If you need a dynamically sized array, use List. If you need a single sized array, use array. Vectors will do a costly resize operation when they need to be resized that Lists can do much better. And if you're not changing the size of an array, then just use an array.

Passing by Reference or Value

  • By Reference — void myFunction(int &x, MyClass* b);
  • Constant Reference — void myFunction(const int &x, const MyClass* b);
  • Pointer to a const (The value it points to cannot change) const int* ptr;
  • Constant Pointer (pointer can't change memory address) int* const ptr;

C++ passes parameters to functions by copying what the caller is sending. For example,

class MyClass {
    public:
        int x;
        MyClass() {
            x = 0;
        }
        void SetParameter(int a) {
            x = a;
        }
};

void MyFunction(int a, MyClass b) {
    a = 99;
    b.SetParameter(99);
}

int main()
{
    int x = 16;
    MyClass* y = new MyClass();
    MyFunction(x, *y);
    // x is still 16
    // y's X parameter is still 0
    return 0;
}

In this example, X's value of 16 is duplicated into a new variable (a) which is then assigned 99. The object Y is duplcated (a new MyClass object is created, using the Copy Constructor with Y's data) and then some parameter is set to 99. This is called Passing by Value.

Both X and Y remain unchanged, despite MyFunction working correctly. This is normal behavior for passing an integer to a function. You wouldn't expect a function to be able to make changes to stuff outside of that function. But you might expect that Y should change since it's an object we're passing in. This is what most other programming languages do.

This is also why we write *y here. Y is a pointer (any time we use the new keyword, we get a pointer back) but the function is expecting an object, not a pointer.

In order to pass in Y so that it can change, we need to tell C++ to copy just the memory address of the object and not copy the entire object.

void myFunction = (int a, MyClass* b)
{
    a = 99;
    b->setParameter(99);
}

int main() {
    int x = 16;
    MyClass* y = new MyClass();
    myFunction(x, y);
    // x is still 16,
    // y's X parameter is now 99!
}

We added * to myFucntion's parameter list indicate we want a pointer now. Additionally, when we call MyFunction, we no longer dereference our pointer (we removed the *).

Now, Y will have its parameter set to 99! Neat! This is how almost any other object-oriented programming language works, but C++ needs us to be a litle more specific. This is Passing by Reference, since we are passing the reference of the object around instead of the entire object itself.

So what if we also want it to make changes to X? We can do that!

void myFunction(int &a, MyClass* b)
{
    a = 99;
    b->setParameter(99);
}

int main() {
    int x = 16;
    MyClass* y = new MyClass();
    myFunction(x, y);
    // x is now set to 99 too!
}

All we have to do is adjust the function's header to require a reference to an integer. Passing an integer by reference is really weird, though. Most people don't expect their normal variables to get changed while passing them into a function.

Finally, you can say a parameter being passed in won't change by using const in the parameter list: myFunction(const int &a, const MyClass* b). For normal variables, this means you won't assign anything to that parameter. For objects, it means you cannot call any function that doesn't have a const keyword.

Class, Struct, Template, and Virtual

class MyClass : public ExtendedClass {
    private:
        int x;

    public:
        MyClass() {
            x = 0;
        }
        void SetParameter(int a) {
            x = a;
        }

        virtual void overridable() {
            x += 1;
        }

        virtual void pureVirt() = 0;
};

struct {
    int x;
    int y;
    int z;
    string name;
} MyStruct;

template <typename T>
T MyMaxFunction(T x, T y) {
    return (x > y)? x : y;
}

template <class T> class MyStack {
    private:
         T list[100];

    public:
        void push(T const&);
        T pop(T);
};

I'm not gonna explain classes.

Struct is like a class, but it doesn't have functions. It's just a way to group up variables into a single type. I see it used a bit in 3D rendering code.

Template allows you to create variations of a function or class. It would be redundant and a waste of time to create a new Linked List class for integers, for floats, for doubles, for each kind of object/class... Template lets you reuse the Linked List code, but for any variable type, even custom classes you create.

Virtual

Functions inside of a class can be declared as virtual. A virtual function can be overriden by a subclass, causing the subclass function to run instead of the parent function. A virtual function does not need to be overwritten, and if it's not present then the parent function will run instead.

class MyClass {
    public:
        virtual int myFunction() {
            return 1;
        }

        int otherFunction() {
            return 1;
        }
};

class SubClass : public MyClass {
    public:
        int myFunction() {
            return 99;
        }

        int otherFunction() {
            return 99;
        }
};

int main()
{
        MyClass* myObject;
        SubClass sub;
        myObject = &sub;

        // Virtual function, binded at runtime
        myObject->myFunction(); // 99

        // Non-virtual function, binded at compile time
        myObject->otherFunction(); // 1
        return 0;
}

Abstract Class

Pure Virtual functions are declared in the base class but never defined in the base class. As a result, the base class cannot be instantiated or created (only pointers of that type can ever really be used). This is also known as an abstract class. A pure virtual function has this odd signature: virtual <returnType> functionName() = 0;

Arguments
int main(int argc, char* argv[])
An integer and a pointer to an array of characters.
argc is the number of arguments (including the name of the program). This tells you the size of argv
argv then has "Program name", "arg1", "arg2", ...
for running the code: myProgram a b c you will get in argv: "myProgram", "a", "b", "c" and an argc of 4.
The type is actually an array of character pointers, not a pointer to an array of characters.
This means, each entry in the array acts like a string and can hold more than 1 character.
Copy Constructor
ClassName(const ClassName &other);
Invoked when passing by value, returning by value, or ClassName c1 = c2;
Overload this one when you need to overload the assignment operator.
Destructor
~ClassName();
Fill this in when you have dynamic memory to clean that all up.
Friends
Allows one class to state another class as a friend.
This second class may now access the private methods and properties of the first class.
Kind of like protected fields, but the original class needs to declare who is a friend. A whitelist.
Be cautious. Friends means classes are tightly coupled, which is usually a bad design.
Overloading Operators
You can have your classes overload operators (like plus, minus, multiplication, etc.) just like how you can overload parent functions in an inheritance situation.
ClassName operator+(const ClassName &other) const;
Parameters are always constant references.
Only overload operators on classes that make sense (i.e. mathematical classes).
Overload the assignment operator first, then define the non-assignment version to call the assignment operator with the copied variable.
MyClass& MyClass::operator+=(const MyClass &other)
{
  // code to add whatever value from Other into this object.
  return *this;
}

MyClass MyClass::operator+(const MyClass &other)
{
  MyClass result = *this; // This is a copy constructor! Neat!
  result += other; // calls the above assignment operator
  return result;
}
Overloading Output Operator <<
class ClassName
{
  friend ostream& operator<<(ostream &outStream, const ClassName &other);
  ...
}
//.cpp
ostream& operator<<(ostream &outStream, const ClassName &other);
// do NOT use the ClassName namespace thing: ClassName::operator<<
This is because the other operators are also 2-variable versions, but the "left" version is implicit when it's a part of the class. This creates a new global operator that can take an ostream to the left of << and a Rational to the right.
Overloading Input Operator >>
//.h
friend istream& operator>>(istream &inStream, ClassName &other);
//.cpp
istream& operator>>(istream &inStream, ClassName &other);
// Again, no ClassName namespace
Overloading Operator: Assignments
//.h
ClassNam& operator+=(const ClassName &other); // notice no const func
This is because the first operand (the class itself) will change. It is being assigned a new value.
Overloading Operator: Assignment
ClassName& operator=(const ClassName &other);
  1. Clean up all dynamic memory on the left-hand side.
  2. Replicate all the dynamic memory from the source into the destination.
  3. Assignming to myself? Just return myself. this == &other; return *this;
Struct
Templates
template<class NameForTemplate>;Put this at the top in your header file before the class keyword and then just use "NameForTemplate " wherever you'd use an identifier.
Also place that code before every method in your implementation file.
You must then #include "cppFileName" at the end of the header file
The cpp compiler still needs to create and compile actual functions for each time that generic template is used. So if a function with a template parameter gets called with an int, with a double, and with a String, the compiler will automatically create functions with the same name, but the appropriate variable type filled in for the function header.
This is why the cpp file neds to be included at the end in the header. The compiler needs to know how to define these functions for any kind of type. And since the include goes in before the #ENDIF, it's defacto covered by the header protections.
Virtual
virtual is used before declaring a method and claims that this method should always use the base class' version, even if an object of this type was initially declared, but a base type was then stored in that variable.
The problem occurs when you have two classes: A and subA (which is a child of A). You can create a variable of type A, and then assign that variable to the type SubA. When you call a function, even if it's overriden in SubA, it'll run the A version. By declaring the function in class A as virtual, it forces the function to run the SubA version.
A pure virtual function (where you "assign the function to the value 0") is just like a Java interface. There is no base implementation.