Core C++ Programming Fundamentals and Practical Usage Guide
Memory Leaks
Memory leaks occur when a program fails to deallocate dynamically allocated heap memory that is no longer needed, resulting in permanent memory wastage during runtime. This does not mean physical memory is lost, but that the application loses all references to an allocated memory block, making it impossible to return the memory to the system for reuse without native garbage collection, which is not available in standard C/C++.
Variable memory in C/C++ is allocated via three primary methods: stack allocation (automatic management for local variables), global/static memory allocation, and heap allocation. Memory leaks exclusively affect heap-allocated memory, as stack and static memory are automatically managed by the compiler and runtime.
To manually release heap memory when it is no longer needed, use the delete operator for single valuees, or delete[] for arrays:
int* dynamic_int = new int;
*dynamic_int = 42;
std::cout << *dynamic_int << std::endl;
delete dynamic_int;
Pseudo-Random Number Generation
The C++ standard library provides the rand() function in the <cstdlib> header to generate pseudo-random integers:
#include <iostream>
#include <cstdlib>
int main() {
std::cout << rand() << std::endl;
return 0;
}
To generate range-bound random integers, use the modulo (%) operator:
rand() % 10returns a value between 0 and 9 inclusive13 + rand() % 51returns a value between 13 and 63 inclusive
int main() {
for (int iter = 0; iter < 10; iter++) {
std::cout << 1 + (rand() % 5) << std::endl;
}
return 0;
}
By default, rand() uses a fixed seed value, so it produces identical sequences of values on every program run. To generate unique sequences, use the srand() function to seed the random number generator, typically with the current system time from the <ctime> header:
#include <iostream>
#include <cstdlib>
#include <ctime>
int main() {
srand((unsigned int)time(NULL));
int random_val = rand();
std::cout << random_val << std::endl;
return 0;
}
Functon Overloading
C++ allows multiple functions with the same identifier in the same scope, as long as thier parameter lists differ in count, type, or order of types. Return type alone is not sufficient to overload functions:
#include <iostream>
void printValue(int val) {
std::cout << "Integer value: " << val << std::endl;
}
void printValue(float val) {
std::cout << "Floating-point value: " << val << std::endl;
}
int main() {
int int_input = 45;
float float_input = 72.341f;
printValue(int_input);
printValue(float_input);
return 0;
}
Invalid overload example (only return type differs):
// Compilation error: ambiguous function signature
int getValue(int x) { return x; }
float getValue(int x) { return (float)x; }
Recursion
Recursive functions call themselves during execution, and require a base termination case to prevent infinite recursion:
int calculateFactorial(int n) {
if (n <= 1) {
return 1;
}
return n * calculateFactorial(n - 1);
}
int main() {
std::cout << calculateFactorial(4) << std::endl; // Outputs 24
return 0;
}
Passing Arrays to Functions
Arrays passed to functions decay to pointers to their first element, so you must also pass the array size as a separate parameter:
void printArrayElements(int arr[], int length) {
for (int i = 0; i < length; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int calculateArraySum(int arr[], int length) {
int total = 0;
for (int i = 0; i < length; i++) {
total += arr[i];
}
return total;
}
int main() {
int test_arr[5] = {12, 8, 19, 3, 27};
printArrayElements(test_arr, 5);
std::cout << "Sum: " << calculateArraySum(test_arr, 5) << std::endl;
return 0;
}
Function Parameter Passing Modes
Pass-by-value
Copies the argument value to the function parameter, so modifications inside the function do not affect the original variable:
void modifyValue(int val) {
val = 100;
}
int main() {
int original = 20;
modifyValue(original);
std::cout << original << std::endl; // Outputs 20, unchanged
return 0;
}
Pass-by-reference
Uses pointers or C++ references to allow the function to modify the original argument:
void modifyValue(int* val_ptr) {
*val_ptr = 100;
}
int main() {
int original = 20;
modifyValue(&original);
std::cout << original << std::endl; // Outputs 100, modified
return 0;
}
Object-Oriented Programming Basics
Object-oriented programming models software as interacting objects that mirror real-world entities, each with unique identity, state (attributes), and behavior (methods). Classes are blueprints for creating objects, defining the shared attributes and methods all instances of the class will have:
#include <iostream>
#include <string>
class BankAccount {
public:
void greetUser() {
std::cout << "Welcome to your bank account!" << std::endl;
}
};
int main() {
BankAccount user_account;
user_account.greetUser();
return 0;
}
Abstraction and Encapsulation
Abstraction exposes only essential public interfaces to external code, hiding implementation details. Encapsulation bundles data and methods into a class, restricting direct access to internal data via access specifiers: public, protected, private:
class Student {
private:
std::string student_name;
public:
void setName(std::string name) {
student_name = name;
}
std::string getName() {
return student_name;
}
};
int main() {
Student s1;
s1.setName("Alice Johnson");
std::cout << s1.getName() << std::endl;
return 0;
}
Constructors
Special member functions executed automatically when a class instance is created, with no return type and the same name as the class. Can be overloaded to accept parameters for initializing attributes:
class Student {
private:
std::string student_name;
public:
Student(std::string name) {
student_name = name;
}
std::string getName() {
return student_name;
}
};
int main() {
Student s1("Bob Smith");
std::cout << s1.getName() << std::endl;
return 0;
}
Splitting Classes Across Files
Header files (.h/.hpp) contain class declarations and method prototypes, while source files (.cpp) contain method implementations. Include guards prevent duplicate header inclusion:
MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
public:
MyClass();
void printMessage();
~MyClass();
};
#endif
MyClass.cpp
#include "MyClass.h"
#include <iostream>
MyClass::MyClass() {
std::cout << "Constructor called" << std::endl;
}
void MyClass::printMessage() {
std::cout << "Hello from MyClass" << std::endl;
}
MyClass::~MyClass() {
std::cout << "Destructor called" << std::endl;
}
Destructors
Special member functions called automatically when an object is destroyed, prefixed with ~, no parameters or return type, used to release allocated resources.
Member Access
Use the . operator for object instances, and the -> operator for pointers to objects.
Const Objects and Methods
Const-qualified objects cannot be modified after initialization. Only const-qualified methods can be called on const objects, as they guarantee not to modify object state:
class MyClass {
public:
void print() const {
std::cout << "Const method called" << std::endl;
}
};
int main() {
const MyClass obj;
obj.print();
return 0;
}
Member Initializer Lists
Used to initialize class members (especially const members and reference types) before the constructor body executes:
class MyClass {
private:
int regular_var;
const int const_var;
public:
MyClass(int a, int b) : regular_var(a), const_var(b) {}
};
Composition
Classes can contain instances of other classes as members, modeling "has-a" relationships between objects:
class Birthday {
private:
int month, day, year;
public:
Birthday(int m, int d, int y) : month(m), day(d), year(y) {}
void printDate() const {
std::cout << month << "/" << day << "/" << year << std::endl;
}
};
class Person {
private:
std::string full_name;
Birthday birth_date;
public:
Person(std::string name, Birthday bd) : full_name(name), birth_date(bd) {}
void printInfo() const {
std::cout << "Name: " << full_name << std::endl;
std::cout << "Birthdate: ";
birth_date.printDate();
}
};
int main() {
Birthday bd(12, 15, 1998);
Person p("Charlie Davis", bd);
p.printInfo();
return 0;
}
Friend Functions and Classes
Declared with the friend keyword inside a class, allowing external functions or other classes to access its private and protected members:
class MyClass {
private:
int secret_val = 0;
friend void accessSecret(MyClass& obj);
};
void accessSecret(MyClass& obj) {
obj.secret_val = 99;
std::cout << obj.secret_val << std::endl;
}
this Pointer
Implicit pointer available in all non-static member functions, pointing to the current object instance. Can be used to access members, resolve name conflicts, or return the current object:
class MyClass {
private:
int val;
public:
MyClass(int val) : val(val) {}
void printVal() {
std::cout << val << std::endl;
std::cout << this->val << std::endl;
std::cout << (*this).val << std::endl;
}
};
Operator Overloading
Most C++ operators can be overloaded to work with user-defined types, using the operator keyword:
class Vector2D {
public:
int x, y;
Vector2D(int x = 0, int y = 0) : x(x), y(y) {}
Vector2D operator+(const Vector2D& other) {
return Vector2D(x + other.x, y + other.y);
}
};
int main() {
Vector2D v1(2, 3), v2(4, 5);
Vector2D sum = v1 + v2;
std::cout << sum.x << ", " << sum.y << std::endl; // Outputs 6, 8
return 0;
}
Inheritance
Allows creating derived classes that inherit attributes and methods from a base class, promoting code reuse. Access specifiers for inheritance (public, protected, private) control access to inherited members:
class Animal {
public:
void makeSound() {
std::cout << "Generic animal sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() {
std::cout << "Woof!" << std::endl;
}
};
Protected members are accessible within the class and its derived classes, but not from external code.
Derived class constructor/destructor execution order: base class constructor runs first, then derived class constructor. Destructors run in reverse order: derived class first, then base class.
Polymorphism
Allows calling the same method on different derived class objects via base class pointers/references, executing the implementation matching the actual object type. Uses virtual functions in the basee class:
class Shape {
public:
virtual double calculateArea() = 0; // Pure virtual function, makes Shape an abstract class
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double calculateArea() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double calculateArea() override {
return width * height;
}
};
int main() {
Shape* s1 = new Circle(5);
Shape* s2 = new Rectangle(4, 6);
std::cout << s1->calculateArea() << std::endl; // ~78.54
std::cout << s2->calculateArea() << std::endl; // 24
delete s1;
delete s2;
return 0;
}
Abstract classes contain pure virtual functions and cannot be instantiated, only used as base classes for derived types.
Function Templates
Allow writing generic functions that work with multiple data types without rewriting code for each type:
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add<int>(3, 5) << std::endl; // 8
std::cout << add<double>(2.5, 4.7) << std::endl; // 7.2
return 0;
}
Class Templates
Generic class blueprints that work with arbitrary data types:
template <typename T>
class Box {
private:
T content;
public:
Box(T val) : content(val) {}
T getContent() { return content; }
};
int main() {
Box<int> int_box(10);
Box<std::string> str_box("Test");
return 0;
}
Template Specialization
Allows overriding template behavior for specific data types:
template <typename T>
class TypeChecker {
public:
TypeChecker(T val) {
std::cout << val << " is not a character" << std::endl;
}
};
template <>
class TypeChecker<char> {
public:
TypeChecker(char val) {
std::cout << val << " is a character" << std::endl;
}
};
Exception Handling
Uses try, throw, catch blocks to handle runtime errors gracefully without crashing the program:
int main() {
try {
int divisor;
std::cout << "Enter divisor: ";
std::cin >> divisor;
if (divisor == 0) {
throw std::runtime_error("Division by zero is not allowed");
}
std::cout << "Result: " << 100 / divisor << std::endl;
} catch (std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
File I/O
Uses the <fstream> library to read from and write to files. Types include ofstream (write), ifstream (read), fstream (read/write):
int main() {
// Write to file
std::ofstream out_file("output.txt");
if (out_file.is_open()) {
out_file << "Hello, file!" << std::endl;
out_file.close();
}
// Read from file
std::string line;
std::ifstream in_file("output.txt");
if (in_file.is_open()) {
while (std::getline(in_file, line)) {
std::cout << line << std::endl;
}
in_file.close();
}
return 0;
}