Variables & Data Types

Reviewed & published by Brayan K

By the end of this lesson you'll be able to store text, numbers, and true/false values in C++, choose the right type, convert safely between them, and read input from the user — the foundation of every C++ program you'll ever write.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A variable is a labelled container in a kitchen. A jar labelled "Sugar" (string) holds text; a measuring cup labelled "Cups" (int) holds whole numbers; a scale labelled "Weight" (double) holds decimals. Each container only holds one kind of thing — you can't pour flour into a liquid measuring cup. C++ is statically typed, which means every variable's type is fixed when you declare it and the compiler checks it before your program ever runs. That strictness is a feature: it catches whole classes of mistakes early.

📊 The Core Data Types

TypeHoldsSizeExample
intWhole numbers4 bytesint age = 25;
doubleDecimals (~15 digits)8 bytesdouble h = 1.75;
floatDecimals (~7 digits)4 bytesfloat t = 36.6f;
charOne character1 bytechar g = 'A';
booltrue / false1 bytebool ok = true;
std::stringTextvariesstring n = "Al";

Notice string uses "double quotes" but char uses 'single quotes' — mixing these up is the #1 beginner error, so it's worth burning in now. (string lives in the <string> header.)

1. Declaring & Initializing Variables

Every variable needs a type and a name, and you usually give it a value on the same line: type name = value;. Use descriptive camelCase names like firstName or totalPrice — code is read far more often than it's written, so a clear name pays for itself. Read this worked example, run it, then check the output against the comments.

#include <iostream>
#include <string>
using namespace std;

int main() {
    // A variable = a named box that stores ONE type of value.
    // Pattern:  type  name  =  value;
    int age = 25;              // whole number (no decimal point)
    double price = 19.99;      // decimal number (~15 digits, the default)
    float temperature = 36.6f; // decimal, less precision -> note the 'f'
    char grade = 'A';          // a SINGLE character -> 'single quotes'
    bool isStudent = true;     // true or false (prints as 1 / 0)
    string name = "Alice";     // text -> always "double quotes"

    // <<  feeds values into cout (the screen).  endl adds a new line.
    cout << "Name: " << name << endl;          // Name: Alice
    cout << "Age: " << age << endl;            // Age: 25
    cout << "Price: $" << price << endl;       // Price: $19.99
    cout << "Temp: " << temperature << endl;   // Temp: 36.6
    cout << "Grade: " << grade << endl;        // Grade: A
    cout << "Student? " << isStudent << endl;  // Student? 1  (true)
    return 0;
}

// ✅ Expected output:
//    Name: Alice
//    Age: 25
//    Price: $19.99
//    Temp: 36.6
//    Grade: A
//    Student? 1

There's more than one way to give a variable its first value. The modern brace initialization — int x{5} — is preferred because it refuses to silently lose data (it blocks "narrowing", like squeezing 3.9 into an int). And auto lets the compiler pick the type for you from the value on the right.

#include <iostream>
#include <string>
using namespace std;

int main() {
    // Three ways to create a variable:
    int a = 5;        // copy initialization (the classic =)
    int b{10};        // brace initialization -> the modern, safer way
    int c;            // declare only -> value is GARBAGE until you set it
    c = 15;           // now c is safe to use

    // auto: let the compiler work out the type from the value.
    auto x = 42;          // x is int (because 42 is a whole number)
    auto y = 3.14;        // y is double
    auto greeting = string("Hi"); // greeting is a std::string

    cout << a << " " << b << " " << c << endl; // 5 10 15
    cout << x << " " << y << " " << greeting << endl; // 42 3.14 Hi

    // Brace init also REFUSES to lose data silently:
    // int oops{3.9};  // ERROR: narrowing 3.9 into an int is blocked
    return 0;
}

// ✅ Expected output:
//    5 10 15
//    42 3.14 Hi

Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints in the comments, then run it.

#include <iostream>
#include <string>
using namespace std;

int main() {
    // 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".

    // 1) Make an int called "year" set to the current year
    int year = ___;        // 👉 a whole number, e.g. 2026

    // 2) Make a string called "city" set to a city name
    string city = ___;     // 👉 text in "double quotes"

    // 3) Make a double called "price" set to 9.99
    double price = ___;    // 👉 a decimal number

    // These lines already work once your variables exist:
    cout << "In " << year << ", a coffee in " << city
         << " costs $" << price << endl;

    // ✅ Expected output (example):
    //    In 2026, a coffee in London costs 9.99
    return 0;
}

2. Constants, Signedness & Sizes

Some values should never change — a tax rate, a maximum number of lives, pi. Mark them const and the compiler will reject any attempt to reassign them. constexpr goes one step further: the value must be known at compile time, which lets the compiler use it as a fixed size or array length. Numbers are also signed by default (they can be negative); marking one unsigned drops the negatives and reaches a bigger positive range with the same memory. sizeof tells you exactly how many bytes a type uses.

#include <iostream>
using namespace std;

int main() {
    // const = a value that can NEVER change after it is set.
    const double PI = 3.14159265;
    // PI = 3.14;  // ERROR: assignment of read-only variable 'PI'

    // constexpr = known at COMPILE time (even stronger than const).
    constexpr int MAX_PLAYERS = 100;

    double radius = 5.0;
    double area = PI * radius * radius;   // 78.5398
    cout << "Area: " << area << endl;
    cout << "Max players: " << MAX_PLAYERS << endl;

    // signed vs unsigned: unsigned cannot hold negatives, so it
    // reaches a bigger positive range with the same memory.
    int balance = -50;          // signed (the default) -> can be negative
    unsigned int lives = 3;     // unsigned -> 0 and up only
    cout << "Balance: " << balance << ", Lives: " << lives << endl;

    // sizeof tells you how many bytes a type uses in memory.
    cout << "int: " << sizeof(int) << " bytes" << endl;     // 4
    cout << "double: " << sizeof(double) << " bytes" << endl; // 8
    cout << "char: " << sizeof(char) << " byte" << endl;    // 1
    cout << "bool: " << sizeof(bool) << " byte" << endl;    // 1
    return 0;
}

// ✅ Expected output:
//    Area: 78.5398
//    Max players: 100
//    Balance: -50, Lives: 3
//    int: 4 bytes
//    double: 8 bytes
//    char: 1 byte
//    bool: 1 byte

3. Type Conversion & Casting

Sometimes a value is the wrong type and you need to convert it. Implicit conversion happens automatically when it's safe (an int fits inside a double). When the conversion can lose data — like double to int — C++ may still do it but the decimals just vanish. To convert deliberately you use static_cast<type>(value), which states your intent clearly and is far safer than old C-style (int) casts. The classic trap: dividing two int values gives an int result.

#include <iostream>
using namespace std;

int main() {
    // Implicit conversion — automatic and SAFE (no data lost).
    int whole = 10;
    double asDecimal = whole;   // int -> double happens for you
    cout << "Implicit: " << asDecimal << endl;  // 10

    // Narrowing — automatic but LOSES data (the decimals vanish).
    double pi = 3.14159;
    int truncated = pi;         // 3  (chopped, NOT rounded)
    cout << "Truncated: " << truncated << endl; // 3

    // Integer division trap: both ints -> the remainder is dropped.
    int total = 17, items = 5;
    cout << "Wrong: " << total / items << endl; // 3  (not 3.4!)

    // static_cast<type>(value) — you ASK for the conversion clearly.
    double average = static_cast<double>(total) / items;
    cout << "Right: " << average << endl;       // 3.4
    return 0;
}

// ✅ Expected output:
//    Implicit: 10
//    Truncated: 3
//    Wrong: 3
//    Right: 3.4

Now you try. The two values below are int, so a plain / would chop the decimals off. Cast one side to double so the answer keeps its fraction:

#include <iostream>
using namespace std;

int main() {
    // 🎯 YOUR TURN — both numbers are ints, so plain division truncates.
    int distanceKm = 7;
    int hours = 2;

    // 1) Cast distanceKm to a double so the division keeps decimals
    double speed = ___ / hours;   // 👉 static_cast<double>(distanceKm)

    cout << distanceKm << "km in " << hours << "h = "
         << speed << " km/h" << endl;

    // ✅ Expected output:  7km in 2h = 3.5 km/h
    return 0;
}

4. Scope & Reading Input with cin

Scope is simply where a variable is visible. A variable lives inside the nearest pair of { } braces and disappears at the closing brace — try to use it outside and the compiler won't know what you mean. You read keyboard input with std::cin >> variable: the >> operator pulls what the user typed into your variable, automatically matching the variable's type (a number into an int, a word into a string).

#include <iostream>
#include <string>
using namespace std;

int main() {
    // SCOPE = where a variable is visible. A variable lives inside the
    // nearest { } block and disappears at the closing brace.
    int outer = 1;
    {
        int inner = 2;          // 'inner' only exists in this block
        cout << outer << " " << inner << endl; // 1 2  (both visible)
    }
    // cout << inner;  // ERROR: 'inner' was not declared in this scope

    // cin reads typed input INTO a variable using >>
    string name;
    int birthYear;
    cout << "Enter your name: ";
    cin >> name;                // reads one word into 'name'
    cout << "Enter birth year: ";
    cin >> birthYear;           // reads a whole number into 'birthYear'

    int age = 2026 - birthYear; // arithmetic with your variables
    cout << "Hi " << name << ", you are about " << age << endl;
    return 0;
}

🔎 Deep Dive: auto vs spelling out the type

auto asks the compiler to deduce the type from the value: auto city = string("London"); is exactly a string. It's still strongly typed — once deduced, the type is fixed. Use auto to cut noise when the type is obvious or very long, but prefer the explicit type when it makes beginner code clearer.

const and constexpr both mean "do not reassign". const is read-only at run time; constexpr is fixed at compile time. Constants are written in UPPER_SNAKE_CASE by convention so they stand out.

auto greeting = string("Hi"); // compiler infers std::string
const int MAX_LIVES = 3;       // read-only at run time
constexpr int BOARD = 8;       // known at compile time
// MAX_LIVES = 5;              // ❌ error: assignment of read-only variable

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCodeResult
Declare integerint x = 10;10
Brace initint x{5};5 (safe)
Infer typeauto y = 3.14;double
Constantconst int MAX = 100;read-only
Safe caststatic_cast<double>(x)double
Memory sizesizeof(int)4
Read inputcin >> age;into age

Mini-Challenge: Profile Card

No blanks this time — just a brief and a blank canvas (with an outline to keep you on track). Build it, run it, and check your output against the example in the comments. This is exactly the kind of small program real apps are made of.

#include <iostream>
#include <string>
using namespace std;

int main() {
    // 🎯 MINI-CHALLENGE: Profile card
    // 1. Create variables: name (string), age (int),
    //    heightMeters (double), likesCoding (bool).
    // 2. Print a tidy 4-line profile using cout << ... << endl.
    // 3. BONUS: it is their birthday — add 1 to age with  age = age + 1;
    //    then print "Next year you'll be " << age.
    //
    // ✅ Example output:
    //    Name: Sam
    //    Age: 20
    //    Height: 1.8m
    //    Likes coding: 1
    //    Next year you'll be 21

    // your code here
    return 0;
}

🎉 Lesson Complete

Practice quiz

Which type should you use to store a whole number like an age or a count?

  • double
  • int
  • char
  • bool

Answer: int. int holds whole numbers; double and float are for decimals.

What is the default floating-point type in C++ (about 15 digits of precision)?

  • float
  • decimal
  • double
  • real

Answer: double. double is the default floating-point type, with roughly 15 significant digits.

What is the difference between 'A' and "A" in C++?

  • They are identical
  • 'A' is a char, "A" is a std::string
  • 'A' is a string, "A" is a char
  • 'A' is invalid syntax

Answer: 'A' is a char, "A" is a std::string. Single quotes make a char (one character); double quotes make a std::string.

What does std::cout print for 7 / 2 when both are int?

  • 3.5
  • 3
  • 4
  • 3.0

Answer: 3. Two ints do integer division: the remainder is dropped, giving 3.

Which keyword lets the compiler infer a variable's type from its value?

  • var
  • auto
  • let
  • infer

Answer: auto. auto deduces the type from the initializer, e.g. auto x = 42; makes x an int.

What keyword marks a value that must be known at compile time?

  • const
  • static
  • constexpr
  • final

Answer: constexpr. constexpr is stronger than const: the value must be known at compile time.

How do you convert an int to a double deliberately and clearly?

  • double(x)!
  • static_cast<double>(x)
  • convert<double>(x)
  • x as double

Answer: static_cast<double>(x). static_cast<double>(x) states the conversion clearly and is safer than C-style casts.

What does brace initialization int n{3.9}; do?

  • Sets n to 4
  • Sets n to 3
  • Fails to compile (narrowing blocked)
  • Sets n to 3.9

Answer: Fails to compile (narrowing blocked). Brace init blocks narrowing, so squeezing 3.9 into an int won't compile.

What does sizeof(int) typically return on a 64-bit desktop?

  • 1
  • 2
  • 4
  • 8

Answer: 4. An int is typically 4 bytes; sizeof reports the byte size of a type.

What happens to a variable declared with 'int c;' but never assigned before use?

  • It is automatically 0
  • It holds a garbage value
  • The program won't compile
  • It becomes null

Answer: It holds a garbage value. An uninitialized local holds a garbage value until you assign one — always initialize.

Continue this course

Frequently asked questions

When should I use int, double, or float?

Use int for whole numbers (ages, counts, indexes). Use double for decimals — it is the default floating-point type in C++ and gives about 15 significant digits. Only reach for float when memory is very tight, because it keeps roughly half the precision.

Why did 7 / 2 give me 3 instead of 3.5?

Both 7 and 2 are int values, so C++ does integer division and throws away the remainder. Make one side a decimal — 7.0 / 2 — or cast it with static_cast<double>(7) / 2 to get 3.5.

What is the difference between 'A' and "A"?

'A' in single quotes is a char — exactly one character. "A" in double quotes is a std::string of length 1. They are different types, so mixing the quotes is a common beginner error.

Should I use auto for every variable?

No. auto is great when the type is obvious from the right-hand side or very long to write, but spelling out int or double often makes beginner code clearer. Use auto to remove noise, not to hide what a variable actually is.

What is the difference between const and constexpr?

const means the value cannot change after it is set. constexpr is stronger: the value must be known at compile time, so the compiler can bake it in. Use const for read-only values and constexpr for true compile-time constants like a maximum size.