C++ variables and data types - holographic int double char string bool data boxes

C++ Variables & Data Types: Complete Guide to int, double, char, bool & More

Back to C++ RoadmapC++ Programming Course • 65 Lessons

What Are Variables?

A variable is a named piece of memory that holds a value of a specific type. When you write int age = 25;, you are telling the compiler: allocate enough memory to hold an integer, name that memory location age, and store the value 25 in it. Unlike Python, where a variable is just a name pointing to an object, a C++ variable is the storage itself — a fixed number of bytes at a known address.

Every variable in C++ has three properties: a name (the identifier you use in code), a type (which determines how many bytes it occupies and how the bits are interpreted), and a value (the data currently stored). The type is set at compile time and never changes — C++ is statically typed.

Declaring and Initializing Variables

C++ gives you multiple ways to create variables. Modern C++ (C++11 and later) prefers brace initialization because it prevents accidental narrowing conversions:

#include <iostream>
#include <string>

int main() {
    // 1. C-style initialization (works, but old-fashioned)
    int a = 10;
    double b = 3.14;

    // 2. Constructor-style initialization
    int c(20);
    double d(2.718);

    // 3. Brace initialization (PREFERRED in modern C++)
    int e{30};
    double f{1.618};

    // 4. Brace initialization catches narrowing errors
    // int g{3.14};  // ERROR: narrowing conversion from double to int
    // This is a FEATURE — it protects you from accidental data loss

    // 5. auto with brace init
    auto h = 42;          // int
    auto i = 3.14;        // double
    auto j = 'A';         // char
    auto k = true;        // bool

    std::cout << "a=" << a << " e=" << e << " h=" << h << "\n";
    return 0;
}

Rule of thumb: Use int x{value}; for simple types and auto x = value; when the type is obvious from the right-hand side. Avoid leaving variables uninitialized — in C++, reading an uninitialized variable is undefined behavior.

Integer Types — int, short, long, long long

C++ provides several integer types with different sizes. The exact size depends on the platform, but the C++ standard guarantees minimum sizes:

#include <iostream>
#include <climits>    // INT_MAX, LONG_MAX, etc.
#include <cstdint>    // Fixed-width integers

int main() {
    // Standard integer types
    short s = 32767;              // At least 16 bits
    int i = 2147483647;           // At least 16 bits (usually 32)
    long l = 2147483647L;         // At least 32 bits
    long long ll = 9223372036854775807LL;  // At least 64 bits

    // Unsigned variants (no negative numbers, double positive range)
    unsigned int ui = 4294967295U;
    unsigned long long ull = 18446744073709551615ULL;

    std::cout << "short: " << s << " (max: " << SHRT_MAX << ")\n";
    std::cout << "int: " << i << " (max: " << INT_MAX << ")\n";
    std::cout << "long long: " << ll << "\n";

    // Fixed-width integers (RECOMMENDED for portability)
    int8_t   i8  = 127;           // Exactly 8 bits
    int16_t  i16 = 32767;         // Exactly 16 bits
    int32_t  i32 = 2147483647;    // Exactly 32 bits
    int64_t  i64 = 9223372036854775807LL;  // Exactly 64 bits

    uint8_t  u8  = 255;           // Unsigned 8 bits
    uint32_t u32 = 4294967295U;   // Unsigned 32 bits

    std::cout << "int32_t: " << i32 << "\n";
    std::cout << "uint8_t: " << static_cast<int>(u8) << "\n";  // cast to print as number

    return 0;
}

Use int for most things. Use int64_t when you need guaranteed 64-bit values. Use size_t (from <cstddef>) for array indices and sizes — it is an unsigned type sized to fit any object in memory.

Floating-Point Types — float, double, long double

#include <iostream>
#include <iomanip>    // For std::setprecision
#include <cmath>      // For math functions
#include <cfloat>     // FLT_MAX, DBL_MAX

int main() {
    float f = 3.14159f;           // 32 bits, ~7 decimal digits precision
    double d = 3.141592653589793; // 64 bits, ~15 decimal digits precision
    long double ld = 3.14159265358979323846L; // 80+ bits (platform-dependent)

    std::cout << std::setprecision(20);
    std::cout << "float:       " << f << "\n";
    std::cout << "double:      " << d << "\n";
    std::cout << "long double: " << ld << "\n";

    // Floating-point gotcha: precision loss
    float a = 0.1f + 0.2f;
    std::cout << "0.1f + 0.2f = " << a << "\n";
    // Output: 0.30000001192092895508 (NOT exactly 0.3!)

    // Never compare floats with ==
    if (std::abs(a - 0.3f) < 1e-6f) {
        std::cout << "Approximately equal (correct way)\n";
    }

    // Special values
    double inf = 1.0 / 0.0;       // Infinity
    double nan = 0.0 / 0.0;       // NaN (Not a Number)
    std::cout << "inf: " << inf << " nan: " << nan << "\n";
    std::cout << "isnan: " << std::isnan(nan) << "\n";

    return 0;
}

Use double by default. It has enough precision for virtually everything and is the default floating-point type in C++. Only use float when memory is tight (GPU programming, large arrays) or when an API specifically requires it.

Character and Boolean Types

#include <iostream>

int main() {
    // char — stores a single character (1 byte, typically ASCII)
    char letter = 'A';
    char digit = '7';
    char newline = '\n';

    std::cout << letter << " has ASCII value: " << static_cast<int>(letter) << "\n";
    // Output: A has ASCII value: 65

    // char arithmetic works because chars are just small integers
    char next = letter + 1;    // 'B'
    char lower = letter + 32;  // 'a' (ASCII trick: uppercase + 32 = lowercase)
    std::cout << next << " " << lower << "\n";

    // bool — true or false (1 byte, but only uses 1 bit of information)
    bool isReady = true;
    bool isEmpty = false;

    std::cout << "isReady: " << isReady << "\n";          // Prints 1
    std::cout << std::boolalpha << "isReady: " << isReady << "\n"; // Prints true

    // bool converts to int: true=1, false=0
    int count = isReady + isEmpty + true + false;
    std::cout << "count: " << count << "\n";  // 2

    // Any nonzero value is true, zero is false
    if (42) std::cout << "42 is truthy\n";
    if (!0) std::cout << "0 is falsy\n";

    return 0;
}

Strings in C++ — std::string

#include <iostream>
#include <string>

int main() {
    // std::string — dynamic, safe, easy to use
    std::string greeting = "Hello";
    std::string name = "World";

    // Concatenation with +
    std::string message = greeting + ", " + name + "!";
    std::cout << message << "\n";

    // Length
    std::cout << "Length: " << message.length() << "\n";  // or .size()

    // Accessing characters
    std::cout << "First char: " << message[0] << "\n";       // H
    std::cout << "Safe access: " << message.at(0) << "\n";   // H (bounds-checked)

    // Substring
    std::string sub = message.substr(0, 5);  // "Hello"

    // Find
    size_t pos = message.find("World");
    if (pos != std::string::npos) {
        std::cout << "Found 'World' at position " << pos << "\n";
    }

    // Comparison (works naturally with ==, !=, <, >)
    if (greeting == "Hello") {
        std::cout << "Strings compare correctly!\n";
    }

    // Iteration
    for (char c : message) {
        std::cout << c << " ";
    }
    std::cout << "\n";

    return 0;
}

Always use std::string instead of C-style char* strings. It manages memory automatically, prevents buffer overflows, and supports all the operations you would expect. For the C-style string functions, see the C string functions lesson — but in C++, you rarely need them.

Type Deduction with auto

#include <iostream>
#include <vector>
#include <string>

int main() {
    // auto deduces the type from the initializer
    auto x = 42;          // int
    auto pi = 3.14;       // double
    auto ch = 'Z';        // char
    auto flag = true;     // bool
    auto msg = std::string("Hello");  // std::string

    // auto shines with complex types
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    // Without auto — verbose
    for (std::vector<std::string>::iterator it = names.begin(); it != names.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << "\n";

    // With auto — clean
    for (auto it = names.begin(); it != names.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << "\n";

    // Even cleaner with range-based for
    for (const auto& name : names) {
        std::cout << name << " ";
    }
    std::cout << "\n";

    return 0;
}

Use auto when the type is obvious from context or when the full type name is verbose. Do not use auto when it would make the code harder to understand — auto x = getData(); is unclear because you cannot tell what type x is.

const and constexpr — Immutable Values

#include <iostream>

int main() {
    // const — value cannot be changed after initialization
    const int MAX_USERS = 1000;
    const double PI = 3.14159265358979;
    // MAX_USERS = 2000;  // ERROR: cannot assign to const variable

    // constexpr — value computed at COMPILE TIME (stronger than const)
    constexpr int BUFFER_SIZE = 1024;
    constexpr double TAU = 2.0 * 3.14159265358979;

    // constexpr functions
    // The result is computed during compilation, not at runtime
    constexpr int square(int x) { return x * x; }
    constexpr int area = square(5);  // Computed at compile time = 25

    // Arrays need compile-time sizes — constexpr guarantees this
    int buffer[BUFFER_SIZE];  // OK: BUFFER_SIZE is constexpr

    std::cout << "MAX_USERS: " << MAX_USERS << "\n";
    std::cout << "Area: " << area << "\n";
    std::cout << "TAU: " << TAU << "\n";

    return 0;
}

Rule: If a value should never change, make it const. If it can be computed at compile time, make it constexpr. This makes your intent clear, prevents bugs, and lets the compiler optimize aggressively.

sizeof and Type Sizes

#include <iostream>

int main() {
    std::cout << "bool:        " << sizeof(bool) << " byte(s)\n";
    std::cout << "char:        " << sizeof(char) << " byte(s)\n";
    std::cout << "short:       " << sizeof(short) << " byte(s)\n";
    std::cout << "int:         " << sizeof(int) << " byte(s)\n";
    std::cout << "long:        " << sizeof(long) << " byte(s)\n";
    std::cout << "long long:   " << sizeof(long long) << " byte(s)\n";
    std::cout << "float:       " << sizeof(float) << " byte(s)\n";
    std::cout << "double:      " << sizeof(double) << " byte(s)\n";
    std::cout << "long double: " << sizeof(long double) << " byte(s)\n";
    std::cout << "pointer:     " << sizeof(int*) << " byte(s)\n";

    // sizeof works on variables too
    int arr[10];
    std::cout << "arr size: " << sizeof(arr) << " bytes (" 
              << sizeof(arr) / sizeof(arr[0]) << " elements)\n";

    return 0;
}
// Typical output on 64-bit system:
// bool:        1 byte(s)
// char:        1 byte(s)
// short:       2 byte(s)
// int:         4 byte(s)
// long:        8 byte(s)  (4 on Windows!)
// long long:   8 byte(s)
// float:       4 byte(s)
// double:      8 byte(s)
// pointer:     8 byte(s)

Integer Overflow and Undefined Behavior

#include <iostream>
#include <climits>

int main() {
    // Signed overflow is UNDEFINED BEHAVIOR — the compiler can do anything
    int max_int = INT_MAX;  // 2147483647
    std::cout << "INT_MAX: " << max_int << "\n";
    // int overflow = max_int + 1;  // UB! Don't do this.

    // Unsigned overflow is DEFINED — it wraps around (modular arithmetic)
    unsigned int u = 0;
    u = u - 1;  // Wraps to UINT_MAX
    std::cout << "0u - 1 = " << u << "\n";  // 4294967295

    // Detecting overflow before it happens
    int a = 2000000000;
    int b = 1000000000;
    if (a > INT_MAX - b) {
        std::cout << "Addition would overflow!\n";
    } else {
        std::cout << "Safe: " << a + b << "\n";
    }

    return 0;
}

Type Conversion and Casting

#include <iostream>

int main() {
    // Implicit conversion (automatic, sometimes dangerous)
    int i = 42;
    double d = i;      // int → double (safe, no data loss)
    std::cout << "int to double: " << d << "\n";

    double pi = 3.14159;
    int truncated = pi;  // double → int (DATA LOSS: decimal part removed)
    std::cout << "double to int: " << truncated << "\n";  // 3

    // Explicit casting (modern C++ style)
    double result = static_cast<double>(7) / 2;  // 3.5 (not 3!)
    std::cout << "7/2 = " << result << "\n";

    // Without cast: integer division
    int wrong = 7 / 2;  // 3 (truncated!)
    std::cout << "7/2 (int) = " << wrong << "\n";

    // char to int (ASCII value)
    char ch = 'A';
    int ascii = static_cast<int>(ch);
    std::cout << "'" << ch << "' = " << ascii << "\n";  // 65

    return 0;
}

Always use static_cast<T>(value) for explicit conversions in C++. Avoid C-style casts like (int)3.14 — they bypass type safety checks and are harder to search for in code.

Naming Conventions and Best Practices

// Good variable names — descriptive, readable
int userCount = 0;
double accountBalance = 1500.75;
std::string firstName = "Chirag";
bool isAuthenticated = false;
const int MAX_RETRIES = 3;

// Bad variable names — unclear, too short
int n = 0;          // What does n mean?
double x = 1500.75; // What is x?
std::string s;      // Useless name
bool flag;          // What flag?

// Common C++ conventions:
// camelCase for variables and functions: userName, getTotal()
// PascalCase for classes: HttpClient, DatabaseConnection
// UPPER_SNAKE_CASE for constants: MAX_BUFFER_SIZE, PI
// snake_case is also common (STL uses it): my_variable, file_path

Practice Exercises

// Exercise 1: Declare variables to store your name, age, height (in meters),
// and whether you are a student. Print them all.

// Exercise 2: Write a program that swaps two integers without using a third variable.
// Hint: Use arithmetic or XOR.

// Exercise 3: Print the size in bytes of every fundamental type using sizeof.

// Exercise 4: Demonstrate integer overflow with unsigned int.
// Start from 0, subtract 1, and print the result.

// Exercise 5: Write a temperature converter:
// - Ask for a temperature in Celsius
// - Convert to Fahrenheit (F = C * 9.0/5.0 + 32)
// - Convert to Kelvin (K = C + 273.15)
// - Print all three values with 2 decimal places (use std::fixed and std::setprecision)

Summary

C++ variables are typed, fixed-size regions of memory. You learned the fundamental types — int, double, char, bool, and std::string — along with their sizes, ranges, and gotchas. You know how to use auto for type deduction, const and constexpr for immutability, static_cast for safe conversions, and brace initialization to prevent narrowing bugs. In the next lesson, we cover input and output — reading from the keyboard and formatting output to the terminal.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *