Free Handbook · Every example compiled & verified

Fundamentals

Input and output with iostream, variables and the fundamental types, brace initialization, const and constexpr, operators, casts and std::string basics.

0 / 145 lessons🔥 0 day streak
ShareXLinkedIn

Module 01 · what you'll be able to do

  • Print with std::cout, read with std::cin and std::getline, and know when std::endl is worth its cost
  • Pick the right fundamental type (int, long long, double, char, bool) and check its size with sizeof
  • Initialize variables with {} so the compiler rejects narrowing, and use auto, const and constexpr deliberately
  • Predict integer division, %, prefix/postfix ++ and compound assignment, and convert types with static_cast
  • Recognise the signed/unsigned traps that turn size() - 1 into 18 quintillion
01

main, cout and cin

Every C++ program starts in main. To talk to the outside world you include <iostream>, which gives you three stream objects: std::cout (standard output), std::cin (standard input) and std::cerr (error output). You push values into an output stream with << and pull values out of an input stream with >>. The arrows point the way the data flows.

C++main.cpp
#include <iostream>

int main() {
    std::cout << "Order summary\n";
    std::cout << "Items: " << 3 << ", total: " << 59.5 << '\n';

    // Chaining works because each << returns the stream itself
    std::cout << "A" << 'B' << 1 << true << '\n';

    std::cerr << "(this line goes to standard error)\n";
    return 0;
}
Outputcompiled & run with real C++
Order summary
Items: 3, total: 59.5
AB11

The std::cerr line is not in the output above: it goes to a separate stream (standard error) that the terminal shows but a > file.txt redirect does not capture. true prints as 1 unless you stream std::boolalpha first.

Your turn

Add std::cout << std::boolalpha << true << '\n'; and see it print true instead of 1.

Reading input with cin

std::cin >> x skips any leading whitespace, then reads characters until they stop making sense for the type of x. So reading into a std::string stops at the first space: it reads one word, not a line. The example below is fed the input Asha 29.

C++main.cpp
#include <iostream>
#include <string>

int main() {
    std::string name;
    int age = 0;

    std::cout << "Name and age: ";
    std::cin >> name >> age;          // reads "Asha", then 29

    std::cout << '\n' << name << " will be " << age + 1 << " next year\n";
}
Outputcompiled & run with real C++
Name and age:
Asha will be 30 next year
Your turn

Run it yourself and type a word instead of the age. age stays 0 because the read failed; check if (!std::cin) after reading to detect it.

'\n'

  • Ends the line. That is all.
  • Output is buffered and written in large, cheap chunks.
  • Use it by default.

std::endl

  • Ends the line and flushes the buffer to the device.
  • A flush is a system call; in a loop of a million lines it can make output several times slower.
  • Use it only when the text must appear right now (for example before a long computation or a crash).

Comments

// text comments to the end of the line; /* text */ can span lines but cannot be nested. Comment why, not what: i++; // add one to i tells the reader nothing, while // retry once: the first request after wake-up often times out saves the next person an hour.

02

Variables and the fundamental types

A variable is a named piece of memory with a fixed type. The type decides how many bytes it occupies, which values it can hold and which operations are allowed. C++ types are checked at compile time and never change at runtime: an int is an int forever.

The standard only guarantees minimums (an int is at least 16 bits); the sizes shown are what every mainstream 64-bit compiler uses. The one that differs: long is 8 bytes on Linux and macOS but 4 bytes on Windows, which is why portable code uses long long or std::int64_t.
TypeTypical sizeHoldsLiteral
bool1 bytetrue / falsetrue
char1 byteone byte, usually an ASCII character'A'
int4 bytesabout ±2.1 billion42, 1'000'000
long long8 bytesabout ±9.2 × 10188100000000LL
double8 bytes~15–16 significant digits3.14, 2.5e-3
float4 bytes~7 significant digits3.14f
unsigned (int)4 bytes0 to about 4.29 billion42u
std::size_t8 bytes on 64-bitsizes and indexes (unsigned)returned by size()
C++main.cpp
#include <iostream>
#include <limits>

int main() {
    std::cout << "bool      " << sizeof(bool) << '\n';
    std::cout << "char      " << sizeof(char) << '\n';
    std::cout << "int       " << sizeof(int) << '\n';
    std::cout << "long long " << sizeof(long long) << '\n';
    std::cout << "double    " << sizeof(double) << '\n';

    std::cout << "int max       " << std::numeric_limits<int>::max() << '\n';
    std::cout << "long long max " << std::numeric_limits<long long>::max() << '\n';

    long long population = 8'100'000'000LL;   // ' is a digit separator (C++14)
    char grade = 'A';
    bool shipped = false;
    std::cout << population << ' ' << grade << ' ' << shipped << '\n';
}
Outputcompiled & run with real C++
bool      1
char      1
int       4
long long 8
double    8
int max       2147483647
long long max 9223372036854775807
8100000000 A 0
Your turn

Print std::numeric_limits<unsigned int>::max() and std::numeric_limits<double>::digits10 (the number of decimal digits a double reliably keeps).

Signed overflow is undefined behaviour
Adding 1 to the largest int does not wrap to a negative number in any guaranteed way: it is undefined behaviour, and the optimiser is allowed to assume it never happens. If a value could exceed about 2 billion (money in paise, file sizes, timestamps in milliseconds) use long long from the start.
Error you will hit

variable is uninitialized when used here

C++
#include <iostream>

int main() {
    int count;
    count++;
    std::cout << count << '\n';
}
main.cpp:5:5: warning: variable 'count' is uninitialized when used here [-Wuninitialized]
    5 |     count++;
      |     ^~~~~
main.cpp:4:14: note: initialize the variable 'count' to silence this warning
    4 |     int count;
      |              ^
      |               = 0
Why the compiler said that

A local variable of a fundamental type is not set to zero for you. int count; reserves four bytes and leaves whatever was already in them; reading it is undefined behaviour, so the program may print 1, 32768 or anything else. This is only a warning (you need -Wall to see it) and the program still builds, which is exactly why it is dangerous.

The fix

Always give a variable a value when you create it. int count{}; means "zero".

C++
#include <iostream>

int main() {
    int count{};
    count++;
    std::cout << count << '\n';
}
03

Initialization styles and auto

C++ has several ways to give a variable its first value. They mostly do the same thing, with one important difference: brace initialization {} refuses to silently lose information.

SyntaxNameNarrowing (e.g. 3.7 into an int)
int a = 3.7;copy initializationallowed silently, a becomes 3
int b(3.7);direct initializationallowed silently, b becomes 3
int c{3.7};brace (list) initializationcompile error
int d{};value initializationno value given: d is 0
C++main.cpp
#include <iostream>
#include <string>

int main() {
    int apples = 5;          // copy initialization
    int pears(7);            // direct initialization
    int plums{9};            // brace initialization: preferred
    double ratio{};          // empty braces = zero

    auto total = apples + pears + plums;   // auto deduces int
    auto price = 2.5;                      // double
    auto label = std::string{"fruit"};     // std::string

    std::cout << label << ": " << total << " at " << price << '\n';
    std::cout << "ratio starts at " << ratio << '\n';
}
Outputcompiled & run with real C++
fruit: 21 at 2.5
ratio starts at 0
Your turn

Change auto label = std::string{"fruit"}; to auto label = "fruit";. It still prints, but label is now a const char*, not a std::string: try label.size() and read the error.

Error you will hit

type 'double' cannot be narrowed to 'int' in initializer list

C++
#include <iostream>

int main() {
    double price = 19.99;
    int whole{price};
    std::cout << whole << '\n';
}
main.cpp:5:15: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
    5 |     int whole{price};
      |               ^~~~~
main.cpp:5:15: note: insert an explicit cast to silence this issue
    5 |     int whole{price};
      |               ^~~~~
      |               static_cast<int>( )
Why the compiler said that

Converting 19.99 to int throws away .99. With = or () the compiler would do it silently; braces make lossy conversions an error so you have to say you meant it. That is the main reason modern style guides prefer {}.

The fix

If you really want the whole part, say so with static_cast. If you did not, keep the type as double.

C++
#include <iostream>

int main() {
    double price = 19.99;
    int whole{static_cast<int>(price)};   // explicit: 19
    std::cout << whole << '\n';
}
When auto helps and when it hides
auto is still static typing: the compiler picks one type from the initializer and it never changes. It shines when the type is long or obvious (auto it = names.begin();). It hurts when the reader cannot tell the type, and it deduces const char* from a string literal and int from 5 even if you wanted long long. Beware auto x{5}; too: it is an int, but auto x = {5}; is a std::initializer_list<int>.
04

const and constexpr

const means "this variable will not be changed after it is initialized". The value can come from anywhere, including user input. constexpr is stronger: the value must be known at compile time, so the compiler can bake it into the program, use it as an array size or a template argument, and evaluate constexpr functions before the program ever runs.

C++main.cpp
#include <array>
#include <iostream>

constexpr int square(int x) { return x * x; }

int main() {
    constexpr double kGstRate = 0.18;        // known when compiling
    constexpr int kSlots = square(4);         // computed by the compiler: 16
    std::array<int, kSlots> board{};          // sizes must be compile-time constants

    int quantity = 3;                         // could have come from input
    const double subtotal = quantity * 250.0; // fixed once computed, but at runtime

    std::cout << "slots: " << board.size() << '\n';
    std::cout << "subtotal: " << subtotal << '\n';
    std::cout << "with GST: " << subtotal * (1 + kGstRate) << '\n';
}
Outputcompiled & run with real C++
slots: 16
subtotal: 750
with GST: 885
Your turn

Add static_assert(kSlots == 16); after the constexpr line. It is checked while compiling: change 16 to 15 and the build fails with a clear message.

Error you will hit

cannot assign to variable with const-qualified type

C++
#include <iostream>

int main() {
    const int maxUsers = 100;
    maxUsers = 200;
    std::cout << maxUsers << '\n';
}
main.cpp:5:14: error: cannot assign to variable 'maxUsers' with const-qualified type 'const int'
    5 |     maxUsers = 200;
      |     ~~~~~~~~ ^
main.cpp:4:15: note: variable 'maxUsers' declared const here
    4 |     const int maxUsers = 100;
      |     ~~~~~~~~~~^~~~~~~~~~~~~~
Why the compiler said that

That is const doing its job. The note even points back to the declaration so you can decide which line is wrong: the const, or the assignment.

The fix

If the value genuinely changes, drop const. If it does not, make a new variable for the new value.

C++
#include <iostream>

int main() {
    const int maxUsers = 100;
    const int maxUsersPro = maxUsers * 2;
    std::cout << maxUsersPro << '\n';
}
Error you will hit

constexpr variable must be initialized by a constant expression

C++
#include <iostream>

int main() {
    int n = 0;
    std::cin >> n;
    constexpr int doubled = n * 2;
    std::cout << doubled << '\n';
}
main.cpp:6:19: error: constexpr variable 'doubled' must be initialized by a constant expression
    6 |     constexpr int doubled = n * 2;
      |                   ^         ~~~~~
main.cpp:6:29: note: read of non-const variable 'n' is not allowed in a constant expression
    6 |     constexpr int doubled = n * 2;
      |                             ^
main.cpp:4:9: note: declared here
    4 |     int n = 0;
      |         ^
Why the compiler said that

n is only known once the program runs and reads the keyboard, so there is no way to compute n * 2 while compiling.

The fix

Use const for "does not change once set at runtime"; keep constexpr for values the compiler can work out.

C++
#include <iostream>

int main() {
    int n = 0;
    std::cin >> n;
    const int doubled = n * 2;
    std::cout << doubled << '\n';
}
Rule of thumb
Make every variable const unless you have a reason to change it, and every true constant (tax rates, buffer sizes, limits) constexpr. Fewer things that can change means fewer places a bug can hide, and const is also what lets you pass big objects cheaply by const& in Module 03.
05

Operators: arithmetic, increment and compound assignment

Arithmetic in C++ follows the types of the operands, not the type you store the result in. When both sides of / are integers, the result is an integer and the fraction is thrown away (truncated towards zero). % gives the remainder and only works on integers; its sign follows the left operand.

C++main.cpp
#include <iostream>

int main() {
    std::cout << 7 / 2 << '\n';        // 3   (int / int)
    std::cout << 7.0 / 2 << '\n';      // 3.5 (one double makes it double)
    std::cout << -7 / 2 << '\n';       // -3  (truncates towards zero)
    std::cout << 7 % 3 << '\n';        // 1
    std::cout << -7 % 3 << '\n';       // -1  (sign of the left side)

    int minutes = 135;
    std::cout << minutes / 60 << "h " << minutes % 60 << "m\n";

    double avg = (4 + 5) / 2;           // int division happens BEFORE storing
    std::cout << avg << '\n';           // 4, not 4.5
}
Outputcompiled & run with real C++
3
3.5
-3
1
-1
2h 15m
4
Your turn

Fix avg so it prints 4.5 by changing only the right-hand side. There are at least three ways (2.0, static_cast<double>, (4 + 5.0)).

Prefix and postfix ++

++i increments and then gives you the new value. i++ gives you the old value and increments afterwards. On their own line they do the same thing; the difference only shows when the expression's value is used. Compound operators (+=, -=, *=, /=, %=) are shorthand for x = x op y.

VisualizePrefix, postfix and compound assignmentStep 1 / 6
int i = 5;
int a = i++;
int b = ++i;
i += 10;
i %= 4;
int c = i * 2 + 1;
Line 1

i starts at 5.

Variables now
i5
All 6 steps as a table
StepLineWhat happenedVariables now
11i starts at 5.i = 5
22Postfix: a gets the OLD value 5, then i becomes 6.i = 6 a = 5
33Prefix: i becomes 7 first, then b gets the NEW value 7.i = 7 b = 7
44i += 10 is i = i + 10.i = 17
55i %= 4: 17 divided by 4 is 4 remainder 1.i = 1
66* binds tighter than +: 1 * 2 + 1 = 3.c = 3
When in doubt, add parentheses. Nobody has ever been fired for (a * b) + c.
OperatorsMeaningNote
* / %multiply, divide, remainderbind tighter than + -
+ -add, subtract
< <= > >= == !=comparison, result is bool= assigns, == compares
&& || !logical and, or, not&& and || short-circuit
= += -= *= /= %=assignmentlowest precedence, right to left
Error you will hit

use of undeclared identifier

C++
#include <iostream>

int main() {
    int total = 10;
    std::cout << totla << '\n';
}
main.cpp:5:18: error: use of undeclared identifier 'totla'
    5 |     std::cout << totla << '\n';
      |                  ^~~~~
Why the compiler said that

Every name must be declared before it is used, and names are case-sensitive: totla, Total and total are three different names. The same error appears when you use a variable outside the { } block it was declared in, or forget an #include.

The fix

Correct the spelling, or declare the variable in a scope that reaches this line.

C++
#include <iostream>

int main() {
    int total = 10;
    std::cout << total << '\n';
}
06

Conversions, static_cast and the signed/unsigned trap

When an expression mixes types, C++ converts them to a common type before operating: int + double becomes double, char + int becomes int. Going the other way (double into int) truncates. When you want a conversion, write static_cast<T>(value): it is searchable, obvious in code review, and the compiler still refuses conversions that make no sense.

C++main.cpp
#include <iostream>

int main() {
    int total = 7;
    int count = 2;
    double avg = static_cast<double>(total) / count;   // 3.5, not 3
    std::cout << avg << '\n';

    double temperature = 36.9;
    int whole = static_cast<int>(temperature);          // truncates: 36
    std::cout << whole << '\n';

    char letter = 'A';
    std::cout << letter + 1 << '\n';                    // char + int is int: 66
    std::cout << static_cast<char>(letter + 1) << '\n'; // back to a char: B
    std::cout << static_cast<int>('0') << '\n';         // character code 48
    std::cout << '7' - '0' << '\n';                     // digit char to number: 7
}
Outputcompiled & run with real C++
3.5
36
66
B
48
7
Your turn

Round 36.9 to the nearest integer instead of truncating: include <cmath> and use std::lround(temperature).

Signed and unsigned: the bug that looks correct

Unsigned types cannot hold negative numbers, so arithmetic on them wraps around (this is well-defined, unlike signed overflow): 0 minus 1 is the largest value the type can hold. Every container's size() returns an unsigned std::size_t, which is where most juniors meet this.

C++main.cpp
#include <iostream>
#include <vector>

int main() {
    unsigned int stock = 0;
    stock = stock - 1;                                  // wraps around
    std::cout << stock << '\n';

    std::vector<int> empty;
    std::cout << empty.size() - 1 << '\n';              // 0 - 1 as size_t

    std::cout << static_cast<unsigned int>(-1) << '\n'; // what -1 looks like unsigned

    // Safe version: convert the size to a signed type before subtracting
    long long last = static_cast<long long>(empty.size()) - 1;
    std::cout << last << '\n';
}
Outputcompiled & run with real C++
4294967295
18446744073709551615
4294967295
-1

So for (std::size_t i = 0; i <= v.size() - 1; i++) on an empty vector runs about 18 quintillion times. And -1 < v.size() is false, because the -1 is converted to unsigned before comparing. C++20 added std::ssize(v), which returns a signed size.

Your turn

Replace the static_cast on the last line with std::ssize(empty) - 1 (it lives in <iterator>, which <vector> already brings in).

In real jobs
Code review will flag C-style casts like (int)x, because they silently fall back to the most dangerous conversion that compiles. The named casts say what you mean: static_cast for ordinary conversions, const_cast to remove const (rarely correct), reinterpret_cast to reinterpret raw bits (almost never in application code). Teams also build with -Wall -Wextra -Wconversion to catch implicit narrowing.
07

std::string basics

std::string (from <string>) is a growable sequence of characters that manages its own memory. You can join strings with +, compare them with == and <, get their length with size() and read a single character with [i]. To read a whole line, spaces included, use std::getline rather than >>.

C++main.cpp
#include <iostream>
#include <string>

int main() {
    std::string fullName;
    std::getline(std::cin, fullName);         // whole line: "Riya Sharma"

    std::string greeting = "Hi, " + fullName + "!";
    std::cout << greeting << '\n';
    std::cout << "length: " << greeting.size() << '\n';
    std::cout << "first letter: " << fullName[0] << '\n';

    fullName += " (admin)";                   // append in place
    std::cout << fullName << '\n';

    std::string a = "apple", b = "banana";
    std::cout << std::boolalpha << (a < b) << ' ' << (a == "apple") << '\n';
}
Outputcompiled & run with real C++
Hi, Riya Sharma!
length: 16
first letter: R
Riya Sharma (admin)
true true
Your turn

Read a line with std::cin >> fullName instead and notice you only get Riya. Mixing the two is a classic bug: after std::cin >> age, the newline is still waiting, so the next getline reads an empty line. Call std::cin.ignore() in between.

More on strings later
Searching, slicing with substr, converting with std::stoi/std::to_string, std::string_view and the difference from C strings are covered in Module 04. "Hi, " + "there" (two literals, no std::string) does not compile; that card is there too.
std::cout / std::cin
The standard output and input streams from <iostream>. Write with <<, read with >>.
std::endl
Writes a newline and flushes the output buffer. Prefer '\n' unless you need the flush.
Brace initialization
T x{value}; Initializes a variable and rejects narrowing (lossy) conversions at compile time.
Narrowing conversion
A conversion that can lose information, such as double to int or long long to int.
auto
Lets the compiler deduce a variable's type from its initializer. Still statically typed.
constexpr
A value or function the compiler can evaluate at compile time. Stronger than const, which only forbids later changes.
static_cast
The named, checked cast for ordinary conversions: static_cast<double>(n).
std::size_t
The unsigned integer type used for sizes and indexes. Subtracting past zero wraps to a huge value.
Undefined behaviour (UB)
Code the standard gives no meaning to (reading an uninitialized int, signed overflow). The program may do anything, including appear to work.
Quick check

What does this print? int a = 9, b = 2; double r = a / b; std::cout << r;

Quick check

Which line fails to compile?

Frequently asked questions

Should I use std::endl or '\n' in C++?
Use '\n' by default. std::endl writes a newline and also flushes the output buffer, which is a comparatively slow system call. Flush only when output must appear immediately.
Why do C++ programmers write int x{5} instead of int x = 5?
Brace initialization works the same way for every type and turns narrowing conversions, such as putting a double into an int, into compile errors instead of silent data loss. int x{}; also guarantees zero instead of an uninitialized value.
What is the difference between const and constexpr?
const means a variable cannot be changed after initialization, but its value may be computed at runtime. constexpr means the value is known at compile time, so it can be used for array sizes, template arguments and static_assert.

Finish the C++ handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.