C++ Programming

Last Modified: 2/12/2025

Ⅰ C++ Fundamentals

1 C & C++ Introduction

  1. C/C++ is a compiled language.

  2. 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

Compilation Disadvantages

Compile & Run

Normal C/C++ Compile & Run:

Bash
$ g++ -std=c++20 main.cpp -o main

2 Types & Structs

2.1 Primitive Types

Fundamental TypesExampleMemory
intint val = 5;4 bytes
charchar ch = 'F';1 byte
floatfloat decimalVal1 = 5.0;4 bytes
doubledouble decimalVal2 = 5.0;8 bytes
boolbool bVal = true;1 byte
std::stringstd::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!

student.cpp
/* 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.

shape.cpp
#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.

enum.cpp
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"
enumClass.cpp
20 collapsed lines
// Using enum class
enum class Status1 {
SUCCESS,
PENDING,
FAILED,
ERROR
};
// Using enum struct (equivalent to enum class)
enum struct Status2 {
OK,
PROCESSING,
ERROR
};
Status1 status1 = Status1::ERROR; // scoped enum
Status2 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

Terminal window
# 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++:

  1. Direct Initialization
Terminal window
int numOne = 12.0; // numOne is 12, doesn't type check with direct initialzation
  1. Uniform Initialization (C++ 11)
Terminal window
int numTwo {12.0};
// Narrowing conversion of '1.2e+1' from 'double' to 'int'
// Type checks with uniform initialization
  1. 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

3.2 References

int x = 5;
int& ref = x; // ref is a reference to x
ref = 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 nums
void 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 Way
void 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; // e
str[1] = 'a'; // Hallo, World!

Example in C

char str[] = "Hello, World!";
printf("%c\n", str[1]); // e
str[1] = 'a'; // Hallo, World!

4.2 Stringstreams

Properties

4.2.1 Output Stringstreams

Examples

std::ostringstream oss("Ito-En Green Tea");
std::cout << oss.str() << std::endl; // Ito-En Green Tea
oss << "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().

  1. 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;
}
  1. 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

Terminal window
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.

  1. tellg()
istringstream.cpp
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;
}
  1. seekg(pos)
istringstream.cpp
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.

Example

istringstream.cpp
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

stringstream.cpp
#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

  1. Good Bit: Ready for read/write (nothing unusual, on when other bits are off).
  2. Fail Bit: Previous operation failed, all future operations frozen (type mismatch, file can’t be opened, seekg failed, etc.).
  3. EOF Bit: Previous operation reached the end of buffer content.
  4. Bad Bit: External error, like irrecoverable (e.g., the file you are reading from suddenly is deleted).

Example

stringstream.cpp
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

  1. std::cin: Standard input stream.
  1. std::getline: Another input strean function. istream& getline(istream& is, string& str, char delim)

Example:

input.cpp
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;
}
  1. std::ifstream: Input file stream.

Example:

inputFile.cpp
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

  1. std::cout: Standard output stream.
  2. std::cerr: Standard error stream (unbuffered), used for displaying error messages and diagnostic information.
  3. std::clog: Standard error stream (buffered), used for displaying logging information.
  4. std::ofstream: Output file stream.
outputStream.cpp
#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:

Terminal window
./outputStream 2> error.log

You can see the messages of std::cerr in the error.log file.