C++

Basics

  • Declarations: data types, function signatures, classes
    • Lets compiler ensures types match
    • Typically kept in header (.h) files
  • Definitions: variable initialization, function implementation
    • note that variable initialization is read right to left, eg int * & p says p is a reference to a pointer to an int
    • the first const in a definition/declaration can be on either side of the variable:
      • int const * : pointer to a const int
      • const int * : pointer to a const int
      • int const * const * : pointer to a const pointer to a const int
    • so essentially reading backwards works up until the last element, where the const could be on either (otherwise it is always to the right)
  • Scopes: symbol only visible within its scope
    • whole program (global scope)
    • namespace (namespace scope)
    • members of class (class scope)
    • function (function scope)
    • block (block scope)
  • Namespaces
    • “using” keyword used to add entire namespace to current scope
    • can introduce elements selectively using ::, the scope operator. For example, x::y says to look in scope of x for y
    • note that namespaces aren’t the actual source for a method, the are just a means for keeping access to external methods organized. For example, while we must use “std::cout” to reference the “cout” method, that alone is not enough. We must also include it’s actual definition using #include <iostream>
    • including the header file Foo.h in a program gives the program access to its function declarations, how parts interact, their size in memory, etc. but it does not give access to actual implementations of these functions yet. The pre-compiler simply checks to ensure the invocation of a Foo object plays nicely in the Main.cpp file. At runtime, the compiler actually compiles Foo.cpp separately, along with Main.cpp, and realizes that there are Foo objects in Main that don’t have functionality. The namespace/scoping operator allows us to scope into the namespace within which Foo is defined, and see how that particular instance is actually implemented so it can be executed within Main.
    #include <iostream>
    using std::cout;
    cout << "hello, world!" << std::endl;
// include a file, typically a header file
// compiler essentially copy-pastes file contents here

// this form of include is generally used to include local project headers
#include "header.h"

// this form looks first in a default "includes" path for libraries
#include <iostream>

// using directive tells compiler to include code with separate namespaces
// use entire namespace
using namespace std;

// can also access specific functions
using std::cout;

// main function returns 0 by convention for success
// argc: number of parameters
// argv: array of pointers to C-style char strings
int main(int argc, char * argv[]) {
    for (int i = 0; i < argc; ++i) {
        // << is operator for inserting into stream
        cout << argv[i] << endl;
    }
    return 0;
}

Data Types

  • List of types
    • Native types
      • int, long, short, char (signed, integer arithmetic)
      • float, double (floating point arithmetic)
      • bool
    • User Defined Types
      • enumerations (unscoped and scoped)
      • functions and operators
      • structs and classes
  • C++ string class
    • C style strings are arrays of chars
    • need to include the <string> header file and use string namespace from std
    • overloads operations for use on strings
    #include <string>
    using std::string;
    
    // C-style string, w and h are pointers to location of "w" and "h" in memory
    char * w = "world";
    char * h = "hello";
    
    // C++ style string, sw and sh of class string, easier to use
    string sw = "world";
    string sh = "hello":
    
    cout << (h < w) << endl; // returns 0
    cout << (sw < sh) << endl; // returns 1
    
    h += w; // illegal, cant add memory locations
    sh += sw;
  • Pointers
    • raw memory address of object or variable
    • the pointer type constrains what variable type it can point to
    • unary star * used to declare a pointer; used to dereference a variable when used in a definition
    • Pointer pointer arithmetic: only subtraction is allowed, tells difference between two memory locations
    • Pointer arithmetic: can add int value to pointer to shift that pointer forward in memory
      • for arrays, adding one to pointer shifts pointer reference to next array element
      • adding one to pointer shifts pointer’s reference in memory by the size of its type
    // only points to int
    int * p;
    
    // p now points to i, & used to get address
    p = &i
    
    // arrow -> used for member access via pointer
    C c;
    C * cp = &c
    cp->add(3)
    
    // shift p forward by n of size int (say n*(4 bytes)) in memory
    *(p+n); // same as p[n]
  • References
    • “alias” for an object or variable
    • reference type constrains what variable type it can point to
    • unary ampersand & used to declare a reference; used to get the address of a variable when used in a pointer definition
    • pointer but with a nicer interface; it’s a direct way to interact with the object and the compiler hides the indirection
    // reference refers to int i
    int & r = i
    
    // dot used for member access via reference
    C c;
    C & cr = c;
    cr.add(3)
  • Arrays
    • hold contiguous sequence of memory locations
    • can refer to locations using either index or pointer notation
    // declaring an array with length 3
    int arr[3];
    
    // 2d 5x3 array declaration
    int arr[5][3]
    
    // declaration with array literal (w or w/o size)
    int arr[] = {0, 1, 2};
    int arr[3] = {0, 1, 2};
    
    // get reference location of arr's first element
    int * p = &arr[0];
    
    // arrays are just references to their first element
    int * q = arr;
    
    // p and q point to the same place
    
    // get value at arr[1], compiler knows to increment memory location by 
    // size of int (q's type) in memory when adding 1
    *(q+1);
  • Vectors
    • nicer to work with than arrays, ie dynamic size and resizing
    • can push element to the back of vector, can’t index particular location without
    • like Python list, Java ArrayList
    #include <vector>
    
    // initialize vector w no particular size
    vector<int> v;
    
    // cant set v[0]=1, v[1]=2, etc
    // must add/append elements to back of vector
    
    v.push_back(1);
    v.push_back(2);
    v.push_back(4);
    
    // print vector's elements
    for (size_t s = 0; s < v.size(); ++s) {
        cout << "v[" << s << "] is " << v[s} << endl; 
    }
  • Streams
    • Streams are an abstraction representing a “device” where input and output operations are performed. This can be the keyboard, a file, a string; anything that can be represented as a source or destination of characters of indefinite length. Elements from the stream are “pulled off” the stream over time; the stream is responsible for making data available over time. This is often useful due to the simple fact that virtually every process involving the transfer of data from one place to another occurs sequentially: when writing data to disk each byte must be processed and written individually, sending data over a network requires sending bytes one by one, etc. The basic IO library allows for interactions with certain types of streams:
      • <iostream>: used to communicate via standard input and output (ie cin, cout) through the keyboard
      • <fstream>: is file stream; used to read and write characters to files using streams
      • <sstream>: manipulates strings as if they were streams
  • Other Declaration Keywords
    • typedef: introduces a “type alias”
    • auto: asks compiler to take variable’s type from expression used initialize it
    • decltype: infer variable’s type without initializing it
    // sets both type and value
    auto y = m*x+b;
    
    // set only the type, not value
    decltype(m*x+b) y; 

Functions

Program Call Stack

  • stack frame is pushed to the call stack when a function is called
  • a stack frame contains variables local to the function, parameters passed to the function, a previous frame pointer, and a return address
  • allows for recursion by nature

Overloading

  • can overload a function by specifying different arguments to accept under same function name

Parameters

  • passed by value: makes copy of passed variable, the called function cannot actually change that variable
  • passed by reference: an reference/alias of a variable is passed, can modify the value in this variable

Linker

When a .cpp source file is compiled, it is ready to run except in one aspect: if it uses functions from other source files, then it cannot execute those functions because it does not have their definitions. In order to call the external function in the first place, the source file must include a header declaring the external function. When compilation occurs, the empty function calls made from the source file are filled by the linker, who links the reference to the function (through the header) to the actual function’s definition.

For example, say we have class.h and class.cpp files that declare and define some useful class. Now we have a main.cpp source file in which we’d like to use the functionality defined in class.cpp. So we #include "class.h" in both our class.cpp and main.cpp files so that each file has a reference to the declaration of methods laid out in class.cpp. When the compiler is run, it compiles both class.cpp and main.cpp into “object code”. main.cpp does not immediately have access the function definitions available in class.cpp. The linker notices these unresolved references to methods in class.cpp, and because main.cpp included the header file containing declarations to the functions it wanted to use: 1) the function declarations in class.h are matched with their corresponding definitions in class.cpp, and 2) the linker “links” these definitions to the unresolved references in main.cpp. So all in all, as functions that include headers are compiled, the linker is on the lookout for function definitions to the declarations found in a particular header file. When it finds those definitions available in some source file that has included it, it creates a connection for that function’s definition so that all other source files that have included the header now have access to the actual function behavior.

Expressions

  • lvalue gives a location; left hand side of an assignment, has a location in the program
  • rvalue gives a value; temporary values computed
  • = operator means initialization when variable is first declared, otherwise it is assignment

Statements

  • basic units of execution, ending with ;
  • statements have scopes in which rvalues or temporary values are destroyed once the statement finishes executing

Exceptions

  • can catch, throw, refine, rethrow, use, or inspect exceptions
  • standard errors can be included with #include <stdexcept> and accessed under std namespace
  • order matters; place more specific catch blocks before more general ones
try {
    throw 2;
} catch(int &i) {
    cout << "Caught value " << i;
} catch (...) {
    // ... is default catch
    cout << "Another expression caught.""
}

Classes

  • Used to package up related data and behavior (known as encapsulation)
  • Classes have members private by default
  • Classes are used when the behavior is more important and want to make use of encapsulation and polymorphism
class Date {
  public:    // member functions, visible outside the class
    Date (); // default constructor
    Date (const Date &); // copy constructor
    Date (int d, int m, int y); // another constructor
    virtual ~Date (); // (virtual) destructor

    Date & operator= (const Date &); // assignment operator
    int d () const; int m () const; int y () const; // accessors
    void d (int); void m (int); void y (int); // mutators
    string yyyymmdd () const; // generate a formatted string
  private: // member variables, visible only within functions above
    int d_;
    int m_;
    int y_;
};

// initializer lists
// default constructor
Date::Date () 
 : d_(0), m_(0), y_(0)
{}

// copy constructor
Date::Date (const Date &d) 
 : d_(d.d_), m_(d.m_), y_(d.y_) 
{}

// another constructor
Date::Date (int d, int m, int y)
 : d_(d), m_(m), y_(y) 
{}
  • Compiler automatically defines default constructor, copy constructor, destructor, and assignment operator if not explicitly specified
  • Initialization lists: a variable like d_ is initialized with the value passed to the constructor (or in this case 0) by d_(0)
  • Abstract class: a class with at least one pure virtual function, i.e. must be overridden by implementing child classes
  • C++ interface: just an abstract class with all methods pure virtual

Structs

  • Like classes, used to package up related data and behavior
  • Structs have members public by default
  • Structs are used when it’s more about the data, typically add constructors and define operators so the struct can be used elsewhere

Lifecycle

  • Precompiler runs first, handles #includes
  • Compiler runs next, turns source files (text) into object file
  • Linker takes object files and resolves function calls, putting them into a single file. Helps associate correct function with associated function call

Memory

4 major segments

  • Global: variables outside stack, heap; fixed use
  • Code: the compiled program; read-only
  • Heap: dynamically allocated variables; dynamic use
  • Stack: parameters, automatics and temporary variables; dynamic use

New and Delete

  • New: operator new is called, which allocates raw untyped memory from heap; appropriate constructors are called to construct the objects; pointer to newly allocated and constructed object is returned as expression’s value
  • Delete: pointer to memory to be de-allocated is passed; destructor is run at that position

Smart Pointers

Inheritance Initialization and Destruction

  • initialization: base class constructor is called first (recursively if needed), then member constructors called, the constructor body is run. When a derived class is initialized, it is an object of the same type as its parent. So the base class constructor is called (if there are more up the chain, this is done recursively), its methods and variables are in essence merged into the scope of the derived, as it inherits all of this functionality. The remainder of the derived class’s constructor executes after this point.
  • destruction: happens in reverse, destructor body is called, then member destructors, then base destructor (recursively if needed)

OOP

  • Virtual functions
  • Principles
    • Inheritance
    • Encapsulation
    • Abstraction
    • Polymorphism