C and C++ from Zero to Professional: 100 Compiled Examples with Real Output

C and C++ from Zero to Professional: 100 Compiled Examples with Real Output

C is a small, fast language that runs close to the hardware. C++ builds on C with classes, templates and a large standard library, and it is used for games, browsers, databases and embedded systems. This article takes you from the first printf to modern C++20 in 100 examples. The first 50 are C: variables, control flow, functions, arrays, pointers, structs, memory, files and the preprocessor. The last 50 are C++: references, classes, RAII, smart pointers, templates, the standard library, concepts, exceptions, threads and formatting. Every example was compiled with GCC 16 and run, and the real output is shown after the code.

What you need

A computer with GCC or Clang installed (on Windows, install the WinLibs GCC or MSYS2 toolchain; on macOS, Xcode command-line tools; on Linux, your package manager). Check the installation with gcc --version and g++ --version. A plain text editor is enough. You need no programming experience, but the first examples assume you know what a number and a word are.

How to compile and run

C examples use gcc -std=c17 and C++ examples use g++ -std=c++20, one file per example. For example, save a C++ program as main.cpp, compile it with g++ -std=c++20 -Wall -Wextra main.cpp -o main, and run ./main. The output shown in the article comes from a real run with GCC 16.

Topic 1: Getting Started, Variables and Types

Every C program starts at main. These examples show how to print, store values in variables, and choose the right type for each value.

The structure of a C program

A C program is a set of functions, and main is where it starts. #include <stdio.h> brings in the printf function. printf prints text, and the format specifiers %d, %f and %c print an int, a double and a char. Return 0 from main to report success.

Integer and floating-point types

int, short and long hold whole numbers, but the exact size depends on the platform, so use sizeof to check it. float and double store decimals with limited precision. 1.0 / 3.0 is not exactly one third, which is why printing with more digits shows the error.

Constants

const creates a typed value that the compiler protects from change. #define replaces text before the compiler runs, so it has no type. Prefer const for typed values in new code and use #define mainly for compile-time settings.

Illustrations

How a C or C++ program becomes an executable: the preprocessor expands macros and includes, the compiler turns each source file into object code, and the linker joins the object files into one program
How a C or C++ program becomes an executable: the preprocessor expands macros and includes, the compiler turns each source file into object code, and the linker joins the object files into one program

Example 1: Hello, World

Your first program: print one line of text.

#include <stdio.h>

int main(void) {
    printf("Hello, World!\n");
    return 0;
}

Output:

Hello, World!

Example 2: Variables and format specifiers

Store values and print them with printf placeholders.

#include <stdio.h>

int main(void) {
    int apples = 7;
    double price = 2.5;
    char initial = 'A';
    printf("%d apples, %.2f each, initial %c\n", apples, price, initial);
    printf("total = %.2f\n", apples * price);
    return 0;
}

Output:

7 apples, 2.50 each, initial A
total = 17.50

Example 3: Integer types and sizeof

Each integer type takes a different number of bytes.

#include <stdio.h>

int main(void) {
    printf("char   : %zu byte(s)\n", sizeof(char));
    printf("short  : %zu byte(s)\n", sizeof(short));
    printf("int    : %zu byte(s)\n", sizeof(int));
    printf("long   : %zu byte(s)\n", sizeof(long));
    printf("long long: %zu byte(s)\n", sizeof(long long));
    return 0;
}

Output:

char   : 1 byte(s)
short  : 2 byte(s)
int    : 4 byte(s)
long   : 4 byte(s)
long long: 8 byte(s)

Example 4: Floating point and precision

float and double store decimals with limited precision.

#include <stdio.h>

int main(void) {
    float f = 1.0f / 3.0f;
    double d = 1.0 / 3.0;
    printf("float  : %.10f\n", f);
    printf("double : %.10f\n", d);
    printf("0.1 + 0.2 == 0.3 ? %s\n", (0.1 + 0.2 == 0.3) ? "yes" : "no");
    return 0;
}

Output:

float  : 0.3333333433
double : 0.3333333333
0.1 + 0.2 == 0.3 ? no

Example 5: char and ASCII values

A char is a small integer; 'a' is 97.

#include <stdio.h>

int main(void) {
    char c = 'a';
    printf("%c is %d\n", c, c);
    printf("upper of %c is %c\n", c, c - 'a' + 'A');
    printf("digit '7' minus '0' = %d\n", '7' - '0');
    return 0;
}

Output:

a is 97
upper of a is A
digit '7' minus '0' = 7

Example 6: Constants: const and #define

Fixed values that the program must not change.

#include <stdio.h>

#define MAX_USERS 100
static const double TAX_RATE = 0.07;

int main(void) {
    double price = 200.0;
    printf("max users: %d\n", MAX_USERS);
    printf("price with tax: %.2f\n", price * (1.0 + TAX_RATE));
    return 0;
}

Output:

max users: 100
price with tax: 214.00

Topic 2: Operators and Conversions

Operators combine values. Converting between types is a common source of bugs, so the rules are worth learning early.

Arithmetic and integer division

+, -, *, / and % work on numbers. Dividing two ints drops the fraction, so 17 / 5 is 3. Use a double operand, such as 17.0 / 5, to keep the decimal part. The % operator gives the remainder.

Increment, compound assignment and comparisons

++ and -- change a variable by one, and the position before or after the name decides when the change is seen. += and *= update in one step. In C, comparisons give 1 for true and 0 for false, so there is no separate boolean type unless you include stdbool.h.

Bitwise operators and casts

&, |, ^, ~ and the shifts << and >> work on the bits of an integer, which is useful for flags. A cast such as (double) changes the type of a value for one expression and does not change the variable.

Example 7: Arithmetic and integer division

Dividing two ints drops the fraction.

#include <stdio.h>

int main(void) {
    int a = 17, b = 5;
    printf("%d %d %d %d %d\n", a + b, a - b, a * b, a / b, a % b);
    printf("17.0 / 5 = %.2f\n", 17.0 / b);
    return 0;
}

Output:

22 12 85 3 2
17.0 / 5 = 3.40

Example 8: Increment and compound assignment

++, -- and += change a variable in one step.

#include <stdio.h>

int main(void) {
    int n = 5;
    printf("%d\n", n++);
    printf("%d\n", ++n);
    n += 10;
    n *= 2;
    printf("final %d\n", n);
    return 0;
}

Output:

5
7
final 34

Example 9: Comparison and logical operators

Tests give 1 for true and 0 for false in C.

#include <stdio.h>

int main(void) {
    int age = 20;
    int has_ticket = 1;
    printf("adult: %d\n", age >= 18);
    printf("can enter: %d\n", age >= 18 && has_ticket);
    printf("needs help: %d\n", !has_ticket || age < 12);
    return 0;
}

Output:

adult: 1
can enter: 1
needs help: 0

Example 10: Bitwise operators and shifts

Work on the individual bits of an integer.

#include <stdio.h>

int main(void) {
    unsigned int flags = 0x5;
    printf("flags & 4 = %u\n", flags & 4u);
    printf("flags | 2 = %u\n", flags | 2u);
    printf("flags ^ 1 = %u\n", flags ^ 1u);
    printf("1 << 4 = %u\n", 1u << 4);
    printf("256 >> 2 = %u\n", 256u >> 2);
    return 0;
}

Output:

flags & 4 = 4
flags | 2 = 7
flags ^ 1 = 4
1 << 4 = 16
256 >> 2 = 64

Example 11: Casting and conversion

Change a value to another type with (type).

#include <stdio.h>

int main(void) {
    int total = 7, count = 2;
    double average = (double)total / count;
    printf("average = %.2f\n", average);
    printf("truncated = %d\n", (int)3.99);
    printf("mixed = %.1f\n", total / 2 + 0.5);
    return 0;
}

Output:

average = 3.50
truncated = 3
mixed = 3.5

Topic 3: Decisions and Loops

Control flow decides what runs and how often. Pick the statement that matches the problem: if for a choice, switch for a set of values, and a loop for repetition.

if, switch and the ternary operator

if and else choose one branch from conditions. switch picks a case by value; without break, execution falls through to the next case, which is occasionally useful but a common bug. The ternary operator is a short if-else that produces a value.

for, while and do-while

for is best when you know how many times to repeat. while checks its condition before each pass, and do-while runs its body at least once. Choose the loop that makes the condition easiest to read.

break, continue and nested loops

break leaves the innermost loop, and continue skips to the next pass. A loop inside another loop is the usual way to walk a grid or a table. Keep nested loops short, and give loop counters clear names.

Example 12: if, else if and else

Choose one branch from several conditions.

#include <stdio.h>

int main(void) {
    int score = 73;
    if (score >= 80) {
        printf("A\n");
    } else if (score >= 70) {
        printf("B\n");
    } else {
        printf("C\n");
    }
    return 0;
}

Output:

B

Example 13: switch with fall-through

Choose a case by value; break stops the fall-through.

#include <stdio.h>

int main(void) {
    for (int day = 1; day <= 3; day++) {
        switch (day) {
            case 1:
            case 2:
                printf("day %d: early\n", day);
                break;
            default:
                printf("day %d: later\n", day);
                break;
        }
    }
    return 0;
}

Output:

day 1: early
day 2: early
day 3: later

Example 14: The ternary operator

A short if-else that produces a value.

#include <stdio.h>

int main(void) {
    int a = 4, b = 9;
    int larger = (a > b) ? a : b;
    printf("larger = %d\n", larger);
    printf("%s\n", (larger % 2 == 0) ? "even" : "odd");
    return 0;
}

Output:

larger = 9
odd

Example 15: The for loop

Repeat a block with a counter.

#include <stdio.h>

int main(void) {
    int sum = 0;
    for (int i = 1; i <= 10; i++) {
        sum += i;
    }
    printf("sum 1..10 = %d\n", sum);
    return 0;
}

Output:

sum 1..10 = 55

Example 16: while and do-while

Loop on a condition; do-while always runs once.

#include <stdio.h>

int main(void) {
    int n = 3;
    while (n > 0) {
        printf("while %d\n", n--);
    }
    int m = 0;
    do {
        printf("do-while runs once, m = %d\n", m);
    } while (m > 0);
    return 0;
}

Output:

while 3
while 2
while 1
do-while runs once, m = 0

Example 17: break and continue

Leave a loop early or skip to the next pass.

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 10; i++) {
        if (i % 2 == 0) continue;
        if (i > 7) break;
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

Output:

1 3 5 7

Example 18: Nested loops: a table

A loop inside a loop prints a grid.

#include <stdio.h>

int main(void) {
    for (int row = 1; row <= 3; row++) {
        for (int col = 1; col <= 3; col++) {
            printf("%3d", row * col);
        }
        printf("\n");
    }
    return 0;
}

Output:

  1  2  3
  2  4  6
  3  6  9

Topic 4: Functions

A function gives a name to a block of code, takes inputs and returns a result. Good functions do one job and are easy to test.

Parameters and return values

Declare each parameter with its type and the return type before the name. C passes arguments by value, so the function works on a copy. Return a result with return, and use void for a function that returns nothing.

Pointers as parameters

To change the caller's variable, pass its address with & and read or write it with *. The classic swap function shows the difference: the value version changes only its own copy.

Recursion and static locals

A recursive function calls itself and needs a stopping case, or it never ends and overflows the stack. A static local keeps its value between calls, which is useful for counters and caches, but it makes the function harder to test.

Example 19: Functions with return values

Give a block of code a name and send back a result.

#include <stdio.h>

static int add(int a, int b) {
    return a + b;
}

static double average(int a, int b) {
    return (a + b) / 2.0;
}

int main(void) {
    printf("%d\n", add(3, 4));
    printf("%.1f\n", average(3, 4));
    return 0;
}

Output:

7
3.5

Example 20: Pass by value and by pointer

Copies do not change the caller; pointers do.

#include <stdio.h>

static void try_swap_value(int a, int b) {
    int t = a; a = b; b = t;
}

static void swap(int *a, int *b) {
    int t = *a; *a = *b; *b = t;
}

int main(void) {
    int x = 1, y = 2;
    try_swap_value(x, y);
    printf("after value swap: %d %d\n", x, y);
    swap(&x, &y);
    printf("after pointer swap: %d %d\n", x, y);
    return 0;
}

Output:

after value swap: 1 2
after pointer swap: 2 1

Example 21: Recursion

A function that calls itself, with a stopping case.

#include <stdio.h>

static unsigned long long factorial(unsigned int n) {
    return n <= 1 ? 1ULL : n * factorial(n - 1);
}

static unsigned long long fib(unsigned int n) {
    return n < 2 ? n : fib(n - 1) + fib(n - 2);
}

int main(void) {
    printf("10! = %llu\n", factorial(10));
    printf("fib(20) = %llu\n", fib(20));
    return 0;
}

Output:

10! = 3628800
fib(20) = 6765

Example 22: Static local variables

A local variable that keeps its value between calls.

#include <stdio.h>

static int next_id(void) {
    static int counter = 0;
    counter++;
    return counter;
}

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("id %d\n", next_id());
    }
    return 0;
}

Output:

id 1
id 2
id 3

Topic 5: Arrays and Strings

Arrays store several values of one type, indexed from 0. In C, a string is an array of char that ends with a zero byte.

Arrays and their length

int scores[5] creates five ints. C does not check indexes, so writing past the end corrupts memory silently. sizeof array / sizeof array[0] gives the number of elements when the array is declared in the same function.

Two-dimensional arrays and arrays of strings

int grid[2][3] is a table with two rows and three columns. An array of const char pointers holds several strings of different lengths, which is the usual way to store a list of names.

String functions

strlen counts characters before the zero byte. strcpy and strcat copy and join strings, but they do not check the buffer size, so they are easy to misuse. strcmp compares strings, returning 0 when they are equal.

Example 23: Arrays

A fixed number of values of one type, indexed from 0.

#include <stdio.h>

int main(void) {
    int scores[5] = {90, 75, 88, 64, 95};
    for (int i = 0; i < 5; i++) {
        printf("scores[%d] = %d\n", i, scores[i]);
    }
    return 0;
}

Output:

scores[0] = 90
scores[1] = 75
scores[2] = 88
scores[3] = 64
scores[4] = 95

Example 24: Sum, minimum and maximum

Walk an array once and track three results.

#include <stdio.h>

int main(void) {
    int values[] = {12, 7, 19, 3, 25};
    int n = (int)(sizeof values / sizeof values[0]);
    int sum = 0, min = values[0], max = values[0];
    for (int i = 0; i < n; i++) {
        sum += values[i];
        if (values[i] < min) min = values[i];
        if (values[i] > max) max = values[i];
    }
    printf("sum=%d min=%d max=%d avg=%.2f\n", sum, min, max, (double)sum / n);
    return 0;
}

Output:

sum=66 min=3 max=25 avg=13.20

Example 25: Two-dimensional arrays

A table of values with rows and columns.

#include <stdio.h>

int main(void) {
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    for (int r = 0; r < 2; r++) {
        for (int c = 0; c < 3; c++) {
            printf("%d ", grid[r][c]);
        }
        printf("\n");
    }
    return 0;
}

Output:

1 2 3 
4 5 6

Example 26: Strings as char arrays

A string is a char array that ends with a zero byte.

#include <stdio.h>

int main(void) {
    char name[] = "Ada";
    printf("%s has %zu characters\n", name, sizeof name - 1);
    name[0] = 'E';
    printf("now: %s\n", name);
    return 0;
}

Output:

Ada has 3 characters
now: Eda

Example 27: String functions from string.h

strlen, strcpy, strcat and strcmp.

#include <stdio.h>
#include <string.h>

int main(void) {
    char buffer[32] = "Hello";
    strcat(buffer, ", C");
    printf("%s (%zu)\n", buffer, strlen(buffer));
    printf("compare: %d\n", strcmp("apple", "banana") < 0);
    char copy[32];
    strcpy(copy, buffer);
    printf("copy: %s\n", copy);
    return 0;
}

Output:

Hello, C (8)
compare: 1
copy: Hello, C

Topic 6: Pointers

A pointer holds the address of a value. Pointers are the heart of C: they let functions change data, walk arrays and build dynamic structures.

Address and dereference

& gives the address of a variable, and * reads or writes the value at a pointer's address. A pointer's type tells the compiler how big each step is, so int * steps by sizeof(int).

Pointer arithmetic on arrays

An array name acts like a pointer to its first element. p + 2 points two elements further, and *(p + 2) reads that element, which is the same as p[2]. Going past the end is undefined behaviour.

Passing arrays and pointers to functions

A function receives an array as a pointer, so it must also receive the length. The const keyword on the pointer tells the reader and the compiler that the function will not change the data.

Example 28: Pointers: address and dereference

& gets an address; * reads or writes the value there.

#include <stdio.h>

int main(void) {
    int value = 42;
    int *p = &value;
    printf("value via pointer: %d\n", *p);
    *p = 100;
    printf("value now: %d\n", value);
    return 0;
}

Output:

value via pointer: 42
value now: 100

Example 29: Pointer arithmetic

Move a pointer along an array with + and -.

#include <stdio.h>

int main(void) {
    int nums[4] = {10, 20, 30, 40};
    int *p = nums;
    for (int i = 0; i < 4; i++) {
        printf("%d ", *(p + i));
    }
    printf("\n");
    printf("third via index: %d, via pointer: %d\n", nums[2], *(nums + 2));
    return 0;
}

Output:

10 20 30 40 
third via index: 30, via pointer: 30

Example 30: Passing arrays to functions

An array is passed as a pointer plus its length.

#include <stdio.h>

static int sum(const int *values, size_t count) {
    int total = 0;
    for (size_t i = 0; i < count; i++) {
        total += values[i];
    }
    return total;
}

int main(void) {
    int data[] = {1, 2, 3, 4, 5};
    printf("sum = %d\n", sum(data, sizeof data / sizeof data[0]));
    return 0;
}

Output:

sum = 15

Example 31: Arrays of strings

An array of pointers to text.

#include <stdio.h>

int main(void) {
    const char *fruits[] = {"apple", "banana", "cherry"};
    size_t n = sizeof fruits / sizeof fruits[0];
    for (size_t i = 0; i < n; i++) {
        printf("%zu: %s\n", i, fruits[i]);
    }
    return 0;
}

Output:

0: apple
1: banana
2: cherry

Topic 7: Structs, Unions, Enums and typedef

These types group values together and give names to sets of constants. They are the basis for data structures in C.

Structs and pointers to structs

A struct groups fields under one name. The arrow operator -> reaches a field through a pointer, so p->x is the same as (*p).x. Pass a pointer to a large struct instead of copying it.

Unions and enums

A union shares one block of memory between several types, so only one value is meaningful at a time. An enum gives names to integer constants, which makes switch statements easier to read.

typedef

typedef gives a type a shorter name, such as Rect for struct with two doubles. Function pointer types become much easier to read with typedef.

Example 32: Structs

Group related values under one name.

#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main(void) {
    struct Point p = {3, 4};
    p.x += 1;
    printf("point (%d, %d)\n", p.x, p.y);
    return 0;
}

Output:

point (4, 4)

Example 33: Struct pointers and ->

Reach the fields of a struct through a pointer.

#include <stdio.h>

struct Book {
    const char *title;
    int pages;
};

static void print_book(const struct Book *b) {
    printf("%s has %d pages\n", b->title, b->pages);
}

int main(void) {
    struct Book books[] = {{"C Basics", 120}, {"Pointers", 96}};
    for (int i = 0; i < 2; i++) {
        print_book(&books[i]);
    }
    return 0;
}

Output:

C Basics has 120 pages
Pointers has 96 pages

Example 34: Unions

Several types share the same memory; one value at a time.

#include <stdio.h>

union Value {
    int as_int;
    float as_float;
};

int main(void) {
    union Value v;
    v.as_int = 65;
    printf("as int: %d\n", v.as_int);
    v.as_float = 1.5f;
    printf("as float: %.1f\n", v.as_float);
    return 0;
}

Output:

as int: 65
as float: 1.5

Example 35: Enums

Give names to a set of integer constants.

#include <stdio.h>

enum Color { RED, GREEN = 5, BLUE };

int main(void) {
    enum Color c = BLUE;
    printf("RED=%d GREEN=%d BLUE=%d\n", RED, GREEN, BLUE);
    printf("chosen: %d\n", c);
    return 0;
}

Output:

RED=0 GREEN=5 BLUE=6
chosen: 6

Example 36: typedef and function pointer types

Give a type a shorter name.

#include <stdio.h>

typedef struct {
    double width;
    double height;
} Rect;

typedef double (*Measure)(Rect);

static double area(Rect r) {
    return r.width * r.height;
}

int main(void) {
    Rect r = {3.0, 4.5};
    Measure m = area;
    printf("area = %.2f\n", m(r));
    return 0;
}

Output:

area = 13.50

Topic 8: Dynamic Memory and Files

Dynamic memory lets a program ask for space at run time. Files let it keep data after it stops. Both need careful handling in C, where the programmer is responsible for cleanup.

malloc, calloc, realloc and free

malloc asks for a block of bytes, calloc gives zeroed memory, and realloc resizes a block. Every successful call must be matched by one free. Check for NULL before using the result, because allocation can fail.

Avoiding leaks and dangling pointers

After free, set the pointer to NULL so you do not use it by mistake. Free each block once. Keep track of who owns each block, because a block freed in one function must not be used by another.

Text and binary files

fopen opens a file, and the mode tells it whether to read, write or append. fprintf and fgets handle text, while fwrite and fread copy raw bytes. Always check the return value of fopen and close every file you open.

Example 37: Dynamic memory with malloc and free

Ask for memory at run time and give it back.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n = 5;
    int *squares = malloc(n * sizeof *squares);
    if (squares == NULL) {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    for (int i = 0; i < n; i++) {
        squares[i] = i * i;
    }
    for (int i = 0; i < n; i++) {
        printf("%d ", squares[i]);
    }
    printf("\n");
    free(squares);
    return 0;
}

Output:

0 1 4 9 16

Example 38: calloc and realloc

Zeroed memory, and resizing a block.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *items = calloc(3, sizeof *items);
    printf("zeroed: %d %d %d\n", items[0], items[1], items[2]);
    int *bigger = realloc(items, 6 * sizeof *bigger);
    if (bigger == NULL) {
        free(items);
        return 1;
    }
    items = bigger;
    items[5] = 99;
    printf("last item: %d\n", items[5]);
    free(items);
    return 0;
}

Output:

zeroed: 0 0 0
last item: 99

Example 39: Freeing memory safely

Free once, then clear the pointer.

#include <stdio.h>
#include <stdlib.h>

static char *make_greeting(const char *name) {
    char *out = malloc(64);
    if (out == NULL) return NULL;
    snprintf(out, 64, "Hello, %s", name);
    return out;
}

int main(void) {
    char *msg = make_greeting("Mali");
    if (msg != NULL) {
        printf("%s\n", msg);
        free(msg);
        msg = NULL;
    }
    return 0;
}

Output:

Hello, Mali

Example 40: Text files: write and read

fopen, fprintf, fgets and fclose.

#include <stdio.h>

int main(void) {
    FILE *out = fopen("notes.txt", "w");
    if (out == NULL) return 1;
    fprintf(out, "first line\nsecond line\n");
    fclose(out);

    FILE *in = fopen("notes.txt", "r");
    if (in == NULL) return 1;
    char line[64];
    while (fgets(line, sizeof line, in) != NULL) {
        printf("read: %s", line);
    }
    fclose(in);
    remove("notes.txt");
    return 0;
}

Output:

read: first line
read: second line

Example 41: Binary files with fwrite and fread

Store raw values in a file and read them back.

#include <stdio.h>

int main(void) {
    int data[] = {10, 20, 30};
    FILE *f = fopen("values.bin", "wb");
    if (f == NULL) return 1;
    fwrite(data, sizeof data[0], 3, f);
    fclose(f);

    int back[3] = {0};
    f = fopen("values.bin", "rb");
    if (f == NULL) return 1;
    size_t got = fread(back, sizeof back[0], 3, f);
    fclose(f);
    remove("values.bin");
    printf("read %zu values: %d %d %d\n", got, back[0], back[1], back[2]);
    return 0;
}

Output:

read 3 values: 10 20 30

Topic 9: The Preprocessor, Function Pointers and Variadic Functions

These tools change how code is built and how functions are called. Used well, they make programs flexible; used carelessly, they make them hard to debug.

Macros and conditional compilation

A macro is text substituted before compilation. Wrap every macro argument and the whole expression in parentheses, or SQUARE(2 + 1) gives the wrong answer. #if and #ifdef include code only when a condition holds at build time.

Function pointers and qsort

A function pointer stores the address of a function, so you can pass behaviour to another function. qsort uses a comparison callback, which lets one routine sort any type of array.

Variadic functions and command-line arguments

stdarg.h lets a function take any number of arguments, read with va_arg. main receives argc and argv, which hold the arguments the user typed after the program name.

Example 42: Command-line arguments

argc and argv give the arguments the user typed.

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("argument count: %d\n", argc);
    for (int i = 1; i < argc; i++) {
        printf("arg %d: %s\n", i, argv[i]);
    }
    return 0;
}

Output:

argument count: 1

Example 43: Macros and function-like macros

Text substitution done before compiling; parenthesize arguments.

#include <stdio.h>

#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main(void) {
    printf("SQUARE(4) = %d\n", SQUARE(4));
    printf("SQUARE(2 + 1) = %d\n", SQUARE(2 + 1));
    printf("MAX(3, 9) = %d\n", MAX(3, 9));
    return 0;
}

Output:

SQUARE(4) = 16
SQUARE(2 + 1) = 9
MAX(3, 9) = 9

Example 44: Conditional compilation

Include code only when a condition is true at build time.

#include <stdio.h>

#define DEBUG_MODE 1

int main(void) {
#if DEBUG_MODE
    printf("debug build\n");
#else
    printf("release build\n");
#endif
#ifdef __STDC_VERSION__
    printf("C standard version is known\n");
#endif
    return 0;
}

Output:

debug build
C standard version is known

Example 45: Function pointers

Store a function in a variable and call it through the pointer.

#include <stdio.h>

static int add(int a, int b) { return a + b; }
static int mul(int a, int b) { return a * b; }

static int apply(int (*op)(int, int), int x, int y) {
    return op(x, y);
}

int main(void) {
    int (*ops[])(int, int) = {add, mul};
    printf("add: %d\n", apply(ops[0], 6, 7));
    printf("mul: %d\n", apply(ops[1], 6, 7));
    return 0;
}

Output:

add: 13
mul: 42

Example 46: Sorting with qsort

Sort any array with a comparison callback.

#include <stdio.h>
#include <stdlib.h>

static int compare_ints(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void) {
    int nums[] = {42, 7, 19, 3, 25};
    size_t n = sizeof nums / sizeof nums[0];
    qsort(nums, n, sizeof nums[0], compare_ints);
    for (size_t i = 0; i < n; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");
    return 0;
}

Output:

3 7 19 25 42

Example 47: Variadic functions

A function that takes any number of arguments with stdarg.

#include <stdarg.h>
#include <stdio.h>

static int sum_all(int count, ...) {
    va_list args;
    va_start(args, count);
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }
    va_end(args);
    return total;
}

int main(void) {
    printf("%d\n", sum_all(3, 1, 2, 3));
    printf("%d\n", sum_all(5, 10, 20, 30, 40, 50));
    return 0;
}

Output:

6
150

Topic 10: Bits, Errors and Scope

Low-level bit work, error checking and the rules of scope are the everyday skills of C programmers.

Counting and testing bits

x &= x - 1 clears the lowest set bit, so a loop that does it counts the set bits. Bit masks such as flags & 4 test a single bit without changing the others.

Checking errors with errno

Many library functions return a special value on failure and set errno to say why. Check the return value first, then read errno, and use strerror to turn it into text.

static at file scope

A static function or variable at file scope is visible only in that file. This keeps internal helpers from clashing with names in other files and is a basic step toward good structure.

Example 48: Counting set bits

Clear the lowest set bit in a loop until nothing is left.

#include <stdio.h>

static int count_bits(unsigned int x) {
    int count = 0;
    while (x != 0) {
        x &= x - 1;
        count++;
    }
    return count;
}

int main(void) {
    printf("bits in 0 = %d\n", count_bits(0));
    printf("bits in 7 = %d\n", count_bits(7));
    printf("bits in 255 = %d\n", count_bits(255));
    return 0;
}

Output:

bits in 0 = 0
bits in 7 = 3
bits in 255 = 8

Example 49: Error handling with errno

Check a return value, then read errno for the reason.

#include <errno.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    FILE *f = fopen("no-such-file-here.txt", "r");
    if (f == NULL) {
        printf("open failed: errno set = %s\n", errno != 0 ? "yes" : "no");
        return 0;
    }
    fclose(f);
    return 0;
}

Output:

open failed: errno set = yes

Example 50: Static functions and global state

static limits a name to one file.

#include <stdio.h>

static int call_count = 0;

static void record_call(void) {
    call_count++;
}

static int calls_so_far(void) {
    return call_count;
}

int main(void) {
    record_call();
    record_call();
    printf("calls: %d\n", calls_so_far());
    return 0;
}

Output:

calls: 2

Topic 11: C++ Basics: References, Overloading, Strings and Vectors

C++ keeps C's speed and adds types, references and a standard library. These examples introduce the everyday tools.

References and overloading

A reference is another name for an existing variable, so changing the reference changes the variable. Several functions can share a name when their parameter types differ, and the compiler picks the right one.

std::string and std::vector

std::string grows and shrinks, and knows its own length, so you do not manage the memory by hand. std::vector is a resizable array that owns its items. Both work well with range-based for loops.

auto, default arguments and iostream

auto lets the compiler deduce a type from the value, and default arguments let a caller leave out a parameter. std::cout with << replaces printf and checks types at compile time.

Example 51: Hello with iostream

Print text to the console with std::cout.

#include <iostream>

int main() {
    std::cout << "Hello, C++!" << '\n';
    return 0;
}

Output:

Hello, C++!

Example 52: References

A reference is another name for the same variable.

#include <iostream>

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

int main() {
    int count = 5;
    int &alias = count;
    alias += 10;
    increment(count);
    std::cout << count << '\n';
}

Output:

16

Example 53: Function overloading

Several functions with the same name, chosen by argument types.

#include <iostream>
#include <string>

std::string describe(int n) { return "int " + std::to_string(n); }
std::string describe(double d) { return "double " + std::to_string(d); }
std::string describe(const std::string &s) { return "string " + s; }

int main() {
    std::cout << describe(7) << '\n';
    std::cout << describe(2.5) << '\n';
    std::cout << describe(std::string("hi")) << '\n';
}

Output:

int 7
double 2.500000
string hi

Example 54: Default arguments

Parameters that can be left out in a call.

#include <iostream>

int power(int base, int exponent = 2) {
    int result = 1;
    for (int i = 0; i < exponent; ++i) result *= base;
    return result;
}

int main() {
    std::cout << power(3) << ' ' << power(3, 4) << '\n';
}

Output:

9 81

Example 55: std::string basics

Join, measure, cut and search text safely.

#include <iostream>
#include <string>

int main() {
    std::string name = "Ada";
    name += " Lovelace";
    std::cout << name << " has " << name.size() << " characters\n";
    std::cout << name.substr(0, 3) << '\n';
    std::cout << (name.find("Love") != std::string::npos) << '\n';
}

Output:

Ada Lovelace has 12 characters
Ada
1

Example 56: std::vector basics

A growable array that owns its items.

#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores{70, 85, 92};
    scores.push_back(64);
    int total = 0;
    for (int s : scores) total += s;
    std::cout << "count=" << scores.size() << " total=" << total << '\n';
}

Output:

count=4 total=311

Example 57: Range-based for and auto

Loop over a container without index arithmetic.

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stock{{"apple", 3}, {"pear", 0}};
    for (const auto &[item, qty] : stock) {
        std::cout << item << ": " << qty << '\n';
    }
}

Output:

apple: 3
pear: 0

Topic 12: Classes, RAII and Copy and Move

A class combines data and behaviour, and it is where C++ becomes powerful. RAII ties a resource to an object's lifetime, which makes programs safe without manual cleanup.

Classes, constructors and const members

A constructor sets up an object when it is created. A member function marked const promises not to change the object. Keep data private, expose only what callers need, and validate input before storing it.

Destructors and RAII

A destructor runs when an object goes out of scope, so a resource acquired in a constructor is released there. This is RAII: a file, lock or memory block is released automatically, even when an error occurs.

Copy, move and operators

An object that owns memory needs a copy constructor and copy assignment, or two objects share one block and free it twice. std::move lets an object hand its resources over instead of copying them. Operator overloading makes + and << work for your own types.

Example 58: Classes and constructors

Bundle data and behaviour, and set up objects in a constructor.

#include <iostream>
#include <string>

class Book {
public:
    Book(std::string title, int pages) : title_(std::move(title)), pages_(pages) {}
    std::string title() const { return title_; }
    int pages() const { return pages_; }
private:
    std::string title_;
    int pages_;
};

int main() {
    Book b{"Clean C++", 320};
    std::cout << b.title() << " (" << b.pages() << " pages)\n";
}

Output:

Clean C++ (320 pages)

Example 59: const member functions

Promise that a method does not change the object.

#include <iostream>

class Counter {
public:
    void add(int n) { total_ += n; }
    int total() const { return total_; }
private:
    int total_ = 0;
};

int report(const Counter &c) {
    return c.total();
}

int main() {
    Counter c;
    c.add(4);
    c.add(6);
    std::cout << report(c) << '\n';
}

Output:

10

Example 60: Encapsulation and exceptions

Keep data private and reject invalid input.

#include <iostream>
#include <stdexcept>

class Account {
public:
    explicit Account(int opening) : balance_(opening) {}
    void deposit(int amount) {
        if (amount <= 0) throw std::invalid_argument("amount must be positive");
        balance_ += amount;
    }
    int balance() const { return balance_; }
private:
    int balance_;
};

int main() {
    Account a{100};
    a.deposit(50);
    std::cout << a.balance() << '\n';
}

Output:

150

Example 61: Destructors and RAII

A destructor releases a resource when the object goes out of scope.

#include <iostream>
#include <string>

class Trace {
public:
    explicit Trace(std::string name) : name_(std::move(name)) {
        std::cout << "open " << name_ << '\n';
    }
    ~Trace() { std::cout << "close " << name_ << '\n'; }
private:
    std::string name_;
};

int main() {
    Trace outer{"outer"};
    {
        Trace inner{"inner"};
        std::cout << "work\n";
    }
    std::cout << "done\n";
}

Output:

open outer
open inner
work
close inner
done
close outer

Example 62: Copy constructor and copy assignment

Copy an object that owns memory without sharing it.

#include <iostream>
#include <cstring>

class Buffer {
public:
    explicit Buffer(const char *text) : data_(new char[std::strlen(text) + 1]) {
        std::strcpy(data_, text);
    }
    Buffer(const Buffer &other) : data_(new char[std::strlen(other.data_) + 1]) {
        std::strcpy(data_, other.data_);
    }
    Buffer &operator=(const Buffer &other) {
        if (this != &other) {
            delete[] data_;
            data_ = new char[std::strlen(other.data_) + 1];
            std::strcpy(data_, other.data_);
        }
        return *this;
    }
    ~Buffer() { delete[] data_; }
    const char *text() const { return data_; }
private:
    char *data_;
};

int main() {
    Buffer a{"hello"};
    Buffer b = a;
    b = Buffer{"world"};
    std::cout << a.text() << ' ' << b.text() << '\n';
}

Output:

hello world

Example 63: Move semantics

Transfer a resource instead of copying it.

#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
    std::string source = "a long piece of text";
    std::string target = std::move(source);
    std::cout << "target: " << target << '\n';
    std::cout << "source empty: " << source.empty() << '\n';

    std::vector<std::string> items;
    items.push_back(std::move(target));
    std::cout << "items: " << items.size() << '\n';
}

Output:

target: a long piece of text
source empty: 1
items: 1

Example 64: Operator overloading

Make + and << work with your own type.

#include <iostream>

struct Vec2 {
    double x, y;
    Vec2 operator+(const Vec2 &o) const { return {x + o.x, y + o.y}; }
    Vec2 operator*(double k) const { return {x * k, y * k}; }
};

std::ostream &operator<<(std::ostream &os, const Vec2 &v) {
    return os << '(' << v.x << ", " << v.y << ')';
}

int main() {
    Vec2 a{1, 2}, b{3, 4};
    std::cout << (a + b) * 2.0 << '\n';
}

Output:

(8, 12)

Topic 13: Inheritance, Smart Pointers and Library Types

Inheritance and virtual functions let one piece of code work with many types. Smart pointers, optional and variant express ownership and possibility in the type system.

Inheritance and virtual functions

A derived class inherits from a base class and overrides virtual functions. A pointer to the base class calls the right version at run time. Always give a polymorphic base class a virtual destructor.

unique_ptr and shared_ptr

unique_ptr has exactly one owner and frees its object automatically. shared_ptr counts its owners, and weak_ptr watches an object without keeping it alive. Prefer unique_ptr, and use shared_ptr only when ownership really is shared.

optional, variant and std::function

std::optional holds a value that may be missing. std::variant holds one of several types, and std::visit handles each case. std::function stores any callable, such as a lambda, in a variable.

Example 65: Inheritance and virtual functions

Derived classes change behaviour through a base pointer.

#include <iostream>
#include <string>

class Animal {
public:
    virtual ~Animal() = default;
    virtual std::string sound() const { return "..."; }
};

class Dog : public Animal {
public:
    std::string sound() const override { return "Woof"; }
};

class Cat : public Animal {
public:
    std::string sound() const override { return "Meow"; }
};

int main() {
    Dog d;
    Cat c;
    const Animal *pets[] = {&d, &c};
    for (const Animal *p : pets) std::cout << p->sound() << '\n';
}

Output:

Woof
Meow

Example 66: Abstract classes

A base class with a pure virtual function cannot be created directly.

#include <iostream>
#include <memory>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Square : public Shape {
public:
    explicit Square(double s) : side_(s) {}
    double area() const override { return side_ * side_; }
private:
    double side_;
};

int main() {
    std::unique_ptr<Shape> s = std::make_unique<Square>(3.0);
    std::cout << s->area() << '\n';
}

Output:

9

Example 67: Polymorphism with smart pointers

Store different types in one vector of unique_ptr.

#include <iostream>
#include <memory>
#include <vector>

struct Task {
    virtual ~Task() = default;
    virtual void run() const = 0;
};

struct Print : Task {
    void run() const override { std::cout << "print\n"; }
};

struct Save : Task {
    void run() const override { std::cout << "save\n"; }
};

int main() {
    std::vector<std::unique_ptr<Task>> queue;
    queue.push_back(std::make_unique<Print>());
    queue.push_back(std::make_unique<Save>());
    for (const auto &t : queue) t->run();
}

Output:

print
save

Example 68: unique_ptr ownership

Exactly one owner; the object is freed automatically.

#include <iostream>
#include <memory>

struct Resource {
    explicit Resource(int id) : id_(id) { std::cout << "acquire " << id_ << '\n'; }
    ~Resource() { std::cout << "release " << id_ << '\n'; }
    int id() const { return id_; }
private:
    int id_;
};

std::unique_ptr<Resource> make(int id) {
    return std::make_unique<Resource>(id);
}

int main() {
    auto r = make(7);
    std::cout << "using " << r->id() << '\n';
}

Output:

acquire 7
using 7
release 7

Example 69: shared_ptr and weak_ptr

Shared ownership with a count, and a weak observer.

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> a = std::make_shared<int>(42);
    std::weak_ptr<int> watcher = a;
    std::cout << "use count: " << a.use_count() << '\n';
    {
        std::shared_ptr<int> b = a;
        std::cout << "use count: " << a.use_count() << '\n';
    }
    std::cout << "after scope: " << a.use_count() << '\n';
    std::cout << "still alive: " << !watcher.expired() << '\n';
}

Output:

use count: 1
use count: 2
after scope: 1
still alive: 1

Example 70: std::optional

A value that may be missing, without null pointers.

#include <iostream>
#include <optional>
#include <string>

std::optional<int> parse_int(const std::string &text) {
    try {
        return std::stoi(text);
    } catch (...) {
        return std::nullopt;
    }
}

int main() {
    for (const char *s : {"42", "oops"}) {
        if (auto v = parse_int(s)) {
            std::cout << "value " << *v << '\n';
        } else {
            std::cout << "not a number: " << s << '\n';
        }
    }
}

Output:

value 42
not a number: oops

Example 71: std::variant and std::visit

One value that can be one of several types.

#include <iostream>
#include <string>
#include <variant>

using Value = std::variant<int, double, std::string>;

std::string kind(const Value &v) {
    return std::visit([](const auto &x) -> std::string {
        using T = std::decay_t<decltype(x)>;
        if constexpr (std::is_same_v<T, int>) return "int";
        else if constexpr (std::is_same_v<T, double>) return "double";
        else return "string";
    }, v);
}

int main() {
    std::cout << kind(Value{3}) << ' ' << kind(Value{2.5}) << ' ' << kind(Value{std::string("x")}) << '\n';
}

Output:

int double string

Example 72: Lambdas and captures

Small anonymous functions that can use outside variables.

#include <iostream>

int main() {
    int base = 10;
    auto add_base = [base](int x) { return x + base; };
    auto count = 0;
    auto bump = [&count]() { ++count; };
    bump();
    bump();
    std::cout << add_base(5) << ' ' << count << '\n';
}

Output:

15 2

Example 73: std::function

Store any callable in a variable.

#include <functional>
#include <iostream>
#include <vector>

int main() {
    std::vector<std::function<int(int)>> steps{
        [](int x) { return x + 1; },
        [](int x) { return x * 3; },
    };
    int value = 2;
    for (const auto &step : steps) value = step(value);
    std::cout << value << '\n';
}

Output:

9

Topic 14: Algorithms, Containers and Templates

The standard library provides containers, algorithms and iterators that work together. Templates let you write code once for many types.

Algorithms and iterators

std::sort, std::find_if and std::transform work on any range through iterators. Lambdas make the conditions short and clear. Prefer an algorithm to a hand-written loop when one exists.

Maps, sets and arrays

std::map keeps keys sorted, and std::unordered_map uses hashing for fast lookup. std::set keeps unique values sorted, std::array has a fixed size, and std::pair with structured bindings returns two values neatly.

Templates, specialization and concepts

A function template works for any type that supports the operations it uses. A class template such as Stack<T> is written once. Concepts in C++20 name the requirements a template type must meet, so errors appear at the call site.

Example 74: Algorithms: sort, find_if and transform

Standard algorithms work on any range of values.

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v{5, 2, 9, 1, 7};
    std::sort(v.begin(), v.end());
    auto big = std::find_if(v.begin(), v.end(), [](int x) { return x > 4; });
    std::transform(v.begin(), v.end(), v.begin(), [](int x) { return x * 10; });
    std::cout << "first > 4: " << *big << '\n';
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';
}

Output:

first > 4: 50
10 20 50 70 90

Example 75: Iterators and std::map

Walk a sorted key-value container with iterators.

#include <iostream>
#include <map>

int main() {
    std::map<int, const char *> names{{3, "c"}, {1, "a"}, {2, "b"}};
    for (auto it = names.begin(); it != names.end(); ++it) {
        std::cout << it->first << '=' << it->second << '\n';
    }
    auto found = names.find(2);
    std::cout << "found " << found->second << '\n';
}

Output:

1=a
2=b
3=c
found b

Example 76: unordered_map word count

Count words with a hash map.

#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>

int main() {
    std::string text = "the cat and the hat and the bat";
    std::unordered_map<std::string, int> counts;
    std::istringstream words(text);
    std::string w;
    while (words >> w) ++counts[w];
    std::cout << "the=" << counts["the"] << " and=" << counts["and"] << " bat=" << counts["bat"] << '\n';
}

Output:

the=3 and=2 bat=1

Example 77: std::set and std::multiset

Sorted collections without and with duplicates.

#include <iostream>
#include <set>

int main() {
    std::set<int> unique{3, 1, 3, 2};
    std::multiset<int> all{3, 1, 3, 2};
    std::cout << "set size " << unique.size() << '\n';
    std::cout << "multiset size " << all.size() << '\n';
    for (int x : unique) std::cout << x << ' ';
    std::cout << '\n';
}

Output:

set size 3
multiset size 4
1 2 3

Example 78: std::array

A fixed-size array with size and iterators.

#include <array>
#include <iostream>
#include <numeric>

int main() {
    std::array<int, 4> a{1, 2, 3, 4};
    int sum = std::accumulate(a.begin(), a.end(), 0);
    std::cout << "size " << a.size() << " sum " << sum << '\n';
}

Output:

size 4 sum 10

Example 79: std::pair and structured bindings

Return two values and unpack them by name.

#include <iostream>
#include <utility>

std::pair<int, int> divide(int a, int b) {
    return {a / b, a % b};
}

int main() {
    auto [quotient, remainder] = divide(17, 5);
    std::cout << quotient << " remainder " << remainder << '\n';
}

Output:

3 remainder 2

Example 80: Function templates

One function that works for many types.

#include <iostream>
#include <string>

template <typename T>
T largest(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << largest(3, 8) << '\n';
    std::cout << largest(2.5, 1.5) << '\n';
    std::cout << largest(std::string("pear"), std::string("apple")) << '\n';
}

Output:

8
2.5
pear

Example 81: Class templates: a Stack

A generic container you write once.

#include <iostream>
#include <stdexcept>
#include <vector>

template <typename T>
class Stack {
public:
    void push(const T &value) { items_.push_back(value); }
    T pop() {
        if (items_.empty()) throw std::out_of_range("stack is empty");
        T top = items_.back();
        items_.pop_back();
        return top;
    }
    bool empty() const { return items_.empty(); }
private:
    std::vector<T> items_;
};

int main() {
    Stack<int> s;
    s.push(1);
    s.push(2);
    std::cout << s.pop() << ' ' << s.pop() << '\n';
}

Output:

2 1

Example 82: Template specialization

A special version of a template for one type.

#include <iostream>
#include <string>

template <typename T>
std::string describe(const T &) { return "other"; }

template <>
std::string describe<int>(const int &x) { return "int " + std::to_string(x); }

template <>
std::string describe<std::string>(const std::string &s) { return "string of " + std::to_string(s.size()); }

int main() {
    std::cout << describe(4) << '\n';
    std::cout << describe(std::string("hello")) << '\n';
    std::cout << describe(1.5) << '\n';
}

Output:

int 4
string of 5
other

Example 83: Variadic templates

Templates that take any number of arguments.

#include <iostream>

template <typename... Args>
int count_args(Args... args) {
    return sizeof...(args);
}

template <typename T, typename... Rest>
void print_all(const T &first, const Rest &...rest) {
    std::cout << first;
    ((std::cout << ' ' << rest), ...);
    std::cout << '\n';
}

int main() {
    std::cout << count_args(1, 'a', 2.0) << '\n';
    print_all("values:", 1, 2.5, 'x');
}

Output:

3
values: 1 2.5 x

Example 84: Concepts (C++20)

Name the requirements a template type must meet.

#include <concepts>
#include <iostream>

template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

template <Numeric T>
T twice(T value) {
    return value * 2;
}

int main() {
    std::cout << twice(21) << '\n';
    std::cout << twice(1.25) << '\n';
}

Output:

42
2.5

Example 85: constexpr and static_assert

Compute values at compile time and check them.

#include <iostream>

constexpr long long factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr long long kTenFactorial = factorial(10);
static_assert(kTenFactorial == 3628800, "factorial check failed at compile time");

int main() {
    std::cout << kTenFactorial << '\n';
}

Output:

3628800

Topic 15: Modern C++: Exceptions, Concurrency, Formatting and Testing

These final topics cover the tools professional C++ code uses every day: error handling, threads, formatting, file I/O, and a small test harness.

Exceptions, namespaces and enum class

throw sends an error up the call stack to a matching catch. Custom exceptions derive from std::runtime_error. Namespaces avoid name clashes, and enum class keeps enumerators scoped and strongly typed.

Threads, mutexes and futures

std::thread runs a function concurrently, and join waits for it to finish. A std::mutex with lock_guard protects shared data from data races. std::async returns a future that gives the result when you call get.

Ranges, format, files and tests

Ranges build readable pipelines with filter and transform. std::format checks its arguments against the format string at compile time. fstream reads and writes files, and a small test harness reports pass or fail for each check.

Example 86: Exceptions: try, catch and throw

Report an error and handle it higher up.

#include <iostream>
#include <stdexcept>

int safe_divide(int a, int b) {
    if (b == 0) throw std::domain_error("division by zero");
    return a / b;
}

int main() {
    try {
        std::cout << safe_divide(10, 2) << '\n';
        std::cout << safe_divide(1, 0) << '\n';
    } catch (const std::domain_error &e) {
        std::cout << "caught: " << e.what() << '\n';
    }
}

Output:

5
caught: division by zero

Example 87: Custom exception hierarchy

Your own error types derived from std::runtime_error.

#include <iostream>
#include <stdexcept>
#include <string>

class AppError : public std::runtime_error {
public:
    using std::runtime_error::runtime_error;
};

class NotFound : public AppError {
public:
    explicit NotFound(const std::string &what) : AppError("not found: " + what) {}
};

void load(const std::string &key) {
    if (key != "config") throw NotFound(key);
}

int main() {
    try {
        load("cache");
    } catch (const AppError &e) {
        std::cout << e.what() << '\n';
    }
}

Output:

not found: cache

Example 88: Namespaces

Group names so they do not clash.

#include <iostream>

namespace geometry {
    namespace units {
        constexpr double kMeter = 1.0;
    }
    double area(double w, double h) { return w * h * units::kMeter; }
}

namespace app = geometry;

int main() {
    std::cout << app::area(3.0, 2.0) << '\n';
    std::cout << geometry::units::kMeter << '\n';
}

Output:

6
1

Example 89: enum class

A strongly typed enumeration.

#include <iostream>

enum class Level { Low = 1, Medium = 5, High = 10 };

std::string_view name(Level l) {
    switch (l) {
        case Level::Low: return "low";
        case Level::Medium: return "medium";
        case Level::High: return "high";
    }
    return "unknown";
}

int main() {
    Level l = Level::Medium;
    std::cout << name(l) << ' ' << static_cast<int>(l) << '\n';
}

Output:

medium 5

Example 90: std::chrono durations

Time values with units that the compiler checks.

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    using namespace std::chrono_literals;
    milliseconds timeout{1500};
    seconds rounded = duration_cast<seconds>(timeout);
    seconds wait = rounded + 2min;
    std::cout << timeout.count() << " ms = " << rounded.count() << " s\n";
    std::cout << "wait " << wait.count() << " s\n";
}

Output:

1500 ms = 1 s
wait 121 s

Example 91: std::thread

Run a function on another thread and wait for it.

#include <iostream>
#include <thread>
#include <vector>

int main() {
    std::vector<int> results(4, 0);
    std::vector<std::thread> workers;
    for (int i = 0; i < 4; ++i) {
        workers.emplace_back([&results, i] { results[i] = (i + 1) * (i + 1); });
    }
    for (auto &t : workers) t.join();
    for (int r : results) std::cout << r << ' ';
    std::cout << '\n';
}

Output:

1 4 9 16

Example 92: std::mutex and lock_guard

Protect shared data from data races.

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

int main() {
    int counter = 0;
    std::mutex m;
    std::vector<std::thread> threads;
    for (int t = 0; t < 4; ++t) {
        threads.emplace_back([&] {
            for (int i = 0; i < 1000; ++i) {
                std::lock_guard<std::mutex> lock(m);
                ++counter;
            }
        });
    }
    for (auto &t : threads) t.join();
    std::cout << "counter = " << counter << '\n';
}

Output:

counter = 4000

Example 93: std::async and std::future

Run work in the background and collect the result.

#include <future>
#include <iostream>

long long sum_range(long long from, long long to) {
    long long total = 0;
    for (long long i = from; i <= to; ++i) total += i;
    return total;
}

int main() {
    auto left = std::async(std::launch::async, sum_range, 1, 500);
    auto right = std::async(std::launch::async, sum_range, 501, 1000);
    std::cout << "total = " << left.get() + right.get() << '\n';
}

Output:

total = 500500

Example 94: Ranges (C++20)

Filter and transform a sequence in one readable pipeline.

#include <iostream>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8};
    auto result = nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; });
    for (int v : result) std::cout << v << ' ';
    std::cout << '\n';
}

Output:

4 16 36 64

Example 95: std::format (C++20)

Type-safe formatting with a readable template.

#include <format>
#include <iostream>
#include <string>

int main() {
    std::string item = "Coffee";
    double price = 45.5;
    std::cout << std::format("{:<10}|{:>8.2f}", item, price) << '\n';
    std::cout << std::format("{} items, {:03d} id", 3, 7) << '\n';
}

Output:

Coffee    |   45.50
3 items, 007 id

Example 96: Designated initializers (C++20)

Set struct members by name.

#include <iostream>
#include <string>

struct Server {
    std::string host;
    int port = 80;
    bool tls = false;
};

int main() {
    Server s{.host = "example.com", .tls = true};
    std::cout << s.host << ':' << s.port << (s.tls ? " tls" : " plain") << '\n';
}

Output:

example.com:80 tls

Example 97: File I/O with fstream

Write a file, then read it line by line.

#include <fstream>
#include <iostream>
#include <string>

int main() {
    {
        std::ofstream out("log.txt");
        out << "first\n" << "second\n";
    }
    std::ifstream in("log.txt");
    std::string line;
    while (std::getline(in, line)) {
        std::cout << "read: " << line << '\n';
    }
    in.close();
    std::remove("log.txt");
}

Output:

read: first
read: second

Example 98: Parsing with stringstream

Split a text row into typed values.

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::istringstream row("apple,12,0.5");
    std::string name;
    int qty = 0;
    double price = 0.0;
    std::getline(row, name, ',');
    row >> qty;
    row.ignore(1);
    row >> price;
    std::cout << name << " x" << qty << " @ " << price << '\n';
}

Output:

apple x12 @ 0.5

Example 99: A tiny test harness

Check results and report pass or fail.

#include <iostream>
#include <string>

int failures = 0;

void check(bool ok, const std::string &name) {
    std::cout << (ok ? "PASS " : "FAIL ") << name << '\n';
    if (!ok) ++failures;
}

int add(int a, int b) { return a + b; }

int main() {
    check(add(2, 2) == 4, "add small numbers");
    check(add(-1, 1) == 0, "add opposites");
    check(add(3, 3) == 6, "add positives");
    std::cout << failures << " failure(s)\n";
    return failures == 0 ? 0 : 1;
}

Output:

PASS add small numbers
PASS add opposites
PASS add positives
0 failure(s)

Example 100: Putting it together: an inventory report

Combine classes, smart pointers, optional and format.

#include <algorithm>
#include <format>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <vector>

struct Item {
    std::string name;
    int quantity;
    double unit_price;
};

class Inventory {
public:
    void add(Item item) { items_.push_back(std::move(item)); }

    std::optional<Item> cheapest() const {
        if (items_.empty()) return std::nullopt;
        return *std::min_element(items_.begin(), items_.end(),
            [](const Item &a, const Item &b) { return a.unit_price < b.unit_price; });
    }

    double total_value() const {
        double sum = 0.0;
        for (const auto &i : items_) sum += i.quantity * i.unit_price;
        return sum;
    }

    const std::vector<Item> &items() const { return items_; }

private:
    std::vector<Item> items_;
};

int main() {
    auto inventory = std::make_unique<Inventory>();
    inventory->add({"pen", 40, 5.0});
    inventory->add({"notebook", 12, 45.5});
    inventory->add({"stapler", 3, 120.0});

    for (const auto &i : inventory->items()) {
        std::cout << std::format("{:<10}{:>4} x {:>8.2f}\n", i.name, i.quantity, i.unit_price);
    }
    if (auto c = inventory->cheapest()) {
        std::cout << "cheapest: " << c->name << '\n';
    }
    std::cout << std::format("total value: {:.2f}\n", inventory->total_value());
}

Output:

pen         40 x     5.00
notebook    12 x    45.50
stapler      3 x   120.00
cheapest: pen
total value: 1106.00

Practice Project: A Small Inventory Report

Combine the ideas into one program. Write it yourself first, then compare with the examples. Each step uses one or two examples' worth of work.

1. Write a struct Item with a name, a quantity and a unit price, and store three items in an array.

2. Rewrite the same program in C++ with a class Inventory that holds a std::vector of items and has a total_value method.

3. Add a cheapest method that returns std::optional<Item>, empty when the inventory has no items.

4. Print each row with std::format, aligning the name to ten characters and the price to two decimals.

5. Add a test harness with three checks, and make the program return a non-zero exit code when a check fails.

Next steps

Next, learn a build system such as CMake, write unit tests with a framework such as Catch2 or GoogleTest, and read the C++ Core Guidelines. The output shown for each example was captured from a real run with GCC 16, so if your output differs, check your compiler version and flags first.

Summary

C gives you direct control over memory, and C++ adds classes, templates and a large standard library while keeping that control. Through 100 compiled examples, this article covered variables, control flow, functions, arrays, pointers, structs, dynamic memory, files, the preprocessor, C++ classes and RAII, smart pointers, the standard containers and algorithms, templates, concepts, exceptions, threads and formatting. Each example shows the full code and its real output. Practise by changing a value, predicting the result, and running the program to check.