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:
| Category | Types |
|---|
| Integer | int, short, long, long long, and their unsigned variants |
| Floating-point | float, double, long double |
| Character | char, wchar_t, char8_t, char16_t, char32_t |
| Boolean | bool |
| Void | void (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 matter —
3.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 constexpr — const prevents modification after initialization (value may be runtime); constexpr guarantees compile-time evaluation and enables zero-overhead constants.- Named casts over C-style casts —
static_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.
Comments
Loading comments...
Leave a Comment