Phase 1 · Modern C++ FoundationsModule 2~48 min read

Variables, Fundamental Types & Operators

Represent and transform data with fundamental types, safe initialization, constants, inference, expressions, and conversions.

What you'll learn

Every C++ object has a type, value, lifetime, and region of storage. This lesson turns that model into practical code through safe initialization, fundamental types, constants, expressions, conversions, and numeric boundaries.

By the end, you'll be able to:

  • Declare initialized variables using appropriate fundamental types
  • Use const, constexpr, and auto deliberately
  • Predict integer division and common conversions
  • Prevent narrowing and reason about overflow before arithmetic

Objects & variables

An object is a region of storage with a type and lifetime. A variable is an object or reference introduced by a declaration with a name. Initialization gives an object its first value; assignment replaces that value later.

variables.cpp
#include <iostream>

int main() {
    int age{25};              // declaration + initialization
    double price{19.95};
    char grade{'A'};
    bool enrolled{true};

    age = 26;                 // assignment replaces the value

    std::cout << age << ' ' << price << ' '
              << grade << ' ' << std::boolalpha << enrolled << '\n';
}
A type gives stored data meaning
ageint
25
whole number
pricedouble
19.95
approximate real
gradechar
'A'
character code
readybool
true
logical value

Tip

Give names to domain ideas and include units where ambiguity is possible:timeout_seconds communicates more than t.

Fundamental types

FamilyExamplesPurpose
Booleanbooltrue or false
Characterchar, wchar_t, char8_t, char16_t, char32_tCharacter and code-unit representations
Signed integershort, int, long, long longWhole numbers including negatives
Unsigned integerunsigned int and related formsModular nonnegative arithmetic and bits
Floating pointfloat, double, long doubleApproximate real-number values
No valuevoidNo returned value or incomplete generic pointer target

The standard guarantees minimum ranges and relationships, not one universal byte width. Plain int is normally the best default for ordinary whole-number arithmetic;double is the usual floating-point default.

Watch out

Unsigned types are not a general “nonnegative” replacement. Mixed signed/unsigned comparisons can convert a negative value to a very large unsigned value. Use unsigned types when modular arithmetic or representation requires them.

Safe initialization

C++ offers several initialization syntaxes. Braces are a strong beginner default because an empty pair produces a zero-like value and list initialization rejects many silent narrowing conversions.

initialization.cpp
int count;          // uninitialized local: do not read it
int zero{};         // value-initialized to 0
int students{24};   // direct-list initialization
double rate{0.15};

// int narrowed{3.8}; // error: braces reject narrowing
int deliberate = static_cast<int>(3.8); // visible conversion

Uninitialized is not zero

Reading an uninitialized local fundamental object is invalid. Initialize every object unless a measured low-level design specifically requires delayed initialization.

const, constexpr & auto

const prevents modification through that name after initialization.constexpr says a value or function can participate in compile-time evaluation when its inputs allow it. auto asks the compiler to deduce a type from an initializer.

constants.cpp
#include <numbers>

constexpr int days_per_week{7};
constexpr double circle_area(double radius) {
    return std::numbers::pi * radius * radius;
}

int main() {
    const double account_limit{1'500.0}; // runtime read-only object
    constexpr double unit_circle{circle_area(1.0)};

    auto attempts = 3;        // int
    auto average = 4.5;       // double
    // attempts = 3.5;        // allowed conversion, but attempts remains int

    (void) account_limit;
    (void) unit_circle;
    (void) attempts;
    (void) average;
}
ToolUse it when
constAn object must not change after initialization
constexprA value or calculation should be usable at compile time
autoThe initializer makes the type obvious or the spelled type is noisy

Note

auto does not make C++ dynamically typed. The compiler deduces one exact static type, and that type does not change when the variable is later assigned.

Literals, size & limits

Literal suffixes help express a value's intended type; digit separators improve readability without changing value. sizeof reports storage in bytes, whilestd::numeric_limits exposes the properties of a numeric type.

limits.cpp
#include <cstdint>
#include <iostream>
#include <limits>

int main() {
    std::int32_t temperature{-12};
    std::uint64_t stars{10'000'000'000ULL};

    std::cout << "int bytes: " << sizeof(int) << '\n';
    std::cout << "int max: " << std::numeric_limits<int>::max() << '\n';
    std::cout << temperature << ' ' << stars << '\n';
}
This is common output, not a guarantee for every conforming implementation.

Types such as std::int32_t from <cstdint> are useful when an external format requires exactly 32 bits. Prefer types from the standard library instead of assuming the width of int or long.

Operators & expressions

An expression computes a value. When both division operands are integers, the result is an integer and the fractional part is discarded toward zero.

operators.cpp
#include <iostream>

int main() {
    int total{17};
    int count{5};

    std::cout << total + count << '\n'; // 22
    std::cout << total / count << '\n'; // 3: integer division
    std::cout << total % count << '\n'; // 2: remainder

    total += 3;                         // total = total + 3
    bool valid{count > 0 && total >= count};
    std::cout << total << ' ' << std::boolalpha << valid << '\n';
}
CategoryOperators
Arithmetic+ - * / %
Comparison== != < <= > >=
Logical! && ||
Assignment= += -= *= /= %=
Increment++ --

Key idea

&& and || short-circuit: the right operand is evaluated only when needed. This is useful for guarding an operation, but the condition should remain readable.

Conversions & casts

Expressions perform promotions and conversions to find compatible operand types. Prefer C++ named casts because they state the intended conversion and are easy to search. At this level, static_cast covers ordinary checked-at-compile-time numeric conversions.

conversions.cpp
#include <iostream>

int main() {
    int total{7};
    int count{2};

    double wrong{total / count};
    double average{static_cast<double>(total) / count};
    int truncated{static_cast<int>(9.8)};

    std::cout << wrong << ' ' << average << ' ' << truncated << '\n';
}

Tip

Convert before the operation whose behavior must change. Casting the result oftotal / count is too late: integer division has already discarded the fraction.

Overflow & numeric care

Signed integer overflow is undefined behavior. Unsigned arithmetic wraps modulo one more than its maximum value, but defined wraparound can still be a logic or security defect. Validate before performing an operation that may exceed the destination range.

checked_add.cpp
#include <iostream>
#include <limits>

bool can_add(int left, int right) {
    if (right > 0 && left > std::numeric_limits<int>::max() - right) {
        return false;
    }
    if (right < 0 && left < std::numeric_limits<int>::min() - right) {
        return false;
    }
    return true;
}

int main() {
    int balance{2'000'000'000};
    int deposit{500'000'000};

    if (can_add(balance, deposit)) {
        balance += deposit;
    } else {
        std::cerr << "Balance would overflow\n";
    }
}
  • Check denominator values before division or remainder
  • Check multiplication before calculating allocation or collection sizes
  • Expect floating-point rounding; most decimal fractions are not exact in binary
  • Compare computed floating-point results with a domain-appropriate tolerance
  • Keep warnings for sign conversion and narrowing enabled in serious builds

Recap & quick check

Key takeaways

  • Every C++ object has a type, value, lifetime, and storage; initialize before use.
  • Brace initialization communicates intent and prevents many narrowing conversions.
  • const prevents mutation, constexpr enables constant evaluation, and auto performs static type deduction.
  • Integer widths vary; query limits or use fixed-width types when an external contract requires them.
  • Integer division truncates, and signed overflow has undefined behavior.

Quick check

1. Why is int value{3.8}; rejected?

2. What type does auto count = 3; deduce?

3. What does 7 / 2 produce when both operands are int?

4. What is guaranteed after signed integer overflow?

You can now represent and calculate with C++ values. Next: Module 3 — Console I/O, Strings & Formatting, where programs begin communicating robustly with users.