C++ Programming
Last Modified: 2/12/2025
Ⅰ C++ Fundamentals
1 C & C++ Introduction
-
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.

Normal C/C++ Compile & Run:
$ g++ -std=c++20 main.cpp -o main
2 Types & Structs
2.1 Primitive Types
Fundamental Types | Example | Memory |
---|---|---|
int | int val = 5; | 4 bytes |
char | char ch = 'F'; | 1 byte |
float | float decimalVal1 = 5.0; | 4 bytes |
double | double decimalVal2 = 5.0; | 8 bytes |
bool | bool bVal = true; | 1 byte |
std::string | std::string str = "Hello"; | - |
2.2 Structs
A struct is a group of named variables, each with their own type, that allows programmers to bundle different types together!
/* C++ Style */struct Student { string name; // these are called fields string state; // separate these by semicolons int age;};
/* C Style */typedef struct { char name[50]; char state[3]; int age;} Student;
Student s;s.name = "Haven";s.state = "AR";s.age = 22; // use . to access fields
2.3 Union
Unions are similar to structs, but all members share the same memory location, and union only provides enough space for the largest element.
#include <iostream>
union Shape { int radius; // For circle struct { int width; int height; } rectangle; // For rectangle};
int main() { Shape shape; shape.radius = 5; std::cout << "Radius: " << shape.radius << std::endl; // Radius: 5
shape.rectangle.width = 10; shape.rectangle.height = 20; std::cout << "Width: " << shape.rectangle.width << ", Height: " << shape.rectangle.height << std::endl; // Width: 10, Height: 20
std::cout << "Radius: " << shape.radius << std::endl; // No meaning, the radius has been overwritten!
return 0;}
2.4 Enum
An enumeration is a distinct type whose value is restricted to a range of values, which may include several explicitly named constants (“enumerators”).
For C and earlier versions of C++, you can declare enum
. Since C++ 11, you can declare
enum
, enum
class and enum
struct. enum
class and struct are strongly typed and
create a namespace for its enumerators.
9 collapsed lines
enum Color { RED, GREEN, BLUE};
Color myColor = GREEN;int colorValue = myColor; // Implicit conversion - ok// myColor = 5; // Error:a value of type "int" cannot be assigned to an entity of type "Color"
20 collapsed lines
// Using enum classenum class Status1 { SUCCESS, PENDING, FAILED, ERROR};
// Using enum struct (equivalent to enum class)enum struct Status2 { OK, PROCESSING, ERROR};
Status1 status1 = Status1::ERROR; // scoped enumStatus2 status2 = Status2::ERROR;
// int s = status1; // Error: a value of type "Status1" cannot be used to initialize an entity of type "int"int s = static_cast<int>(status1); // Explicit conversion - ok
2.5 C++ Preprocessor Macros
Preprocessor macros are a way to define constants or perform text substitution in your code before compilation.
Example
# define PI 3.14159
int main() { double radius = 5.0; double area = PI * radius * radius; std::cout << "Area of the circle: " << area << std::endl; // Output: Area of the circle: 78.5397 return 0;}
3 Initialization & References
3.1 Initialization
There are three types of initialization in C++:
- Direct Initialization
int numOne = 12.0; // numOne is 12, doesn't type check with direct initialzation
- Uniform Initialization (C++ 11)
int numTwo {12.0};// Narrowing conversion of '1.2e+1' from 'double' to 'int'// Type checks with uniform initialization
- Structure 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
int x = 5;int& ref = x; // ref is a reference to xref = 10; // x is now 10
3.3 Pointers
int x = 5;int* ref = &x; // ref is a pointer to x*ref = 10; // x is now 10
A classic reference-copy bug:
// We are modifying the std::pair's inside of numsvoid shift(std::vector<std::pair<int, int>> &nums) { // nums passed by reference for (auto [num1, num2] : nums) { // num1 and num2 are copies num1++; num2++; }}
In this code, nums
is passed by reference to the shift function, which means any modifications to nums
should affect the original vector. However, within the for loop,num1
and num2
are declared as copies of the elements in nums. As a result, the increments num1++
and num2++
modify only the local copies, and the original elements in nums remain unchanged. Below is the correct way:
// Correct Wayvoid shift(std::vector<std::pair<int, int>> &nums) { for (auto& [num1, num2] : nums) { num1++; num2++; }}
4 Streams
4.1 Strings
Example in C++
std::string str = "Hello, World!";std::cout << str[1] << std::endl; // estr[1] = 'a'; // Hallo, World!
Example in C
char str[] = "Hello, World!";printf("%c\n", str[1]); // estr[1] = 'a'; // Hallo, World!
4.2 Stringstreams
Properties
- Constructors with initial text in the buffer.
- Can optionally provide “modes” such as
ate
(start at end) orbin
(read as binary).
4.2.1 Output Stringstreams
Examples
std::ostringstream oss("Ito-En Green Tea");std::cout << oss.str() << std::endl; // Ito-En Green Teaoss << "16.9 Ounces";std::cout << oss.str() << std::endl; // 16.9 Ouncesn Tea
std::ostringstream oss("Ito-En Green Tea", std::ostringstream::ate);oss << "16.9 Ounces";std::cout << oss.str() << std::endl; // Ito-En Green Tea16.9 Ounces
Positioning functions in output stringstream include tellp()
and seekp()
.
tellp()
: Returns the current position of the put pointer in the output stringstream.
#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 stringstream.
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).
33 collapsed lines
#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 "He"
std::cout << oss.str() << std::endl; // Output: Hillo 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: Hillo 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: Hillo-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: Hillo-Wo+l???
return 0;}
4.2.2 Input Stringstreams
Example
std::istringstream iss("16.9 Ounces");double amount;std::string unit;iss >> amount >> unit; // amount = 16.9, unit = "Ounces"
std::istringstream iss("16.9 Ounces");int amount;std::string unit;iss >> amount >> unit; // amount = 16, unit = ".9"
Positioning functions in input stringstream include tellg()
and seekg()
, which are similar to output stringstream.
tellg()
18 collapsed lines
#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: " << iss.tellg() << std::endl; // Output: 5
return 0;}
seekg(pos)
31 collapsed lines
#include <iostream>#include <sstream>
int main() { std::istringstream iss("Hello World");
iss.seekg(7); char char1; iss.get(char1); std::cout << "Character read: " << char1 << std::endl; // Output: o
iss.seekg(6, std::ios::beg); std::string word1; iss >> word1; std::cout << "Word read: " << word1 << std::endl; // Output: World
iss.seekg(-2, std::ios::cur); char char2; iss.get(char2); std::cout << "Character read: " << char2 << std::endl; // Output: d
iss.seekg(-5, std::ios::end); std::string word2; iss >> word2; std::cout << "Word read: " << word2 << std::endl; // Output: World
return 0;}
There are two data types for positions in streams: std::streampos
and std::streamoff
.
std::streampos
: Represents the position of the get pointer.std::streamoff
: Represents the difference (offset) between twostreampos
values.
Example
33 collapsed lines
#include <iostream>#include <sstream>
int main() { std::ostringstream oss; oss << "Hello, world!";
// Get the current position using streampos std::streampos pos = oss.tellp(); std::cout << "Current position in output stream: " << pos << std::endl; // Outputs: 13
// Move the position using streamoff oss.seekp(5, std::ios::beg); pos = oss.tellp(); std::cout << "New position in output stream after seekp: " << pos << std::endl; // Outputs: 5
// Create an input string stream with the data from the output stream std::istringstream iss(oss.str());
// Get the current position using streampos pos = iss.tellg(); std::cout << "Current position in input stream: " << pos << std::endl; // Outputs: 0
// Move the position using streamoff iss.seekg(7, std::ios::beg); pos = iss.tellg(); std::cout << "New position in input stream after seekg: " << pos << std::endl; // Outputs: 7
return 0;}
4.2.3 Stringstreams
Example
#include <iostream>#include <sstream>
int main() { std::stringstream ss("Hello, World!"); std::string word; ss >> word; std::cout << word << std::endl; // Hello,
ss << "CS106L"; std::cout << ss.str() << std::endl; // Hello, World!CS106L
return 0;}
4.2.4 State Bits
- Good Bit: Ready for read/write (nothing unusual, on when other bits are off).
- Fail Bit: Previous operation failed, all future operations frozen (type mismatch, file can’t be opened,
seekg
failed, etc.). - EOF Bit: Previous operation reached the end of buffer content.
- Bad Bit: External error, like irrecoverable (e.g., the file you are reading from suddenly is deleted).
Example
std::istringstream iss("17");int amount;iss >> amount;std::cout << (iss.eof() ? "EOF" : "Not EOF") << std::endl;
4.3 iostreams
4.3.1 Input Streams
std::cin
: Standard input stream.
- The position pointer skips whitespace after the token with each
>>
operator. - The position pointer does the following:
- consumes 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.
std::getline
: Another input strean function.istream& getline(istream& is, string& str, char delim)
getline()
reads an input streamis
, up until thedelim
char and stores it in some bufferstr
.- The
delim
char is by default ‘\n’. getline()
consumes thedelim
character!
Example:
15 collapsed lines
#include <iostream>#include <string>
int main() { double pi; std::string name; std::cin >> pi; std::cin.ignore(); // ignore the newline character // std::getline(std::cin, name); // alternative way to ignore std::getline(std::cin, name); std::cin >> r; std::cout << "Hello, " << name << "!" << std::endl; std::cout << "Value of pi: " << pi << std::endl; return 0;}
std::ifstream
: Input file stream.
Example:
20 collapsed lines
#include <iostream>#include <fstream>
int main() { std::ifstream file("input.txt");
if (!file.is_open()) { std::cerr << "File not found!" << std::endl; return 1; } else { std::string line; while (std::getline(file, line)) { std::cout << line << std::endl; } file.close(); }
return 0;}
4.3.2 Output Streams
std::cout
: Standard output stream.std::cerr
: Standard error stream (unbuffered), used for displaying error messages and diagnostic information.std::clog
: Standard error stream (buffered), used for displaying logging information.std::ofstream
: Output file stream.
#include <iostream>
int main() { bool error = true; std::clog << "Starting program execution...\n"; std::clog << "Processing data...\n"; if (error) { std::cerr << "Error: Data processing failed!\n"; } std::clog << "Program finished.\n"; return 0;}
Now if we execute the program with:
./outputStream 2> error.log
You can see the messages of std::cerr
in the error.log
file.