System Programming Topic

Modern C++ Features

1. Modern C++ Features Overview

auto - Automatic type deduction (C++11)

cpp

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

int main() {
    // AUTO: Compiler deduces type automatically
    auto x = 10;            // int
    auto y = 3.14;          // double
    auto z = "Hello";       // const char*
    auto flag = true;       // bool
    
    cout << "x type: " << typeid(x).name() << ", value: " << x << endl;
    cout << "y type: " << typeid(y).name() << ", value: " << y << endl;
    
    // Complex types become readable
    vector<pair<string, int>> data = {{"Alice", 25}, {"Bob", 30}};
    
    // Without auto - verbose
    vector<pair<string, int>>::iterator it1 = data.begin();
    
    // With auto - clean
    auto it2 = data.begin();
    
    // Iterating containers
    cout << "\nUsing auto with iterators:" << endl;
    for (auto it = data.begin(); it != data.end(); ++it) {
        cout << it->first << ": " << it->second << endl;
    }
    
    // auto in function return types (C++14)
    auto add = [](auto a, auto b) { return a + b; };
    cout << "\nAdd with auto: 5 + 3.14 = " << add(5, 3.14) << endl;
    
    // decltype(auto) - preserves references and const-ness (C++14)
    const int& cref = x;
    decltype(auto) d1 = cref;  // d1 is const int&
    
    return 0;
}

nullptr - Type-safe null pointer (C++11)

cpp

#include <iostream>
using namespace std;

void func(int* ptr) {
    if (ptr == nullptr) {
        cout << "Received null pointer" << endl;
    } else {
        cout << "Pointer points to: " << *ptr << endl;
    }
}

void func(int num) {
    cout << "Received integer: " << num << endl;
}

int main() {
    // NULLPTR: Type-safe null pointer (replaces NULL/0)
    
    int* ptr1 = nullptr;       // Good - type safe
    int* ptr2 = NULL;          // Old style (usually 0)
    int* ptr3 = 0;             // Problematic - ambiguous
    
    int value = 5;
    int* ptr = &value;
    
    // Check for null
    if (ptr != nullptr) {
        cout << "Pointer is not null" << endl;
    }
    
    // Function overloading example
    func(nullptr);     // Calls func(int*) - unambiguous
    // func(NULL);     // Ambiguous in some cases (calls func(int))
    func(0);          // Calls func(int)
    
    // Use nullptr in templates
    auto nullPtr = nullptr;  // Type is std::nullptr_t
    
    return 0;
}

Range-based for loops - Clean iteration over containers (C++11)

cpp

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <array>
using namespace std;

int main() {
    // RANGE-BASED FOR LOOPS: Clean syntax for container iteration
    
    vector<int> numbers = {1, 2, 3, 4, 5};
    
    // Traditional loop
    cout << "Traditional for loop:" << endl;
    for (size_t i = 0; i < numbers.size(); ++i) {
        cout << numbers[i] << " ";
    }
    cout << endl;
    
    // Range-based for loop (by value - copies)
    cout << "\nRange-based for loop (by value):" << endl;
    for (int num : numbers) {
        cout << num << " ";
        num *= 2;  // Doesn't modify original
    }
    cout << endl;
    
    // Range-based for loop (by reference - modifies original)
    cout << "Range-based for loop (by reference):" << endl;
    for (int& num : numbers) {
        num *= 2;  // Modifies original
        cout << num << " ";
    }
    cout << endl;
    
    // Range-based for loop (const reference - read-only)
    cout << "Range-based for loop (const reference):" << endl;
    for (const int& num : numbers) {
        cout << num << " ";
        // num *= 2;  // Error: can't modify const reference
    }
    cout << endl;
    
    // Works with arrays
    int arr[] = {10, 20, 30, 40, 50};
    cout << "\nArray iteration:" << endl;
    for (int val : arr) {
        cout << val << " ";
    }
    cout << endl;
    
    // Works with initializer lists
    cout << "Initializer list:" << endl;
    for (int val : {100, 200, 300}) {
        cout << val << " ";
    }
    cout << endl;
    
    // Works with maps
    map<string, int> scores = {{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};
    cout << "\nMap iteration:" << endl;
    for (const auto& pair : scores) {
        cout << pair.first << ": " << pair.second << endl;
    }
    
    // Structured bindings with range-based for (C++17)
    cout << "\nWith structured bindings (C++17):" << endl;
    for (const auto& [name, score] : scores) {
        cout << name << ": " << score << endl;
    }
    
    return 0;
}

constexpr - Compile-time evaluation (C++11/14/17)

cpp

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

// CONSTEXPR: Indicates value/function can be evaluated at compile time

// Constexpr function (C++11)
constexpr int square(int x) {
    return x * x;
}

// Constexpr function with if (C++14)
constexpr int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

// Constexpr with loops (C++14)
constexpr int sumUpTo(int n) {
    int sum = 0;
    for (int i = 1; i <= n; ++i) {
        sum += i;
    }
    return sum;
}

// Constexpr variable
constexpr double PI = 3.141592653589793;

// Constexpr class
class Circle {
private:
    double radius;
    
public:
    // Constexpr constructor
    constexpr Circle(double r) : radius(r) {}
    
    // Constexpr member function
    constexpr double getArea() const {
        return PI * radius * radius;
    }
    
    constexpr double getRadius() const { return radius; }
};

int main() {
    // Compile-time constants
    constexpr int size = 10;
    constexpr int squared = square(5);       // Computed at compile time
    constexpr int fact = factorial(5);       // Computed at compile time
    constexpr int sum = sumUpTo(10);         // Computed at compile time
    
    cout << "Square of 5: " << squared << endl;
    cout << "Factorial of 5: " << fact << endl;
    cout << "Sum up to 10: " << sum << endl;
    
    // Using constexpr with arrays (size must be compile-time constant)
    array<int, square(3)> arr;  // Array size = 9
    
    // Constexpr objects
    constexpr Circle smallCircle(2.0);
    constexpr double area = smallCircle.getArea();  // Computed at compile time
    
    cout << "\nCircle with radius " << smallCircle.getRadius() 
         << " has area " << area << endl;
    
    // Runtime vs compile-time
    int input;
    cout << "\nEnter a number: ";
    cin >> input;
    
    int runtimeSquare = square(input);  // Computed at runtime
    cout << "Square of " << input << " (runtime): " << runtimeSquare << endl;
    
    // const vs constexpr
    const int constValue = 10;          // Can be runtime or compile-time
    constexpr int constexprValue = 10;  // Must be compile-time
    
    // Array example
    constexpr int arraySize = 5;
    int compileTimeArray[arraySize];    // OK
    
    int userSize;
    cout << "Enter array size: ";
    cin >> userSize;
    const int runtimeSize = userSize;
    // int runtimeArray[runtimeSize];   // Error in standard C++ (variable length array)
    
    return 0;
}

enum class - Type-safe enumerations (C++11)

cpp

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

// ENUM CLASS: Strongly typed, scoped enumerations

// Traditional enum (unscoped, implicit conversion to int)
enum Color { RED, GREEN, BLUE };  // RED is in global scope

// enum class (scoped, no implicit conversion)
enum class TrafficLight { RED, YELLOW, GREEN };

// enum class with underlying type
enum class StatusCode : int { OK = 200, NOT_FOUND = 404, ERROR = 500 };

// enum class with string conversion
enum class LogLevel { DEBUG, INFO, WARNING, ERROR };

string logLevelToString(LogLevel level) {
    switch (level) {
        case LogLevel::DEBUG:   return "DEBUG";
        case LogLevel::INFO:    return "INFO";
        case LogLevel::WARNING: return "WARNING";
        case LogLevel::ERROR:   return "ERROR";
        default: return "UNKNOWN";
    }
}

int main() {
    // Traditional enum issues
    Color color1 = RED;      // OK
    Color color2 = GREEN;    // OK
    // int num = RED;        // Implicit conversion (not type-safe)
    
    // Problems with traditional enums
    enum Fruit { APPLE, BANANA, ORANGE };
    // Color myColor = APPLE;  // Error: name conflict (both have value 0)
    
    // enum class - type safe
    TrafficLight light = TrafficLight::RED;
    // TrafficLight light2 = RED;  // Error: must use TrafficLight::RED
    
    // No implicit conversion
    // int lightValue = light;  // Error: no implicit conversion
    int lightValue = static_cast<int>(light);  // Explicit conversion
    
    cout << "Traffic light value: " << lightValue << endl;
    
    // Using switch with enum class
    switch (light) {
        case TrafficLight::RED:
            cout << "Stop!" << endl;
            break;
        case TrafficLight::YELLOW:
            cout << "Slow down!" << endl;
            break;
        case TrafficLight::GREEN:
            cout << "Go!" << endl;
            break;
    }
    
    // enum class with underlying type
    StatusCode code = StatusCode::OK;
    int httpCode = static_cast<int>(code);
    cout << "\nHTTP Status Code: " << httpCode << endl;
    
    // Compare enum values
    if (code == StatusCode::OK) {
        cout << "Request successful!" << endl;
    }
    
    // Log level example
    LogLevel currentLevel = LogLevel::INFO;
    cout << "\nCurrent log level: " << logLevelToString(currentLevel) << endl;
    
    // Iterating over enum values (requires workaround)
    cout << "\nAll log levels:" << endl;
    for (int i = 0; i <= static_cast<int>(LogLevel::ERROR); ++i) {
        LogLevel level = static_cast<LogLevel>(i);
        cout << logLevelToString(level) << endl;
    }
    
    // Benefits of enum class:
    // 1. No name collisions (scoped)
    // 2. No implicit conversions (type-safe)
    // 3. Can specify underlying type
    // 4. Can use with overloaded operators
    
    return 0;
}

2. Lambda Expressions

Lambda Expressions - Anonymous functions (C++11+)

cpp

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

int main() {
    // LAMBDA EXPRESSIONS: Anonymous function objects
    
    // Basic syntax: [capture](parameters) -> return_type { body }
    
    // 1. Basic lambda
    auto greet = []() {
        cout << "Hello from lambda!" << endl;
    };
    greet();
    
    // 2. Lambda with parameters and return type
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    cout << "5 + 3 = " << add(5, 3) << endl;
    
    // 3. Lambda with auto parameters (C++14)
    auto multiply = [](auto a, auto b) {
        return a * b;
    };
    cout << "5 * 3.14 = " << multiply(5, 3.14) << endl;
    
    // 4. Immediately invoked lambda
    []() {
        cout << "Immediately invoked!" << endl;
    }();
    
    int result = [](int x, int y) { return x * y; }(4, 5);
    cout << "4 * 5 = " << result << endl;
    
    // CAPTURE LISTS: How lambda accesses external variables
    
    int x = 10;
    int y = 20;
    
    // 5. Capture by value [=]
    auto lambda1 = [=]() {
        cout << "Capture by value: x=" << x << ", y=" << y << endl;
        // x = 30;  // Error: can't modify captured by value
    };
    lambda1();
    
    // 6. Capture by reference [&]
    auto lambda2 = [&]() {
        cout << "Capture by reference: x=" << x << ", y=" << y << endl;
        x = 30;  // OK: modifies original
        y = 40;
    };
    lambda2();
    cout << "After lambda2: x=" << x << ", y=" << y << endl;
    
    // 7. Mixed capture [x, &y]
    x = 10; y = 20;
    auto lambda3 = [x, &y]() {
        cout << "Mixed capture: x=" << x << ", y=" << y << endl;
        // x = 50;  // Error: captured by value
        y = 60;     // OK: captured by reference
    };
    lambda3();
    cout << "After lambda3: x=" << x << ", y=" << y << endl;
    
    // 8. Capture with initialization (C++14)
    auto lambda4 = [z = x + y]() {
        cout << "Capture with init: z=" << z << endl;
    };
    lambda4();
    
    // 9. Mutable lambdas (allows modifying captured by value)
    auto lambda5 = [x]() mutable {
        x = 100;  // OK: modifies local copy
        cout << "Mutable lambda: x=" << x << endl;
    };
    lambda5();
    cout << "Original x unchanged: " << x << endl;
    
    // PRACTICAL USE CASES
    
    vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    
    // 10. Use with algorithms
    cout << "\nOriginal numbers: ";
    for_each(numbers.begin(), numbers.end(), [](int n) { cout << n << " "; });
    cout << endl;
    
    // Sort with custom comparator
    sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;  // Sort descending
    });
    cout << "Sorted descending: ";
    for (int n : numbers) cout << n << " ";
    cout << endl;
    
    // Count if
    int countEven = count_if(numbers.begin(), numbers.end(), [](int n) {
        return n % 2 == 0;
    });
    cout << "Even numbers count: " << countEven << endl;
    
    // Transform
    vector<int> squares;
    squares.resize(numbers.size());
    transform(numbers.begin(), numbers.end(), squares.begin(),
              [](int n) { return n * n; });
    cout << "Squares: ";
    for (int n : squares) cout << n << " ";
    cout << endl;
    
    // Find if
    auto it = find_if(numbers.begin(), numbers.end(), [](int n) {
        return n > 5;
    });
    if (it != numbers.end()) {
        cout << "First number > 5: " << *it << endl;
    }
    
    // 11. Function composition
    auto increment = [](int n) { return n + 1; };
    auto squareFn = [](int n) { return n * n; };
    
    // Compose functions
    auto incrementThenSquare = [increment, squareFn](int n) {
        return squareFn(increment(n));
    };
    cout << "Increment then square of 4: " << incrementThenSquare(4) << endl;
    
    // 12. Callback functions
    class Button {
    public:
        using ClickHandler = function<void()>;
        
        void setOnClick(ClickHandler handler) {
            onClick = handler;
        }
        
        void click() {
            if (onClick) onClick();
        }
        
    private:
        ClickHandler onClick;
    };
    
    Button btn;
    string userName = "Alice";
    
    btn.setOnClick([userName]() {
        cout << "Button clicked by " << userName << "!" << endl;
    });
    
    btn.click();
    
    // 13. Generic lambda with auto (C++14)
    auto printPair = [](const auto& p) {
        cout << "Pair: " << p.first << ", " << p.second << endl;
    };
    
    printPair(make_pair(10, "Hello"));
    printPair(make_pair(3.14, true));
    
    // 14. Lambda as return type
    auto makeMultiplier = [](int factor) {
        return [factor](int n) { return n * factor; };
    };
    
    auto doubleIt = makeMultiplier(2);
    auto tripleIt = makeMultiplier(3);
    
    cout << "Double 5: " << doubleIt(5) << endl;
    cout << "Triple 5: " << tripleIt(5) << endl;
    
    return 0;
}

3. Concurrency & Multithreading

std::thread - Basic threading (C++11)

cpp

#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <mutex>
#include <future>
#include <atomic>
using namespace std;

// MUTEX: Mutual exclusion for thread safety
mutex coutMutex;  // Global mutex for safe console output

void printSafe(const string& message) {
    lock_guard<mutex> lock(coutMutex);
    cout << message << endl;
}

void basicThreadExample() {
    cout << "\n=== BASIC THREAD EXAMPLE ===" << endl;
    
    // Creating threads
    auto worker = [](int id) {
        this_thread::sleep_for(chrono::milliseconds(100 * id));
        printSafe("Thread " + to_string(id) + " finished");
    };
    
    thread t1(worker, 1);
    thread t2(worker, 2);
    thread t3(worker, 3);
    
    // Main thread continues execution
    
    // Wait for threads to finish
    t1.join();
    t2.join();
    t3.join();
    
    cout << "All threads completed" << endl;
}

// RACE CONDITION: Unsynchronized access to shared data
void raceConditionExample() {
    cout << "\n=== RACE CONDITION EXAMPLE ===" << endl;
    
    int sharedCounter = 0;
    const int iterations = 100000;
    
    auto incrementer = [&sharedCounter, iterations]() {
        for (int i = 0; i < iterations; ++i) {
            sharedCounter++;  // Not thread-safe!
        }
    };
    
    thread t1(incrementer);
    thread t2(incrementer);
    
    t1.join();
    t2.join();
    
    // Expected: 200000, but we get less due to race condition
    cout << "Counter value (expected " << 2 * iterations << "): " 
         << sharedCounter << endl;
}

// Solving race condition with MUTEX
void mutexExample() {
    cout << "\n=== MUTEX EXAMPLE ===" << endl;
    
    int sharedCounter = 0;
    const int iterations = 100000;
    mutex counterMutex;
    
    auto safeIncrementer = [&sharedCounter, iterations, &counterMutex]() {
        for (int i = 0; i < iterations; ++i) {
            lock_guard<mutex> lock(counterMutex);  // RAII lock
            sharedCounter++;  // Thread-safe
        }
    };
    
    thread t1(safeIncrementer);
    thread t2(safeIncrementer);
    
    t1.join();
    t2.join();
    
    cout << "Counter value (correct): " << sharedCounter << endl;
}

// ATOMIC operations (lock-free)
void atomicExample() {
    cout << "\n=== ATOMIC EXAMPLE ===" << endl;
    
    atomic<int> atomicCounter(0);  // Atomic variable
    const int iterations = 100000;
    
    auto atomicIncrementer = [&atomicCounter, iterations]() {
        for (int i = 0; i < iterations; ++i) {
            atomicCounter++;  // Atomic operation
        }
    };
    
    thread t1(atomicIncrementer);
    thread t2(atomicIncrementer);
    
    t1.join();
    t2.join();
    
    cout << "Atomic counter value: " << atomicCounter.load() << endl;
}

// std::async - Higher-level abstraction for async tasks
void asyncExample() {
    cout << "\n=== ASYNC EXAMPLE ===" << endl;
    
    // Function that simulates long computation
    auto compute = [](int x) -> int {
        this_thread::sleep_for(chrono::milliseconds(1000));
        return x * x;
    };
    
    // Launch async tasks
    auto start = chrono::high_resolution_clock::now();
    
    future<int> future1 = async(launch::async, compute, 5);
    future<int> future2 = async(launch::async, compute, 10);
    
    // Do other work while tasks run...
    
    // Get results (blocks if not ready)
    int result1 = future1.get();
    int result2 = future2.get();
    
    auto end = chrono::high_resolution_clock::now();
    auto duration = chrono::duration_cast<chrono::milliseconds>(end - start);
    
    cout << "Result 1: " << result1 << endl;
    cout << "Result 2: " << result2 << endl;
    cout << "Total time (parallel): " << duration.count() << "ms" << endl;
    
    // Compare with sequential execution
    start = chrono::high_resolution_clock::now();
    int seq1 = compute(5);
    int seq2 = compute(10);
    end = chrono::high_resolution_clock::now();
    duration = chrono::duration_cast<chrono::milliseconds>(end - start);
    
    cout << "Sequential time: " << duration.count() << "ms" << endl;
}

// Producer-Consumer pattern
void producerConsumerExample() {
    cout << "\n=== PRODUCER-CONSUMER EXAMPLE ===" << endl;
    
    vector<int> buffer;
    const int maxSize = 5;
    mutex bufferMutex;
    condition_variable bufferNotEmpty;
    condition_variable bufferNotFull;
    
    atomic<bool> done(false);
    
    auto producer = [&](int id) {
        for (int i = 0; i < 10; ++i) {
            unique_lock<mutex> lock(bufferMutex);
            
            // Wait if buffer is full
            bufferNotFull.wait(lock, [&]() { 
                return buffer.size() < maxSize; 
            });
            
            // Produce item
            int item = id * 100 + i;
            buffer.push_back(item);
            printSafe("Producer " + to_string(id) + " produced: " + to_string(item));
            
            lock.unlock();
            bufferNotEmpty.notify_one();
            
            this_thread::sleep_for(chrono::milliseconds(100));
        }
    };
    
    auto consumer = [&](int id) {
        while (!done || !buffer.empty()) {
            unique_lock<mutex> lock(bufferMutex);
            
            // Wait if buffer is empty and not done
            bufferNotEmpty.wait_for(lock, chrono::milliseconds(100),
                [&]() { return !buffer.empty() || done; });
            
            if (!buffer.empty()) {
                // Consume item
                int item = buffer.back();
                buffer.pop_back();
                printSafe("Consumer " + to_string(id) + " consumed: " + to_string(item));
                
                lock.unlock();
                bufferNotFull.notify_one();
            }
        }
    };
    
    // Start producers and consumers
    vector<thread> producers;
    vector<thread> consumers;
    
    for (int i = 0; i < 2; ++i) {
        producers.emplace_back(producer, i);
        consumers.emplace_back(consumer, i);
    }
    
    // Let producers finish
    for (auto& t : producers) t.join();
    done = true;
    
    // Let consumers finish
    for (auto& t : consumers) t.join();
    
    cout << "Producer-consumer completed" << endl;
}

int main() {
    cout << "=== MODERN C++ CONCURRENCY ===" << endl;
    
    basicThreadExample();
    raceConditionExample();
    mutexExample();
    atomicExample();
    asyncExample();
    producerConsumerExample();
    
    cout << "\nAll concurrency examples completed!" << endl;
    return 0;
}

4. File Handling

File Streams - Reading and writing files

cpp

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iomanip>
#include <sstream>
using namespace std;

void textFileExample() {
    cout << "=== TEXT FILE HANDLING ===" << endl;
    
    // WRITING TO A TEXT FILE
    
    // ofstream: Output file stream
    ofstream outFile("example.txt");
    
    if (!outFile) {
        cerr << "Error creating file!" << endl;
        return;
    }
    
    // Write data
    outFile << "Hello, File Handling!" << endl;
    outFile << "This is line 2." << endl;
    outFile << "Line 3 with number: " << 42 << endl;
    outFile << "Pi: " << 3.14159 << endl;
    
    // Write formatted output
    outFile << left << setw(15) << "Name" 
            << setw(10) << "Age" 
            << setw(10) << "Score" << endl;
    outFile << string(35, '-') << endl;
    outFile << left << setw(15) << "Alice" 
            << setw(10) << 25 
            << setw(10) << fixed << setprecision(2) << 95.5 << endl;
    outFile << left << setw(15) << "Bob" 
            << setw(10) << 30 
            << setw(10) << 87.25 << endl;
    
    outFile.close();
    cout << "Data written to example.txt" << endl;
    
    // READING FROM A TEXT FILE
    
    // ifstream: Input file stream
    ifstream inFile("example.txt");
    
    if (!inFile) {
        cerr << "Error opening file!" << endl;
        return;
    }
    
    cout << "\nReading file line by line:" << endl;
    string line;
    while (getline(inFile, line)) {
        cout << line << endl;
    }
    
    inFile.close();
    
    // READING WITH DIFFERENT TECHNIQUES
    
    cout << "\nReading word by word:" << endl;
    inFile.open("example.txt");
    string word;
    while (inFile >> word) {
        cout << word << " ";
    }
    cout << endl;
    inFile.close();
    
    // READING SPECIFIC DATA
    cout << "\nReading structured data:" << endl;
    inFile.open("example.txt");
    
    // Skip header lines
    for (int i = 0; i < 5; ++i) getline(inFile, line);
    
    // Read table data
    string name;
    int age;
    double score;
    
    while (inFile >> name >> age >> score) {
        cout << "Name: " << name 
             << ", Age: " << age 
             << ", Score: " << score << endl;
    }
    inFile.close();
}

void binaryFileExample() {
    cout << "\n=== BINARY FILE HANDLING ===" << endl;
    
    struct Person {
        char name[50];
        int age;
        double salary;
    };
    
    vector<Person> people = {
        {"Alice Johnson", 25, 55000.50},
        {"Bob Smith", 30, 65000.75},
        {"Charlie Brown", 35, 75000.25}
    };
    
    // WRITING BINARY DATA
    
    ofstream outFile("people.bin", ios::binary);
    
    if (!outFile) {
        cerr << "Error creating binary file!" << endl;
        return;
    }
    
    // Write each person
    for (const auto& person : people) {
        outFile.write(reinterpret_cast<const char*>(&person), sizeof(Person));
    }
    
    outFile.close();
    cout << "Binary data written to people.bin" << endl;
    
    // READING BINARY DATA
    
    ifstream inFile("people.bin", ios::binary);
    
    if (!inFile) {
        cerr << "Error opening binary file!" << endl;
        return;
    }
    
    cout << "\nReading binary data:" << endl;
    
    Person person;
    while (inFile.read(reinterpret_cast<char*>(&person), sizeof(Person))) {
        cout << "Name: " << person.name 
             << ", Age: " << person.age 
             << ", Salary: " << person.salary << endl;
    }
    
    inFile.close();
    
    // APPENDING TO BINARY FILE
    
    fstream appFile("people.bin", ios::binary | ios::in | ios::out | ios::app);
    
    if (!appFile) {
        cerr << "Error opening file for appending!" << endl;
        return;
    }
    
    Person newPerson = {"David Wilson", 28, 60000.00};
    appFile.write(reinterpret_cast<const char*>(&newPerson), sizeof(Person));
    
    // Go to beginning and read all
    appFile.seekg(0, ios::beg);
    
    cout << "\nAfter appending:" << endl;
    while (appFile.read(reinterpret_cast<char*>(&person), sizeof(Person))) {
        cout << "Name: " << person.name 
             << ", Age: " << person.age 
             << ", Salary: " << person.salary << endl;
    }
    
    appFile.close();
}

void fileManipulationExample() {
    cout << "\n=== FILE MANIPULATION ===" << endl;
    
    // CHECKING FILE PROPERTIES
    
    ifstream testFile("example.txt");
    
    if (testFile.is_open()) {
        cout << "File is open" << endl;
        
        // Get file size
        testFile.seekg(0, ios::end);
        streampos fileSize = testFile.tellg();
        cout << "File size: " << fileSize << " bytes" << endl;
        
        testFile.seekg(0, ios::beg);  // Reset to beginning
        
        // Get current position
        streampos pos = testFile.tellg();
        cout << "Current position: " << pos << endl;
        
        testFile.close();
    }
    
    // FILE STATES
    
    cout << "\nFile states:" << endl;
    
    ifstream stateFile("nonexistent.txt");
    
    if (!stateFile) {
        cout << "File not found or couldn't open" << endl;
    }
    
    if (stateFile.fail()) {
        cout << "failbit set (I/O operation failed)" << endl;
    }
    
    if (stateFile.bad()) {
        cout << "badbit set (unrecoverable error)" << endl;
    }
    
    if (stateFile.eof()) {
        cout << "eofbit set (end of file reached)" << endl;
    }
    
    stateFile.close();
    
    // FILE COPY EXAMPLE
    
    cout << "\nCopying file..." << endl;
    
    ifstream source("example.txt", ios::binary);
    ofstream destination("example_copy.txt", ios::binary);
    
    if (!source || !destination) {
        cerr << "Error in file copy!" << endl;
        return;
    }
    
    // Efficient file copy
    destination << source.rdbuf();
    
    source.close();
    destination.close();
    
    cout << "File copied successfully!" << endl;
}

void csvFileExample() {
    cout << "\n=== CSV FILE HANDLING ===" << endl;
    
    // WRITING CSV
    
    ofstream csvFile("data.csv");
    
    csvFile << "Name,Age,City,Salary" << endl;
    csvFile << "Alice,25,New York,55000.50" << endl;
    csvFile << "Bob,30,Los Angeles,65000.75" << endl;
    csvFile << "Charlie,35,Chicago,75000.25" << endl;
    
    csvFile.close();
    
    // READING CSV
    
    ifstream inCsv("data.csv");
    string csvLine;
    
    cout << "\nReading CSV file:" << endl;
    
    // Skip header
    getline(inCsv, csvLine);
    
    while (getline(inCsv, csvLine)) {
        stringstream ss(csvLine);
        string name, city;
        int age;
        double salary;
        
        // Parse CSV line
        getline(ss, name, ',');
        ss >> age;
        ss.ignore();  // Ignore comma
        getline(ss, city, ',');
        ss >> salary;
        
        cout << "Name: " << name 
             << ", Age: " << age 
             << ", City: " << city 
             << ", Salary: " << salary << endl;
    }
    
    inCsv.close();
}

void practicalFileExample() {
    cout << "\n=== PRACTICAL FILE EXAMPLE ===" << endl;
    
    // Simple database using files
    class StudentDatabase {
    private:
        string filename;
        
        struct Student {
            int id;
            string name;
            double gpa;
            
            string toString() const {
                return to_string(id) + "," + name + "," + to_string(gpa);
            }
            
            static Student fromString(const string& str) {
                stringstream ss(str);
                string token;
                Student s;
                
                getline(ss, token, ',');
                s.id = stoi(token);
                getline(ss, s.name, ',');
                getline(ss, token, ',');
                s.gpa = stod(token);
                
                return s;
            }
        };
        
    public:
        StudentDatabase(const string& fname) : filename(fname) {}
        
        void addStudent(int id, const string& name, double gpa) {
            ofstream file(filename, ios::app);
            if (file) {
                file << Student{id, name, gpa}.toString() << endl;
            }
        }
        
        vector<Student> getAllStudents() {
            vector<Student> students;
            ifstream file(filename);
            
            string line;
            while (getline(file, line)) {
                students.push_back(Student::fromString(line));
            }
            
            return students;
        }
        
        Student findStudent(int id) {
            ifstream file(filename);
            string line;
            
            while (getline(file, line)) {
                Student s = Student::fromString(line);
                if (s.id == id) {
                    return s;
                }
            }
            
            return Student{0, "Not Found", 0.0};
        }
    };
    
    // Usage
    StudentDatabase db("students.csv");
    
    // Add students
    db.addStudent(101, "Alice Johnson", 3.8);
    db.addStudent(102, "Bob Smith", 3.5);
    db.addStudent(103, "Charlie Brown", 3.9);
    
    // Retrieve and display
    auto students = db.getAllStudents();
    
    cout << "\nAll Students:" << endl;
    cout << left << setw(10) << "ID" 
         << setw(20) << "Name" 
         << setw(10) << "GPA" << endl;
    cout << string(40, '-') << endl;
    
    for (const auto& student : students) {
        cout << setw(10) << student.id
             << setw(20) << student.name
             << setw(10) << fixed << setprecision(2) << student.gpa << endl;
    }
    
    // Search
    Student found = db.findStudent(102);
    cout << "\nFound student 102: " << found.name 
         << " (GPA: " << found.gpa << ")" << endl;
}

int main() {
    cout << "=== MODERN C++ FILE HANDLING ===" << endl;
    
    textFileExample();
    binaryFileExample();
    fileManipulationExample();
    csvFileExample();
    practicalFileExample();
    
    cout << "\nAll file handling examples completed!" << endl;
    
    // Clean up created files
    remove("example.txt");
    remove("example_copy.txt");
    remove("people.bin");
    remove("students.csv");
    remove("data.csv");
    
    return 0;
}

Key Takeaways:

Modern C++ Features:

  1. auto: Type deduction - cleaner code, especially with complex types

  2. nullptr: Type-safe null pointer - eliminates ambiguity with NULL/0

  3. Range-based loops: Clean iteration over containers - for (auto& item : container)

  4. constexpr: Compile-time evaluation - performance optimization

  5. enum class: Type-safe enums - prevents name collisions, no implicit conversions

Lambda Expressions:

  • Syntax: [capture](params) -> return_type { body }

  • Capture lists: [=] (by value), [&] (by ref), [x, &y] (mixed)

  • Use cases: Callbacks, STL algorithms, async operations

  • Mutable lambdas: Allow modifying captured-by-value variables

Concurrency & Multithreading:

  • std::thread: Basic thread management

  • Mutex/Locks: Prevent race conditions (lock_guard, unique_lock)

  • Condition variables: Thread synchronization

  • std::async: High-level async task execution

  • Atomic operations: Lock-free thread-safe operations

File Handling:

  • Text files: ifstream/ofstream with >>/<< operators

  • Binary files: Use read()/write() with ios::binary

  • File states: good(), fail(), bad(), eof()

  • Manipulation: seekg(), tellg(), file copying

  • Practical patterns: CSV parsing, simple databases, logging

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?