Coding · Programming languages
C++ tutorial for beginners
Short answer
C++ is a compiled, statically typed language that combines the speed and low-level control of C with classes, templates and a large standard library. Compile hello.cpp with g++ -std=c++20 hello.cpp -o hello and run ./hello. Modern C++ code uses std::string, std::vector and std::map instead of raw arrays, smart pointers instead of manual delete, and RAII so that resources are released automatically.
What C++ is and what it is used for
C++ was created by Bjarne Stroustrup at Bell Labs, starting in 1979 as "C with Classes". It is standardized by ISO; C++11 began the "modern C++" era, and C++17, C++20 and C++23 followed. C++ is used where performance and control matter: game engines (Unreal Engine), web browsers (Chrome, Firefox), databases, trading systems, embedded devices, compilers and machine-learning runtimes. Its guiding idea is zero-overhead abstraction: features you do not use cost nothing.
Install a compiler and run Hello, world
Use GCC (g++) or Clang (clang++) on Linux and macOS, or Microsoft Visual C++ on Windows (via Visual Studio). Pick a standard version explicitly.
#include <iostream>
int main() {
std::cout << "Hello, world\n";
return 0;
}g++ -std=c++20 -Wall -Wextra hello.cpp -o hello
./hello
# Hello, worldstd::cout is the standard output stream and << writes to it. Everything in the standard library lives in the namespace std.
Variables, types and auto
C++ has the same built-in types as C, plus bool as a keyword, references, and library types such as std::string. auto asks the compiler to deduce the type from the initializer. Brace initialization {} refuses narrowing conversions.
| Type | Example | Notes |
|---|---|---|
int, long long | int n{42}; | Signed integers |
double | double x{3.14}; | 64-bit floating point |
bool | bool ok{true}; | true or false |
char | char c{'A'}; | One byte |
std::string | std::string s{"hi"}; | Growable text, from <string> |
auto | auto v = 2.5; | Deduced, here double |
T& | int& r = n; | A reference: another name for n |
#include <iostream>
#include <string>
int main() {
int count{7};
auto avg = count / 2.0; // double, 3.5
std::string name{"Ada"};
name += " Lovelace";
const int& ref = count; // read-only alias of count
std::cout << name << " " << name.size() << " " << avg << " " << ref << "\n";
// Ada Lovelace 12 3.5 7
}Control flow and functions
Control flow is the same as in C, plus the range-based for loop. Pass large objects by const reference to avoid copies, and by non-const reference when the function should change them.
#include <iostream>
#include <string>
#include <vector>
int square(int x) { return x * x; }
void addSuffix(std::string& s, const std::string& suffix = "!") {
s += suffix;
}
int main() {
int score = 72;
if (score >= 90) std::cout << "A\n";
else if (score >= 70) std::cout << "B\n"; // B
else std::cout << "C\n";
std::vector<int> nums{1, 2, 3};
for (int n : nums) std::cout << square(n) << " "; // 1 4 9
std::cout << "\n";
std::string word = "hi";
addSuffix(word);
std::cout << word << "\n"; // hi!
}Classes
A class groups data members and member functions. Constructors set up an object; private members are hidden from outside code. A struct is the same as a class except that members are public by default.
#include <iostream>
#include <string>
#include <utility>
class Account {
public:
explicit Account(std::string owner) : owner_(std::move(owner)) {}
void deposit(double amount) {
if (amount > 0) balance_ += amount;
}
double balance() const { return balance_; }
const std::string& owner() const { return owner_; }
private:
std::string owner_;
double balance_ = 0.0;
};
int main() {
Account acc{"Ann"};
acc.deposit(50);
acc.deposit(-5); // ignored
std::cout << acc.owner() << ": " << acc.balance() << "\n"; // Ann: 50
}Inheritance (class Savings : public Account) and virtual functions give runtime polymorphism. Mark overriding functions with override so the compiler checks them.
Standard library containers and algorithms
The standard library (often called the STL) provides containers and algorithms that work together through iterators.
| Container | Like | Use it for |
|---|---|---|
std::vector<T> | Dynamic array | The default sequence container |
std::array<T, N> | Fixed array | Size known at compile time |
std::map<K, V> | Sorted dictionary | Ordered keys, O(log n) lookup |
std::unordered_map<K, V> | Hash table | Fast average O(1) lookup |
std::set<T> | Sorted unique set | Membership, ordered output |
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::vector<int> nums{3, 1, 2, 5};
std::sort(nums.begin(), nums.end());
auto evens = std::count_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
std::cout << nums.front() << " " << nums.back() << " " << evens << "\n"; // 1 5 1
std::map<std::string, int> ages{{"bob", 25}, {"ann", 31}};
ages["cy"] = 40;
for (const auto& [name, age] : ages) { // structured bindings
std::cout << name << "=" << age << " "; // ann=31 bob=25 cy=40
}
std::cout << "\n";
}[](int n) { ... } is a lambda, an unnamed function written inline. Templates are what make one std::vector work for any element type; you can write your own with template <typename T>.
Signature feature: RAII and smart pointers
RAII (Resource Acquisition Is Initialization) means a resource is owned by an object: the constructor acquires it and the destructor releases it. Because destructors run automatically when an object goes out of scope, even when an exception is thrown, memory, files and locks cannot leak. Smart pointers apply RAII to heap memory.
#include <iostream>
#include <memory>
struct Noisy {
int id;
explicit Noisy(int i) : id(i) { std::cout << "create " << id << "\n"; }
~Noisy() { std::cout << "destroy " << id << "\n"; }
};
int main() {
auto a = std::make_unique<Noisy>(1); // sole owner
{
auto b = std::make_shared<Noisy>(2); // shared, reference-counted
auto c = b; // two owners now
std::cout << "owners: " << b.use_count() << "\n";
} // destroy 2 happens here
std::cout << "end of main\n";
} // destroy 1 happens hereOutput: create 1, create 2, owners: 2, destroy 2, end of main, destroy 1. In modern code you should almost never write new and delete yourself.
Error handling with exceptions
Throw an exception object and catch it by const reference. Standard exception types are in <stdexcept>. Functions that never throw can be marked noexcept.
#include <iostream>
#include <stdexcept>
#include <string>
int parseAge(const std::string& text) {
int age = std::stoi(text); // throws std::invalid_argument on "abc"
if (age < 0) throw std::out_of_range("negative age");
return age;
}
int main() {
try {
std::cout << parseAge("41") << "\n"; // 41
std::cout << parseAge("abc") << "\n";
} catch (const std::invalid_argument&) {
std::cout << "not a number\n"; // not a number
} catch (const std::exception& e) {
std::cout << "error: " << e.what() << "\n";
}
}For expected failures, many codebases return std::optional<T> (C++17) or std::expected<T, E> (C++23) instead of throwing.
A small complete program: word frequency
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << argv[0] << " FILE\n";
return 1;
}
std::ifstream in(argv[1]);
if (!in) {
std::cerr << "cannot open " << argv[1] << "\n";
return 1;
}
std::unordered_map<std::string, int> counts;
std::string word;
while (in >> word) {
std::string clean;
for (unsigned char ch : word)
if (std::isalpha(ch)) clean += static_cast<char>(std::tolower(ch));
if (!clean.empty()) ++counts[clean];
}
std::vector<std::pair<std::string, int>> top(counts.begin(), counts.end());
std::sort(top.begin(), top.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
for (size_t i = 0; i < top.size() && i < 5; ++i)
std::cout << top[i].second << " " << top[i].first << "\n";
}Compile with g++ -std=c++20 -O2 words.cpp -o words and run ./words notes.txt. The file is closed automatically when in goes out of scope, which is RAII again.
Common beginner mistakes
- Writing
using namespace std;in header files, which pollutes every file that includes them. - Passing large objects such as
std::vectorby value when aconst&would do. - Mixing signed and unsigned in comparisons, such as
int iagainstv.size(). Enable warnings to see these. - Keeping an iterator or reference into a
std::vectorafterpush_back, which may move the elements. - Using raw
newanddeleteinstead ofstd::make_unique,std::vectoror other owning types.
Where to go next
Use cppreference.com as the language and library reference, and the C++ Core Guidelines (edited by Bjarne Stroustrup and Herb Sutter) for good practice. The Standard C++ Foundation site isocpp.org has a getting-started page with recommended books. Learn CMake for building projects, then move on to move semantics, templates, constexpr and concurrency with std::thread.
FAQ
Should I learn C before C++?
It is not required. Modern C++ teaching starts with std::string, std::vector and classes, and introduces raw pointers and C-style arrays later. Learning C first does help you understand memory.
Which C++ standard should I use?
C++17 and C++20 are well supported by current GCC, Clang and MSVC, and C++23 support is growing. Choose one explicitly with a flag such as -std=c++20.
What is the difference between a pointer and a reference?
A reference is an alias that must be bound when created and cannot be reseated or be null. A pointer stores an address, can be null and can be changed to point elsewhere.
Is C++ hard to learn?
The language is large, so expect a longer learning curve than Python or Go. Sticking to modern features, standard containers and smart pointers avoids most of the classic pitfalls.