Data Types & Variables

Scope: Every value in a C++ program has a type that defines what kind of data it holds and what operations are allowed. This lesson covers the fundamental types, how to create variables, constants, type deduction, conversions, and how to write literals.

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.

TypeMeaningTypical sizeExample value
booltruth value1 bytetrue, false
charsingle character1 byte'A', '9'
intwhole number4 bytes-27, 1024
doubledecimal number8 bytes3.14159
floatdecimal (smaller)4 bytes1.5f
voidno 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:

  1. Declare variables for your name (a string), age (int), and height in meters (double). Print them.
  2. Assign double price = 9.99; then int whole = price; — print whole. What happened to the decimals?
  3. Compile int x{4.5}; with -std=c++17 -Wall -Wextra. The braces reject the narrowing — read the error.
  4. Print the max of int and double using std::numeric_limits.