Fundamentals

Variables and Data Types

Master C++ built-in types — integers, floats, characters, booleans, auto, const, type casting, overflow, and how C++ stores data in memory.

Variables and Data Types

A variable is a named location in memory that holds a value. C++ is statically typed — every variable has a fixed type determined at compile time, and the compiler enforces type rules before your program even runs.


Declaring Variables

cpp
#include <iostream>
using namespace std;

int main() {
    // Declaration only
    int age;

    // Declaration with initialisation (preferred)
    int score = 100;
    double price = 19.99;
    char grade = 'A';
    bool isActive = true;

    // C++11: uniform initialisation (brace syntax — prevents narrowing)
    int count{42};
    double ratio{0.75};

    // Multiple variables of the same type
    int x = 1, y = 2, z = 3;

    cout << score << " " << price << " " << grade << " " << isActive << "\n";
    // Output: 100 19.99 A 1
    return 0;
}

Note: Always initialise variables when you declare them. Uninitialized variables contain garbage values — a common source of undefined behaviour in C++.


Integer Types

cpp
#include <iostream>
#include <climits>   // INT_MAX, LONG_MAX, etc.
using namespace std;

int main() {
    // Signed integers (can hold negative values)
    short  s  = 32767;           // 2 bytes: -32,768 to 32,767
    int    i  = 2147483647;      // 4 bytes: ~-2.1B to 2.1B
    long   l  = 2147483647L;     // 4 or 8 bytes (platform-dependent)
    long long ll = 9223372036854775807LL;  // 8 bytes: ≈ ±9.2 × 10^18

    // Unsigned integers (non-negative only, double the positive range)
    unsigned int ui  = 4294967295U;  // 4 bytes: 0 to ~4.3B
    unsigned long long ull = 18446744073709551615ULL;

    // Fixed-width types (guaranteed sizes) — prefer these in cross-platform code
    #include <cstdint>
    int8_t   a = 127;       // Exactly 8 bits signed
    uint8_t  b = 255;       // Exactly 8 bits unsigned
    int32_t  c = 2147483647;
    uint64_t d = 18446744073709551615ULL;

    cout << "INT_MAX = " << INT_MAX << "\n";  // 2147483647
    cout << "sizeof(int) = " << sizeof(int) << " bytes\n";  // 4
    return 0;
}

Floating-Point Types

cpp
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    float  f  = 3.14f;          // 4 bytes, ~7 significant digits
    double d  = 3.14159265358;  // 8 bytes, ~15 significant digits
    long double ld = 3.14159265358979L; // 8-16 bytes (platform-dependent)

    cout << fixed << setprecision(10);
    cout << "float:       " << f  << "\n";  // 3.1400001049
    cout << "double:      " << d  << "\n";  // 3.1415926536
    cout << "long double: " << ld << "\n";  // 3.1415926536

    // Floating-point literals
    double sci = 1.5e10;   // 1.5 × 10^10 = 15,000,000,000.0
    double neg = 2.5e-3;   // 2.5 × 10^-3 = 0.0025

    // Floating-point comparison — NEVER use ==
    double a = 0.1 + 0.2;
    double b = 0.3;
    // a == b is FALSE due to binary representation
    double epsilon = 1e-9;
    bool equal = (a - b < epsilon && b - a < epsilon);  // Correct
    cout << "0.1+0.2 == 0.3: " << equal << "\n";  // 1 (true)

    return 0;
}

Warning: Use double as your default floating-point type, not float. The float type has only 7 significant digits of precision — subtle errors creep in quickly. Reserve float for performance-critical code like graphics.


Character Type

cpp
#include <iostream>
#include <cctype>   // toupper, tolower, isdigit, isalpha
using namespace std;

int main() {
    char ch = 'A';
    cout << ch << "\n";           // A
    cout << (int)ch << "\n";      // 65  (ASCII value)

    char next = ch + 1;
    cout << next << "\n";         // B   (arithmetic on chars)

    // Character testing functions
    cout << isalpha('Z') << "\n";  // 1 (non-zero = true)
    cout << isdigit('5') << "\n";  // 1
    cout << toupper('a') << "\n";  // 65 (ASCII of 'A')

    // Escape characters
    cout << 'A' << '\t' << 'B' << '\n';  // A   B
    cout << "He said \"hello\"\n";        // He said "hello"

    // Wide characters (Unicode)
    wchar_t wch = L'€';
    return 0;
}

Boolean Type

cpp
#include <iostream>
using namespace std;

int main() {
    bool isOpen = true;
    bool isEmpty = false;

    cout << boolalpha;           // Print "true"/"false" instead of 1/0
    cout << isOpen << "\n";      // true
    cout << isEmpty << "\n";     // false

    // Any non-zero integer converts to true
    bool fromInt = 42;
    cout << fromInt << "\n";     // true

    // Booleans in arithmetic
    int count = true + true + false;
    cout << count << "\n";       // 2

    return 0;
}

The auto Keyword

C++11 introduced auto — the compiler deduces the type from the initialiser:

cpp
#include <iostream>
#include <vector>
using namespace std;

int main() {
    auto x    = 42;          // int
    auto y    = 3.14;        // double
    auto z    = 'A';         // char
    auto flag = true;        // bool
    auto name = "hello";     // const char* (NOT std::string!)

    // Most useful with complex types
    vector<int> v = {1, 2, 3, 4, 5};
    for (auto it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";
    }
    // 1 2 3 4 5

    // auto by reference (avoids copying)
    for (auto& elem : v) {
        elem *= 2;
    }
    // v = {2, 4, 6, 8, 10}

    return 0;
}

Constants

cpp
#include <iostream>
using namespace std;

int main() {
    // const — value cannot be changed after initialisation
    const int MAX_SCORE = 100;
    // MAX_SCORE = 101;  // ERROR: cannot assign to const

    // constexpr — evaluated at compile time (faster, can be used in array sizes)
    constexpr double PI = 3.14159265358979;
    constexpr int BUFFER_SIZE = 1024;

    char buf[BUFFER_SIZE];  // OK — BUFFER_SIZE is a compile-time constant

    // constexpr function (computed at compile time if arguments are constants)
    constexpr int square(int n) { return n * n; }
    constexpr int result = square(5);  // 25, computed at compile time

    cout << PI << "\n";      // 3.14159265358979
    cout << result << "\n";  // 25

    return 0;
}

Type Casting

cpp
#include <iostream>
using namespace std;

int main() {
    // Implicit (automatic) conversion — C++ widens types safely
    int i = 42;
    double d = i;      // int → double: OK, no data loss
    cout << d << "\n"; // 42.0

    // Narrowing — data loss may occur (compiler may warn)
    double pi = 3.14159;
    int truncated = pi;   // 3 (decimal part lost)
    cout << truncated;    // 3

    // C-style cast (avoid in modern C++)
    double ratio = (double)7 / 2;   // 3.5

    // C++ static_cast (preferred — explicit and checked at compile time)
    double result = static_cast<double>(7) / 2;
    cout << result << "\n";   // 3.5

    int large = 1000;
    char c = static_cast<char>(large);  // Narrowing — intentional
    cout << (int)c << "\n";             // Truncated to char range

    return 0;
}
Cast typeWhen to use
static_cast<T>()Safe, checked at compile time — use for most conversions
reinterpret_cast<T>()Low-level bit reinterpretation — use with extreme caution
const_cast<T>()Remove const — almost always a design smell
dynamic_cast<T>()Safe downcast in class hierarchies — runtime checked

Overflow and Undefined Behaviour

cpp
#include <iostream>
using namespace std;

int main() {
    // Integer overflow — undefined behaviour for signed types!
    int max = 2147483647;
    int overflow = max + 1;   // UB — could be anything
    cout << overflow << "\n"; // -2147483648 on most platforms (but not guaranteed)

    // Unsigned overflow is defined — it wraps around
    unsigned int u = 4294967295U;  // UINT_MAX
    unsigned int wrapped = u + 1;
    cout << wrapped << "\n";       // 0 (wraps around)

    // Safe check before overflow
    if (max > INT_MAX - 1) {
        cout << "Would overflow!\n";
    }

    return 0;
}

Practical Example: Unit Converter

cpp
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    const double KM_PER_MILE  = 1.60934;
    const double KG_PER_POUND = 0.453592;
    const double C_TO_F_SCALE = 9.0 / 5.0;
    const double C_TO_F_OFFSET = 32.0;

    cout << fixed << setprecision(2);

    // Distance
    double miles = 26.2188;   // Marathon distance
    double km = miles * KM_PER_MILE;
    cout << miles << " miles = " << km << " km\n";
    // 26.22 miles = 42.20 km

    // Weight
    double pounds = 165.0;
    double kg = pounds * KG_PER_POUND;
    cout << pounds << " lbs = " << kg << " kg\n";
    // 165.00 lbs = 74.84 kg

    // Temperature
    double celsius = 100.0;
    double fahrenheit = celsius * C_TO_F_SCALE + C_TO_F_OFFSET;
    cout << celsius << "°C = " << fahrenheit << "°F\n";
    // 100.00°C = 212.00°F

    // Integer division trap
    int total = 7, parts = 2;
    cout << "7/2 (int) = " << total / parts << "\n";                         // 3
    cout << "7/2 (double) = " << static_cast<double>(total) / parts << "\n"; // 3.5

    return 0;
}

Summary

  • C++ has several integer types: short, int, long, long long — prefer fixed-width types (int32_t, uint64_t) for portable code
  • Use double (not float) as your default floating-point type
  • char stores a single character and is just a small integer (ASCII value)
  • bool stores true or false; any non-zero integer is true
  • auto lets the compiler deduce the type — most useful with iterators and complex types
  • const prevents modification; constexpr guarantees compile-time evaluation
  • Use static_cast<T>() for explicit type conversions — never use C-style casts in new code
  • Signed integer overflow is undefined behaviour — check before operating near the limits

Next, we'll explore C++ operators — arithmetic, relational, logical, bitwise, and how operator precedence determines the order of evaluation.