Study Guide 2023+

cpp

Warning: These notes are partial, ongoing, incomplete, and may contain typos/inaccuracies. (They are kept factually accurate, time permitting.)

They are being united from many disparate notes created in the past and the layout/organization will gradually improve with time!

Please view them on a computer as they are not optimized for mobile (although you can still view them on Mobile along with the Flashcards at your own risk)!

Topics and code examples are lazy-loaded and may require two-clicks from the TOC to correctly calculate the updated x,y coordinates (after rendering). Thanks!

C++: General Concepts

A compiled, Object Oriented, statically typed programming language that predates most others. Some quirks:

  1. C++ programs will execute even if they don't compile!
  2. C++ supports Pointers and Dereferencing.
  3. C++ divides files into Class Definitions (suffixed .h files) and their implementations (suffixed .cpp files).
  4. C++ often requires more explicit use of Garbage Collecting and memory management (especially w.r.t. the Heap).

Runtime Environment

Use gcc through Xcode (if on a Mac) and cmake for compilation and executation of C++ code:

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.16)
Target: x86_64-apple-darwin19.4.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
$ cmake --version
cmake version 3.16.6

CMake suite maintained and supported by Kitware (kitware.com/cmake).

Console Out

cout << “hello world”;

Note that the << operator is the concatenation operator (. or + in other languages) it's outputs data to the console. >> receives data (from say cin).

Code samples:

  1. https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples

C++: Files and OOD

There are no Interfaces or Abstract Classes in C++. However:

  1. filename.h a Class Definition that’s pre-compiled (since it's not likely to change).
  2. filename.cpp the implementation of that Class Definition (with full method definitions/implementations).
  3. :: is the scope resolution operator and defines the implemented methods of the class definition.
  4. : is the subclassing operator (equivalent of the extends keyword in Java).

Constructors

C++ Constructors come in a few flavors:

  1. Default Constructors - C++ supported default constructors that are automatically supplied for each Class. It's convenient to think of these as akin to the @NoArgs, @AllArgs annotations in Spring.
  2. Customized Constructors - developer-supplied and defined constructors.
  3. Copy Constructors - used internally or explicitly invoked to initialize a new Object with the values of another member of the same Class.

Refer to: https://en.cppreference.com/w/cpp/language/default_constructor, https://en.cppreference.com/w/cpp/language/copy_constructor

For example, given ExampleClass.h, ExampleClass.cpp, and main.cpp:

ExampleClass.h:

// header file - think interface/abstract class skeleton - not likely to change and doesn't need to be recompiled
#ifndef Example_Class_H // pragma guard - used at compile time to prevent redundant appending/prepending of compiled code
#define Example_Class_H // if not already defined, add - otherwise it will be ignored

namespace ExampleNamespace {
    class ExampleClass {
        public:
            // even explicitly specifying constructor here defines a custom
            ExampleClass();
            ExampleClass(int _a, int _b);
            int a, b;
            void exampleMethod();
    };
}

#endif

ExampleClass.cpp:

// example_class implementation
#include <iostream> // header in standard library
#include "ExampleClass.h" // header in custom library

using namespace ExampleNamespace; // now you don't have to prepend the namespace when calling methods, etc.
using namespace std;

// implement class method without class syntax here
void ExampleNamespace::ExampleClass::exampleMethod() {
    a = 5;
    b = 100;
    std::cout << "I'm a console out message from ExampleClass exampleMethod()" << endl;
}

// custom default constructor
ExampleNamespace::ExampleClass::ExampleClass() {
    a = 100;
    b = 100;
}

// custom constructor
ExampleNamespace::ExampleClass::ExampleClass(int _a, int _b) {
    a = _a;
    b = _b;
}

main.cpp:

#include <iostream>
#include "ExampleClass.h"

using namespace ExampleNamespace;
using namespace std;

int main() {
    try {
        ExampleClass ec; // custom default constructor
        std::cout << &ec << " with " << ec.a << " " << ec.b << std::endl;

        ExampleClass ecc(1,1); // custom constructor
        std::cout << &ecc << " with " << ecc.a << " " << ecc.b << std::endl;

        ExampleClass * eccc = new ExampleClass; // pointer with custom default constructor
        std::cout << eccc << " with " << (*eccc).a << " " << (*eccc).b << std::endl;
        delete eccc;

        ExampleClass * ecccc = new ExampleClass(111,111); // pointer with custom constructor
        std::cout << ecccc << " with " << (*ecccc).a << " " << (*ecccc).b << std::endl;
        delete ecccc;

        // copies - keeps different addresses
        ExampleClass copyExample;
        copyExample.a = 1000;
        copyExample.b = 1000;

        ExampleClass otherCopyExample;
        otherCopyExample.a = 1111;
        otherCopyExample.b = 1111;

        std::cout << copyExample.a << " " << copyExample.b  << " at: " << &copyExample << " " << otherCopyExample.a << " " << otherCopyExample.b << " at: " << &otherCopyExample << std::endl;
        copyExample = otherCopyExample;
        std::cout << copyExample.a << " " << copyExample.b  << " at: " << &copyExample << " " << otherCopyExample.a << " " << otherCopyExample.b << " at: " << &otherCopyExample << std::endl;

        // memory assignment - note how the memory addresses for the variables remain distinct despite assigning their pointers to each other.
        ExampleClass x;
        x.a = 111;
        x.b = 111;
        ExampleClass * xx = &x;

        ExampleClass y;
        y.a = 1000;
        y.b = 1000;
        ExampleClass * yy = &y;

        std::cout << x.a << " " << x.b  << " at: " << &x << " " << xx << " " << y.a << " " << y.b << " at: " << &y << " " << yy << std::endl;
        xx = yy;
        std::cout << x.a << " " << x.b  << " at: " << &x << " " << xx << " " << y.a << " " << y.b << " at: " << &y << " " << yy << std::endl;
        std::cout << (*xx).a << " " << (*xx).b << " at: " << xx << " " << (*yy).a << " " << (*yy).b << " at: " << yy << std::endl;

        // references
        ExampleClass refCopyExample;
        refCopyExample.a = 1000;
        refCopyExample.b = 1000;

        ExampleClass & otherRefCopyExample = refCopyExample;

        std::cout << refCopyExample.a << " " << refCopyExample.b  << " at: " << &refCopyExample << " " << otherRefCopyExample.a << " " << otherRefCopyExample.b << " at: " << &otherRefCopyExample << std::endl;


    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl;
    }

    return 0;
}

Class Object Assignments

Remember that objects created in the Stack do not automatically persist in the Heap. One illuminating topic is how objects in two distinct functions can be assigned.

ExampleClass methodOne() {  
    ExampleClass ec;
    return ec;  
}

int main() {  
    ExampleClass ecc;
    ecc = methodOne();  
}

Will the above throw an error? No, the following process is performed:

  1. Default Constructor called in both methodOne() (for ec) and main() (for ecc).
  2. Copy Constructor is called when returning methodOne() (this link two discrete events on the Stack).
  3. Assignment Operator is called to copy the "innards" from ec to ecc (values are copied from ec to ecc).

Access modifiers

Can be declared in classes (for encapsulated class fields) using the following:

// header file - think interface/abstract class skeleton - not likely to change and doesn't need to be recompiled
#ifndef Example_Class_One_H // pragma guard - used at compile time to prevent redundant appending/prepending of compiled code
#define Example_Class_One_H // if not already defined, add - otherwise it will be ignored

namespace ExampleNamespace {
    class ExampleClassOne {
        public:
            int num;
            void exampleMethodOne();
    };
}

#endif

Class Methods

Example One

ExampleClassOne.h:

// header file - think interface/abstract class skeleton - not likely to change and doesn't need to be recompiled
#ifndef Example_Class_One_H // pragma guard - used at compile time to prevent redundant appending/prepending of compiled code
#define Example_Class_One_H // if not already defined, add - otherwise it will be ignored

namespace ExampleNamespace {
    class ExampleClassOne {
        public:
            int num;
            void exampleMethodOne();
    };
}

#endif

main.cpp:

// example_class implementation
#include <iostream> // header in standard library
#include "ExampleClassOne.h" // header in custom library

using namespace ExampleNamespace; // now you don't have to prepend the namespace when calling methods, etc.
using namespace std;

// implement class method without class syntax here
void ExampleClassOne::exampleMethodOne() {
    cout << "I'm a console out message from ExampleClassOne exampleMethodOne()" << endl;
}

// executable code (must be wrapped in main method)
int main() {
    try {
        ExampleClassOne exampleOne;
        cout << "Review the random number assigned here: " << exampleOne.num << endl;
        exampleOne.num = 2;
        exampleOne.exampleMethodOne();
        cout << exampleOne.num << endl;

    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl;
    }
    
    return 0;
}

Refer to: https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/6%20-%20class

Example Two

add.h:

#ifndef Example_Class_H
#define Example_Class_H

int add(int x, int y)
{
    return x + y;
}

#endif

main.cpp:

#include <iostream>
#include "add.h"

int main() {
    try {
        std::cout << "The sum of 3 and 4 is " << add(3, 4) << '\n';
    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl;
    }
    return 0;
}

Refer to: https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/3%20-%20dependency

Inheritance

Given a pair of Class Definitions and their implementations: BaseClass and SuperClass.

SuperClass.h:

#include <iostream>
#include "SuperClass.h"

using namespace ExampleNamespace;
using namespace std;

void SuperClass::superClassMethod() {
    num = 500;
    std::cout << "superClassMethod() called in SuperClass " << num << std::endl;
}

SuperClass.cpp:

#ifndef SUPER_CLASS_H
#define SUPER_CLASS_H

namespace ExampleNamespace {
    class SuperClass {
    public:
        int num;
        virtual void superClassMethod();
    };
}

#endif

BaseClass.h:

#ifndef BASE_CLASS_H
#define BASE_CLASS_H

#include "SuperClass.h"

// Specify an associated Namespace - this is akin to a package in Java
namespace ExampleNamespace {
    class BaseClass: virtual public SuperClass {
        public:
            int num;
            void baseClassMethod();
            // Note that this may throw a warning - it can be ignored
            // warning: 'override' keyword is a C++11 extension [-Wc++11-extensions]
            void superClassMethod() override;
            void superEquivalentMethod();
    };
}

#endif

BaseClass.cpp:

#include <iostream>
#include "BaseClass.h"

using namespace ExampleNamespace;
using namespace std;

void BaseClass::baseClassMethod() {
    num = 3;
    std::cout << "baseClassMethod() " << num << std::endl;
}

void BaseClass::superClassMethod() {
    num = 7;
    std::cout << "superClassMethod() override " << num << std::endl;
}

void BaseClass::superEquivalentMethod() {
    SuperClass::superClassMethod();
    std::cout << "superEquivalentMethod() called in BaseClass " << num << std::endl;
}

main.cpp:

// simple classless executable with function.

#include <iostream>
#include "BaseClass.h"

using namespace ExampleNamespace;

int main() {
    try {
        ExampleNamespace::BaseClass bc;
        bc.baseClassMethod();
        bc.superClassMethod();
        bc.superEquivalentMethod();
    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl;
    }
    return 0;
}

Refer to: https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/8%20-%20inheritance

  1. https://en.cppreference.com/w/cpp/language/default_constructor
  2. https://en.cppreference.com/w/cpp/language/copy_constructor

Code samples:

  1. https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/7%20-%20constructors
  2. https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/6%20-%20class
  3. https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/3%20-%20dependency
  4. https://github.com/Thoughtscript/cplusplus-coursera/tree/master/examples/8%20-%20inheritance

C++: Memory

Generally, C++ requires and offers more precise control over memory use in terms of Garbage Collection, variable declaration, and memory addressing.

Garbage Collection

Pointers and References

Consider:

int num = 0;
&num //address of value of num
int* pointer_num = &num; //address of value of num
*pointer_num; //0 

Tip: think of ** as being the same as no * (the two cancel out) - akin to double-negation elimination.

Example

#include <iostream>

int main()
{
    try {
        // ------------------- variable with value -------------------
        int num = 100;
        // note using std::cout explicitly here instead of the using keyword at top of file
        std::cout << "num " << num << std::endl;

        // ------------------- pointer variable with a reference to the address of the variable above  -------------------
        int *numAddress = &num;
        std::cout << "numAddress " << numAddress << std::endl;

        // ------------------- dereference the address to get the value back -------------------
        int derefNum = *numAddress;
        std::cout << "derefNum " << derefNum << std::endl;
        *numAddress = 42;
        std::cout << "numAddress " << numAddress << std::endl;
        std::cout << "*numAddress " << *numAddress << std::endl;

        // ------------------- Reference variables -------------------
        int & refVar = derefNum;
        std::cout << "refVar to derefNum " << refVar << std::endl;

        // ------------------- heap example #1 -------------------
        // declare a pointer variable using new keyword - which automatically (always) assigns memory to the heap
        int * exampleA = new int;
        std::cout << "Initialized to last value on heap: " << exampleA << " " << *exampleA << std::endl;
        delete exampleA;

        // -------------------  heap example #2  -------------------
        // declare a pointer variable and allocate a memory address in heap
        int * heapVariable = (int*) malloc(1);
        // assign a value to the pointer variable that doesn't exceed the specified size
        heapVariable[0] = 45;
        std::cout << "Heap assigned value " << heapVariable[0] << std::endl;
        std::cout << "Heap pointer variable / address " << heapVariable << std::endl;
        // return the allocated memory block back to the heap
        free(heapVariable);

        // ------------------- heap example #3 -------------------
        // declare a pointer variable using new keyword - which automatically assigns memory to the heap
        int * newVar = new int;
        std::cout << "Note the value initialized to is " << newVar << " " << *newVar << std::endl;
        *newVar = 1000;
        std::cout << "newVar " << *newVar << " at " << newVar << std::endl;
        // declare a pointer variable assigning NULL
        int * nullVar = NULL;
        // use delete keyword only for variables declared with new keyword or NULL
        delete newVar;
        delete nullVar;

        // ------------------- null pointer versus NULL -------------------
        // NULL is a value that can be assigned to a variable
        // null pointer keyword specifies a null address - technically, 0x0
        int * pointerVar = nullptr;
        // Cannot access nor delete - will throw error or exit code 11 if you attempt either

    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }

    // main() must always return an exit code
    return 0;

}

C++: Template Functions

C++ supports Template Functions which are akin to using Java Generics in Method definitions.

Example

ExampleClass.h:

#include <iostream>

#ifndef EXAMPLE_CLASS_H
#define EXAMPLE_CLASS_H

using namespace std;

namespace ExampleNamespace {

    // only one type need be flexibly declared
    template<typename T> class ExampleClass {
    public:
        int num;
        T flexibleVar;

        // best to define these in the same class with template<typename T> declaration
        void exampleMethodOne() {
            cout << "exampleMethodOne() " << flexibleVar << " " << typeid(flexibleVar).name() << endl;
        }

        T flexibleMethodOne(T a) {
            cout << "flexibleMethodOne() " << a << " " << typeid(a).name() << endl;
            return a;
        }

        T flexibleMethodTwo(T a, T b) {
            T result = a + b;
            cout << "flexibleMethodTwo() " << result << " " << typeid(result).name() << endl;
            return a + b;
        }
    };
}

#endif

main.cpp:

#include <iostream>
#include "ExampleClass.h"

using namespace std;
using namespace ExampleNamespace;

// Within the definition of a Function
template<typename V>
V standaloneExampleMethod(V x) {
    return x;
}

int main() {
    try {

        ExampleClass<string> ec;
        string random = "I am a random string";
        ec.flexibleVar = random;
        ec.num = 0;

        ec.exampleMethodOne();
        ec.flexibleMethodOne("text");
        ec.flexibleMethodTwo("hello","world");
        std::cout << "My values are " << ec.flexibleVar << " " << ec.num  << '\n';

        ExampleClass<int> ecc;
        ecc.flexibleVar = 5;
        ecc.num = 100;
        ecc.flexibleMethodTwo(1,2);
        std::cout << "My values are " << ecc.flexibleVar << " " << ecc.num  << '\n';

        string a = standaloneExampleMethod("hello");
        int b = standaloneExampleMethod(5);
        std::cout << "Flexible template values " << a << " " << b  << '\n';

    } catch (const std::exception &e) {
        std::cout << e.what() << std::endl;
    }
    
    return 0;
}