Home

C++

My personal notes on C++.

To Do

main

The main function is the entry point of a program.

Data Types

The smallest memory unit is 1 byte(8 bits).

Int based data types

Name Bytes Notes
bool 1 Can use true or false keywords
char 1 Uses ''s
wchar_t 2 or 4 Larger char encoding standards like unicode.
short 2 Same as short int
int 2 or 4 Bytes depends on the platform
long long 8 Same as long long int

Float based data types

Name Bytes
float 4
double 8
long double 12

Data type notes

Truthy and Falsy

Multiple variables on one line

Variable names/Identifiers

Assigning values

Int vs Float Division

Dividing by zero

Int vs Float Modulus

post-fix vs pre-fix increment

Const

Auto

auto tells the compiler to determine the variable’s type by the initial value given.

Headers

The purpose of header files is to allow you to separate the declaration(.h) and the definitions(.cpp). The declarations(.h) can be shared across multiple files. This has a couple of benefits:

// .h
#pragma once

class Class {
	private:
		int _x = 0;
	public:
		void print();
};
// .cpp
#include "Class.h"
#include <iostream>

Class::print() {
	std::cout << _x << "\n";
}

Namespaces

Namespaces define a region/scope used to prevent naming conflicts

namespace namespaceName {
	int x;
}

namespaceName::x = 10;

Character escapes

Character Description
\'  
\"  
\\ backslash
\a audible bell
\b backspace
\f form feed - new page
\n new line
\r carriage return
\t horizontal tab
\v vertical tab
\o## octal output
\x## hexadecimal output
\u#### prints unicode. Only works on g++
\Nname unicode character from name
\0 Null

Pointers and references

Code Description
int* ptr; Creates a pointer pointing to an int. Enough memory for an address.
*ptr Dereferences the ptr
int& x = y; x is a reference to y. x and y always have the same value.
&ptr Gets address of ptr.
ptr Gets the value in ptr. The address it points to.
ptr-> Is the same as (*ptr).
const int* xPtr = &x; Pointer to a const int. Can’t change the dereference.
int* const xPtr = &x; Pointer to an int, but the pointer cannot change.

Function pointers

void (*func)(int, int);

You can also use #include <functional> and function<void(int, int)> func;

Arrays

Arrays in C++ store a sequences of variables in contiguous memory. The array is a pointer to the first element of the array. Accessing an element with arr[index] is equivalent to dereferencing the pointer at the memory location *(arrPtr + index).

Two dimensional arrays

Since arrays are stored in continuous memory, a two dimensional array(or more dimensions) can also be represented with a one dimensional array.

Each of these array may contain the same memory, but their data types are different.

Order of operations

Operations with the same precedence are executed according to their associativity, left to right or right to left.

Precedence Operation Associativity
1 :: - scope Left to right
2 x++, x– Left to right
  type() - functional case  
  func(), arr[]  
  ., ->  
3 ++x, –x Right to left
  +x, -x, !, ~  
  (type) - C-style case  
  *ptr, &var, sizeof  
  new, new[], delete, delete[]  
4 ., -> Left to right
5 x*y, x/y, x%y Left to right
6 x+y, x-y Left to right
7 «, » Left to right
8 <, <=, >, >= Left to right
9 ==, != Left to right
10 & Left to right
11 ^ Left to right
12 | Left to right
13 && Left to right
14 || Left to right
15 x?y:z, throw, = Right to left
  +=, -=, *=, /=, %=, «=, »=  
  &=, ^=, |=  
16 , Left to right

Loops and Conditions

Switch

switch (x) {
	case 0:
		break;
	case 1:
	case 2:
		// 1 or 2
		break;
	default:
		break;
}

Do While loops

do {
} while (true);

Single line conditions

if (true) cout << "true" << endl;

if (true)
	cout << "true" << endl;
else
	cout << "false" << endl;

for (int i = 0; i < 10; i++)
	cout << i << endl;

while (true)
  cout << "infinity ";

Range based for loop

std::vector<int> arr = {1, 2, 3, 4};

for (int num : arr) {
}
// This is the same as
for (std::vector<int>::iterator it = arr.begin(); it != arr.end(); it++) {
	int num = *it;
}

Functions

Term Example
prototype void func(int);
header void func(int arg)
implementation { /* body */ }

Types of functions

   
constexpr void func(); A function can be evaluated at compile time if possible.
inline void func(); Compiler searches and replaces. Speed up performance, but uses more memory.

Pass by value or reference

Following these rules makes it clear that an object is being modified by a function because you have to specify the address when passing it as an argument.

Optional arguments

int test(int a, int b = 4, int c = 10){}

test(1, 2, 3);
test(1, 2);
test(1);

Function overloading

Lambda functions

Lambda functions allow you to easily pass small functions into another function. They take the form of [capture list](arguments){function body}. You can also specify a return type with []() -> type {}

Global and static variables

Exception/Error handling

void func1() {
	throw "String error";
}

void func2() {
	func1();
}

void func3() {
	try {
		func2();
	} catch (const char* error) {
		// handle error
	}
}

Templates

Templates are used to allow the same piece of code to use different data types.

template <typename T>
void print(T value) {
	std::cout << value << "\n";
}

print(5) // Defined implicitly based upon the type of the argument
print<char>('a') // Defined explicitly
template<typename T>
void Class<T>::method() {}

new and delete

Heap memory is used when teh size is only known at run time.

int* ptr = new int;
delete ptr;
// With arrays
int* arr = new int[10];
delete[] arr;

Enums

Enums define a variable’s allowed values by restricting it to a predefined set of named constants, improving code clarity and reducing invalid inputs.

enum Shape {CIRCLE, SQUARE, TRIANGLE};
enum class Color {RED, GREEN, BLUE};

Shape s = CIRCLE;
Color c = Color::RED;

Structs

The only difference between structs and classes is that by default structs’ members are public. However, the convention is to only use Structs to group variables together.

struct Point {
	int x;
	int y = 10; // Default value
};

Point pt = {10, 20};
	// Or
Point pt;
pt.x = 10;
pt.y = 20;
	// Or
Point pt = {.x=10, .y=20};

Classes

Classes are used to combine data and functions that handel that data.

// Declarations
	// These are usually in .h files
class Point {
	private:
		double _x = 0, _y = 0;
	public:
		double x() const {return _x;}
		double y() const {return _y;}
		double distance(const Point& pt) const;
};
// Definitions
	// These are usually in .cpp files
double Point::distance(const Point& pt) const {
	double a = _x - pt.x();
	double b = _y - pt.y();
	return std::sqrt(a * a + b * b);
}

Database classes

A common pattern is to have one class represent what you want to store and another class hold the database(like vector or linked list) of the units.

class Points {
	private:
		std::vector<Point*> _points;
	public:
		~Points();
		Point*& at(int index) const; // Used to get and update
		void remove(int index);
		void add(int index, Point* pt);
};

Constructors

Constructors are used to initialize the member variables of an object. Constructors have the same name as the class itself, but do not have a return type.

Point::Point(int x, int y) {
	_x = x;
	_y = y;
}

If member variables have default values, they are initialized with those defaults before the constructor runs. The constructor then overrides them, which is often unnecessary. Additionally, if the default value involves dynamic memory allocation, it can lead to a memory leak. Using an initializer list allows the constructor to directly initialize the values, bypassing the default initialization and avoiding potential inefficiencies or leaks.

Point::Point(int x, int y) : _x(x), _y(y) {}
// Formatting if there are a lot of member variables
Point::Point(int x, int y)
	: _x(x),
	  _y(y)
{}

The default constructor is automatically generated by the compiler if no other constructors are defined. It allows you to create an object without providing any arguments, such as Point pt;

The most vexing parse

There are only a few ways to create object.

// Uses teh default constructor
Point pt;
Point pt = Point();
Point pt{};
// The most vexing parse
Point pt();
	// This defines a function called pt which returns a Point. This doesn't create an object.

Using heap memory in classes

When using heap memory(new) in a class, it is recommended to follow the “Rule of Three”:

  1. Destructor - The destructor deletes any heap memory that was allocated by the object.
Class::~Class() {
	// Deletes all the heap memory in the class
	for (int* element : _arr) {
		delete element;
	}
}
  1. Overloading the assignment operator If an object has member variables that are pointers, assigning one object to another copies the pointers themselves, not the underlying data they point to. If those pointers refer to heap-allocated memory, this can lead to a memory leak. Overloading the assignment operator allows you to handle this situation by:
Class& Class::operator=(const Class& rhs) {
	if (this == &rhs) return *this; // Avoid self assignment
	// Assign any non pointer member variables
	_var = rhs.var();
	// Delete your old heap
	delete _heap;
	// Create new heap
	_heap = new int;
	// Assign value from rhs to new heap
	*_heap = rhs.value();
	return *this;
}
  1. Copy constructor The copy constructor is similar to overloading the assignment operator, but there is no need to check for self-assignment or delete any old heap memory, as the object is being created for the first time.
Class::Class(const Class& rhs) {
	// Assign any non pointer member variables
	_var = rhs.var();
	// Create new heap
	_heap = new int;
	// Assign value from rhs to new heap
	*_heap = rhs.value();
}

Operator overloading

Left hand side(lhs) means that it can be assigned to, like a variable. The right hand side(rhs) means that i cannot be assigned to, like a literal.

// This is what most operator overloading looks like
Point Point::operator+(const Point& rhs) const {
	Point result;
	result.x() = _x + rhs.x();
	result.y() = _y + rhs.y();
	return result;
}

Comparison operator overloading

These compare the left and right hand side and return a bool.

bool Point::operator==(const Point& rhs) const {
	return _x == rhs.x() && _y == rhs.y();
}

bool Point::operator<(const Point& rhs) const {
    if (_x == rhs.x()) {
        return _y < rhs.y();
    }
    return _x < rhs.x();
}

You can define all the other comparisons from just these two(== and <).

Inheritance

Inheritance allows one class to acquire the members of another class. This decreases the amount of code needed because different classes can share the same code.

class Shape {
	protected:
		Point _center = Point(0, 0);
		double _length = 1;
	public:
		Point& center() const {return _center;}
};

class Circle : public Shape {
	public:
		double diameter() const {return _length * 2;}
};

Virtual and Override

Runtime polymorphism allows an object of a child class to be treated as an object of its parent class.

class Shape {
	public:
		virtual void draw() const { std::cout << "Shape\n"; }
		void draw2() const { std::cout << "Shape\n"; }
};

class Circle : public Shape {
	public:
		void draw() const override { std::cout << "Circle\n"; }
		void draw2() const { std::cout << "Circle\n"; }
};

class Rectangle : public Shape {
	public:
		void draw() const override { std::cout << "Rectangle\n"; }
		void draw2() const { std::cout << "Rectangle\n"; }
};

int main() {
	Shape shape;
	Circle circle;
	Rectangle rectangle;

	std::vector<Shape*> shapes;
	shapes.push_back(&shape);
	shapes.push_back(&circle);
	shapes.push_back(&rectangle);

	for (Shape* shape : shapes) {
		shape->draw();
	}
	/* virtual
	Shape
	Circle
	Rectangle
	*/

	for (Shape* shape : shapes) {
		shape->draw2();
	}
	/* non-virtual
	Shape
	Shape
	Shape
	*/
}
Abstract classes

An abstract class is a class that contains at least one pure virtual method, which must be overridden by any concrete child class.

Composition vs Inheritance

Composition is used when you have a “has a” relationship between classes. Inheritance is used when you have a “is a” relationship.