C++ · lesson 1 of 24
Beginner
6Intermediate
11- 5 Operators and Operator Overloading in C++
- 8 Classes and Object-Oriented Programming in C++
- 9 Inheritance and Polymorphism in C++
- 11 STL Containers in C++
- 12 STL Algorithms in C++
- 13 Smart Pointers in C++
- 15 Lambdas and Functional Programming in C++
- 18 Error Handling in C++
- 19 File I/O in C++
- 23 C++ Interview Preparation: Top 35 Questions
- 24 Static Members in C++
Advanced
7Introduction 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.
| Feature | C | C++ |
|---|---|---|
| Classes & structs with methods | No | Yes |
| RAII (automatic resource cleanup) | No | Yes |
| Templates (generic programming) | No | Yes |
| Standard Template Library (STL) | No | Yes |
| Exceptions | No | Yes |
| References | No | Yes |
| Function/operator overloading | No | Yes |
nullptr | No (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
-
Change
hello.cppto read a name from standard input withstd::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 | ./helloprintsHello, Ada!.getlinereads the whole line, spaces included;std::cin >> namewould stop at the first space. -
In
raii.cpp, add a thirdNoisynamed"socket"afterlock. 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.
-
Make
workthrow astd::runtime_errorinstead of returning early, and catch it inmain. 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>.) -
Add a
deposit(double)method toBankAccountthat ignores non-positive amounts, and show thatdeposit(-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 touchbalance_. -
Recompile
modern.cppwith-std=c++17. What breaks, and why?Solution
It fails to compile:
<ranges>views andstd::formatare C++20 features, so under-std=c++17GCC reports thatstd::viewsandstd::formatare not declared. The standard flag controls which library features are visible, not just language syntax.