Beginner

Variables and Types in C++

Learn about variables, data types, type conversions, and constants in C++ with practical Docker-ready examples

C++ is a statically and strongly typed language — every variable has a fixed type determined at compile time, and the compiler enforces those types rigorously. As a systems language descended from C, C++ gives you fine-grained control over numeric sizes, signed vs. unsigned values, and memory representation, while also offering higher-level types like std::string through the standard library.

Modern C++ (C++11 and beyond) introduced auto for type inference, which lets the compiler deduce a variable’s type from its initializer. This reduces verbosity without sacrificing the safety of static typing — the type is still fixed at compile time, you just don’t have to write it out every time.

In this tutorial you will learn how to declare and initialize variables of the fundamental types, use const and constexpr for constants, convert between types, and understand how the C++ type system protects you from common mistakes.

Fundamental Types

C++ provides a rich set of built-in (primitive) types organized into several categories:

CategoryTypes
Integerint, short, long, long long, and their unsigned variants
Floating-pointfloat, double, long double
Characterchar, wchar_t, char8_t, char16_t, char32_t
Booleanbool
Voidvoid (no value)

Create a file named variables_basic.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>

int main() {
    // Integer types
    int age = 30;
    short year = 2026;
    long population = 8100000000L;
    long long bigNumber = 9223372036854775807LL;

    // Unsigned integers (non-negative only)
    unsigned int score = 42;
    unsigned long fileSize = 1048576UL;

    // Floating-point types
    float temperature = 98.6f;
    double pi = 3.141592653589793;
    long double preciseValue = 1.234567890123456789L;

    // Boolean
    bool isRunning = true;
    bool hasError = false;

    // Character
    char grade = 'A';

    // std::string (from the standard library)
    std::string name = "C++";

    std::cout << "Integer types:" << std::endl;
    std::cout << "  age        = " << age << std::endl;
    std::cout << "  year       = " << year << std::endl;
    std::cout << "  population = " << population << std::endl;
    std::cout << "  bigNumber  = " << bigNumber << std::endl;

    std::cout << "\nUnsigned types:" << std::endl;
    std::cout << "  score    = " << score << std::endl;
    std::cout << "  fileSize = " << fileSize << std::endl;

    std::cout << "\nFloating-point types:" << std::endl;
    std::cout << "  temperature  = " << temperature << std::endl;
    std::cout << "  pi           = " << pi << std::endl;
    std::cout << "  preciseValue = " << preciseValue << std::endl;

    std::cout << "\nBoolean:" << std::endl;
    std::cout << "  isRunning = " << std::boolalpha << isRunning << std::endl;
    std::cout << "  hasError  = " << hasError << std::endl;

    std::cout << "\nCharacter and string:" << std::endl;
    std::cout << "  grade = " << grade << std::endl;
    std::cout << "  name  = " << name << std::endl;

    return 0;
}

Type Inference with auto

C++11 introduced auto, which tells the compiler to deduce the type from the initializer. The variable is still statically typed — auto just removes redundant type annotations.

Create a file named variables_auto.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>
#include <typeinfo>

int main() {
    // auto deduces the type at compile time
    auto count = 10;           // int
    auto ratio = 3.14;         // double
    auto flag = true;          // bool
    auto letter = 'Z';         // char
    auto message = std::string("Hello, auto!"); // std::string

    // Suffix literals influence deduced type
    auto floatVal = 2.5f;      // float (not double)
    auto longVal = 100L;       // long (not int)
    auto unsignedVal = 50U;    // unsigned int

    std::cout << "auto-deduced types:" << std::endl;
    std::cout << "  count       = " << count        << " (type: " << typeid(count).name()       << ")" << std::endl;
    std::cout << "  ratio       = " << ratio        << " (type: " << typeid(ratio).name()       << ")" << std::endl;
    std::cout << "  flag        = " << std::boolalpha << flag << " (type: " << typeid(flag).name() << ")" << std::endl;
    std::cout << "  letter      = " << letter       << " (type: " << typeid(letter).name()      << ")" << std::endl;
    std::cout << "  message     = " << message      << " (type: " << typeid(message).name()     << ")" << std::endl;
    std::cout << "  floatVal    = " << floatVal     << " (type: " << typeid(floatVal).name()    << ")" << std::endl;
    std::cout << "  longVal     = " << longVal      << " (type: " << typeid(longVal).name()     << ")" << std::endl;
    std::cout << "  unsignedVal = " << unsignedVal  << " (type: " << typeid(unsignedVal).name() << ")" << std::endl;

    return 0;
}

Constants: const and constexpr

C++ provides two ways to define constants. const marks a value that cannot be changed after initialization. constexpr goes further — it must be evaluable at compile time, which allows the compiler to substitute the value directly into the machine code.

Create a file named variables_constants.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>

// constexpr at namespace scope — computed at compile time
constexpr double PI = 3.14159265358979323846;
constexpr double GRAVITY = 9.80665;
constexpr int MAX_PLAYERS = 4;
constexpr double CIRCLE_AREA(double r) { return PI * r * r; }

int main() {
    // const — cannot be modified after initialization
    const int birthYear = 1985;
    const std::string language = "C++";

    // constexpr — value known at compile time
    constexpr int BUFFER_SIZE = 1024;
    constexpr double SPEED_OF_LIGHT = 299792458.0; // m/s

    // constexpr computed at compile time
    constexpr double area = CIRCLE_AREA(5.0);

    std::cout << "const values:" << std::endl;
    std::cout << "  birthYear = " << birthYear << std::endl;
    std::cout << "  language  = " << language << std::endl;

    std::cout << "\nconstexpr values:" << std::endl;
    std::cout << "  BUFFER_SIZE    = " << BUFFER_SIZE << std::endl;
    std::cout << "  SPEED_OF_LIGHT = " << SPEED_OF_LIGHT << " m/s" << std::endl;
    std::cout << "  GRAVITY        = " << GRAVITY << " m/s^2" << std::endl;
    std::cout << "  MAX_PLAYERS    = " << MAX_PLAYERS << std::endl;
    std::cout << "  Circle area (r=5) = " << area << std::endl;

    // Attempting to modify a const causes a compile error:
    // birthYear = 1990;  // error: assignment of read-only variable

    return 0;
}

Type Conversions

C++ supports both implicit (automatic) and explicit (cast) conversions. As a strongly-typed language, C++ will warn about or reject conversions that lose data, and provides named cast operators to make intent clear.

Create a file named variables_conversions.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <string>

int main() {
    // --- Implicit (widening) conversions --- safe, no data loss
    int i = 42;
    long l = i;          // int → long (widening)
    double d = i;        // int → double (widening)
    float f = 3.14f;
    double d2 = f;       // float → double (widening)

    std::cout << "Implicit widening conversions:" << std::endl;
    std::cout << "  int " << i << " -> long  " << l << std::endl;
    std::cout << "  int " << i << " -> double " << d << std::endl;
    std::cout << "  float " << f << " -> double " << d2 << std::endl;

    // --- Explicit (narrowing) conversions with static_cast ---
    double pi = 3.14159;
    int truncated = static_cast<int>(pi);       // truncates, not rounds
    float narrowed = static_cast<float>(pi);    // reduced precision

    std::cout << "\nExplicit narrowing with static_cast:" << std::endl;
    std::cout << "  double " << pi << " -> int    " << truncated << " (truncated)" << std::endl;
    std::cout << "  double " << pi << " -> float  " << narrowed  << " (reduced precision)" << std::endl;

    // --- char and int conversions ---
    char ch = 'A';
    int ascii = static_cast<int>(ch);
    char back = static_cast<char>(66);

    std::cout << "\nChar <-> int conversions:" << std::endl;
    std::cout << "  char '" << ch << "' -> int " << ascii << std::endl;
    std::cout << "  int 66 -> char '" << back << "'" << std::endl;

    // --- int and bool conversions ---
    int zero = 0;
    int nonzero = 7;
    bool fromZero = static_cast<bool>(zero);
    bool fromNonzero = static_cast<bool>(nonzero);
    int fromTrue = static_cast<int>(true);
    int fromFalse = static_cast<int>(false);

    std::cout << "\nint <-> bool conversions:" << std::endl;
    std::cout << "  int 0 -> bool  " << std::boolalpha << fromZero    << std::endl;
    std::cout << "  int 7 -> bool  " << fromNonzero << std::endl;
    std::cout << "  true  -> int   " << std::noboolalpha << fromTrue   << std::endl;
    std::cout << "  false -> int   " << fromFalse << std::endl;

    // --- String conversions (requires standard library) ---
    int num = 42;
    std::string numStr = std::to_string(num);
    double parsed = std::stod("3.14");
    int parsed_i = std::stoi("100");

    std::cout << "\nString conversions:" << std::endl;
    std::cout << "  int 42 -> string \"" << numStr << "\"" << std::endl;
    std::cout << "  string \"3.14\" -> double " << parsed << std::endl;
    std::cout << "  string \"100\" -> int " << parsed_i << std::endl;

    return 0;
}

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the official GCC image
docker pull gcc:14

# Compile and run the basic types example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++17 -o variables_basic variables_basic.cpp && ./variables_basic'

# Compile and run the auto example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++17 -o variables_auto variables_auto.cpp && ./variables_auto'

# Compile and run the constants example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++17 -o variables_constants variables_constants.cpp && ./variables_constants'

# Compile and run the conversions example
docker run --rm -v $(pwd):/app -w /app gcc:14 \
    sh -c 'g++ -std=c++17 -o variables_conversions variables_conversions.cpp && ./variables_conversions'

Expected Output

Running variables_basic:

Integer types:
  age        = 30
  year       = 2026
  population = 8100000000
  bigNumber  = 9223372036854775807

Unsigned types:
  score    = 42
  fileSize = 1048576

Floating-point types:
  temperature  = 98.6
  pi           = 3.14159
  preciseValue = 1.23457

Boolean:
  isRunning = true
  hasError  = false

Character and string:
  grade = A
  name  = C++

Running variables_auto:

Note: The typeid().name() output is implementation-defined. GCC typically prints single-character codes (i for int, d for double, b for bool, c for char, f for float, l for long, j for unsigned int). The variable values will be consistent across platforms.

Running variables_constants:

const values:
  birthYear = 1985
  language  = C++

constexpr values:
  BUFFER_SIZE    = 1024
  SPEED_OF_LIGHT = 2.99792e+08 m/s
  GRAVITY        = 9.80665 m/s^2
  MAX_PLAYERS    = 4
  Circle area (r=5) = 78.5398

Running variables_conversions:

Implicit widening conversions:
  int 42 -> long  42
  int 42 -> double 42
  float 3.14 -> double 3.14

Explicit narrowing with static_cast:
  double 3.14159 -> int    3 (truncated)
  double 3.14159 -> float  3.14159 (reduced precision)

Char <-> int conversions:
  char 'A' -> int 65
  int 66 -> char 'B'

int <-> bool conversions:
  int 0 -> bool  false
  int 7 -> bool  true
  true  -> int   1
  false -> int   0

String conversions:
  int 42 -> string "42"
  string "3.14" -> double 3.14
  string "100" -> int 100

Key Concepts

  • Static typing — every variable’s type is fixed at compile time; the compiler catches type mismatches before the program runs.
  • Fundamental types reflect hardware — C++ integer and float sizes map directly to CPU registers and memory, giving you control over memory layout and performance.
  • auto is still static — type inference with auto does not make C++ dynamically typed; the compiler deduces the type from the initializer and locks it in at compile time.
  • Suffix literals matter3.14f is a float, 3.14 is a double; 100L is a long, 100U is an unsigned int. These suffixes control what type auto deduces and prevent silent precision loss.
  • const vs constexprconst prevents modification after initialization (value may be runtime); constexpr guarantees compile-time evaluation and enables zero-overhead constants.
  • Named casts over C-style castsstatic_cast<T>(x) is explicit about intent and is checked by the compiler; the old C-style cast (T)x bypasses those checks and should be avoided in modern C++.
  • std::string for text — unlike C’s null-terminated char arrays, std::string manages memory automatically and provides a rich set of operations.
  • Narrowing conversions truncate, not round — casting 3.9 to int gives 3, not 4. Be explicit with static_cast when narrowing to make the potential data loss intentional.

Running Today

All examples can be run using Docker:

docker pull gcc:14
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining