Post

C++ Notes

This note is intended for readers having C programming experience and familiar with OOP concepts. It is a collection of notes on C++ programming language, including the basics, advanced features, and best practices.

C++ Basics

  • Dev Environment: Visual Studio / VS Code + MinGW + CMake
  • C++ is a statically typed, compiled, general-purpose, case-sensitive, free-form programming language that supports procedural, object-oriented, and generic programming.
  • C++ is a middle-level language, as it encapsulates both high and low-level language features.
  • The header files do not have a .h extension and the C++ standard library functions are declared in the std namespace.
  • Variables:
    • wchar_t: Wide character type. Usually 2 or 4 bytes.
    • A float’s memory structure in 4 bytes:
      • alt text
    • Double:
      • alt text
  • Type Specifiers for variables:
    • const: The value of the variable cannot be changed.
    • volatile: The value of the variable can be changed by external sources.
    • mutable: The value of the variable can be changed in a const member function.
    • static: The variable is shared among all instances of the class.
    • restrict: The variable is the only pointer to the memory it points to.
  • Type Specifiers for functions and variables:
    • auto: The type of the variable is automatically deduced from its initializer.
    • extern: The function or variable is defined in another file.
    • thread_local: The variable is local to the thread.(C++11)
  • Operator precedence:
    1. Affix: () [] -> . ++ --
    2. Unary(RTL): * & sizeof
    3. Multiplicative: * / %
    4. Additive: + -
    5. Shift: << >>
    6. Relational: < <= > >=
    7. Equality: == !=
    8. Bitwise: & ^ |
    9. Logic: && ||
    10. Conditional(RTL)
    11. Assignment(RTL)
    12. Comma

Lambda Functions

In C++, a lambda function is a convenient way of defining an anonymous function object (a function without a name) right at the location where it is invoked or passed as an argument to a function. Lambda functions were introduced in C++11 and have since become a fundamental part of the language, allowing for more succinct code, especially when working with algorithms or when defining simple function objects.

A lambda expression in C++ has the following general syntax:

1
2
3
[capture](parameters) -> return_type {
    // function body
}
  • Capture Clause: The capture clause [capture] specifies which variables from the surrounding scope are available inside the lambda and how (by value, by reference, etc.). It’s possible to capture variables explicitly or implicitly.
  • Parameters: Similar to regular functions, you can define parameters for the lambda function inside the parentheses (). These parameters can be used inside the lambda body.
  • Return Type: The -> return_type part is optional. If omitted, the return type is inferred automatically by the compiler based on the return statement in the lambda body. For simple lambdas, specifying the return type is often unnecessary.
  • Function Body: The function body contains the statements that define what the lambda does. It is enclosed in {} braces.

Examples

Basic Lambda (No Capture)
1
2
3
4
5
6
// the type of sum is a unique lambda function object
auto sum = [](int a, int b) -> int {
    return a + b;
};

std::cout << sum(5, 3); // Outputs: 8
Lambda Capturing Local Variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int x = 4;
int y = 6;

// Capture x and y by value
auto sum = [x, y]() -> int {
    return x + y;
};

std::cout << sum(); // Outputs: 10

// Capture all local variables by value
auto product = [=]() -> int {
    return x * y;
};

std::cout << product(); // Outputs: 24

// Capture all local variables by reference
auto modifier = [&]() {
    x *= 2;
    y *= 2;
};

modifier();
std::cout << x << ", " << y; // Outputs: 8, 12

Lambda functions can be used directly as arguments to functions that expect a function object, such as the standard algorithms (std::sort, std::find_if, etc.), making them very powerful for inline operations and custom sorting or searching criteria.

1
2
3
4
5
6
7
8
std::vector<int> v = {4, 1, 3, 5, 2};

// Use a lambda expression to sort the vector in descending order
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;
});

// v is now {5, 4, 3, 2, 1}

Lambda functions in C++ provide a flexible and concise way to define function objects, greatly enhancing the expressiveness and readability of the code.

Array

While C++ also requires the size of a static array to be a compile-time constant, it introduces templates and classes like std::array and std::vector that provide more flexibility. std::vector is a dynamic array that can change size at runtime.

String

C++, being an object-oriented language, provides a more abstracted and convenient way to work with strings through its Standard Template Library (STL). The std::string class in C++ simplifies string manipulation significantly:

  • The std::string class encapsulates character arrays and provides automatic management of memory, resizing as necessary. Users don’t need to worry about the null terminator; the class handles it internally.
  • std::string provides a rich set of member functions for string manipulation, such as appending (+= operator or append() method), inserting (insert()), removing (erase()), and accessing individual characters (operator[] or at()).
  • With std::string, the need for manual memory management is greatly reduced. The class automatically handles memory allocations and deallocations as the string’s size changes.
  • While std::string in C++ offers ease of use and safety, certain operations might introduce overhead due to dynamic memory allocations and other safety checks. In C, direct manipulation of character arrays can be more efficient in performance-critical applications, at the cost of increased complexity and potential for errors.

Pointers

  • With the Standard Template Library (STL) and later additions in C++11 and beyond, C++ introduced smart pointers (such as std::unique_ptr, std::shared_ptr, and std::weak_ptr). These are template classes that provide automatic memory management and help prevent memory leaks by automatically deleting the object when it’s no longer needed, something which has to be manually managed in C.

  • C++ introduces nullptr keyword in C++11, which is a type-safe null pointer constant. This helps avoid ambiguity overloading functions between integer 0 and null pointers.

References

In C++, references are a powerful feature introduced to offer an alternative to pointers with a simpler syntax and somewhat different semantics. A reference in C++ is essentially an alias for another variable. Once a reference is initialized with a variable, it can be used to read or modify the original variable’s value. References are particularly useful for passing arguments to functions by reference, allowing the functions to modify the original arguments. They also play a crucial role in operator overloading and other advanced features of C++.

Here’s a simple example of using a reference in C++:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

void increment(int &ref) {
    ref++;
}

int main() {
    int a = 5;
    increment(a); // Passes 'a' by reference
    std::cout << a; // Outputs: 6
    return 0;
}

In this example, increment takes an integer reference as its parameter. When a is passed to it, increment directly modifies the original variable a.

Reference can be used as functions parameters, return values, and class members. It is important to note that references cannot be reseated to refer to a different variable after initialization, and they cannot be null. This makes them safer than pointers in many cases.

References in C

C, being an older language than C++, does not have the concept of references as C++ does. In C, the primary means of achieving similar functionality is through the use of pointers. Pointers can be used to refer to the memory location of a variable, and thus, indirectly access or modify the variable’s value.

Here’s how you could achieve a similar outcome as the above C++ example, but in C, using pointers:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

void increment(int *ptr) {
    (*ptr)++;
}

int main() {
    int a = 5;
    increment(&a); // Passes address of 'a'
    printf("%d", a); // Outputs: 6
    return 0;
}

In this C example, increment takes an integer pointer as its argument. By passing the address of a (&a), the function is able to modify the original variable a through dereferencing the pointer (*ptr).

In summary, while C++ offers references as a more straightforward alternative to pointers for some use cases, C relies exclusively on pointers to achieve similar functionality.

  • ctime library: time_t is a data type that represents time. It is a type suitable for storing the calendar time.

File I/O

The input/output (I/O) operations in C++ and C are fundamentally different due to the design and paradigms of the two languages. C is a procedural programming language, and it handles I/O primarily using functions from the standard I/O library (stdio.h). C++, on the other hand, is a multi-paradigm language that supports both procedural and object-oriented programming, and it introduces a more streamlined and object-oriented approach to I/O through its Standard Template Library (STL).

C I/O

In C, I/O operations are performed using functions from the stdio.h library. The most commonly used functions are:

  • printf() for output formatting to the standard output (usually the screen).
  • scanf() for formatted input from the standard input.
  • File operations are handled using fopen(), fclose(), fread(), fwrite(), fprintf(), fscanf(), etc.

These functions are very powerful and flexible, but they can be somewhat low-level and verbose, especially for complex operations.

Example of writing to a file in C:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "w");
    if (fp == NULL) {
        perror("Error opening file");
        return -1;
    }
    fprintf(fp, "Hello, world!\n");
    fclose(fp);
    return 0;
}

C++ I/O

C++ introduces an object-oriented approach to I/O through its I/O streams library. The header files <iostream>, <fstream>, and <sstream> provide classes for input and output operations.

  • std::cin, std::cout, std::cerr, and std::clog are used for standard input, standard output, standard error, and logging operations, respectively.
  • File I/O is handled using std::ifstream for input files, std::ofstream for output files, and std::fstream for files that will be used for both input and output.

These classes provide a high level of abstraction and make it easy to perform I/O operations while also taking advantage of C++’s object-oriented features. They can be used with overloaded operators << and >> for output and input, respectively, making the code more concise and readable.

Example of writing to a file in C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <fstream>
#include <iostream>

int main() {
    std::ofstream outFile("example.txt");
    if (!outFile) {
        std::cerr << "Error opening file" << std::endl;
        return -1;
    }
    outFile << "Hello, world!\n";
    outFile.close();
    return 0;
}

Differences Summary

  • Paradigm & Syntax: C++ uses an object-oriented approach with stream classes and overloaded operators for I/O, making syntax more concise and readable. C uses procedural programming with a function-based approach.
  • Safety and Ease of Use: C++’s I/O streams provide type safety and help prevent common errors like mismatching format specifiers, which are common in C’s printf and scanf.
  • Flexibility: C’s I/O functions are very flexible and powerful but can be more complex to use correctly and safely.
  • Performance: The performance of C++ streams is generally comparable to C’s I/O functions for most applications, though the abstraction level of C++ streams can introduce overhead in some cases.

In summary, if you’re familiar with C’s I/O, you’ll find C++’s approach to be more abstracted and easier to integrate with the language’s object-oriented features, though it’s useful to understand both, especially if you’ll be working with C++ codebases that might interact with C libraries or legacy code.

Class

Classes are one of the fundamental features of C++, providing the means to bundle data and functions together into a single unit called an object. They are a cornerstone of object-oriented programming (OOP) in C++, enabling concepts such as encapsulation, inheritance, and polymorphism. Here’s a basic overview of classes in C++:

Definition

A class in C++ is defined using the class keyword, followed by the class name and a body that contains declarations of members (variables) and member functions (methods) enclosed in braces {}. The members can have different access specifiers, primarily public, private, and protected:

  • public: Members are accessible from outside the class.
  • private: Members are accessible only from within the class or its friends.
  • protected: Members are accessible from within the class, its friends, and its derived classes.

Basic Syntax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClassName {
public:
    // Constructor
    ClassName() {
        // Initialization code
    }
    
    // Public member function
    void publicFunction() {
        // Function code
    }
    
private:
    // Private member variable
    int privateVariable;
};

Creating Objects

An object is an instance of a class. You can create objects of a class by simply declaring them, similar to variables:

1
ClassName objectName;

Constructors and Destructors

  • Constructors: Special member functions that are called automatically when an object of the class is created. Constructors have the same name as the class and are used for initializing objects(like __init__ in Python).
  • Destructors: Special member functions called automatically when an object is destroyed. Destructors have the same name as the class, preceded by a tilde ~, and are used for cleanup tasks.

Example

Here’s a simple example demonstrating the definition of a class, creation of an object, and the use of a constructor and a member function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
using namespace std;

class Box {
public:
    double length; // Length of a box
    double breadth; // Breadth of a box
    double height; // Height of a box
    
    // Constructor
    Box() : length(0), breadth(0), height(0) {} // Initialize members to 0
    
    // Member function to calculate volume
    double getVolume() {
        return length * breadth * height;
    }
};

int main() {
    Box box1; // Declare box1 of type Box
    box1.length = 5.0; // Assign values to members
    box1.breadth = 6.0;
    box1.height = 7.0;
    
    // Calculate and display the volume of box1
    cout << "Volume of box1 : " << box1.getVolume() << endl;
    
    return 0;
}

In this example, Box is a class that represents a geometric box with length, breadth, and height. It includes a constructor that initializes these dimensions to 0 and a member function getVolume() that calculates the volume of the box. An object box1 of type Box is then created and used to demonstrate setting member variables and calling the member function.

Key Concepts

  • Encapsulation: The bundling of data (attributes) and methods (functions) that operate on the data into a single unit or class, and controlling their access.
  • Inheritance: The capability of a class to derive properties and characteristics from another class.
  • Polymorphism: The ability to call the same method on different objects and have each of them respond in their own way.

Classes in C++ are a powerful mechanism for organizing and structuring code, enabling the development of complex software systems through object-oriented principles.

Pointers to Class

Pointers to classes are used in C++ to access class members using a pointer. It is similar to accessing members using the dot . operator, but with a pointer to the class. The arrow -> operator is used to access the members of a class using a pointer. Like structs.

this Pointer

Resembles the self pointer in Python. It is a pointer that points to the object for which the member function is called. It is used to access the members of the class(Similar to self in Python).

Inheritance

The format of inheritance in C++ is:

1
class derived_class: access_specifier base_class

Overloading

Function Overloading

Function Overloading is a feature that allows us to have more than one function with the same name, if the function has different parameters. It is a type of polymorphism. The compiler selects the appropriate function at the time of calling based on the number and type of arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class PrintData {
    public:
        void print(int i) {
            cout << "Printing int: " << i << endl;
        }
        void print(double f) {
            cout << "Printing float: " << f << endl;
        }
        void print(char* c) {
            cout << "Printing character: " << c << endl;
        }
};

int main() {
    PrintData pd;
    pd.print(5);
    pd.print(500.263);
    pd.print("Hello C++");
    return 0;
}

Operator Overloading

Operator overloading is a feature in C++ where different operators can be made to work for user-defined classes. It allows the same operator to have different meanings for different classes(Similar to Python’s __add__, __sub__, etc.).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Complex {
    private:
        int real, imag;
    public:
        Complex(int r = 0, int i = 0) : real(r), imag(i) {}
        Complex operator + (Complex const &obj) {
            Complex res;
            res.real = real + obj.real;
            res.imag = imag + obj.imag;
            return res;
        }
        void print() {
            cout << real << " + i" << imag << endl;
        }
};

Virtual Functions

In C++, a virtual function is a member function in a base class that you expect to redefine in derived classes. When you use a pointer or a reference to a base class to call a virtual function, C++ determines which function version to invoke at runtime based on the type of the object that the pointer or reference actually points to. This mechanism is known as dynamic binding or late binding, and it’s a cornerstone of polymorphism in object-oriented programming.

Key Points About Virtual Functions

  • Declaration: You declare a virtual function in a base class using the virtual keyword. It can then be overridden in any derived class.

  • Overriding: To override a virtual function in a derived class, you simply define a function with the same signature as the virtual function in the base class. The override keyword can be optionally used in C++11 and later to make intentions clear and enable compiler checks for correct overriding.

  • Destructors: Destructors can and often should be virtual in base classes, especially when you’re dealing with inheritance. This ensures that destructors of derived classes are called correctly when an object is deleted through a pointer to the base class.

  • Pure Virtual Functions: A virtual function can be made “pure” by appending = 0 to its declaration in the base class. A class with at least one pure virtual function is called an abstract class, and it cannot be instantiated directly. Pure virtual functions must be overridden by derived classes unless those derived classes are also abstract.

Here’s a simple example to illustrate the use of virtual functions and polymorphism in C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() { // Virtual function
        cout << "Base class show function called." << endl;
    }
    virtual ~Base() {} // Virtual destructor
};

class Derived : public Base {
public:
    void show() override { // Overriding virtual function
        cout << "Derived class show function called." << endl;
    }
};

int main() {
    Base *bptr;
    Derived d;
    bptr = &d;

    // Virtual function, binded at runtime
    bptr->show();

    return 0;
}

In this example:

  • The Base class has a virtual function named show() and a virtual destructor.
  • The Derived class overrides the show() function.
  • A Base pointer bptr is used to point to an object of the Derived class.
  • When bptr->show() is called, the program determines at runtime that bptr points to a Derived object, so it calls the Derived version of show(), demonstrating polymorphism.

Why Use Virtual Functions?

Virtual functions enable polymorphism, allowing you to write more general and reusable code. For example, you can write a function that takes a pointer or a reference to a base class but can work with objects of any class derived from that base class. The correct member function of the derived class is called, even though the function was written without specific knowledge of the derived class. This is especially useful in frameworks and libraries, where flexibility and extensibility are critical.

This post is licensed under CC BY 4.0 by the author.