C++ Programming
Ⅰ C++ Fundamentals
1 C & C++ Introduction
Properties:
C/C++ is a compiled language.
C/C++ compilers map C/C++ programs into architecture-specific machine code (string of 0s and 1s).
Unlike Java, which converts to architecture-independent bytecode (run by JVM => Java Virtual Machine).
Unlike Python, which directly interprets the code.
Main difference is when your program is mapped to low-level machine instructions, CPU will directly interprets and runs.
Compilation Advantages:
Excellent run-time performance:
Generally much faster than Python or Java for comparable code because it optimizes for the given architecture.
Fair compilation time:
Enhancements in compilation procedure (Makefiles) allow us to recompile only the modified files.
Compilation Disadvantages:
Compiled files, including the executable, are arcitecture-specific (CPU type and OS).
Executable must be rebuilt on each new system.
i.e. "porting your code" to a new architecture.
Instead of "Edit -> Run [repeat]" cycle, "Edit -> Compile -> Run [repeat]" iteration cycle can be slow.
2 Types and Structs
2.1 Primitive Types
Fundamental Types | Example | Memory |
---|---|---|
int | int val = 5; | 32 bits (usually) |
char | char ch = 'F'; | 8 bits (usually) |
float | float decimalVal1 = 5.0; | 32 bits (usually) |
double | double decimalVal2 = 5.0; | 64 bits (usually) |
bool | bool bVal = true; | 1 bit |
std::string | std::string str = "Haven"; | Depends on architecture |
2.2 Structs
Definition:
Struct: A struct is a group of named variables, each with their own type, that allows programmers to bundle different types together!
Example:
3 Initialization & References
3.1 Initialization
There are three types of initialization:
Direct Initialization:
int numOne = 12.0; // numOne = 12, doesn't type check with direct initializationUniform Initialization (C++ 11):
int numTwo {12.0}; // narrowing conversion of '1.2e+1' from 'double' to 'int' // type checks with uniform initializationStructured binding (C++ 17): Can access multiple values returned by a function.
#include <iostream> #include <tuple> #include <string> std::tuple<std::string, std::string, std::string> getclassInfo() { std::string className = "CS106L"; std::string buildingName = "Turing Auditorium"; std::string language = "C++"; return {className, buildingName, language}; } int main() { auto [className, buildingName, language] = getclassInfo(); std::cout << "Come to " << buildingName << " and join us for " << className << " to learn " << language << "!" << std::endl; // Output: Come to Turing Auditorium and join us for CS106L to learn C++! return 0; }
Advantages for Uniform Initialization:
It's safe! It doesn't allow for narrowing conversions — which can lead to unexpected behaviour (or critical system failures)
It's ubiquitous! it works for all types like vectors, maps, and custom classes, among other things!
3.2 References
Example:
A classic reference-copy bug:
4 Streams
4.1 Strings
For more information on strings, please visit strings in Java.
Examples in C++:
4.2 Stringstreams
Constructors with initialtext in the buffer.
Can optionally provide "modes" such as ate (start at end) or bin (read as binary).
4.2.1 Output Stringstreams
Examples
Positioning Functions:
tellp()
Returns the current position of the put pointer in the output string stream.
#include <iostream> #include <sstream> int main() { std::ostringstream oss; oss << "Hello"; std::streampos currentPos = oss.tellp(); std::cout << "Current put pointer position: " << currentPos << std::endl; // Output: 5 oss << " World!"; currentPos = oss.tellp(); std::cout << "New put pointer position: " << currentPos << std::endl; // Output: 12 return 0; }seekp(pos)
Moves the put pointer to a specific position within the output string stream.
The position can be an absolute offset from the beginning of the stream, or a relative offset from the current position (using std::ios::beg, std::ios::cur, or std::ios::end as a second argument).
#include <iostream> #include <sstream> int main() { std::ostringstream oss; oss << "Hello World!"; // 1. Absolute positioning (from the beginning) oss.seekp(0); // Move to the beginning oss << "Hi"; // Overwrite "Hello" std::cout << oss.str() << std::endl; // Output: Hi World! // 2. Relative positioning (from the end) oss.seekp(-2, std::ios::end); // Move 2 positions back from the end oss << "???"; // Overwrite "d!" std::cout << oss.str() << std::endl; // Output: Hi Worl??? // 3. Using std::ios::beg (from the beginning) oss.seekp(5, std::ios::beg); // Move 5 positions from the beginning oss << "-"; // Insert "-" std::cout << oss.str() << std::endl; // Output: Hi Worl-??? // 4. Using std::ios::cur (from the current position) oss.seekp(2, std::ios::cur); // Move 2 positions forward from the current position oss << "+"; // Insert "+" std::cout << oss.str() << std::endl; // Output: Hi Worl-?+?? return 0; }
4.2.2 Input Stringstreams
Examples
Positioning Functions:
tellg()
Returns the current position of the get pointer in the input string stream.
#include <iostream> #include <sstream> int main() { std::istringstream iss("Hello World"); iss >> std::ws; // Skip leading whitespaces std::cout << "Current position: " << iss.tellg() << std::endl; // Output: 0 std::string word; iss >> word; // Read "Hello" std::cout << "Current position after reading 'Hello': " << iss.tellg() << std::endl; // Output: 5 (or 6 if there's a space after Hello) return 0; }seekg(pos)
Moves the put pointer to a specific position within the output string stream.
The position can be an absolute offset from the beginning of the stream, or a relative offset from the current position (using std::ios::beg, std::ios::cur, or std::ios::end as a second argument).
#include <iostream> #include <sstream> int main() { std::istringstream iss("Hello World"); // Using absolute position from the beginning iss.seekg(7); // Move to the 7th position from the beginning (equivalent to iss.seekg(7, std::ios::beg)) char char1; iss.get(char1); std::cout << "Character read after seeking to absolute position 7: " << char1 << std::endl; // Output: o // Using ios::beg (beginning) iss.seekg(6, std::ios::beg); // Move to the 6th position from the beginning std::string word1; iss >> word1; std::cout << "Word read after seeking from beginning: " << word1 << std::endl; // Output: World // Using ios::cur (current) iss.seekg(2, std::ios::cur); // Move 2 positions forward from the current position (which is after "World") char char2; iss.get(char2); std::cout << "Character read after seeking from current: " << char2 << std::endl; // Output: d // Using ios::end (end) iss.seekg(-5, std::ios::end); // Move 5 positions backward from the end std::string word2; iss >> word2; std::cout << "Word read after seeking from end: " << word2 << std::endl; // Output: World return 0; }
4.2.3 Getline
is: The input stream from which to read.
str: The string where the read line will be stored.
delim: The delimiter character that specifies where to stop reading (optional, by default '\n').
Exampless
4.2.4 State Bits
Good bit - ready for read /write. (Nothing unusal, on when other bits are off)
Fail bit - previous operation failed, all future operations frozen. (Type mismatch, file can't be opened, seekg failed)
EOF bit - previous operation reached the end of buffer content (reached the end of buffer).
Bad bit - external error, like irrecoverable.(e.g. the file you are reading from suddenly is deleted)
Examples
4.3 Input Streams
There are four standard iostreams
cout: Standard Output Stream .
cin: Standard Input Stream (buffered).
cerr (Standard Error Stream): used to output errors (unbuffered).
clog (Standard Logging Stream): used for non-critical event logging (buffered).
cin
The program hangs and waits for user input when the position reaches EOF, past the last token in the buffer.
The position pointer skips whitespace after the token with each >> operation.
The position pointer does the following:
consume all whitespaces (spaces, newlines, '\t', '\n', etc .)
reads as many characters until:
a whitespace is reached, or...
for primitives, the maximum number of bytes necessary to form a valid variable.
Example: if we extract an int from "86.2", we'll get 86, with pos at the decimal point.
Examples
4.4 Output Streams
cerr & clog
Input File Streams & Output File Streams
ifstream | ofstream | |
---|---|---|
Purpose | Input from a file | Output to a file |
Mode | Opens a file in read mode | Opens a file in write mode |
Default behavior | If the file doesn't exist, it fails to open. | If the file doesn't exist, it creates a new one. If it exists, it overwrites the content by default. |
Operators | Primarily used with the extraction operator (>>) to read data from the file. | Primarily used with the insertion operator (<<) to write data to the file. |
Similarities |
|
Example:
5 Modern C++ Types
5.1 Auto
When a type name is too long and a simpler alias makes the code more readable, use it.
In libraries there is a common name for a type within each class. Example:
vector::iterator, map::iterator, string::iterator
vector::reference, map::reference, string::reference
5.2 Pair/Tuple
A pair is simply two objects bundled together.
Examples
5.3 Conversions
Exampless
5.4 initializer_list
Definition: An initializer list is a lightweight vector that can be used as a parameter.
Example:
5.5 using
Create type aliases with the using keyword.
Ⅱ Standard Template Library (STL)
The Standard Template Library (STL) is a software library originally designed by Alexander Stepanov for the C++ programming language that influenced many parts of the C++ Standard Library. It provides four components called algorithms, containers, functions, and iterators.
The C++ Standard Library is a collection of classes and functions, which are written in the core language and part of the C++ ISO Standard itself.
6 Containers
6.1 Sequence Containers
Sequence Containers: Containers which provide access to a linear sequence of elements.
std::vector<T>
std::deque<T>
std::array<T>
std::list<T>
std::forward_list<T>
6.1.1 Vector
Vector: An array with changeable size.
Example
|
| |
---|---|---|
Bounds Checking | No | Yes |
Speed | Fast | Slow |
Advantages:
Fast, lightweight & intuitive.
Grow efficiently in one direction.
Disadvantages:
cannot push_front!
6.1.2 Deque
Deque: A deque is a doubly ended queue.
Implementation
Instead of storing all elements in a single contiguous block, deque internally manages a collection of fixed-size arrays called "chunks" or "buffers." => separate subarrays and allocated independently
Deque maintains a dynamic array (usually a small array or a tree-like structure) called a "map" or "central index". This map stores pointers to the beginning of each chunk.
Advantages:
Can push_front!
Disadvantages:
For other operations, vector outperform deque.
6.2 Container Adapters
Stacks:
The standard containers
std::vector
,std::deque
,std::list
satisfy these requirements.Just limit the functionality of a vector/deque to only allow
push_back
andpop_back
.
Queues:
The standard containers
std::deque
andstd::list
satisfy these requirements.Just limit the functionality of a deque to only allow
push_back
andpop_front
.
6.3 Associative Containers
Associative containers: Data is accessed using the key instead of index.
6.4 Iterators
Four iterator operations:
Create iterator:
std::set<int>::iterator iter = mySet.begin()
Dereference iterator to read value currently pointed to:
int val = *iter
Advance iterator:
iter++
; or++iter
Compare against another iterator (especially
.end()
iterator)
Map Iterators:
6.4.1 Input Iterators
For sequential, single-pass input.
Read only, i.e. can only be dereferenced on right side of expression.
Example:
6.4.2 Output Iterators
For sequential, single-pass output.
Read only, i.e. can only be dereferenced on left side of expression.
Example:
6.4.3 Forward Iterators
Combines input and output, plus can make multiple passes.
Can read from and write to (if not const iterator).
Example:
6.4.4 Bidirectional Iterators
Same as forward iterators, plus can go backwards with the decrement operator (--).
Use cases: std::map
, std::set
, std::list
6.4.5 Random Access Iterators
Same as bidirectional iterators, plus can be implemented or decremented by arbitrary amounts using + and -.
Use cases: std::vector
, std::deque
, std::string
7 Templates
We can use templates to keep the logic, but change the type!
7.1 Template Functions
Example
The following code:
Semantic error: you can't call operator < on two streams.
Conceptual error: you can't find the min or max of two streams.
The compiler deduces the types and literally replaces the types. Compiler will produce semantic errors, not conceptual error.
Problem lies in indexing list[i]
.
Or:
7.2 Template Classes
Template Class
Add template declaration for class
Add all the member type aliases
Add the template declaration to every single class member
Move everything to the .h file => separate compilation template classes are not classes themselves
8 Functions and Algorithms
8.1 Lambda Functions
auto
: We don't know the type, ask compiler.[limit]
: Capture clause, gives access to outside variables.(auto)
: Parameter list, can use auto!-> bool
: Return type, optional.
Two types of capture clause: By reference or by value.
std::bind
adapts existing function objects to create new ones with specific argument values pre-filled.
std::placeholders::_1
means "take the first argument passed to operation".std::placeholders::_2
means "take the second argument passed to operation".4 is the third argument of
multiplyAndAdd
.
8.2 Algorithms
8.2.1 std::sort
Syntax:
Example:
Parameters:
first (iterator): An iterator pointing to the beginning of the range you want to sort.
last (iterator): An iterator pointing to one position past the end of the range to be sorted.
comp (comparison function) (optional): A binary function (takes two arguments) that defines the sorting criterion. It should return true if the first argument should come before the second in the sorted order, and false otherwise.
Important notes:
You can sort a portion of the entire vector, array, etc.
std::vector<int> data = {5, 2, 8, 1, 9, 3}; std::sort(data.begin(), data.begin() + 4); // Result: data = {1, 2, 5, 8, 9, 3}You can overload operator < to define default sorting behavior.
struct Item { int id; std::string name; }; // Stable Comparison bool compareByNameStable(const Item& a, const Item& b) { if (a.name == b.name) { return a.id < b.id; // Keep original order based on 'id' if names are equal } return a.name < b.name; } int main() { std::vector<Item> items = { {1, "Apple"}, {2, "Banana"}, {3, "Apple"} }; std::sort(items.begin(), items.end(), compareByNameStable); }Average case:
8.2.2 std::nth_element
Syntax:
Example:
Parameters:
first: Iterator to the beginning of the range.
nth: Iterator pointing to the position you want the nth element to be placed in.
last: Iterator to one past the end of the range.
comp (optional): A binary comparison function, similar to std::sort.
Important notes:
With this, you can efficiently find the median of a dataset without fully sorting it, or determine the
smallest or largest element. It sorts so
element is in correct position, and all elements smaller to left, larger to right, but the elements before the element are not guaranteed to be sorted among themselves, and neither are the elements after it. Average case:
8.2.3 std::stable_partition
Syntax:
Parameters:
BidirectionalIterator: A template parameter indicating the type of iterators used. These iterators must support bidirectional movement (like those from std::list, std::vector, std::deque).
first (BidirectionalIterator): An iterator to the beginning of the range you want to partition.
last (BidirectionalIterator): An iterator to one past the end of the range to be partitioned.
UnaryPredicate: A template parameter representing the type of the predicate function.
pred (UnaryPredicate): A function that takes a single argument (an element from the range) and returns a bool:
true: The element satisfies the partitioning criterion.
false: The element does not satisfy the criterion.
Example:
8.2.4 std::copy_if
Syntax:
InputIterator: Type of iterator used for the input range.
OutputIterator: Type of iterator used for the output range (where copied elements go).
first (InputIterator): An iterator to the beginning of the input range.
last (InputIterator): An iterator to one past the end of the input range.
result (OutputIterator): An iterator to the beginning of the output range.
UnaryPredicate: Type of the predicate function.
pred (UnaryPredicate): A function that takes a single argument (an element from the input range) and returns:
true: Copy the element to the output range.
false: Skip the element.
Example:
8.2.5 std::remove_if
Syntax:
ForwardIterator: Type of iterator used for the range. Must support forward movement.
first (ForwardIterator): An iterator to the beginning of the range.
last (ForwardIterator): An iterator to one past the end of the range.
UnaryPredicate: Type of the predicate function.
pred (UnaryPredicate): A function that takes a single argument (an element from the range) and returns:
true: The element should be "removed."
false: The element should be kept.
Ⅲ Object-Oriented Programming
9 Classes and Consts
9.1 Classes
Definitions:
Class: a template for a new type of objects, defines how objects of a particular type behave.
Object: Entity that combines state and behavior, instance of a class.
Member variables (instance variables, fields): Define state inside each object.
Member functions (methods): Define behavior inside each object.
Constructor: Initializes the state of newly created objects.
Destructor: Called when the object is deleted by the program.
Delete any pointers stored as private members.
delete[] any arrays stored as private members.
Client code: Code that uses the objects defind.
Encapsulation: Hiding implementation details from the client code.
C++ separates classes into two kinds of files:
Header File (.h, .hh, .hpp): Containing the interface (declarations).
Source File (.cpp, .cc, .cxx, .c++, .C): Containing definitions (method bodies).
Classes have three main parts:
Constructor and destructor
Member variables
Member functions
Example (header file):
Example (source file):
9.2 Consts
Consts: A qualifier for objects that declares they cannot be modified.
Consts help us find bugs, and allow us to reason about whether a variable will be changed.
Within a function that takes a const parameter, you cannot call non-const member functions (if the parameter is an object) or modify the value (if it's a fundamental type or a pointer to const data) of that parameter.
Example for value:
Example for member functions:
9.2.1 Const Pointers
9.2.2 Const Iterators
10 Operators
10.1 Basic Operators
There are 40 (+4) operators you can overload!
Examples for default operators behaviors:
In STL,
10.2 Operator Overloading
General rule of Thumb: (Member & Non-Member)
Some operators must be implemented as members (e.g., [], (), ->, =) due to C++ semantics.
Some must be implemented as non-members (eg. <<, if you are writing class for rhs, not lhs).
If unary operator (eg. ++), implement as member.
If binary operator and treats both operands equally (eg. both unchanged) implement as non-member (maybe friend). Examples: +, < .
If binary operator and not both equally (changes lhs), implement as member (allows easy access to lhs private members). Examples: +=
Examples
Member Function:
Non-Member Function:
10.3 Principle of Least Astonishment (POLA)
From the C++ Core Guidelines (section C ):
Design operators primarily to mimic conventional usage.
Use nonmember functions for symmetric operators.
Use nonmember functions for symmetric operators.
Compound operators return reference to *this
Arithmetic operators return copies
In/decrement prefix vs. postfix rules
Indexing requires const and non-const versions
Look at the C++ reference for common patterns!
Always provide all out of a set of related operators.
10.4 Interesting Operators
Advanced Multithreading Support (C++ 20) :
Spaceship operator (C++20):
11 Special Member Functions
Six types of special member functions:
Every class has them by default.
These functions are generated only when they're called (and before any are explicitly defined by you):
Default constructor: Takes no parameters and creates a new object.
Destructor: Called when an object goes out of scope.
Copy constructor: Creates a new object as a member-wise copy of another.
Copy assignment operator: Assigns an already existing object to another.
Move constructor
Move assignment operator
Example:
11.1 Copy Constructor & Copy Assignment Operator
By default, the copy constructor will create copies of each member variable.
Examples
Copy constructor:
Use initializer list to copy members where assignment does the correct thing. e.g., int, other objects, etc.
Deep copy all members where assignment does not work. e.g., pointers to heap memory.
Copy assignment operator:
Clean up any resources in the existing object about to be overwritten.
Copy members using initializer list when assignment works.
Deep copy members where assignment does not work.
11.2 Delete Operations
Setting a special member function to delete removes its functionality.
We can also keep the default copy constructor if we declare other constructors.
Examples
11.3 Rule of Zero/Three
Rule of Zero: If the default operations work, don't define your own custom ones!
When the default one generated by the compiler does not work, you need to write your own special member functions.
Most common reason: ownership issues. A member is a handle on a resource outside of the class (e.g., pointers, mutexes, filestreams) .
Rule of Three: If you explicitly define (or delete) a copy constructor, copy assignment, or destructor, you should define (or delete) all three.
11.4 Copy Elision and Return Value Optimization (RVO)
Examples
12 Move Semantics
12.1 lvalues & rvalues
Definitions
lvalue: An l-value is an expression that has a name (identity).
rvalue: An r-value is an expression that does not have a name (identity).
l-value | r-value | |
---|---|---|
Find address using address-of operator (&var) | Yes | No |
Intuitive Definition | Can appear either left or right of an assignment * | Can appear only on the right of an assignment * |
Lifetime | Decided by scope | Ends on the very next line (unless you purposely extend it!) |
Value References | An l-value reference can bind to an l-value.
auto& ptr2 = (ptr += 3);
| An r-value reference can bind to an r-value.
auto&& v4 = v1 + v2;
|
A const l-value reference can bind to either l or r-value.
const auto& ptr2 = (ptr += 3);
const auto& v4 = v1 + v2;
|
Examples
12.2 Move Semantics
13 Inheritance
13.1 Namespace
Namespace: Namespace is an abstract container or environment created to hold a logical grouping of unique identifiers or symbols (i.e. names).
Goal: To resolve identifier clash. The same identifier can be independently defined in multiple namespaces. That is, an identifier defined in one namespace may or may not have the same meaning as the same identifier defined in another namespace.
Most modern languages use namespaces to fix this.
13.1 Overriding and Overloading
Definition:
Overloading: Methods with the same name but different signature.
Overriding: A subclass to provide a specific implementation of a method that is already provided by its parent class or interface.
13.2 Types of Inheritance
Interface Inheritance:
Specifying the capabilities of a subclass.
Interface: The list of all method signatures.
Specifies what the subclass can do, but not how.
Examples
Implementation Inheritance: Subclasses can inherit signatures AND implementation.
Examples