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 thestd
namespace. - Variables:
- 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 aconst
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:
- Affix:
() [] -> . ++ --
- Unary(RTL):
* & sizeof
- Multiplicative:
* / %
- Additive:
+ -
- Shift:
<< >>
- Relational:
< <= > >=
- Equality:
== !=
- Bitwise:
& ^ |
- Logic:
&& ||
- Conditional(RTL)
- Assignment(RTL)
- Comma
- Affix:
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
, andstd::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 integer0
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
, andstd::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, andstd::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
andscanf
. - 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 namedshow()
and a virtual destructor. - The
Derived
class overrides theshow()
function. - A
Base
pointerbptr
is used to point to an object of theDerived
class. - When
bptr->show()
is called, the program determines at runtime thatbptr
points to aDerived
object, so it calls theDerived
version ofshow()
, 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.