Data Types & Variables
Fundamental types
C++ ships with a small set of built-in types. They fall into three families: numbers, characters, and truth values. The table shows the most common ones and their typical size on a modern desktop machine.
| Type | Meaning | Typical size | Example value |
|---|---|---|---|
bool | truth value | 1 byte | true, false |
char | single character | 1 byte | 'A', '9' |
int | whole number | 4 bytes | -27, 1024 |
double | decimal number | 8 bytes | 3.14159 |
float | decimal (smaller) | 4 bytes | 1.5f |
void | no value | — | used for functions that return nothing |
Sizes are implementation-defined: the standard only guarantees minimums. The practical
rule: use int for whole numbers, double for decimals, bool
for yes/no, and char for a single character. The <cstdint> header
provides fixed-size integers (int32_t, int64_t) for code that must not
depend on the platform.
Variables
A variable is a named box that stores a value of one type. You declare it by writing the type followed by the name, and you usually give it an initial value in the same statement.
int population = 8_000_000; // underscores make long numbers readable
double price = 19.99; // decimal value
bool passed = true; // truth value
char letter = 'Q'; // a single character in single quotes
Once declared, the variable's type never changes — you cannot store a decimal in
population.
Initialization styles
C++ offers several syntaxes for giving a variable its first value. Prefer the brace form
{ }: it refuses dangerous narrowing conversions (see the conversion section below).
int a = 10; // copy initialization (classic)
int b(10); // direct initialization
int c{10}; // brace initialization — preferred, catches narrowing
Uninitialized variables
Declaring without a value leaves the box with an indeterminate value — often garbage from whatever was previously in memory. Reading it is undefined behavior. Always initialize:
int total; // BAD: indeterminate value until assigned
int total = 0; // GOOD: every variable starts with a known value
Constants
Values that must never change after creation are declared const. The compiler then
rejects any attempt to modify them and can apply optimizations knowing the value is fixed.
const double PI = 3.14159; // PI cannot be reassigned
const int DAYS_IN_WEEK = 7; // naming constants in UPPER_SNAKE is a common style
// PI = 3.0; // compile error: assignment of read-only variable
Use const aggressively: it documents intent, prevents accidents, and lets the compiler
help you. A related keyword, constexpr, promises the value is computable at compile
time — you will use it in later lessons.
Type deduction with auto
When the initializer already makes the type obvious, you can let the compiler deduce it with
auto. The variable still has a fixed, known type — you just do not spell it out.
auto score = 95; // score is int
auto rate = 0.075; // rate is double
auto ok = true; // ok is bool
auto shines when the type is long or hard to spell (iterators, smart pointers).
For beginners the rule is: write the explicit type for clarity, reach for auto
when the type is verbose.
Limits & Overflow
Every type has a finite range. The <limits> header reports the exact bounds on
your machine:
#include <iostream>
#include <limits>
int main() {
std::cout << std::numeric_limits<int>::max() << "\n"; // largest int
std::cout << std::numeric_limits<int>::min() << "\n"; // smallest int
return 0;
}
Overflow wraps silently
Adding past the maximum of a signed integer is undefined behavior; for unsigned integers it wraps around like an odometer. Both surprises corrupt real programs. Guard your arithmetic or use a wider type:
unsigned int x = 4'294'967'295; // the largest unsigned int (32-bit)
x = x + 1; // wraps to 0 — probably a bug!
Type Conversions
Mixing types in one expression triggers implicit conversions: the compiler silently converts the smaller type to the larger one. This usually works, but can lose data:
double d = 3.9;
int n = d; // implicit conversion: n becomes 3 (fraction dropped!)
When you intentionally want to convert, say so explicitly with static_cast. The
explicit form tells readers the narrowing is deliberate:
int cents = static_cast<int>(d * 100); // explicit, intentional rounding
Prefer static_cast over C-style casts (int) — it is visible, searchable,
and the compiler checks that the conversion is legal.
Literals
Literals are constant values written directly in code, and their type comes from their form. Suffixes adjust the type explicitly:
42 // int
42u // unsigned int (u suffix)
42L // long
3.14 // double
3.14f // float (f suffix)
'a' // char — single quotes
"text" // C-style string — double quotes
0xFF // hexadecimal (255)
0b1010 // binary (10, C++14)
true // bool
Two classic traps: 'A' (a character) is not "A" (a string), and
0.1 is a double while 0.1f is a less precise
float.
Practice
Compile each snippet with warnings enabled and observe the output:
- Declare variables for your name (a string), age (int), and height in meters (double). Print them.
- Assign
double price = 9.99;thenint whole = price;— printwhole. What happened to the decimals? - Compile
int x{4.5};with-std=c++17 -Wall -Wextra. The braces reject the narrowing — read the error. - Print the max of
intanddoubleusingstd::numeric_limits.