Skip to main content

C++

Welcome to C++

C++ is a powerful, high-performance programming language that builds upon the C language with object-oriented features. Developed by Bjarne Stroustrup in the early 1980s, C++ combines low-level memory manipulation with high-level abstractions, making it suitable for system programming, game development, and performance-critical applications.

Known for its efficiency and flexibility, C++ gives programmers fine-grained control over system resources while providing powerful features like classes, templates, and the Standard Template Library (STL). This combination makes C++ a valuable skill for developers working on operating systems, game engines, embedded systems, and high-frequency trading applications.

Introduction to C++

How to set up a C++ development environment and write your first "Hello, World!" program.

C++ is one of the most widely used programming languages, known for its performance and versatility. Setting up a C++ development environment is straightforward, with options available for all major operating systems.

Step 1: Install a C++ Compiler

  1. Windows
    Install MinGW-w64 or Microsoft Visual Studio with C++ support. For MinGW, download from mingw-w64.org and add the bin directory to your PATH.

  2. macOS
    Install Xcode from the App Store, which includes the Clang compiler. Alternatively, install Command Line Tools by running xcode-select --install in Terminal.

  3. Linux
    Use your distribution's package manager. For Ubuntu/Debian:

    sudo apt install g++

Step 2: Verify Installation

Open a terminal or command prompt and type:

g++ --version

This should display the installed compiler version.

Step 3: Write and Compile Your First C++ Program

  1. Create a file named hello.cpp with the following content:

    #include <iostream>
    using namespace std;
    
    int main() {
        cout << "Hello, World!" << endl;
        return 0;
    }
  2. Compile the program:

    g++ hello.cpp -o hello
  3. Run the executable:

    ./hello   # On Linux/macOS
    hello.exe   # On Windows

    You should see the output: Hello, World!

Congratulations! You have successfully set up a C++ environment and run your first program. 🎉

C++ Syntax Basics

C++ syntax is derived from C, with additions for object-oriented programming. Understanding the basic syntax is crucial for writing correct C++ programs.

1. Basic Structure of a C++ Program

Every C++ program has a specific structure:

#include <iostream>  // Preprocessor directive
using namespace std;   // Using the standard namespace

int main() {           // Main function - program entry point
    // Program statements go here
    return 0;          // Return 0 to indicate successful execution
}

2. Semicolons and Braces

C++ uses semicolons to terminate statements and braces {} to define code blocks:

int x = 5;        // Statement ends with semicolon

if (x > 3) {      // Braces define the if block
    cout << "x is greater than 3";
}                 // Closing brace

3. Comments

C++ supports single-line and multi-line comments:

// This is a single-line comment

/*
 This is a multi-line comment
 It can span multiple lines
*/

4. Case Sensitivity

C++ is case-sensitive, meaning it distinguishes between uppercase and lowercase letters:

int myVar = 5;    // Different from myvar
int MyVar = 10;   // Different from myVar

cout << myVar;    // Outputs: 5
cout << MyVar;    // Outputs: 10

5. Variables and Static Typing

C++ is statically typed, meaning you must declare the type of a variable explicitly:

int x = 10;        // Integer
float y = 3.14;    // Floating-point number
char z = 'A';      // Character
string name = "C++"; // String (requires #include <string>)

cout << typeid(x).name();  // Outputs the type name

Conclusion

Understanding C++ syntax is essential for writing correct and efficient programs. Key takeaways include:

  • C++ programs start execution from the main() function
  • Statements end with semicolons
  • Code blocks are defined with braces {}
  • C++ is case-sensitive
  • C++ uses static typing - variable types must be declared

Output with cout

The cout object in C++ is used to display output on the screen. It is part of the iostream library and is one of the most commonly used features for basic output and debugging.

1. Basic cout Usage

The simplest way to use cout is with the insertion operator <<:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!";  // Outputs: Hello, World!
    cout << 42;               // Outputs: 42
    return 0;
}

2. Outputting Multiple Values

You can chain multiple values with the insertion operator:

cout << "Hello" << " " << "C++";  // Outputs: Hello C++

3. Using endl for New Lines

The endl manipulator inserts a newline and flushes the output buffer:

cout << "First line" << endl;
cout << "Second line" << endl;

Output:

First line
Second line

4. Formatting Output

C++ provides various manipulators for formatting output:

#include <iomanip>  // For formatting manipulators

cout << fixed << setprecision(2) << 3.14159;  // Outputs: 3.14
cout << setw(10) << "Hello";  // Outputs: "     Hello" (right-aligned in 10 spaces)

5. Printing Variables

You can print variables just like literals:

string name = "Alice";
int age = 25;
cout << "Name: " << name << ", Age: " << age;

Conclusion

The cout object is a fundamental tool for output in C++. Key points:

  • Use cout with the insertion operator <<
  • Chain multiple values with multiple << operators
  • Use endl for new lines
  • Include <iomanip> for advanced formatting

Welcome to C++

C++ is a powerful, high-performance programming language that builds upon the C language with object-oriented features. Developed by Bjarne Stroustrup in the early 1980s, C++ combines low-level memory manipulation with high-level abstractions, making it suitable for system programming, game development, and performance-critical applications.

Known for its efficiency and flexibility, C++ gives programmers fine-grained control over system resources while providing powerful features like classes, templates, and the Standard Template Library (STL). This combination makes C++ a valuable skill for developers working on operating systems, game engines, embedded systems, and high-frequency trading applications.

Arithmetic Operators in C++

C++ provides standard arithmetic operators for mathematical calculations. These operators work with numeric data types like integers and floating-point numbers.

Basic Arithmetic Operators

#include <iostream>
using namespace std;

int main() {
    int a = 15, b = 4;
    
    cout << "a + b = " << (a + b) << endl;  // Addition: 19
    cout << "a - b = " << (a - b) << endl;  // Subtraction: 11
    cout << "a * b = " << (a * b) << endl;  // Multiplication: 60
    cout << "a / b = " << (a / b) << endl;  // Division: 3 (integer division)
    cout << "a % b = " << (a % b) << endl;  // Modulus: 3
    
    float x = 15.0, y = 4.0;
    cout << "x / y = " << (x / y) << endl;  // Division: 3.75 (float division)
    
    return 0;
}

Increment and Decrement Operators

int count = 5;
cout << "count = " << count << endl;     // 5
cout << "++count = " << ++count << endl; // 6 (pre-increment)
cout << "count++ = " << count++ << endl; // 6 (post-increment)
cout << "count = " << count << endl;     // 7
cout << "--count = " << --count << endl; // 6 (pre-decrement)

Common Pitfalls

  • Integer division truncates the fractional part: 5 / 2 equals 2, not 2.5
  • Modulus operator % only works with integer types
  • Division by zero causes undefined behavior

Comparison Operators in C++

Comparison operators compare two values and return a boolean result (true or false). These are essential for conditional statements and loops.

Comparison Operators

#include <iostream>
using namespace std;

int main() {
    int x = 7, y = 10;
    
    cout << "x == y: " << (x == y) << endl;  // Equal to: false
    cout << "x != y: " << (x != y) << endl;  // Not equal to: true
    cout << "x > y: " << (x > y) << endl;    // Greater than: false
    cout << "x < y: " << (x < y) << endl;    // Less than: true
    cout << "x >= 7: " << (x >= 7) << endl;  // Greater than or equal: true
    cout << "y <= 7: " << (y <= 7) << endl;  // Less than or equal: false
    
    return 0;
}

Using Comparisons in Conditions

int age = 18;
if (age >= 18) {
    cout << "You are an adult" << endl;
} else {
    cout << "You are a minor" << endl;
}

Common Pitfalls

  • Confusing assignment = with equality ==
  • Comparing floating-point numbers for exact equality can be problematic due to precision issues

Logical Operators in C++

Logical operators combine boolean expressions and return true or false. C++ provides three logical operators: && (AND), || (OR), and ! (NOT).

Logical Operators

#include <iostream>
using namespace std;

int main() {
    bool isSunny = true;
    bool isWarm = false;
    
    cout << "isSunny && isWarm: " << (isSunny && isWarm) << endl;  // AND: false
    cout << "isSunny || isWarm: " << (isSunny || isWarm) << endl;  // OR: true
    cout << "!isWarm: " << (!isWarm) << endl;                      // NOT: true
    
    // Complex conditions
    int age = 25;
    bool hasLicense = true;
    
    if (age >= 18 && hasLicense) {
        cout << "You can drive" << endl;
    }
    
    return 0;
}

Short-Circuit Evaluation

// In AND (&&), if first condition is false, second isn't evaluated
// In OR (||), if first condition is true, second isn't evaluated

int x = 5;
if (x != 0 && 10/x > 1) {  // Safe because x != 0 is checked first
    cout << "Condition satisfied" << endl;
}

Common Pitfalls

  • Using bitwise operators (&, |) instead of logical operators (&&, ||)
  • Forgetting that logical operators have lower precedence than comparison operators

Bitwise Operators in C++

Bitwise operators perform operations on individual bits of integer types. They are used in low-level programming, embedded systems, and performance optimization.

Bitwise Operators

#include <iostream>
#include <bitset>
using namespace std;

int main() {
    unsigned int a = 6;   // binary: 0110
    unsigned int b = 3;   // binary: 0011
    
    cout << "a = " << bitset<4>(a) << " (" << a << ")" << endl;
    cout << "b = " << bitset<4>(b) << " (" << b << ")" << endl;
    
    cout << "a & b = " << bitset<4>(a & b) << " (" << (a & b) << ")" << endl;  // AND: 0010 (2)
    cout << "a | b = " << bitset<4>(a | b) << " (" << (a | b) << ")" << endl;  // OR: 0111 (7)
    cout << "a ^ b = " << bitset<4>(a ^ b) << " (" << (a ^ b) << ")" << endl;  // XOR: 0101 (5)
    cout << "~a = " << bitset<4>(~a) << " (" << (~a) << ")" << endl;           // NOT: 1001 (9)
    cout << "a << 1 = " << bitset<4>(a << 1) << " (" << (a << 1) << ")" << endl; // Left shift: 1100 (12)
    cout << "b >> 1 = " << bitset<4>(b >> 1) << " (" << (b >> 1) << ")" << endl; // Right shift: 0001 (1)
    
    return 0;
}

Practical Applications

// Setting flags
const unsigned int FLAG_A = 1;  // 0001
const unsigned int FLAG_B = 2;  // 0010
const unsigned int FLAG_C = 4;  // 0100

unsigned int settings = 0;
settings |= FLAG_A;  // Set flag A
settings |= FLAG_C;  // Set flag C

// Check if flag B is set
if (settings & FLAG_B) {
    cout << "Flag B is set" << endl;
} else {
    cout << "Flag B is not set" << endl;
}

Common Pitfalls

  • Confusing bitwise AND & with logical AND &&
  • Right-shifting negative numbers has implementation-defined behavior
  • Shifting beyond the bit width causes undefined behavior

Assignment Operators in C++

Assignment operators assign values to variables. C++ provides compound assignment operators that combine assignment with arithmetic or bitwise operations.

Assignment Operators

#include <iostream>
using namespace std;

int main() {
    int x = 10;        // Simple assignment
    cout << "x = " << x << endl;
    
    x += 5;            // Equivalent to x = x + 5
    cout << "x += 5: " << x << endl;  // 15
    
    x -= 3;            // Equivalent to x = x - 3
    cout << "x -= 3: " << x << endl;  // 12
    
    x *= 2;            // Equivalent to x = x * 2
    cout << "x *= 2: " << x << endl;  // 24
    
    x /= 4;            // Equivalent to x = x / 4
    cout << "x /= 4: " << x << endl;  // 6
    
    x %= 4;            // Equivalent to x = x % 4
    cout << "x %= 4: " << x << endl;  // 2
    
    // Bitwise assignment operators
    unsigned int y = 5;
    y &= 3;            // AND assignment: y = y & 3
    y |= 8;            // OR assignment: y = y | 8
    y ^= 4;            // XOR assignment: y = y ^ 4
    
    return 0;
}

Multiple Assignment

int a, b, c;
a = b = c = 10;  // All variables get value 10

// Compound assignment in loops
int sum = 0;
for (int i = 1; i <= 5; i++) {
    sum += i;  // Add i to sum
}
cout << "Sum: " << sum << endl;  // 15

Common Pitfalls

  • Confusing = (assignment) with == (equality comparison)
  • Assignment has right-to-left associativity: a = b = c means a = (b = c)

Integer Data Types in C++

C++ provides several integer types with different sizes and ranges. Choosing the right integer type depends on the required range and memory constraints.

Basic Integer Types

#include <iostream>
#include <climits>
using namespace std;

int main() {
    // Signed integers (can represent negative numbers)
    short s = 100;           // Typically 2 bytes
    int i = 100000;          // Typically 4 bytes
    long l = 100000L;        // Typically 4 or 8 bytes
    long long ll = 100000LL; // Typically 8 bytes
    
    // Unsigned integers (only non-negative numbers)
    unsigned short us = 100;
    unsigned int ui = 100000;
    unsigned long ul = 100000UL;
    unsigned long long ull = 100000ULL;
    
    cout << "short range: " << SHRT_MIN << " to " << SHRT_MAX << endl;
    cout << "int range: " << INT_MIN << " to " << INT_MAX << endl;
    cout << "unsigned int range: 0 to " << UINT_MAX << endl;
    
    return 0;
}

Integer Operations

int a = 10, b = 3;
cout << "a + b = " << (a + b) << endl;  // 13
cout << "a - b = " << (a - b) << endl;  // 7
cout << "a * b = " << (a * b) << endl;  // 30
cout << "a / b = " << (a / b) << endl;  // 3 (integer division)
cout << "a % b = " << (a % b) << endl;  // 1 (modulus)

Type Conversion

// Implicit conversion
int num = 3.14;  // num becomes 3 (truncation)

// Explicit conversion (C-style)
int x = (int)3.14;

// Explicit conversion (C++ style)
int y = static_cast<int>(3.14);

// String to integer
#include <string>
string str = "123";
int z = stoi(str);  // C++11

Common Pitfalls

  • Integer overflow when result exceeds type's maximum value
  • Integer division truncates fractional parts
  • Mixing signed and unsigned types can cause unexpected behavior

Floating-Point Data Types in C++

Floating-point types represent real numbers with fractional parts. C++ provides three floating-point types with different precision levels.

Floating-Point Types

#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

int main() {
    float f = 3.14159f;           // Single precision (typically 4 bytes)
    double d = 3.14159265358979;  // Double precision (typically 8 bytes)
    long double ld = 3.141592653589793238L; // Extended precision
    
    cout << fixed << setprecision(10);
    cout << "float: " << f << endl;
    cout << "double: " << d << endl;
    cout << "long double: " << ld << endl;
    
    // Special values
    cout << "sqrt(-1): " << sqrt(-1) << endl;  // NaN (Not a Number)
    cout << "1.0/0.0: " << 1.0/0.0 << endl;    // inf (Infinity)
    cout << "-1.0/0.0: " << -1.0/0.0 << endl;  // -inf
    
    return 0;
}

Floating-Point Operations

double a = 2.5, b = 1.5;
cout << "a + b = " << (a + b) << endl;  // 4.0
cout << "a - b = " << (a - b) << endl;  // 1.0
cout << "a * b = " << (a * b) << endl;  // 3.75
cout << "a / b = " << (a / b) << endl;  // 1.66667

// Mathematical functions
cout << "sqrt(a) = " << sqrt(a) << endl;
cout << "pow(a, b) = " << pow(a, b) << endl;
cout << "sin(pi/2) = " << sin(M_PI/2) << endl;

Type Conversion

// String to floating-point
string str = "3.14159";
double pi = stod(str);

// Floating-point to integer (truncation)
double d = 3.99;
int i = static_cast<int>(d);  // i becomes 3

// Integer to floating-point
int x = 5;
double y = static_cast<double>(x);  // y becomes 5.0

Common Pitfalls

  • Floating-point numbers have limited precision and rounding errors
  • Comparing floating-point numbers for exact equality is often problematic
  • Operations with very large or very small numbers can cause overflow/underflow

Strings in C++

C++ provides the string class for working with text. Strings are sequences of characters and offer many convenient operations for text manipulation.

Creating and Using Strings

#include <iostream>
#include <string>
using namespace std;

int main() {
    // Different ways to create strings
    string s1 = "Hello";
    string s2("World");
    string s3(5, 'A');  // "AAAAA"
    string s4 = s1 + " " + s2;  // Concatenation
    
    cout << "s1: " << s1 << endl;
    cout << "s2: " << s2 << endl;
    cout << "s3: " << s3 << endl;
    cout << "s4: " << s4 << endl;
    
    // Accessing characters
    cout << "First character: " << s1[0] << endl;  // 'H'
    cout << "Second character: " << s1.at(1) << endl;  // 'e'
    
    // String length
    cout << "Length of s1: " << s1.length() << endl;
    cout << "Is s1 empty? " << (s1.empty() ? "Yes" : "No") << endl;
    
    return 0;
}

String Comparison and Searching

string str1 = "apple", str2 = "banana";
    
// Comparison
if (str1 == str2) {
    cout << "Strings are equal" << endl;
} else if (str1 < str2) {
    cout << str1 << " comes before " << str2 << endl;
} else {
    cout << str1 << " comes after " << str2 << endl;
}

// Searching
string text = "Hello World";
size_t pos = text.find("World");
if (pos != string::npos) {
    cout << "Found 'World' at position " << pos << endl;
}

String Modification

string str = "Hello";
    
// Append
str.append(" World");
str += "!";  // Same as above
    
// Insert
str.insert(5, " C++");
    
// Replace
str.replace(0, 5, "Hi");  // Replace 5 chars from position 0
    
// Erase
str.erase(2, 4);  // Erase 4 chars from position 2
    
cout << "Modified string: " << str << endl;

Common Pitfalls

  • Accessing characters beyond string length with [] causes undefined behavior
  • Use at() for bounds-checked access
  • C-style strings (char*) and C++ strings (std::string) are different types

String Functions in C++

The C++ string class provides many useful member functions for string manipulation, including trimming, searching, replacing, and converting.

Substring Operations

#include <iostream>
#include <string>
using namespace std;

int main() {
    string text = "Hello World";
    
    // Extract substring
    string sub = text.substr(6, 5);  // From position 6, 5 characters
    cout << "Substring: " << sub << endl;  // "World"
    
    // Find operations
    size_t pos1 = text.find('o');        // First 'o'
    size_t pos2 = text.find('o', 5);     // First 'o' after position 5
    size_t pos3 = text.rfind('o');       // Last 'o'
    
    cout << "First 'o': " << pos1 << endl;  // 4
    cout << "Second 'o': " << pos2 << endl; // 7
    cout << "Last 'o': " << pos3 << endl;   // 7
    
    return 0;
}

String Manipulation

// Trimming (C++ doesn't have built-in trim, but we can implement)
string trim(const string& str) {
    size_t start = str.find_first_not_of(" \t\n\r");
    size_t end = str.find_last_not_of(" \t\n\r");
    return (start == string::npos) ? "" : str.substr(start, end - start + 1);
}

string text = "   hello world   ";
cout << "Trimmed: '" << trim(text) << "'" << endl;

// Case conversion
#include <algorithm>
#include <cctype>

string mixed = "Hello World";
transform(mixed.begin(), mixed.end(), mixed.begin(), ::toupper);
cout << "Uppercase: " << mixed << endl;

transform(mixed.begin(), mixed.end(), mixed.begin(), ::tolower);
cout << "Lowercase: " << mixed << endl;

String Splitting and Joining

// Splitting a string
string data = "apple,banana,cherry";
size_t start = 0, end;
vector<string> fruits;

while ((end = data.find(',', start)) != string::npos) {
    fruits.push_back(data.substr(start, end - start));
    start = end + 1;
}
fruits.push_back(data.substr(start));

// Joining strings
string joined;
for (size_t i = 0; i < fruits.size(); ++i) {
    if (i > 0) joined += "-";
    joined += fruits[i];
}
cout << "Joined: " << joined << endl;

Common Pitfalls

  • find() returns string::npos when pattern is not found
  • Always check return value of find() before using the position
  • String manipulation functions often create new strings rather than modifying in place

String Formatting in C++

C++ provides several ways to format strings, from traditional C-style functions to modern C++ approaches with string streams and format libraries.

Using String Streams

#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;

int main() {
    string name = "Alice";
    int age = 25;
    double score = 95.5;
    
    // Using stringstream
    stringstream ss;
    ss << "Name: " << name << ", Age: " << age << ", Score: " << fixed << setprecision(1) << score;
    string result = ss.str();
    cout << result << endl;
    
    // Extracting from stringstream
    string data = "123 45.67 hello";
    stringstream ss2(data);
    int n;
    double d;
    string s;
    ss2 >> n >> d >> s;
    cout << "Extracted: " << n << ", " << d << ", " << s << endl;
    
    return 0;
}

C-Style Formatting

#include <cstdio>

char buffer[100];
int count = 5;
double value = 3.14159;

// sprintf for string formatting
sprintf(buffer, "Count: %d, Value: %.2f", count, value);
cout << buffer << endl;

// snprintf for safer formatting (C++11)
snprintf(buffer, sizeof(buffer), "Count: %d, Value: %.2f", count, value);
cout << buffer << endl;

Modern C++ Formatting (C++20)

// C++20 format library
#include <format>

string name = "Bob";
int visits = 3;
double rating = 4.5;

// Using std::format (C++20)
// string message = format("Hello {}, you have {} visits with rating {:.1f}", name, visits, rating);
// cout << message << endl;

// Alternative: Use fmt library if C++20 not available
// cout << fmt::format("Hello {}, you have {} visits", name, visits) << endl;

Number Formatting

#include <iomanip>

double pi = 3.14159265358979;

cout << "Default: " << pi << endl;
cout << "Fixed: " << fixed << pi << endl;
cout << "Scientific: " << scientific << pi << endl;
cout << "Precision 2: " << fixed << setprecision(2) << pi << endl;

// Integer formatting
int number = 255;
cout << "Decimal: " << number << endl;
cout << "Hex: " << hex << number << endl;
cout << "Octal: " << oct << number << endl;

Common Pitfalls

  • C-style sprintf can cause buffer overflows if not careful
  • Always prefer snprintf over sprintf for safety
  • Format specifiers must match the variable types
  • String streams can be less efficient for simple formatting

Arrays in C++

Arrays are collections of elements of the same type stored in contiguous memory. C++ supports both fixed-size arrays and dynamic arrays.

Fixed-Size Arrays

#include <iostream>
using namespace std;

int main() {
    // Declaration and initialization
    int numbers[5] = {1, 2, 3, 4, 5};
    double prices[] = {1.99, 2.99, 3.49};  // Size inferred
    
    // Accessing elements
    cout << "First element: " << numbers[0] << endl;
    cout << "Last element: " << numbers[4] << endl;
    
    // Modifying elements
    numbers[2] = 100;
    
    // Array size
    int size = sizeof(numbers) / sizeof(numbers[0]);
    cout << "Array size: " << size << endl;
    
    // Iterating through array
    for (int i = 0; i < size; i++) {
        cout << "numbers[" << i << "] = " << numbers[i] << endl;
    }
    
    return 0;
}

Multi-dimensional Arrays

// 2D array (matrix)
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Accessing elements
cout << "Element at [1][2]: " << matrix[1][2] << endl;  // 6

// Nested loops for 2D array
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        cout << matrix[i][j] << " ";
    }
    cout << endl;
}

Array Limitations

// Arrays have fixed size
// Cannot use range-based for with pointer-decayed arrays
// No bounds checking by default

int arr[5] = {1, 2, 3, 4, 5};
// arr[10] = 100;  // Undefined behavior - no bounds checking!

Common Pitfalls

  • Array indices start at 0, not 1
  • No bounds checking - accessing out-of-bounds causes undefined behavior
  • Arrays decay to pointers when passed to functions
  • Cannot return arrays from functions directly

Array Indexing in C++

Array indexing allows access to individual elements in an array using their position. C++ uses zero-based indexing, meaning the first element is at index 0.

Basic Array Indexing

#include <iostream>
using namespace std;

int main() {
    int numbers[5] = {10, 20, 30, 40, 50};
    
    // Accessing elements by index
    cout << "numbers[0] = " << numbers[0] << endl;  // First element: 10
    cout << "numbers[2] = " << numbers[2] << endl;  // Third element: 30
    cout << "numbers[4] = " << numbers[4] << endl;  // Last element: 50
    
    // Modifying elements
    numbers[1] = 25;  // Change second element from 20 to 25
    numbers[3] = 45;  // Change fourth element from 40 to 45
    
    // Display modified array
    for (int i = 0; i < 5; i++) {
        cout << "numbers[" << i << "] = " << numbers[i] << endl;
    }
    
    return 0;
}

Multi-dimensional Array Indexing

// 2D array indexing
int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

cout << "matrix[0][1] = " << matrix[0][1] << endl;  // 2
cout << "matrix[1][2] = " << matrix[1][2] << endl;  // 6

// 3D array indexing
int cube[2][2][2] = {
    {{1, 2}, {3, 4}},
    {{5, 6}, {7, 8}}
};
cout << "cube[1][0][1] = " << cube[1][0][1] << endl;  // 6

Character Array Indexing

char word[] = "Hello";
cout << "word[0] = " << word[0] << endl;  // 'H'
cout << "word[4] = " << word[4] << endl;  // 'o'

// Strings are null-terminated
cout << "word[5] = " << (int)word[5] << endl;  // 0 (null character)

Common Pitfalls

  • Accessing out-of-bounds indices causes undefined behavior
  • Array indices start at 0, not 1
  • Negative indices are not allowed in standard arrays
  • No bounds checking by default

Array Functions in C++

C++ provides several ways to work with arrays, including standard library functions and algorithms from the STL.

Array Size and Basic Operations

#include <iostream>
#include <algorithm>  // For std::sort, std::fill, etc.
using namespace std;

int main() {
    int arr[] = {5, 2, 8, 1, 9};
    int size = sizeof(arr) / sizeof(arr[0]);
    
    // Get array size
    cout << "Array size: " << size << endl;
    
    // Find maximum and minimum
    int* maxPtr = max_element(arr, arr + size);
    int* minPtr = min_element(arr, arr + size);
    cout << "Max: " << *maxPtr << ", Min: " << *minPtr << endl;
    
    // Sort array
    sort(arr, arr + size);
    cout << "Sorted array: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    return 0;
}

Array Manipulation Functions

#include <cstring>  // For C-style string functions

// Working with character arrays
char str1[20] = "Hello";
char str2[20] = "World";

// Copy strings
strcpy(str1, "Hello");  // Copy "Hello" to str1
strcat(str1, " ");      // Append space
strcat(str1, str2);     // Append "World"
cout << "Concatenated: " << str1 << endl;

// Compare strings
if (strcmp(str1, str2) == 0) {
    cout << "Strings are equal" << endl;
} else {
    cout << "Strings are different" << endl;
}

// Get string length
cout << "Length: " << strlen(str1) << endl;

Array Algorithms

#include <numeric>  // For std::accumulate

int numbers[] = {1, 2, 3, 4, 5};
int size = 5;

// Sum of elements
int sum = accumulate(numbers, numbers + size, 0);
cout << "Sum: " << sum << endl;

// Fill array with value
fill(numbers, numbers + size, 10);
cout << "After fill: ";
for (int i = 0; i < size; i++) {
    cout << numbers[i] << " ";
}
cout << endl;

// Reverse array
reverse(numbers, numbers + size);
cout << "Reversed: ";
for (int i = 0; i < size; i++) {
    cout << numbers[i] << " ";
}
cout << endl;

Common Pitfalls

  • C-style arrays don't know their own size - you must track it separately
  • Arrays decay to pointers when passed to functions
  • C-string functions don't check buffer sizes, leading to potential overflows
  • Always ensure arrays are large enough for operations

Pointers in C++

Pointers are variables that store memory addresses. They provide direct access to memory and are essential for dynamic memory allocation and efficient data structures.

Basic Pointer Operations

#include <iostream>
using namespace std;

int main() {
    int number = 42;
    int* ptr = &number;  // ptr stores address of number
    
    cout << "Value of number: " << number << endl;
    cout << "Address of number: " << &number << endl;
    cout << "Value of ptr: " << ptr << endl;
    cout << "Value at ptr: " << *ptr << endl;  // Dereferencing
    
    // Modifying through pointer
    *ptr = 100;
    cout << "New value of number: " << number << endl;
    
    // Pointer to different types
    double pi = 3.14159;
    double* dptr = π
    cout << "Value at dptr: " << *dptr << endl;
    
    return 0;
}

Pointers and Arrays

int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;  // Array name decays to pointer to first element

cout << "First element: " << *ptr << endl;        // 10
cout << "Second element: " << *(ptr + 1) << endl; // 20
cout << "Third element: " << ptr[2] << endl;      // 30 (array syntax works)

// Pointer arithmetic
ptr++;  // Move to next element
cout << "After increment: " << *ptr << endl;  // 20

ptr += 2;  // Move two elements forward
cout << "After +2: " << *ptr << endl;        // 40

Null Pointers and Safety

// Different ways to create null pointers
int* ptr1 = nullptr;  // C++11 preferred
int* ptr2 = 0;
int* ptr3 = NULL;

// Always check pointers before use
if (ptr1 != nullptr) {
    cout << *ptr1 << endl;
} else {
    cout << "Pointer is null" << endl;
}

// Dangerous: dereferencing null pointer
// *ptr1 = 5;  // CRASH! Undefined behavior

Common Pitfalls

  • Dereferencing null or uninitialized pointers causes crashes
  • Pointer arithmetic can easily go out of bounds
  • Memory leaks if dynamically allocated memory isn't freed
  • Dangling pointers after memory is freed

Pointer Arithmetic in C++

Pointer arithmetic allows you to navigate through memory by adding or subtracting from pointers. This is particularly useful with arrays and dynamic memory.

Basic Pointer Arithmetic

#include <iostream>
using namespace std;

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int* ptr = arr;
    
    cout << "Pointer at: " << ptr << endl;
    cout << "Value: " << *ptr << endl;  // 10
    
    ptr++;  // Move to next int (4 bytes forward typically)
    cout << "After ++: " << ptr << endl;
    cout << "Value: " << *ptr << endl;  // 20
    
    ptr += 2;  // Move two ints forward
    cout << "After += 2: " << ptr << endl;
    cout << "Value: " << *ptr << endl;  // 40
    
    ptr--;  // Move one int backward
    cout << "After --: " << ptr << endl;
    cout << "Value: " << *ptr << endl;  // 30
    
    return 0;
}

Pointer Differences and Comparisons

int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int* start = numbers;
int* end = numbers + 10;  // Pointer one past the end

// Calculate number of elements between pointers
int distance = end - start;
cout << "Distance: " << distance << " elements" << endl;

// Pointer comparisons
int* middle = numbers + 5;
if (start < middle) {
    cout << "start comes before middle" << endl;
}

if (middle < end) {
    cout << "middle comes before end" << endl;
}

// Iterate using pointers
for (int* p = start; p < end; p++) {
    cout << *p << " ";
}
cout << endl;

Pointer Arithmetic with Different Types

// Different types have different sizes
char chars[] = {'a', 'b', 'c', 'd', 'e'};
double doubles[] = {1.1, 2.2, 3.3, 4.4, 5.5};

char* cptr = chars;
double* dptr = doubles;

cout << "char pointer: " << (void*)cptr << endl;
cptr++;
cout << "after ++: " << (void*)cptr << endl;  // Moves 1 byte

cout << "double pointer: " << dptr << endl;
dptr++;
cout << "after ++: " << dptr << endl;  // Moves 8 bytes (typically)

Practical Applications

// Finding array length
template
size_t arraySize(T (&array)[N]) {
    return N;
}

int data[] = {1, 2, 3, 4, 5};
cout << "Array size: " << arraySize(data) << endl;

// String length using pointers
const char* str = "Hello";
const char* p = str;
while (*p != '\0') {
    p++;
}
cout << "String length: " << (p - str) << endl;

Common Pitfalls

  • Pointer arithmetic only valid within same array or one past the end
  • Adding two pointers is not allowed
  • Pointer arithmetic on void pointers is not allowed (unknown size)
  • Going beyond array bounds causes undefined behavior

Variables in C++

Variables are named storage locations in memory that hold data. C++ is a statically-typed language, meaning variable types must be declared explicitly.

Variable Declaration and Initialization

#include <iostream>
#include <string>
using namespace std;

int main() {
    // Declaration and initialization
    int age = 25;
    double price = 19.99;
    char grade = 'A';
    string name = "Alice";
    bool isActive = true;
    
    // Multiple variables
    int x = 5, y = 10, z = 15;
    
    // Different initialization styles
    int a = 10;     // Copy initialization
    int b(20);      // Direct initialization
    int c{30};      // Uniform initialization (C++11)
    int d = {40};   // Copy list initialization
    
    cout << "a: " << a << ", b: " << b << ", c: " << c << ", d: " << d << endl;
    
    return 0;
}

Variable Scope

int globalVar = 100;  // Global variable

void demoFunction() {
    int localVar = 50;  // Local variable
    cout << "Local: " << localVar << ", Global: " << globalVar << endl;
    
    {
        int blockVar = 25;  // Block scope
        cout << "Block: " << blockVar << endl;
    }
    // cout << blockVar << endl;  // Error: blockVar not accessible here
}

int main() {
    demoFunction();
    // cout << localVar << endl;  // Error: localVar not accessible here
    return 0;
}

Constants and Type Modifiers

// Constants
const double PI = 3.14159;
const int MAX_SIZE = 100;
// PI = 3.14;  // Error: const variable cannot be modified

// Type modifiers
unsigned int positiveOnly = 4000000000;
short smallNumber = 32767;
long bigNumber = 2147483647;
long long veryBigNumber = 9223372036854775807LL;

// Volatile (tells compiler variable may change unexpectedly)
volatile int hardwareRegister = 0;

// Auto type deduction (C++11)
auto number = 42;        // int
auto decimal = 3.14;     // double
auto text = "Hello";     // const char*
auto active = true;      // bool

Variable Lifetime and Storage Classes

// Automatic (default) - created when block entered, destroyed when exited
void automaticDemo() {
    int x = 10;  // Automatic storage
}

// Static - persists for program lifetime
void staticDemo() {
    static int count = 0;  // Initialized only once
    count++;
    cout << "Count: " << count << endl;
}

// External - accessible across files
extern int externalVar;  // Defined in another file

int main() {
    automaticDemo();
    staticDemo();  // Count: 1
    staticDemo();  // Count: 2
    staticDemo();  // Count: 3
    return 0;
}

Common Pitfalls

  • Uninitialized variables contain garbage values
  • Using variables outside their scope
  • Name shadowing (local variables hiding global ones)
  • Forgetting that char can be signed or unsigned depending on implementation

Conditional Statements: if, else if, else

Conditional statements allow your program to make decisions and execute different code blocks based on conditions.

Basic if Statement

#include <iostream>
using namespace std;

int main() {
    int number;
    cout << "Enter a number: ";
    cin >> number;
    
    // Simple if statement
    if (number > 0) {
        cout << "The number is positive" << endl;
    }
    
    // if-else statement
    if (number % 2 == 0) {
        cout << "The number is even" << endl;
    } else {
        cout << "The number is odd" << endl;
    }
    
    return 0;
}

if-else if-else Chain

int score;
cout << "Enter your score (0-100): ";
cin >> score;

if (score >= 90) {
    cout << "Grade: A" << endl;
} else if (score >= 80) {
    cout << "Grade: B" << endl;
} else if (score >= 70) {
    cout << "Grade: C" << endl;
} else if (score >= 60) {
    cout << "Grade: D" << endl;
} else {
    cout << "Grade: F" << endl;
}

Nested if Statements

int age;
bool hasLicense;
    
cout << "Enter your age: ";
cin >> age;
cout << "Do you have a license? (1 for yes, 0 for no): ";
cin >> hasLicense;
    
if (age >= 18) {
    if (hasLicense) {
        cout << "You can drive legally" << endl;
    } else {
        cout << "You need to get a license first" << endl;
    }
} else {
    cout << "You are too young to drive" << endl;
}

Conditional Operator (Ternary)

int a = 10, b = 20;
    
// Traditional if-else
int max;
if (a > b) {
    max = a;
} else {
    max = b;
}
    
// Using ternary operator
int max2 = (a > b) ? a : b;
    
cout << "Maximum: " << max2 << endl;
    
// Nested ternary (use sparingly!)
int x = 5, y = 10, z = 15;
int largest = (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
cout << "Largest: " << largest << endl;

Common Pitfalls

  • Using assignment = instead of equality == in conditions
  • Forgetting braces {} for multi-statement blocks
  • Dangling else problem (else matches with nearest if)
  • Overusing nested ternary operators reduces readability

for Loop in C++

The for loop is used when you know exactly how many times you want to iterate. It has three parts: initialization, condition, and increment.

Basic for Loop

#include <iostream>
using namespace std;

int main() {
    // Count from 1 to 5
    for (int i = 1; i <= 5; i++) {
        cout << "Count: " << i << endl;
    }
    
    // Countdown from 10 to 1
    for (int i = 10; i >= 1; i--) {
        cout << i << " ";
    }
    cout << endl;
    
    // Iterate through array
    int numbers[] = {2, 4, 6, 8, 10};
    for (int i = 0; i < 5; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;
    
    return 0;
}

Advanced for Loop Variations

// Multiple variables in initialization
for (int i = 0, j = 10; i < j; i++, j--) {
    cout << "i=" << i << ", j=" << j << endl;
}

// Omitting parts (infinite loop with break)
int count = 0;
for (;;) {
    cout << count << " ";
    count++;
    if (count >= 5) break;
}
cout << endl;

// Range-based for loop (C++11)
int arr[] = {1, 2, 3, 4, 5};
for (int value : arr) {
    cout << value << " ";
}
cout << endl;

// Range-based with references (modify original)
for (int& value : arr) {
    value *= 2;  // Double each element
}

Nested for Loops

// Multiplication table
for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= 5; j++) {
        cout << i * j << "\t";
    }
    cout << endl;
}

// Pattern printing
for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
        cout << "*";
    }
    cout << endl;
}

Common Pitfalls

  • Off-by-one errors (using <= instead of <)
  • Modifying loop counter inside the loop body
  • Infinite loops when condition never becomes false
  • Variable scope issues with loop counter declaration

while Loop in C++

The while loop repeats a block of code as long as a condition is true. It's useful when you don't know in advance how many iterations are needed.

Basic while Loop

#include <iostream>
using namespace std;

int main() {
    // Count from 1 to 5
    int i = 1;
    while (i <= 5) {
        cout << "Count: " << i << endl;
        i++;
    }
    
    // User input validation
    int number;
    cout << "Enter a positive number: ";
    cin >> number;
    
    while (number <= 0) {
        cout << "Invalid input. Enter a positive number: ";
        cin >> number;
    }
    cout << "Thank you! You entered: " << number << endl;
    
    return 0;
}

do-while Loop

// do-while executes at least once
char choice;
do {
    cout << "Menu:" << endl;
    cout << "1. Option One" << endl;
    cout << "2. Option Two" << endl;
    cout << "q. Quit" << endl;
    cout << "Enter your choice: ";
    cin >> choice;
    
    switch (choice) {
        case '1': cout << "Option One selected" << endl; break;
        case '2': cout << "Option Two selected" << endl; break;
        case 'q': cout << "Goodbye!" << endl; break;
        default: cout << "Invalid choice" << endl;
    }
} while (choice != 'q');

Practical while Loop Examples

// Reading until sentinel value
int value, sum = 0;
cout << "Enter numbers (0 to stop): ";
cin >> value;

while (value != 0) {
    sum += value;
    cin >> value;
}
cout << "Sum: " << sum << endl;

// File reading
#include <fstream>
ifstream file("data.txt");
string line;

while (getline(file, line)) {
    cout << line << endl;
}
file.close();

// Infinite loop with break
while (true) {
    int num;
    cout << "Enter a number (negative to quit): ";
    cin >> num;
    
    if (num < 0) break;
    cout << "Square: " << num * num << endl;
}

Common Pitfalls

  • Forgetting to update the loop control variable
  • Infinite loops when condition never becomes false
  • Using assignment = instead of comparison == in condition
  • Off-by-one errors in loop conditions

Loop Control Statements

C++ provides three loop control statements: break, continue, and goto (use sparingly). These statements alter the normal flow of loops.

break Statement

#include <iostream>
using namespace std;

int main() {
    // break exits the loop immediately
    for (int i = 1; i <= 10; i++) {
        if (i == 5) {
            break;  // Exit loop when i reaches 5
        }
        cout << i << " ";
    }
    cout << endl;  // Output: 1 2 3 4
    
    // Search example
    int numbers[] = {2, 4, 6, 8, 10, 12, 14};
    int target = 8;
    
    for (int i = 0; i < 7; i++) {
        if (numbers[i] == target) {
            cout << "Found " << target << " at position " << i << endl;
            break;  // Stop searching once found
        }
    }
    
    return 0;
}

continue Statement

// continue skips the rest of current iteration
for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
        continue;  // Skip even numbers
    }
    cout << i << " ";  // Only odd numbers printed
}
cout << endl;  // Output: 1 3 5 7 9

// Input validation with continue
int sum = 0;
for (int i = 0; i < 5; i++) {
    int num;
    cout << "Enter positive number " << (i + 1) << ": ";
    cin >> num;
    
    if (num <= 0) {
        cout << "Invalid number, skipping..." << endl;
        i--;  // Don't count this iteration
        continue;
    }
    sum += num;
}
cout << "Sum of positive numbers: " << sum << endl;

Nested Loops with Control Statements

// break only affects innermost loop
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i * j > 4) {
            cout << "Breaking inner loop" << endl;
            break;  // Only breaks inner loop
        }
        cout << i << " * " << j << " = " << i * j << endl;
    }
}

// Using labels with break (rarely needed)
outer: for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i * j > 4) {
            cout << "Breaking both loops" << endl;
            break outer;  // Break both loops
        }
        cout << i << " * " << j << " = " << i * j << endl;
    }
}

goto Statement (Use With Caution)

// goto can make code hard to read - use sparingly!
int attempts = 0;

retry:
if (attempts >= 3) {
    cout << "Too many attempts!" << endl;
    return 1;
}

int number;
cout << "Enter a positive number: ";
cin >> number;

if (number <= 0) {
    cout << "Invalid input. ";
    attempts++;
    goto retry;
}

cout << "Valid number: " << number << endl;

Common Pitfalls

  • Using break when continue is more appropriate
  • Overusing goto creates "spaghetti code"
  • break only exits the innermost loop
  • Placing code after continue that will never execute

Nested Loops in C++

Nested loops are loops within loops. They are essential for working with multi-dimensional data, matrices, and complex patterns.

Basic Nested Loops

#include <iostream>
using namespace std;

int main() {
    // Simple nested loop
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            cout << "(" << i << "," << j << ") ";
        }
        cout << endl;
    }
    /* Output:
    (1,1) (1,2) (1,3) 
    (2,1) (2,2) (2,3) 
    (3,1) (3,2) (3,3) */
    
    // Multiplication table
    cout << "\nMultiplication Table:" << endl;
    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= 5; j++) {
            cout << i * j << "\t";
        }
        cout << endl;
    }
    
    return 0;
}

Pattern Printing with Nested Loops

// Right triangle
int rows = 5;
for (int i = 1; i <= rows; i++) {
    for (int j = 1; j <= i; j++) {
        cout << "*";
    }
    cout << endl;
}

// Inverted triangle
for (int i = rows; i >= 1; i--) {
    for (int j = 1; j <lt<= i; j++) {
        cout << "*";
    }
    cout << endl;
}

// Pyramid
for (int i = 1; i <= rows; i++) {
    // Print spaces
    for (int j = 1; j <= rows - i; j++) {
        cout << " ";
    }
    // Print stars
    for (int j = 1; j <= 2 * i - 1; j++) {
        cout << "*";
    }
    cout << endl;
}

Working with 2D Arrays

// Matrix operations
const int ROWS = 3;
const int COLS = 3;
int matrix[ROWS][COLS] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Print matrix
cout << "Matrix:" << endl;
for (int i = 0; i < ROWS; i++) {
    for (int j = 0; j < COLS; j++) {
        cout << matrix[i][j] << " ";
    }
    cout << endl;
}

// Sum of all elements
int sum = 0;
for (int i = 0; i < ROWS; i++) {
    for (int j = 0; j < COLS; j++) {
        sum += matrix[i][j];
    }
}
cout << "Sum of all elements: " << sum << endl;

// Transpose matrix
cout << "Transpose:" << endl;
for (int i = 0; i < COLS; i++) {
    for (int j = 0; j < ROWS; j++) {
        cout << matrix[j][i] << " ";
    }
    cout << endl;
}

Mixed Loop Types

// while inside for
for (int i = 1; i <= 3; i++) {
    int j = 1;
    while (j <= 3) {
        cout << "i=" << i << ", j=" << j << endl;
        j++;
    }
}

// do-while inside for
for (int i = 1; i <= 2; i++) {
    int j = 1;
    do {
        cout << "i=" << i << ", j=" << j << endl;
        j++;
    } while (j <= 2);
}

Common Pitfalls

  • O(n²) time complexity can be inefficient for large data
  • Using wrong loop variables in inner vs outer loops
  • Forgetting to reset inner loop variables
  • Excessive nesting reduces readability (try to keep to 2-3 levels)

Functions in C++

Functions are reusable blocks of code that perform specific tasks. They help in organizing code, reducing duplication, and making programs more modular.

Function Definition and Declaration

#include <iostream>
#include <cmath>
using namespace std;

// Function declaration (prototype)
double calculateArea(double radius);

// Function definition
double calculateArea(double radius) {
    return M_PI * radius * radius;
}

// Function with multiple parameters
int addNumbers(int a, int b) {
    return a + b;
}

// Function without parameters
void greet() {
    cout << "Hello, welcome to the program!" << endl;
}

// Function without return value (void)
void printMessage(string message) {
    cout << "Message: " << message << endl;
}

int main() {
    greet();
    
    double radius = 5.0;
    double area = calculateArea(radius);
    cout << "Area of circle with radius " << radius << " is " << area << endl;
    
    int sum = addNumbers(10, 20);
    cout << "Sum: " << sum << endl;
    
    printMessage("Functions make code organized!");
    
    return 0;
}

Function Parameters

// Pass by value (default)
void incrementByValue(int x) {
    x++;  // Only modifies local copy
    cout << "Inside function: " << x << endl;
}

// Pass by reference
void incrementByReference(int &x) {
    x++;  // Modifies original variable
    cout << "Inside function: " << x << endl;
}

// Pass by pointer
void incrementByPointer(int *x) {
    (*x)++;  // Modifies original variable
    cout << "Inside function: " << *x << endl;
}

// Default parameters
void displayInfo(string name, int age = 18, string city = "Unknown") {
    cout << "Name: " << name << ", Age: " << age << ", City: " << city << endl;
}

int main() {
    int num = 5;
    
    incrementByValue(num);
    cout << "After pass by value: " << num << endl;  // Still 5
    
    incrementByReference(num);
    cout << "After pass by reference: " << num << endl;  // Now 6
    
    incrementByPointer(&num);
    cout << "After pass by pointer: " << num << endl;  // Now 7
    
    displayInfo("Alice");              // Uses default age and city
    displayInfo("Bob", 25);            // Uses default city
    displayInfo("Charlie", 30, "NYC"); // Uses all provided values
    
    return 0;
}

Function Overloading

// Same function name, different parameters
int multiply(int a, int b) {
    return a * b;
}

double multiply(double a, double b) {
    return a * b;
}

int multiply(int a, int b, int c) {
    return a * b * c;
}

// Overloaded print functions
void print(int value) {
    cout << "Integer: " << value << endl;
}

void print(double value) {
    cout << "Double: " << value << endl;
}

void print(string value) {
    cout << "String: " << value << endl;
}

int main() {
    cout << multiply(3, 4) << endl;       // Calls int version
    cout << multiply(2.5, 3.5) << endl;   // Calls double version
    cout << multiply(2, 3, 4) << endl;    // Calls three-parameter version
    
    print(10);
    print(3.14);
    print("Hello");
    
    return 0;
}

Recursive Functions

// Factorial using recursion
unsigned long long factorial(int n) {
    if (n <= 1) return 1;           // Base case
    return n * factorial(n - 1);    // Recursive case
}

// Fibonacci sequence
int fibonacci(int n) {
    if (n <= 1) return n;           // Base cases
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    cout << "Factorial of 5: " << factorial(5) << endl;
    
    cout << "Fibonacci sequence: ";
    for (int i = 0; i < 10; i++) {
        cout << fibonacci(i) << " ";
    }
    cout << endl;
    
    return 0;
}

Common Pitfalls

  • Forgetting return statement in non-void functions
  • Function declaration mismatch with definition
  • Infinite recursion without proper base case
  • Default parameters must be at the end of parameter list

Include Directives and Headers in C++

Header files contain declarations that can be shared across multiple source files. Include directives tell the preprocessor to include the contents of header files.

Standard Library Headers

// Input/output operations
#include <iostream>
using namespace std;

// String manipulation
#include <string>

// Mathematical functions
#include <cmath>

// C-style string functions
#include <cstring>

// Standard template library
#include <vector>
#include <algorithm>
#include <map>

// File operations
#include <fstream>

// Memory management
#include <memory>

// Time and date
#include <ctime>

int main() {
    // Examples using different headers
    string text = "Hello World";
    cout << "String length: " << text.length() << endl;
    
    cout << "Square root of 16: " << sqrt(16) << endl;
    
    vector<int> numbers = {1, 2, 3, 4, 5};
    cout << "Vector size: " << numbers.size() << endl;
    
    return 0;
}

Creating and Using Custom Headers

// math_utils.h (header file)
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

// Function declarations
int add(int a, int b);
double calculateCircleArea(double radius);
bool isPrime(int number);

#endif

// math_utils.cpp (implementation)
#include "math_utils.h"
#include <cmath>

int add(int a, int b) {
    return a + b;
}

double calculateCircleArea(double radius) {
    return M_PI * radius * radius;
}

bool isPrime(int number) {
    if (number <= 1) return false;
    for (int i = 2; i * i <= number; i++) {
        if (number % i == 0) return false;
    }
    return true;
}

// main.cpp
#include <iostream>
#include "math_utils.h"
using namespace std;

int main() {
    cout << "5 + 3 = " << add(5, 3) << endl;
    cout << "Area of circle with radius 2: " << calculateCircleArea(2) << endl;
    cout << "Is 17 prime? " << (isPrime(17) ? "Yes" : "No") << endl;
    return 0;
}

Include Guards and Pragma Once

// Traditional include guards
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Header content here
#endif

// Modern alternative (compiler-specific but widely supported)
#pragma once
// Header content here

/* Why use include guards?
   Prevents multiple inclusion of the same header
   Avoids redefinition errors
   Improves compilation speed */

Common Header Organization

// Typical header structure:

// 1. Include guards
#ifndef MY_CLASS_H
#define MY_CLASS_H

// 2. System includes (angle brackets)
#include <iostream>
#include <vector>

// 3. Forward declarations (if needed)
class OtherClass;

// 4. Namespace (if using)
using namespace std;

// 5. Class/function declarations
class MyClass {
private:
    int data;
public:
    MyClass(int value);
    void display() const;
    int getData() const;
};

// 6. Inline function definitions (if any)
inline int MyClass::getData() const {
    return data;
}

// 7. End include guard
#endif

Common Pitfalls

  • Missing include guards causing redefinition errors
  • Circular includes (A includes B, B includes A)
  • Including implementation files (.cpp) instead of headers
  • Forgetting to include necessary headers for used functionality

File Input/Output in C++

C++ provides file stream classes for reading from and writing to files. The main classes are ifstream (input), ofstream (output), and fstream (both).

Basic File Operations

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    // Writing to a file
    ofstream outFile("example.txt");
    if (outFile.is_open()) {
        outFile << "Hello, File!" << endl;
        outFile << "This is line 2" << endl;
        outFile << "Number: " << 42 << endl;
        outFile.close();
        cout << "File written successfully" << endl;
    } else {
        cout << "Error opening file for writing" << endl;
    }
    
    // Reading from a file
    ifstream inFile("example.txt");
    if (inFile.is_open()) {
        string line;
        while (getline(inFile, line)) {
            cout << line << endl;
        }
        inFile.close();
    } else {
        cout << "Error opening file for reading" << endl;
    }
    
    return 0;
}

Different File Opening Modes

// Various file opening modes
ofstream file1("output.txt");                    // Default: truncate
ofstream file2("output.txt", ios::app);         // Append mode
ofstream file3("output.txt", ios::ate);         // Start at end
ofstream file4("output.txt", ios::binary);      // Binary mode

fstream file5("data.txt", ios::in | ios::out);  // Read and write

// Example: Append to file
ofstream logFile("log.txt", ios::app);
if (logFile.is_open()) {
    logFile << "New log entry" << endl;
    logFile.close();
}

Reading Different Data Types

// Writing mixed data types
ofstream dataFile("data.txt");
if (dataFile.is_open()) {
    dataFile << "John Doe 25 85.5" << endl;
    dataFile << "Jane Smith 30 92.0" << endl;
    dataFile.close();
}

// Reading mixed data types
ifstream inputFile("data.txt");
if (inputFile.is_open()) {
    string firstName, lastName;
    int age;
    double score;
    
    while (inputFile >> firstName >> lastName >> age >> score) {
        cout << "Name: " << firstName << " " << lastName 
             << ", Age: " << age << ", Score: " << score << endl;
    }
    inputFile.close();
}

// Reading character by character
ifstream charFile("example.txt");
if (charFile.is_open()) {
    char ch;
    while (charFile.get(ch)) {
        cout << ch;
    }
    charFile.close();
}

Binary File Operations

#include <iomanip>

struct Person {
    char name[50];
    int age;
    double salary;
};

int main() {
    // Writing binary data
    Person p1 = {"Alice", 30, 50000.0};
    Person p2 = {"Bob", 25, 45000.0};
    
    ofstream binOut("people.dat", ios::binary);
    if (binOut.is_open()) {
        binOut.write(reinterpret_cast<char*>(&p1), sizeof(Person));
        binOut.write(reinterpret_cast<char*>(&p2), sizeof(Person));
        binOut.close();
    }
    
    // Reading binary data
    Person p;
    ifstream binIn("people.dat", ios::binary);
    if (binIn.is_open()) {
        while (binIn.read(reinterpret_cast<char*>(&p), sizeof(Person))) {
            cout << "Name: " << p.name << ", Age: " << p.age 
                 << ", Salary: " << fixed << setprecision(2) << p.salary << endl;
        }
        binIn.close();
    }
    
    return 0;
}

File Position Operations

// Working with file positions
fstream file("data.txt", ios::in | ios::out);
if (file.is_open()) {
    // Get current position
    streampos pos = file.tellg();
    cout << "Current position: " << pos << endl;
    
    // Seek to beginning
    file.seekg(0, ios::beg);
    
    // Seek to end
    file.seekg(0, ios::end);
    streampos size = file.tellg();
    cout << "File size: " << size << " bytes" << endl;
    
    // Seek to specific position
    file.seekg(10, ios::beg);  // 10 bytes from beginning
    
    file.close();
}

Common Pitfalls

  • Forgetting to check if file opened successfully
  • Not closing files (automatic in destructors, but good practice)
  • Mixing text and binary modes
  • Assuming file operations always succeed - always check for errors

Standard Template Library (STL) Introduction

The Standard Template Library (STL) is a powerful set of C++ template classes that provide common data structures and algorithms. It includes containers, iterators, and algorithms.

STL Components Overview

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

int main() {
    // Containers - store data
    vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // Iterators - access elements
    cout << "Using iterators: ";
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // Algorithms - operate on data
    sort(numbers.begin(), numbers.end());
    
    cout << "Sorted: ";
    for (int num : numbers) {
        cout << num << " ";
    }
    cout << endl;
    
    // Function objects (functors)
    struct Square {
        int operator()(int x) const { return x * x; }
    };
    
    Square square;
    cout << "5 squared: " << square(5) << endl;
    
    return 0;
}

Key STL Concepts

// 1. Templates - generic programming
template
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// 2. Iterators - uniform access to containers
vector words = {"apple", "banana", "cherry"};
for (auto it = words.rbegin(); it != words.rend(); ++it) {
    cout << *it << " ";  // Reverse iteration
}
cout << endl;

// 3. Algorithms - reusable operations
vector<int> data = {1, 2, 3, 4, 5};
vector<int> result;
transform(data.begin(), data.end(), back_inserter(result),
          [](int x) { return x * x; });

// 4. Function objects and lambdas
auto isEven = [](int x) { return x % 2 == 0; };
int evenCount = count_if(data.begin(), data.end(), isEven);

STL Header Organization

// Sequence containers
#include <vector>    // Dynamic array
#include <deque>     // Double-ended queue
#include <list>      // Doubly-linked list
#include <forward_list> // Singly-linked list
#include <array>     // Fixed-size array

// Associative containers
#include <set>       // Ordered unique elements
#include <map>       // Ordered key-value pairs
#include <unordered_set> // Hash-based set
#include <unordered_map> // Hash-based map

// Container adapters
#include <stack>     // LIFO
#include <queue>     // FIFO
#include <priority_queue> // Priority-based

// Algorithms and utilities
#include <algorithm> // Sort, search, etc.
#include <numeric>   // Numeric operations
#include <iterator>  // Iterator utilities
#include <functional> // Function objects

Benefits of Using STL

// 1. Type safety
vector<int> v1;
// v1.push_back("hello");  // Compile error - type mismatch

// 2. Memory management
vector<int> numbers;
numbers.reserve(100);  // Pre-allocate memory
for (int i = 0; i < 100; i++) {
    numbers.push_back(i);  // Automatic memory management
}

// 3. Algorithm efficiency
vector<int> largeData(1000000);
sort(largeData.begin(), largeData.end());  // Highly optimized

// 4. Code reuse
template<typename Container>
void printContainer(const Container& c) {
    for (const auto& element : c) {
        cout << element << " ";
    }
    cout << endl;
}

// Works with any container type
vector<int> v = {1, 2, 3};
list<string> l = {"a", "b", "c"};
printContainer(v);
printContainer(l);

Common Pitfalls

  • Invalidating iterators by modifying containers
  • Not checking end() iterators before use
  • Using wrong comparison functions for custom types
  • Forgetting that algorithms work with iterator ranges, not containers directly

STL Containers

STL containers are data structures that store collections of objects. They can be categorized as sequence containers, associative containers, and container adapters.

Sequence Containers

#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <array>
using namespace std;

int main() {
    // Vector - dynamic array
    vector vec = {1, 2, 3};
    vec.push_back(4);           // Add to end
    vec.insert(vec.begin(), 0); // Insert at beginning
    cout << "Vector: ";
    for (int v : vec) cout << v << " ";
    cout << endl;
    
    // Deque - double-ended queue
    deque dq = {2, 3, 4};
    dq.push_front(1);          // Add to front
    dq.push_back(5);           // Add to end
    cout << "Deque: ";
    for (int d : dq) cout << d << " ";
    cout << endl;
    
    // List - doubly-linked list
    list lst = {1, 2, 4};
    auto it = lst.begin();
    advance(it, 2);
    lst.insert(it, 3);         // Insert in middle
    cout << "List: ";
    for (int l : lst) cout << l << " ";
    cout << endl;
    
    // Array - fixed size
    array<int, 5> arr = {1, 2, 3, 4, 5};
    cout << "Array size: " << arr.size() << endl;
    
    return 0;
}

Associative Containers

#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>

int main() {
    // Set - unique elements, sorted
    set s = {3, 1, 4, 1, 5};  // Duplicates removed
    s.insert(2);
    cout << "Set: ";
    for (int val : s) cout << val << " ";  // 1 2 3 4 5
    cout << endl;
    
    // Map - key-value pairs, sorted by key
    map ages;
    ages["Alice"] = 25;
    ages["Bob"] = 30;
    ages["Charlie"] = 35;
    
    cout << "Ages:" << endl;
    for (const auto& pair : ages) {
        cout << pair.first << ": " << pair.second << endl;
    }
    
    // Unordered set - hash-based
    unordered_set us = {"apple", "banana", "cherry"};
    
    // Unordered map - hash-based
    unordered_map prices;
    prices["apple"] = 1.99;
    prices["banana"] = 0.99;
    
    return 0;
}

Container Adapters

#include <stack>
#include <queue>
#include <priority_queue>

int main() {
    // Stack - LIFO (Last In First Out)
    stack st;
    st.push(1);
    st.push(2);
    st.push(3);
    cout << "Stack top: " << st.top() << endl;  // 3
    st.pop();
    cout << "After pop: " << st.top() << endl;  // 2
    
    // Queue - FIFO (First In First Out)
    queue q;
    q.push("first");
    q.push("second");
    q.push("third");
    cout << "Queue front: " << q.front() << endl;  // first
    q.pop();
    cout << "After pop: " << q.front() << endl;    // second
    
    // Priority Queue - highest priority first
    priority_queue pq;
    pq.push(30);
    pq.push(10);
    pq.push(20);
    cout << "Priority queue: ";
    while (!pq.empty()) {
        cout << pq.top() << " ";  // 30 20 10
        pq.pop();
    }
    cout << endl;
    
    return 0;
}

Container Operations and Performance

// Common operations across containers
vector v = {1, 2, 3, 4, 5};

// Size operations
cout << "Size: " << v.size() << endl;
cout << "Empty: " << (v.empty() ? "Yes" : "No") << endl;

// Element access
cout << "Front: " << v.front() << endl;
cout << "Back: " << v.back() << endl;
cout << "Element at 2: " << v[2] << endl;

// Modifiers
v.push_back(6);
v.pop_back();
v.insert(v.begin() + 2, 99);
v.erase(v.begin() + 1);

// Performance characteristics
/*
Container    | Insert | Delete | Access | Search
-------------|--------|--------|--------|--------
vector       | O(1)*  | O(1)*  | O(1)   | O(n)
deque        | O(1)   | O(1)   | O(1)   | O(n)
list         | O(1)   | O(1)   | O(n)   | O(n)
set/map      | O(log n)|O(log n)| O(log n)|O(log n)
unordered_*  | O(1)*  | O(1)*  | O(1)   | O(1)*
* = amortized constant time
*/

Common Pitfalls

  • Using [] operator with map creates elements
  • Invalidating iterators when modifying containers
  • Forgetting that associative containers are sorted
  • Not reserving space in vector when known size

STL Algorithms

The STL provides a rich set of algorithms that operate on ranges of elements. These algorithms are generic and work with any container that provides iterators.

Non-modifying Algorithms

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    vector numbers = {1, 2, 3, 4, 5, 2, 3, 2};
    
    // Count occurrences
    int count2 = count(numbers.begin(), numbers.end(), 2);
    cout << "Count of 2: " << count2 << endl;
    
    // Find element
    auto it = find(numbers.begin(), numbers.end(), 3);
    if (it != numbers.end()) {
        cout << "Found 3 at position: " << distance(numbers.begin(), it) << endl;
    }
    
    // Check conditions
    bool allPositive = all_of(numbers.begin(), numbers.end(), 
                             [](int x) { return x > 0; });
    cout << "All positive: " << (allPositive ? "Yes" : "No") << endl;
    
    // Accumulate (sum)
    int sum = accumulate(numbers.begin(), numbers.end(), 0);
    cout << "Sum: " << sum << endl;
    
    return 0;
}

Modifying Algorithms

vector data = {1, 2, 3, 4, 5};
    
// Transform (apply function to each element)
vector squared;
transform(data.begin(), data.end(), back_inserter(squared),
          [](int x) { return x * x; });
    
// Fill with value
fill(data.begin(), data.end(), 0);
    
// Generate with function
vector randomNumbers(5);
generate(randomNumbers.begin(), randomNumbers.end(), 
         []() { return rand() % 100; });
    
// Replace values
replace(data.begin(), data.end(), 0, 10);
    
// Remove elements
vector withDuplicates = {1, 2, 2, 3, 2, 4};
auto newEnd = remove(withDuplicates.begin(), withDuplicates.end(), 2);
withDuplicates.erase(newEnd, withDuplicates.end());

Sorting and Searching

vector nums = {5, 2, 8, 1, 9, 3};
    
// Sort
sort(nums.begin(), nums.end());
cout << "Sorted: ";
for (int n : nums) cout << n << " ";
cout << endl;
    
// Binary search (requires sorted range)
bool found = binary_search(nums.begin(), nums.end(), 5);
cout << "5 found: " << (found ? "Yes" : "No") << endl;
    
// Lower and upper bound
auto lb = lower_bound(nums.begin(), nums.end(), 3);  // First >= 3
auto ub = upper_bound(nums.begin(), nums.end(), 3);  // First > 3
cout << "Range for 3: [" << distance(nums.begin(), lb) << ", " 
     << distance(nums.begin(), ub) << ")" << endl;
    
// Partial sort
vector large = {9, 3, 6, 1, 7, 2, 8, 4, 5};
partial_sort(large.begin(), large.begin() + 3, large.end());
cout << "First 3 sorted: ";
for (int i = 0; i < 3; i++) cout << large[i] << " ";
cout << endl;

Set Operations and Permutations

vector set1 = {1, 2, 3, 4, 5};
vector set2 = {3, 4, 5, 6, 7};
vector result;
    
// Union
set_union(set1.begin(), set1.end(), set2.begin(), set2.end(),
          back_inserter(result));
cout << "Union: ";
for (int n : result) cout << n << " ";
cout << endl;
    
// Intersection
result.clear();
set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),
                 back_inserter(result));
cout << "Intersection: ";
for (int n : result) cout << n << " ";
cout << endl;
    
// Permutations
vector perm = {1, 2, 3};
cout << "Permutations: " << endl;
do {
    for (int n : perm) cout << n << " ";
    cout << endl;
} while (next_permutation(perm.begin(), perm.end()));

Custom Comparisons and Function Objects

// Custom sorting
vector words = {"apple", "banana", "cherry", "date"};
sort(words.begin(), words.end(), 
     [](const string& a, const string& b) {
         return a.length() < b.length();  // Sort by length
     });
    
// Using function objects
struct CaseInsensitive {
    bool operator()(const string& a, const string& b) const {
        return lexicographical_compare(
            a.begin(), a.end(), b.begin(), b.end(),
            [](char c1, char c2) { return tolower(c1) < tolower(c2); }
        );
    }
};
    
set caseInsensitiveSet = {"Apple", "banana", "apple"};
// Only one "apple" will be stored

Common Pitfalls

  • Using invalid iterator ranges
  • Modifying containers while iterating
  • Using unsorted ranges with binary search algorithms
  • Forgetting that algorithms don't change container size

Classes and Objects in C++

Classes are user-defined types that encapsulate data and functions. Objects are instances of classes. C++ supports object-oriented programming with features like encapsulation, inheritance, and polymorphism.

Basic Class Definition

#include <iostream>
#include <string>
using namespace std;

class Person {
private:    // Access specifier - only class members can access
    string name;
    int age;
    
public:     // Public interface
    // Constructor
    Person(string n, int a) : name(n), age(a) {}
    
    // Member functions
    void display() const {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
    
    // Getter functions
    string getName() const { return name; }
    int getAge() const { return age; }
    
    // Setter functions
    void setName(string n) { name = n; }
    void setAge(int a) { 
        if (a >= 0) age = a;  // Validation
    }
};

int main() {
    // Create objects
    Person alice("Alice", 25);
    Person bob("Bob", 30);
    
    // Use objects
    alice.display();
    bob.display();
    
    // Use getters and setters
    alice.setAge(26);
    cout << "Alice's new age: " << alice.getAge() << endl;
    
    return 0;
}

Constructors and Destructors

class Rectangle {
private:
    double width, height;
    
public:
    // Default constructor
    Rectangle() : width(1.0), height(1.0) {}
    
    // Parameterized constructor
    Rectangle(double w, double h) : width(w), height(h) {}
    
    // Copy constructor
    Rectangle(const Rectangle& other) 
        : width(other.width), height(other.height) {
        cout << "Copy constructor called" << endl;
    }
    
    // Destructor
    ~Rectangle() {
        cout << "Rectangle destroyed: " << width << "x" << height << endl;
    }
    
    // Member function
    double area() const {
        return width * height;
    }
};

int main() {
    Rectangle rect1;           // Default constructor
    Rectangle rect2(5.0, 3.0); // Parameterized constructor
    Rectangle rect3 = rect2;   // Copy constructor
    
    cout << "Area: " << rect2.area() << endl;
    return 0;
    // Destructors called automatically when objects go out of scope
}

Static Members and Friends

class BankAccount {
private:
    string owner;
    double balance;
    static int totalAccounts;  // Static member - shared by all objects
    
public:
    BankAccount(string o, double b) : owner(o), balance(b) {
        totalAccounts++;  // Increment when account created
    }
    
    ~BankAccount() {
        totalAccounts--;  // Decrement when account destroyed
    }
    
    // Static member function
    static int getTotalAccounts() {
        return totalAccounts;
    }
    
    // Friend function - can access private members
    friend void transfer(BankAccount& from, BankAccount& to, double amount);
};

// Define static member
int BankAccount::totalAccounts = 0;

// Friend function definition
void transfer(BankAccount& from, BankAccount& to, double amount) {
    if (from.balance >= amount) {
        from.balance -= amount;
        to.balance += amount;
    }
}

int main() {
    BankAccount acc1("Alice", 1000);
    BankAccount acc2("Bob", 500);
    
    cout << "Total accounts: " << BankAccount::getTotalAccounts() << endl;
    
    transfer(acc1, acc2, 200);
    
    return 0;
}

Operator Overloading

class Complex {
private:
    double real, imag;
    
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // Overload + operator
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
    
    // Overload - operator
    Complex operator-(const Complex& other) const {
        return Complex(real - other.real, imag - other.imag);
    }
    
    // Overload << operator for output
    friend ostream& operator<<(ostream& os, const Complex& c) {
        os << c.real << " + " << c.imag << "i";
        return os;
    }
    
    // Overload == operator
    bool operator==(const Complex& other) const {
        return real == other.real && imag == other.imag;
    }
};

int main() {
    Complex c1(2.0, 3.0);
    Complex c2(1.0, 2.0);
    
    Complex sum = c1 + c2;
    Complex diff = c1 - c2;
    
    cout << "c1: " << c1 << endl;
    cout << "c2: " << c2 << endl;
    cout << "Sum: " << sum << endl;
    cout << "Difference: " << diff << endl;
    cout << "Equal? " << (c1 == c2 ? "Yes" : "No") << endl;
    
    return 0;
}

Common Pitfalls

  • Forgetting to initialize members in constructors
  • Not following rule of three/five for resource management
  • Overusing friend functions breaks encapsulation
  • Not making destructors virtual in base classes

Inheritance in C++

Inheritance allows a class to inherit properties and behaviors from another class. This promotes code reuse and establishes hierarchical relationships between classes.

Basic Inheritance

#include <iostream>
#include <string>
using namespace std;

// Base class
class Animal {
protected:  // Accessible by derived classes
    string name;
    int age;
    
public:
    Animal(string n, int a) : name(n), age(a) {}
    
    void eat() {
        cout << name << " is eating." << endl;
    }
    
    void sleep() {
        cout << name << " is sleeping." << endl;
    }
    
    // Virtual function for polymorphism
    virtual void makeSound() {
        cout << name << " makes a generic sound." << endl;
    }
    
    // Virtual destructor for proper cleanup
    virtual ~Animal() = default;
};

// Derived class
class Dog : public Animal {  // Public inheritance
private:
    string breed;
    
public:
    Dog(string n, int a, string b) : Animal(n, a), breed(b) {}
    
    // Override base class function
    void makeSound() override {
        cout << name << " barks: Woof! Woof!" << endl;
    }
    
    void fetch() {
        cout << name << " is fetching the ball." << endl;
    }
    
    void displayInfo() {
        cout << "Dog: " << name << ", Age: " << age 
             << ", Breed: " << breed << endl;
    }
};

// Another derived class
class Cat : public Animal {
public:
    Cat(string n, int a) : Animal(n, a) {}
    
    void makeSound() override {
        cout << name << " meows: Meow! Meow!" << endl;
    }
    
    void climb() {
        cout << name << " is climbing a tree." << endl;
    }
};

int main() {
    Dog myDog("Buddy", 3, "Golden Retriever");
    Cat myCat("Whiskers", 2);
    
    myDog.displayInfo();
    myDog.eat();
    myDog.makeSound();
    myDog.fetch();
    
    cout << endl;
    
    myCat.eat();
    myCat.makeSound();
    myCat.climb();
    
    return 0;
}

Types of Inheritance

class Base {
public:
    int publicVar;
protected:
    int protectedVar;
private:
    int privateVar;
};

// Public inheritance
class PublicDerived : public Base {
    // publicVar remains public
    // protectedVar remains protected  
    // privateVar is inaccessible
};

// Protected inheritance  
class ProtectedDerived : protected Base {
    // publicVar becomes protected
    // protectedVar remains protected
    // privateVar is inaccessible
};

// Private inheritance
class PrivateDerived : private Base {
    // publicVar becomes private
    // protectedVar becomes private
    // privateVar is inaccessible
};

// Multiple inheritance
class A {
public:
    void funcA() { cout << "Function A" << endl; }
};

class B {
public:
    void funcB() { cout << "Function B" << endl; }
};

class MultipleDerived : public A, public B {
public:
    void funcC() { 
        funcA();  // From A
        funcB();  // From B
        cout << "Function C" << endl; 
    }
};

Constructor and Destructor Order

class BaseClass {
public:
    BaseClass() { cout << "BaseClass constructor" << endl; }
    virtual ~BaseClass() { cout << "BaseClass destructor" << endl; }
};

class DerivedClass : public BaseClass {
public:
    DerivedClass() { cout << "DerivedClass constructor" << endl; }
    ~DerivedClass() { cout << "DerivedClass destructor" << endl; }
};

class MostDerived : public DerivedClass {
public:
    MostDerived() { cout << "MostDerived constructor" << endl; }
    ~MostDerived() { cout << "MostDerived destructor" << endl; }
};

int main() {
    cout << "Creating MostDerived object:" << endl;
    MostDerived obj;
    cout << "Destroying MostDerived object:" << endl;
    return 0;
}
/* Output:
Creating MostDerived object:
BaseClass constructor
DerivedClass constructor
MostDerived constructor
Destroying MostDerived object:
MostDerived destructor
DerivedClass destructor
BaseClass destructor
*/

Virtual Functions and Abstract Classes

// Abstract base class (cannot be instantiated)
class Shape {
protected:
    double x, y;
    
public:
    Shape(double xCoord, double yCoord) : x(xCoord), y(yCoord) {}
    
    // Pure virtual function - makes class abstract
    virtual double area() const = 0;
    
    // Virtual function with implementation
    virtual void display() const {
        cout << "Shape at (" << x << ", " << y << ")" << endl;
    }
    
    virtual ~Shape() = default;
};

class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double x, double y, double r) : Shape(x, y), radius(r) {}
    
    // Must implement pure virtual function
    double area() const override {
        return 3.14159 * radius * radius;
    }
    
    void display() const override {
        cout << "Circle at (" << x << ", " << y 
             << ") with radius " << radius << endl;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(double x, double y, double w, double h) 
        : Shape(x, y), width(w), height(h) {}
    
    double area() const override {
        return width * height;
    }
    
    void display() const override {
        cout << "Rectangle at (" << x << ", " << y 
             << ") " << width << "x" << height << endl;
    }
};

int main() {
    // Shape s(1, 2);  // Error: cannot instantiate abstract class
    
    Circle circle(0, 0, 5);
    Rectangle rect(2, 3, 4, 5);
    
    circle.display();
    cout << "Area: " << circle.area() << endl;
    
    rect.display();
    cout << "Area: " << rect.area() << endl;
    
    // Using base class pointers
    Shape* shapes[] = {&circle, &rect};
    for (Shape* shape : shapes) {
        shape->display();
        cout << "Area: " << shape->area() << endl;
    }
    
    return 0;
}

Common Pitfalls

  • Not making destructors virtual in polymorphic base classes
  • Slicing problem when copying derived objects to base objects
  • Diamond problem with multiple inheritance
  • Forgetting to call base class constructors in derived classes

Polymorphism in C++

Polymorphism allows objects of different types to be treated as objects of a common base type. C++ supports compile-time (static) and runtime (dynamic) polymorphism.

Runtime Polymorphism (Virtual Functions)

#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class Animal {
public:
    virtual void speak() const {
        cout << "Some animal sound" << endl;
    }
    
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() const override {
        cout << "Woof! Woof!" << endl;
    }
};

class Cat : public Animal {
public:
    void speak() const override {
        cout << "Meow! Meow!" << endl;
    }
};

class Bird : public Animal {
public:
    void speak() const override {
        cout << "Chirp! Chirp!" << endl;
    }
};

int main() {
    vector<unique_ptr<Animal>> animals;
    animals.push_back(make_unique());
    animals.push_back(make_unique());
    animals.push_back(make_unique());
    
    // Runtime polymorphism - calls appropriate derived class method
    for (const auto& animal : animals) {
        animal->speak();
    }
    
    return 0;
}

Compile-time Polymorphism (Templates)

// Function template - works with any type that supports + operator
template<typename T>
T add(const T& a, const T& b) {
    return a + b;
}

// Class template
template<typename T>
class Container {
private:
    T value;
    
public:
    Container(T v) : value(v) {}
    
    T getValue() const { return value; }
    void setValue(T v) { value = v; }
    
    void display() const {
        cout << "Value: " << value << endl;
    }
};

// Template specialization
template<>
class Container<string> {
private:
    string value;
    
public:
    Container(string v) : value(v) {}
    
    string getValue() const { return value; }
    void setValue(string v) { value = v; }
    
    void display() const {
        cout << "String: \"" << value << "\"" << endl;
    }
    
    void toUpperCase() {
        for (char& c : value) {
            c = toupper(c);
        }
    }
};

int main() {
    // Function template instantiation
    cout << "add(5, 3): " << add(5, 3) << endl;
    cout << "add(2.5, 3.7): " << add(2.5, 3.7) << endl;
    string s1 = "Hello", s2 = "World";
    cout << "add(strings): " << add(s1, s2) << endl;
    
    // Class template instantiation
    Container<int> intContainer(42);
    Container<double> doubleContainer(3.14);
    Container<string> stringContainer("Hello Templates!");
    
    intContainer.display();
    doubleContainer.display();
    stringContainer.display();
    
    stringContainer.toUpperCase();
    stringContainer.display();
    
    return 0;
}

Function Overloading

// Compile-time polymorphism through function overloading
class Calculator {
public:
    // Same function name, different parameters
    int add(int a, int b) {
        return a + b;
    }
    
    double add(double a, double b) {
        return a + b;
    }
    
    string add(const string& a, const string& b) {
        return a + b;
    }
    
    int add(int a, int b, int c) {
        return a + b + c;
    }
};

// Operator overloading
class Vector2D {
public:
    double x, y;
    
    Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
    
    // Overload + operator
    Vector2D operator+(const Vector2D& other) const {
        return Vector2D(x + other.x, y + other.y);
    }
    
    // Overload - operator
    Vector2D operator-(const Vector2D& other) const {
        return Vector2D(x - other.x, y - other.y);
    }
    
    // Overload * operator (scalar multiplication)
    Vector2D operator*(double scalar) const {
        return Vector2D(x * scalar, y * scalar);
    }
    
    // Overload << operator for output
    friend ostream& operator<<(ostream& os, const Vector2D& v) {
        os << "(" << v.x << ", " << v.y << ")";
        return os;
    }
};

int main() {
    Calculator calc;
    cout << "calc.add(2, 3): " << calc.add(2, 3) << endl;
    cout << "calc.add(2.5, 3.7): " << calc.add(2.5, 3.7) << endl;
    cout << "calc.add(1, 2, 3): " << calc.add(1, 2, 3) << endl;
    
    Vector2D v1(1, 2), v2(3, 4);
    Vector2D sum = v1 + v2;
    Vector2D diff = v1 - v2;
    Vector2D scaled = v1 * 2.5;
    
    cout << "v1: " << v1 << endl;
    cout << "v2: " << v2 << endl;
    cout << "v1 + v2: " << sum << endl;
    cout << "v1 - v2: " << diff << endl;
    cout << "v1 * 2.5: " << scaled << endl;
    
    return 0;
}

Virtual Function Mechanism

class Base {
public:
    virtual void func1() { cout << "Base::func1" << endl; }
    virtual void func2() { cout << "Base::func2" << endl; }
    void func3() { cout << "Base::func3" << endl; }  // Non-virtual
};

class Derived : public Base {
public:
    void func1() override { cout << "Derived::func1" << endl; }
    void func2() override { cout << "Derived::func2" << endl; }
    void func3() { cout << "Derived::func3" << endl; }  // Hides Base::func3
};

void demonstratePolymorphism() {
    Base* basePtr = new Derived();
    
    // Virtual function calls - runtime resolution
    basePtr->func1();  // Derived::func1
    basePtr->func2();  // Derived::func2
    
    // Non-virtual function call - compile-time resolution
    basePtr->func3();  // Base::func3 (not overridden)
    
    delete basePtr;
}

// Using typeid and dynamic_cast
void typeInformation(Base* ptr) {
    cout << "Type: " << typeid(*ptr).name() << endl;
    
    // Safe downcasting
    if (Derived* derivedPtr = dynamic_cast<Derived*>(ptr)) {
        cout << "Successfully cast to Derived" << endl;
        derivedPtr->func3();  // Now calls Derived::func3
    } else {
        cout << "Cast to Derived failed" << endl;
    }
}

Common Pitfalls

  • Object slicing when copying derived objects to base objects
  • Forgetting to make base class destructors virtual
  • Hiding instead of overriding functions (different signatures)
  • Using dynamic_cast without RTTI enabled

Advanced C++ Concepts

Advanced C++ features provide powerful tools for writing efficient, safe, and maintainable code. These include smart pointers, move semantics, lambda expressions, and more.

Smart Pointers

#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Resource {
private:
    string name;
    
public:
    Resource(string n) : name(n) {
        cout << "Resource " << name << " created" << endl;
    }
    
    ~Resource() {
        cout << "Resource " << name << " destroyed" << endl;
    }
    
    void use() {
        cout << "Using resource " << name << endl;
    }
};

void demonstrateSmartPointers() {
    // unique_ptr - exclusive ownership
    unique_ptr<Resource> res1 = make_unique<Resource>("Unique1");
    res1->use();
    
    // unique_ptr cannot be copied, only moved
    unique_ptr<Resource> res2 = move(res1);
    if (!res1) {
        cout << "res1 is now empty" << endl;
    }
    res2->use();
    
    // shared_ptr - shared ownership
    shared_ptr<Resource> shared1 = make_shared<Resource>("Shared1");
    {
        shared_ptr<Resource> shared2 = shared1;  // Reference count increases
        cout << "Reference count: " << shared1.use_count() << endl;
        shared2->use();
    }  // shared2 destroyed, reference count decreases
    cout << "Reference count: " << shared1.use_count() << endl;
    shared1->use();
    
    // weak_ptr - non-owning reference
    weak_ptr<Resource> weak = shared1;
    if (auto locked = weak.lock()) {
        cout << "Resource still exists: ";
        locked->use();
    } else {
        cout << "Resource has been destroyed" << endl;
    }
}

int main() {
    demonstrateSmartPointers();
    cout << "All smart pointers automatically cleaned up" << endl;
    return 0;
}

Move Semantics and Rvalue References

class Buffer {
private:
    int* data;
    size_t size;
    
public:
    // Constructor
    Buffer(size_t s) : size(s), data(new int[s]) {
        cout << "Buffer constructed with size " << size << endl;
    }
    
    // Copy constructor
    Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
        copy(other.data, other.data + size, data);
        cout << "Buffer copy constructed" << endl;
    }
    
    // Move constructor
    Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
        other.data = nullptr;
        other.size = 0;
        cout << "Buffer move constructed" << endl;
    }
    
    // Copy assignment
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            copy(other.data, other.data + size, data);
            cout << "Buffer copy assigned" << endl;
        }
        return *this;
    }
    
    // Move assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
            cout << "Buffer move assigned" << endl;
        }
        return *this;
    }
    
    ~Buffer() {
        delete[] data;
        cout << "Buffer destroyed" << endl;
    }
};

Buffer createBuffer() {
    return Buffer(100);  // Return value optimization (RVO)
}

int main() {
    Buffer b1(50);           // Normal construction
    Buffer b2 = b1;          // Copy construction
    Buffer b3 = move(b1);    // Move construction
    Buffer b4 = createBuffer();  // May use move or RVO
    
    b2 = b3;                 // Copy assignment
    b3 = createBuffer();     // Move assignment
    
    return 0;
}

Lambda Expressions

#include <algorithm>
#include <vector>
#include <functional>

void demonstrateLambdas() {
    vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // Basic lambda
    auto simpleLambda = []() { cout << "Hello Lambda!" << endl; };
    simpleLambda();
    
    // Lambda with parameters
    auto adder = [](int a, int b) { return a + b; };
    cout << "5 + 3 = " << adder(5, 3) << endl;
    
    // Lambda with capture
    int factor = 2;
    auto multiplier = [factor](int x) { return x * factor; };
    cout << "5 * " << factor << " = " << multiplier(5) << endl;
    
    // Mutable lambda (can modify captured values)
    auto counter = [count = 0]() mutable { return ++count; };
    cout << "Counter: " << counter() << ", " << counter() << ", " << counter() << endl;
    
    // Using lambdas with algorithms
    cout << "Even numbers: ";
    for_each(numbers.begin(), numbers.end(), [](int n) {
        if (n % 2 == 0) cout << n << " ";
    });
    cout << endl;
    
    // Lambda as predicate
    auto isPrime = [](int n) {
        if (n <= 1) return false;
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    };
    
    auto primeIt = find_if(numbers.begin(), numbers.end(), isPrime);
    if (primeIt != numbers.end()) {
        cout << "First prime: " << *primeIt << endl;
    }
}

// Lambda with std::function
function<int(int)> createFibonacci() {
    function<int(int)> fib;
    fib = [&fib](int n) -> int {
        if (n <= 1) return n;
        return fib(n - 1) + fib(n - 2);
    };
    return fib;
}

Exception Handling

#include <stdexcept>
#include <fstream>

class FileError : public runtime_error {
public:
    FileError(const string& message) : runtime_error(message) {}
};

void readFile(const string& filename) {
    ifstream file(filename);
    if (!file.is_open()) {
        throw FileError("Cannot open file: " + filename);
    }
    
    string line;
    while (getline(file, line)) {
        if (line.empty()) {
            throw invalid_argument("Empty line found in file");
        }
        cout << line << endl;
    }
}

void demonstrateExceptions() {
    try {
        readFile("nonexistent.txt");
    }
    catch (const FileError& e) {
        cout << "File error: " << e.what() << endl;
    }
    catch (const invalid_argument& e) {
        cout << "Invalid argument: " << e.what() << endl;
    }
    catch (const exception& e) {
        cout << "Standard exception: " << e.what() << endl;
    }
    catch (...) {
        cout << "Unknown exception occurred" << endl;
    }
    
    // Exception-safe resource management
    try {
        unique_ptr<int> ptr = make_unique<int>(42);
        // Resource automatically cleaned up even if exception thrown
        throw runtime_error("Something went wrong");
    }
    catch (const exception& e) {
        cout << "Exception caught: " << e.what() << endl;
    }
}

Type Traits and SFINAE

#include <type_traits>

// Using type traits
template<typename T>
void process(const T& value) {
    if constexpr (is_pointer<T>::value) {
        cout << "Pointer to: " << *value << endl;
    } else if constexpr (is_arithmetic<T>::value) {
        cout << "Arithmetic value: " << value << endl;
    } else {
        cout << "Other type" << endl;
    }
}

// SFINAE (Substitution Failure Is Not An Error)
template<typename T>
typename enable_if<is_integral<T>::value, T>::type
increment(const T& value) {
    return value + 1;
}

template<typename T>
typename enable_if<is_floating_point<T>::value, T>::type
increment(const T& value) {
    return value + 0.1;
}

// Concepts (C++20)
/*
template<typename T>
concept Arithmetic = is_arithmetic_v<T>;

template<Arithmetic T>
T multiply(T a, T b) {
    return a * b;
}
*/

int main() {
    int x = 5;
    double y = 3.14;
    string z = "hello";
    
    process(x);    // Arithmetic value: 5
    process(&x);   // Pointer to: 5
    process(z);    // Other type
    
    cout << "Increment int: " << increment(5) << endl;
    cout << "Increment double: " << increment(3.14) << endl;
    
    return 0;
}

Common Pitfalls

  • Not using noexcept for move operations
  • Throwing exceptions from destructors
  • Capturing dangling references in lambdas
  • Forgetting to handle all exception cases

Vectors in C++

Vectors are dynamic arrays that can grow and shrink in size. They are part of the Standard Template Library (STL) and provide many convenient operations.

Creating and Using Vectors

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // Different ways to create vectors
    vector<int> numbers;                    // Empty vector
    vector<int> primes = {2, 3, 5, 7, 11}; // Initialized
    vector<string> names(3, "Unknown");    // 3 elements, all "Unknown"
    
    // Adding elements
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);
    
    // Accessing elements
    cout << "First: " << numbers[0] << endl;
    cout << "Second: " << numbers.at(1) << endl;  // Bounds-checked
    
    // Size and capacity
    cout << "Size: " << numbers.size() << endl;
    cout << "Capacity: " << numbers.capacity() << endl;
    cout << "Empty? " << (numbers.empty() ? "Yes" : "No") << endl;
    
    return 0;
}

Vector Operations

vector<int> vec = {1, 2, 3, 4, 5};
    
// Iterating
cout << "Elements: ";
for (int i = 0; i < vec.size(); i++) {
    cout << vec[i] << " ";
}
cout << endl;

// Range-based for loop (C++11)
cout << "Using range-based for: ";
for (int num : vec) {
    cout << num << " ";
}
cout << endl;

// Modifying elements
vec[0] = 100;
vec.at(1) = 200;

// Inserting and erasing
vec.insert(vec.begin() + 2, 300);  // Insert at position 2
vec.erase(vec.begin() + 3);        // Remove element at position 3

// Clearing
vec.clear();  // Remove all elements

Vector vs Array

// Arrays: Fixed size, stack allocation
int arr[5] = {1, 2, 3, 4, 5};
// arr[5] = 6;  // Error - fixed size

// Vectors: Dynamic size, heap allocation
vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);  // OK - can grow
vec.pop_back();    // Remove last element

Common Pitfalls

  • Accessing elements with [] doesn't do bounds checking
  • Use at() for bounds-checked access
  • Vectors can be less efficient than arrays for very small, fixed-size data
  • Inserting/erasing in the middle is O(n) operation

Comments