Variables and Data Types — Primitives, Auto, Type Deduction, and sizeof
In this tutorial, you will learn about Variables and Data Types. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ variables must be declared with a type before use, but modern C++ offers auto for type deduction while primitive types have platform-dependent sizes that sizeof can reveal.
What You'll Learn
You will master C++ primitive types (int, char, bool, float, double, void), understand signed versus unsigned integers and their pitfalls, use auto for type deduction, measure type sizes with sizeof, discover minimum and maximum values with <limits>, and avoid common type conversion errors.
Why It Matters
Every value in a C++ program occupies memory of a specific size and layout. Unlike dynamically-typed languages where variables can change type freely, C++ determines the type of every variable at compile time. This static typing catches entire categories of bugs before your program runs. Understanding types is foundational to writing correct, efficient C++.
Learning Path
graph LR
A["03: Hello World"] --> B["04: Variables & Types"]
B --> C["05: Constants & Modifiers"]
C --> D["06: Operators"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
Fundamental Types
C++ provides a set of fundamental types that map directly to hardware capabilities:
| Type | Typical Size | Range | Notes |
|---|---|---|---|
bool |
1 byte | true or false | Stored as integer 0/1 |
char |
1 byte | -128 to 127 or 0-255 | Implementation-defined signedness |
signed char |
1 byte | -128 to 127 | |
unsigned char |
1 byte | 0 to 255 | |
short |
2 bytes | -32,768 to 32,767 | |
unsigned short |
2 bytes | 0 to 65,535 | |
int |
4 bytes | ~-2.1B to ~2.1B | Typical, not guaranteed |
unsigned int |
4 bytes | 0 to ~4.2B | |
long |
4 or 8 bytes | Platform-dependent | Same as int on 32-bit |
long long |
8 bytes | -9E18 to 9E18 | At least 64 bits (C++11) |
float |
4 bytes | ~7 decimal digits | IEEE 754 single precision |
double |
8 bytes | ~15 decimal digits | IEEE 754 double precision |
long double |
8/10/16 bytes | Platform-dependent | Extended precision |
Variable Declaration and Initialization
#include <iostream>
int main() {
int x; // default-initialized (garbage value)
int y = 42; // copy-initialization
int z(42); // direct-initialization
int w{42}; // brace-initialization (C++11, preferred)
int v{}; // value-initialization (zero)
std::cout << y << " " << w << " " << v << "\n";
}
Expected output:
42 42 0
Brace initialization (using {}) is the recommended style in modern C++. It prevents narrowing conversions (e.g., int x{3.14}; would be a compilation error). The other forms silently truncate.
Type Deduction with auto
The auto keyword tells the compiler to deduce the type from the initializer:
#include <iostream>
#include <typeinfo>
int main() {
auto a = 42; // int
auto b = 3.14; // double
auto c = 3.14f; // float
auto d = 'A'; // char
auto e = true; // bool
auto f = 42ULL; // unsigned long long
std::cout << typeid(a).name() << "\n";
}
Use auto to avoid writing long type names and to ensure consistency when types change. However, auto is not magic: it strips references and const by default unless you add & or const:
int x = 42;
int& ref = x;
auto a = ref; // a is int, not int&
auto& b = ref; // b is int&
const int c = 10;
auto d = c; // d is int, not const int
const auto e = c; // e is const int
Signed vs Unsigned
Signed types can represent negative and positive values using two's complement. Unsigned types cannot represent negatives but double the positive range.
#include <iostream>
int main() {
unsigned int u = 0;
u = u - 1; // wraparound to 4294967295 (on 32-bit)
std::cout << u << "\n";
int s = 10;
unsigned int t = 5;
auto result = s - t; // s promoted to unsigned, result is unsigned!
std::cout << result << "\n"; // prints 5
// Dangerous:
std::cout << (s < t - 10) << "\n"; // t - 10 wraps to huge value
}
Rule: Avoid mixing signed and unsigned in comparisons or arithmetic. The compiler promotes the signed value to unsigned, often causing surprising behavior.
Fixed-Width Integer Types
The <cstdint> header provides types with guaranteed sizes:
#include <iostream>
#include <cstdint>
int main() {
int8_t i8; // exactly 8 bits, signed
uint16_t u16; // exactly 16 bits, unsigned
int32_t i32; // exactly 32 bits, signed
uint64_t u64; // exactly 64 bits, unsigned
int_least32_t li32; // at least 32 bits
int_fast32_t fi32; // fastest for 32-bit ops
std::cout << sizeof(int64_t) << "\n";
}
Prefer these fixed-width types when you need exact sizes (networking, binary formats, Embedded Systems).
The sizeof Operator
sizeof returns the size of a type or object in bytes:
#include <iostream>
int main() {
std::cout << "int: " << sizeof(int) << "\n";
std::cout << "double: " << sizeof(double) << "\n";
std::cout << "bool: " << sizeof(bool) << "\n";
int arr[10];
std::cout << "array of 10 ints: " << sizeof(arr) << "\n";
std::cout << "elements in arr: " << sizeof(arr) / sizeof(arr[0]) << "\n";
}
Expected output (on a typical 64-bit system):
int: 4
double: 8
bool: 1
array of 10 ints: 40
elements in arr: 10
Type Limits
The <limits> header provides information about type properties:
#include <iostream>
#include <limits>
int main() {
std::cout << "int max: " << std::numeric_limits<int>::max() << "\n";
std::cout << "int min: " << std::numeric_limits<int>::min() << "\n";
std::cout << "double digits: " << std::numeric_limits<double>::digits10 << "\n";
std::cout << "bool is signed: " << std::numeric_limits<bool>::is_signed << "\n";
}
Expected output:
int max: 2147483647
int min: -2147483648
double digits: 15
bool is signed: false
Type Conversion
Implicit Conversion
int i = 42;
double d = i; // int to double, safe
double pi = 3.14;
int trunc = pi; // double to int, truncates to 3 (warning)
Explicit Conversion (Casting)
double pi = 3.14159;
int approx = static_cast<int>(pi); // C++ style
int old = (int)pi; // C style (avoid)
unsigned char byte = 200;
int promoted = byte; // implicit, safe
int big = 1000;
char small = static_cast<char>(big); // narrowing, data may be lost
Prefer static_cast<> for well-defined conversions. It is searchable, visible, and checked by the compiler.
Common Mistakes
Mistake 1: Uninitialized Variables
int count;
std::cout << count; // undefined behavior, may print garbage
Always initialize variables. Use {} for zero-initialization.
Mistake 2: Signed/Unsigned Mismatch
unsigned int u = 10;
int s = -5;
if (s < u) { ... } // false! s converts to unsigned, becomes huge
Enable compiler warnings: -Wsign-compare (part of -Wall).
Mistake 3: Assuming Fixed Sizes
On a 32-bit system long is 4 bytes; on 64-bit Linux it is 8 bytes; on 64-bit Windows it is 4 bytes. Use sizeof() or fixed-width types from <cstdint>.
Mistake 4: Overflow
int x = 2147483647;
x = x + 1; // undefined behavior for signed overflow
Use unsigned types for modular arithmetic, or detect overflow with <limits>.
Mistake 5: Narrowing Brace Initialization
int x{3.14}; // error: narrowing conversion
int y(3.14); // OK (but truncates to 3)
Brace initialization protects against data loss.
Mistake 6: Using char for Arithmetic
char c = 200; // implementation-defined if char is signed
Use unsigned char or uint8_t when storing byte values.
Practice Questions
- What is the difference between
int x = 5;,int x(5);, andint x{5};? - What does
sizeofreturn for abool? Why is that interesting? - Write code that demonstrates an unsigned integer wrapping to zero.
- Use
<cstdint>to declare a variable that is exactly 64 bits wide on all platforms. - What is the output of
std::cout << (10u < -5);? Why?
Challenge
Write a program that uses auto to deduce the type of a lambda expression (you can write a simple one: [](int x){ return x * 2; }). Use typeid to print the human-readable type name.
FAQ
Mini Project
Write a program that prints a table showing the name, size in bytes, minimum value, and maximum value for each fundamental type: bool, char, short, int, long, long long, float, double. Use sizeof, std::numeric_limits, and format with std::cout.
#include <iostream>
#include <limits>
#include <type_traits>
int main() {
std::cout << "Type Size Min Max\n";
std::cout << "---- ---- --- ---\n";
auto print = [](auto name, auto x) {
using T = decltype(x);
std::cout << name << " "
<< sizeof(T) << " "
<< std::numeric_limits<T>::min() << " "
<< std::numeric_limits<T>::max() << "\n";
};
print("bool", bool{});
print("char", char{});
print("short", short{});
print("int", int{});
print("long", long{});
print("long long", long long{});
print("float", float{});
print("double", double{});
}
What's Next
Types give your data meaning. The next lesson covers constants and modifiers: const, constexpr, consteval, volatile, and mutable. You will learn how to make values immutable and execute code at compile time.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro