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, andautodeliberately - 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.
#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';
}ageintpricedoublegradecharreadyboolTip
timeout_seconds communicates more than t.Fundamental types
| Family | Examples | Purpose |
|---|---|---|
| Boolean | bool | true or false |
| Character | char, wchar_t, char8_t, char16_t, char32_t | Character and code-unit representations |
| Signed integer | short, int, long, long long | Whole numbers including negatives |
| Unsigned integer | unsigned int and related forms | Modular nonnegative arithmetic and bits |
| Floating point | float, double, long double | Approximate real-number values |
| No value | void | No 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
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.
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 conversionUninitialized is not zero
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.
#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;
}| Tool | Use it when |
|---|---|
const | An object must not change after initialization |
constexpr | A value or calculation should be usable at compile time |
auto | The 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.
#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';
}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.
#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';
}| Category | Operators |
|---|---|
| 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.
#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
total / 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.
#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.