C++ is a powerful, general-purpose programming language that extends C with object-oriented and generic programming features. It’s widely used for system programming, game development, and high-performance applications due to its efficiency and control over hardware.
This tutorial assumes basic knowledge of computers and introduces C++ concepts step-by-step, from variables to object-oriented programming, with practical examples.
- C++ supports procedural, object-oriented, and generic programming.
- It’s used in applications like game engines (e.g., Unreal Engine) and operating systems.
- Compilers like g++ or clang++ are needed to run C++ code.
- The Standard Template Library (STL) provides reusable components like vectors.
- This guide assumes basic computer knowledge and introduces C++ step-by-step.
Variables and basic data types
Variables store data in a C++ program. They must be declared with a specific type:
int age = 25; // Integer
double price = 19.99; // Floating-point
char grade = 'A'; // Character
bool isActive = true; // Boolean
std::string name = "Alice"; // String (requires <string> header)
Identifiers must start with a letter or underscore, followed by letters, digits, or underscores. C++ is case-sensitive.
int for whole numbers like 25 or -10.
double for decimals like 19.99.
char for single characters like 'A'.
bool for true/false values.
std::string for text, requires <string> header.
The const keyword declares immutable variables:
const double PI = 3.14159;
Constants must be initialized at declaration and cannot be modified. constexpr ensures compile-time evaluation:
constexpr int MAX = 100;
- Constants like
PI cannot be changed after initialization.
constexpr for compile-time constants like MAX.
- Follows same naming rules as variables.
- Prevents accidental modification of critical values.
- Cannot share names with variables in the same scope.
Namespaces prevent name conflicts by grouping code:
namespace mySpace {
int x = 10;
}
int main() {
std::cout << mySpace::x; // Access with scope resolution
return 0;
}
Use using namespace std; to avoid std:: prefixes, but sparingly to avoid conflicts.
- Groups related code to avoid naming conflicts.
- Access with
:: operator, e.g., mySpace::x.
std namespace includes standard library.
using namespace can simplify code but risks conflicts.
- Supports nested namespaces for complex projects.
typedef creates aliases for types:
typedef unsigned long ulong;
C++11’s using is more flexible:
using str = std::string;
str name = "Bob";
typedef simplifies complex type names.
using is preferred in modern C++.
- Improves code readability.
- Useful for function pointers or templates.
- Can alias any type, including classes.
C++ supports operators like +, -, *, /, and % (modulus):
int a = 10, b = 3;
int sum = a + b; // 13
int mod = a % b; // 1
Increment (++) and decrement (--) modify variables:
a++; // a = 11
- Addition:
a + b for summing values.
- Subtraction:
a - b for differences.
- Multiplication:
a * b for products.
- Division:
a / b, integer division truncates.
- Modulus:
a % b for remainders.
Convert between types explicitly with casts:
double d = 5.7;
int i = static_cast<int>(d); // i = 5
Implicit conversion occurs in mixed-type operations:
int x = 10;
double y = x; // y = 10.0
- Explicit casting with
static_cast.
- Implicit conversion in assignments or expressions.
- May lose precision (e.g.,
double to int).
dynamic_cast for polymorphic types.
- C-style casts (
(int)d) are less safe.
Execute code if a condition is true:
if (x > 0) {
std::cout << "Positive";
} else if (x < 0) {
std::cout << "Negative";
} else {
std::cout << "Zero";
}
- Basic
if checks a condition.
else if handles additional conditions.
else covers all other cases.
- Use braces for multiple statements.
- Conditions must evaluate to true/false.
switch evaluates a variable against cases:
switch (day) {
case 1: std::cout << "Monday"; break;
default: std::cout << "Unknown";
}
Use break to prevent fall-through.
- Compares variable to case values.
break exits the switch block.
default handles unmatched cases.
- Works with
int, char, etc.
- More readable than chained
if-else.
A shorthand for if-else:
int max = (a > b) ? a : b;
- Syntax:
condition ? valueIfTrue : valueIfFalse.
- Concise for simple conditions.
- Returns a value, unlike
if.
- Avoid nesting for readability.
- Useful in assignments or expressions.
Combine conditions with && (and), || (or), ! (not):
if (x > 0 && y > 0) {
std::cout << "Both positive";
}
&& requires both conditions true.
|| requires at least one true.
! inverts truth value.
- Short-circuit evaluation optimizes checks.
- Used in complex conditionals.
Useful string methods in C++
The std::string class offers methods:
std::string s = "Hello";
int len = s.length(); // 5
s.append(" World"); // "Hello World"
char c = s.at(0); // 'H'
length() returns string length.
append() adds text to end.
at() accesses characters safely.
substr() extracts substrings.
find() locates text position.
Execute while a condition is true:
int i = 0;
while (i < 5) {
std::cout << i++;
}
- Checks condition before each iteration.
- Executes only if condition is true.
- Risk of infinite loops if condition persists.
- Use braces for multiple statements.
- Common for dynamic iteration counts.
Execute at least once, then continue if true:
int i = 0;
do {
std::cout << i++;
} while (i < 5);
- Executes at least once.
- Condition checked after iteration.
- Useful for input validation loops.
- Braces for multiple statements.
- Can become infinite if condition persists.
Loop with initialization, condition, and update:
for (int i = 0; i < 5; i++) {
std::cout << i;
}
- Initialization runs once.
- Condition checked before each iteration.
- Update runs after each iteration.
- Ideal for fixed iteration counts.
- Combines loop control in one line.
break exits a loop; continue skips to the next iteration:
for (int i = 0; i < 5; i++) {
if (i == 3) break; // Exits loop
if (i == 1) continue; // Skips 1
std::cout << i;
}
break stops loop immediately.
continue skips current iteration.
- Works in all loop types.
- Useful for conditional loop control.
- Can improve loop efficiency.
Loops within loops:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
std::cout << i << j;
}
}
- Inner loop runs fully for each outer iteration.
- Used for multidimensional data.
- Can be computationally intensive.
break affects innermost loop.
- Common in matrix operations.
Arrays store fixed-size sequences:
int numbers[5] = {1, 2, 3, 4, 5};
- Fixed size set at declaration.
- Access via zero-based index.
- Initialize with values or default to 0.
- Out-of-bounds access is undefined.
- Used for sequential data storage.
Returns the size of a type or variable in bytes:
int arr[5];
int size = sizeof(arr); // 20 (5 ints * 4 bytes)
- Works with types or variables.
- Array size:
sizeof(arr) / sizeof(arr[0]).
- Size varies by architecture.
- Useful for loop bounds.
- Evaluated at compile time.
Use a loop to access array elements:
int arr[3] = {1, 2, 3};
for (int i = 0; i < 3; i++) {
std::cout << arr[i];
}
- Use
for or while loops.
- Access via index:
arr[i].
- Avoid hardcoding array size.
- Check bounds to prevent errors.
- Can modify elements during iteration.
Range-based for loop (C++11):
int arr[3] = {1, 2, 3};
for (int num : arr) {
std::cout << num;
}
- Simplifies array iteration.
- Works with containers like
std::vector.
- Use
auto for type deduction.
- Read-only unless using references.
- Introduced in C++11.
Arrays are passed as pointers:
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i];
}
}
- Pass size explicitly.
- Modifications affect original array.
- Use
const to prevent changes.
- Pointer decay occurs in functions.
- Call with array name:
printArray(arr, 5).
Search an array for an element
Linear search example:
int find(int arr[], int size, int target) {
for (int i = 0; i < size; i++) {
if (arr[i] == target) return i;
}
return -1;
}
- Returns index or -1 if not found.
- Works with unsorted arrays.
- Linear time complexity.
- Use
std::find for standard solution.
- Binary search faster for sorted arrays.
Use std::sort from <algorithm>:
#include <algorithm>
int arr[5] = {5, 2, 8, 1, 9};
std::sort(arr, arr + 5);
- Sorts ascending by default.
- Custom comparators for other orders.
- Requires
<algorithm>.
- Efficient for large datasets.
- Modifies array in place.
Use std::fill to set array elements:
#include <algorithm>
int arr[5];
std::fill(arr, arr + 5, 0); // Fills with 0
- Sets range to specified value.
- Requires
<algorithm>.
- Works with any container.
- Faster than manual loops.
- Specify start and end iterators.
Arrays of arrays, e.g., 2D:
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
- Used for grids or matrices.
- Access:
matrix[0][1].
- Fixed size per dimension.
- Requires nested loops for iteration.
- Stored contiguously in memory.
Use & to get a variable’s address:
int x = 10;
std::cout << &x; // Prints memory address
- Addresses are hexadecimal.
- Unique per variable.
- Used with pointers.
- Changes each program run.
- Fundamental for memory management.
Pass by VALUE vs pass by REFERENCE
Pass by value copies; pass by reference modifies:
void byValue(int x) { x++; }
void byReference(int& x) { x++; }
- Value copying preserves original.
- Reference modifies original.
- References use
& in signature.
- References are efficient for large data.
- Use
const for read-only references.
Prevent modification of parameters:
void print(const int& x) { std::cout << x; }
- Ensures parameter integrity.
- Common with references.
- Also works with pointers.
- Improves code safety.
- Allows compiler optimizations.
Store memory addresses:
int x = 10;
int* ptr = &x;
std::cout << *ptr; // Dereference to get 10
- Declared with
*.
- Dereference with
*.
- Points to specific types.
- Used for dynamic memory.
- Requires careful handling.
Pointers not pointing anywhere:
int* ptr = nullptr;
- Uses
nullptr (C++11).
- Prevents undefined behavior.
- Check before dereferencing.
- Safer than
NULL.
- Initializes unassigned pointers.
Allocate memory with new, free with delete:
int* ptr = new int(10);
delete ptr;
new allocates at runtime.
delete frees memory.
- Prevents memory leaks.
- Used for dynamic data structures.
- Manage to avoid dangling pointers.
Functions calling themselves:
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
- Requires base case.
- Useful for hierarchical problems.
- Can be memory-intensive.
- Alternative to iteration.
- Stack overflow risk for deep recursion.
Generic functions for multiple types:
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
- Uses
template keyword.
- Works with any type.
- Reduces code duplication.
- Compile-time type safety.
- Used in STL containers.
Group related data:
struct Person {
std::string name;
int age;
};
- Public members by default.
- Create instances:
Person p;.
- Access with dot operator.
- Simpler than classes.
- Can include methods.
Pass structs as arguments
Pass structs by reference for efficiency:
void printPerson(const Person& p) {
std::cout << p.name << ", " << p.age;
}
- Reference avoids copying.
const prevents modification.
- Access members with dot operator.
- Efficient for large structs.
- Common in function arguments.
Define named integer constants:
enum Color { RED, GREEN, BLUE };
Color c = RED;
- Defaults to
int type.
- Improves code readability.
- Values start at 0 by default.
- Scoped enums:
enum class.
- Used for fixed option sets.
Object Oriented Programming
C++ supports OOP with classes:
class Car {
public:
std::string model;
void drive() { std::cout << "Driving"; }
};
- Encapsulation hides data.
- Classes define objects.
- Supports inheritance.
- Methods define behavior.
- Polymorphism via virtual functions.
Initialize objects:
class Car {
public:
std::string model;
Car(std::string m) : model(m) {}
};
- Called on object creation.
- Can take parameters.
- Initializer lists for efficiency.
- No return type.
- Matches class name.
Multiple constructors with different parameters:
class Car {
public:
std::string model;
int year;
Car(std::string m) : model(m), year(0) {}
Car(std::string m, int y) : model(m), year(y) {}
};
- Different parameter lists.
- Flexible object creation.
- Compiler selects based on arguments.
- Reduces code duplication.
- Can call other constructors.
Control access to class members:
class Car {
private:
std::string model;
public:
void setModel(std::string m) { model = m; }
std::string getModel() { return model; }
};
- Getters retrieve private data.
- Setters validate and set data.
- Enforces encapsulation.
- Protects class invariants.
- Common in OOP design.
Classes inherit properties and methods:
class Vehicle {
public:
std::string brand;
};
class Car : public Vehicle {
public:
std::string model;
};
- Derived class inherits base class.
public inheritance preserves access.
- Supports polymorphism.
protected for derived access.
- Multiple inheritance is possible.