Skip to main content
C++ · lesson 1 of 24
C++ beginner Lesson 1 of 24

Introduction to C++

What C++ is, how it differs from C, and why it powers games, trading and browsers — with RAII, classes and C++20 ranges shown compiling and running.

By SkillByExample · Updated

What is C++?

C++ is a general-purpose, statically typed, compiled language created by Bjarne Stroustrup at Bell Labs in 1979 as an extension of C. It adds classes, templates and a large standard library while keeping C’s low-level memory control.

Unlike Java or Python, C++ compiles straight to machine code: no virtual machine, no garbage collector, no runtime overhead unless you add it. That is why it is the default wherever 60 frames per second, microsecond latency or a few kilobytes of binary are hard constraints.

Your First C++ Program

Every program starts at main(). #include pulls in standard headers, and std::cout writes to standard output.

#include <iostream>
#include <string>

int main() {
    std::string name = "World";
    std::cout << "Hello, " << name << "!\n";
    return 0;
}
$ g++ -std=c++17 -Wall -Wextra -o hello hello.cpp
$ ./hello
Hello, World!

g++ compiles and links in one step; -o hello names the executable, and -Wall -Wextra turn on the warnings you want from day one. The std:: prefix is the standard namespace, which keeps library names from colliding with yours. return 0 from main is the exit status the shell sees — and it’s optional: falling off the end of main also returns 0.

C++ vs C: What Changed?

C gives you direct memory access and almost nothing else. C++ keeps all of that and layers on features for managing large programs.

FeatureCC++
Classes & structs with methodsNoYes
RAII (automatic resource cleanup)NoYes
Templates (generic programming)NoYes
Standard Template Library (STL)NoYes
ExceptionsNoYes
ReferencesNoYes
Function/operator overloadingNoYes
nullptrNo (until C23)Yes (C++11)

The biggest shift is RAII — Resource Acquisition Is Initialization. In C you malloc and must remember to free, on every return path. In C++ a destructor runs automatically when an object leaves scope, however the scope is left.

#include <iostream>
#include <string>

struct Noisy {
    std::string name;
    explicit Noisy(std::string n) : name(std::move(n)) {
        std::cout << "acquire " << name << "\n";
    }
    ~Noisy() { std::cout << "release " << name << "\n"; }
};

bool work(bool fail_early) {
    Noisy file("file");
    Noisy lock("lock");
    if (fail_early) {
        std::cout << "early return\n";
        return false;              // no cleanup code here...
    }
    std::cout << "doing work\n";
    return true;
}

int main() {
    work(false);
    std::cout << "---\n";
    work(true);                    // ...yet both still get released
}
$ g++ -std=c++17 -Wall -Wextra -o raii raii.cpp
$ ./raii
acquire file
acquire lock
doing work
release lock
release file
---
acquire file
acquire lock
early return
release lock
release file

The early-return path has no cleanup code, yet both objects are released — and in reverse order of construction, so lock goes before file. That ordering is guaranteed, which is what lets std::ifstream, std::lock_guard and std::unique_ptr close files, unlock mutexes and free memory without a single explicit call.

Classes vs Structs

Both struct and class can have constructors, methods and destructors. The only difference is default access: struct members are public, class members are private.

#include <cmath>
#include <iostream>
#include <string>

struct Point {                      // struct: members public by default
    double x;
    double y;
    double distance_from_origin() const { return std::sqrt(x * x + y * y); }
};

class BankAccount {                 // class: members private by default
public:
    BankAccount(std::string owner, double balance)
        : owner_(std::move(owner)), balance_(balance) {}
    bool withdraw(double amount) {
        if (amount > balance_) return false;   // invariant: no overdraft
        balance_ -= amount;
        return true;
    }
    double balance() const { return balance_; }
private:
    std::string owner_;
    double balance_;
};

int main() {
    Point p{3.0, 4.0};
    std::cout << "distance: " << p.distance_from_origin() << "\n";
    BankAccount acct("Alice", 1000.0);
    std::cout << std::boolalpha << "withdraw 200: " << acct.withdraw(200.0) << "\n";
    std::cout << "withdraw 5000: " << acct.withdraw(5000.0) << "\n";
    std::cout << "balance: " << acct.balance() << "\n";
}
$ g++ -std=c++17 -Wall -Wextra -o shapes shapes.cpp
$ ./shapes
distance: 5
withdraw 200: true
withdraw 5000: false
balance: 800

p{3.0, 4.0} works because Point’s members are public, so aggregate initialization can fill them directly. BankAccount hides balance_, so the only way to change it is through withdraw, which refuses the overdraft — the 5000 withdrawal returns false and the balance stays at 800. Note #include <cmath> for std::sqrt: leave it out and some compilers still accept the program through transitive includes, others don’t.

The protection is enforced at compile time, not by convention:

#include <string>

class BankAccount {
public:
    explicit BankAccount(double balance) : balance_(balance) {}
private:
    double balance_;
};

int main() {
    BankAccount acct(1000.0);
    acct.balance_ = -1e9;   // try to corrupt the invariant from outside
}
$ g++ -std=c++17 -Wall -Wextra -o private_access private_access.cpp 2>&1 | grep -E 'error|note'
private_access.cpp:12:10: error: 'double BankAccount::balance_' is private within this context
private_access.cpp:7:12: note: declared private here

The compiler refuses the assignment and points at the private: declaration. A struct-style Point would have accepted the same write, which is exactly the intent each keyword signals.

Where C++ Is Used

  • Game engines — Unreal Engine is C++. A frame has about 16 ms for physics, AI, rendering and audio at 60 fps.
  • High-frequency trading — latency is measured in microseconds; a garbage-collection pause is unacceptable.
  • Browsers — Chrome’s V8 and Blink, and Firefox’s Gecko, are C++.
  • Operating systems — Windows components, macOS frameworks and Android’s ART runtime use C++ (the Linux kernel itself is C).
  • Embedded systems — cars, medical devices and industrial controllers, where binary size and determinism matter.

The C++ Standards Timeline

Each ISO revision is named by year, and you choose one with -std=.

  • C++98/03 — STL, templates, exceptions, namespaces.
  • C++11 — the modern turning point: auto, nullptr, range-for, lambdas, move semantics, std::thread, smart pointers.
  • C++14 — generic lambdas, std::make_unique.
  • C++17 — structured bindings, if constexpr, std::optional, std::variant, std::filesystem.
  • C++20 — concepts, ranges, coroutines, std::format, modules, <=>.
  • C++23 — std::expected, std::print, std::mdspan.

A taste of C++20:

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

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // A lazy pipeline: nothing is computed until the loop pulls values
    auto even_squares = nums
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; });

    for (int v : even_squares) {
        std::cout << std::format("{:>4}", v);
    }
    std::cout << "\n" << std::format("__cplusplus = {}\n", __cplusplus);
}
$ g++ -std=c++20 -Wall -Wextra -o modern modern.cpp
$ ./modern
   4  16  36  64 100
__cplusplus = 202002

The ranges pipeline is lazy — filter and transform build a view, and no intermediate vector is ever allocated; values are computed as the for loop pulls them. std::format("{:>4}", v) right-aligns each number in four columns. __cplusplus reports 202002, the value for C++20, confirming which standard the -std= flag selected. One caveat from this machine: GCC 13 ships <format> but not C++23’s <print> — #include <print> fails with “No such file or directory” until GCC 14, so check your toolchain before relying on the newest headers.

Practice

  1. Change hello.cpp to read a name from standard input with std::getline(std::cin, name) and greet it.

    Solution
    #include <iostream>
    #include <string>
    
    int main() {
        std::string name;
        std::getline(std::cin, name);
        std::cout << "Hello, " << name << "!\n";
    }

    echo Ada | ./hello prints Hello, Ada!. getline reads the whole line, spaces included; std::cin >> name would stop at the first space.

  2. In raii.cpp, add a third Noisy named "socket" after lock. Predict the release order for the early-return call before running it.

    Solution

    Acquire order is file, lock, socket; release order is the reverse: socket, lock, file. Destructors always run in reverse order of construction within a scope.

  3. Make work throw a std::runtime_error instead of returning early, and catch it in main. Are the resources still released?

    Solution
    if (fail_early) throw std::runtime_error("boom");
    // in main:
    try { work(true); } catch (const std::exception& e) { std::cout << "caught " << e.what() << "\n"; }

    Yes — “release lock” and “release file” print before “caught boom”. Stack unwinding runs destructors of every fully constructed local, which is what makes RAII exception-safe. (Add #include <stdexcept>.)

  4. Add a deposit(double) method to BankAccount that ignores non-positive amounts, and show that deposit(-50) leaves the balance unchanged.

    Solution
    void deposit(double amount) {
        if (amount > 0) balance_ += amount;
    }

    After deposit(-50), balance() still prints 800. The check lives in one place because nothing outside the class can touch balance_.

  5. Recompile modern.cpp with -std=c++17. What breaks, and why?

    Solution

    It fails to compile: <ranges> views and std::format are C++20 features, so under -std=c++17 GCC reports that std::views and std::format are not declared. The standard flag controls which library features are visible, not just language syntax.

Frequently Asked Questions

Is C++ still relevant today?
Yes. C++ dominates game engines such as Unreal, high-frequency trading systems, browsers, operating-system components and embedded devices — anywhere a garbage collector or virtual machine would cost too much latency or memory.
Should I learn C before C++?
It isn't required. Modern C++ with RAII, std::string, std::vector and smart pointers is safer and shorter than the equivalent C, and learning C first can build habits such as manual malloc/free that modern C++ deliberately avoids.
What is the difference between a struct and a class in C++?
Only the default access level. Members of a struct are public unless you say otherwise; members of a class are private. Both can have constructors, destructors and member functions, so the choice signals intent rather than capability.
Which C++ standard should I compile with?
Use at least C++17 for new code, and C++20 if your compiler supports it — GCC 13 and Clang 18 both handle ranges, concepts and std::format. Pass the standard explicitly, for example g++ -std=c++20, rather than relying on the compiler default.
Why does C++ have no garbage collector?
C++ releases resources deterministically instead: a destructor runs the moment an object goes out of scope. This pattern, RAII, cleans up memory, files and locks at a predictable point with no background collector pausing the program.