C++ Data Types Basics
Understanding Data Types
In C++, every object has a type that determines the values it can hold, how much memory it uses (implementation-defined), and which operations are valid.
Choosing the right type affects performance, correctness, and clarity (e.g., avoiding overflow or precision loss).
Fundamental Type Families
C++ fundamental (built-in) types include:
- Arithmetic types:
- Integral: `bool`, `char`/`signed char`/`unsigned char`, `wchar_t`, `char8_t` (C++20), `char16_t`, `char32_t`, `short`, `int`, `long`, `long long` and their unsigned variants
- Floating-point: `float`, `double`, `long double`
- `void` (no value),
- `std::nullptr_t` (the type of `nullptr`).
Library types like `std::string`, `std::vector`, etc., are not fundamental—they are class types provided by the standard library.
Fundamental Types — Quick Demo
This snippet declares one of each common category and prints them. Note: sizes and ranges are implementation-defined; query them at runtime.
#include <iostream>
#include <string>
#include <limits>
using namespace std;
int main() {
// Integral and floating-point
int age = 30; // Integer
float price = 9.99f; // Single-precision floating point
double pi = 3.14159; // Double-precision floating point
char grade = 'A'; // Character (signedness is implementation-defined)
bool isReady = true; // Boolean
// Library type (not fundamental)
string name = "Alice"; // std::string requires <string>
cout << boolalpha;
cout << age << "\n" << price << "\n" << pi << "\n" << grade << "\n" << isReady << "\n" << name << "\n";
// Sizes (illustrative; do not hard-code)
cout << "sizeof(int) = " << sizeof(int) << " bytes\n";
cout << "sizeof(double) = " << sizeof(double) << " bytes\n";
cout << "sizeof(char) = " << sizeof(char) << " byte\n";
}
Fixed-Width Integer Types (<cstdint>)
When you need exact widths for I/O, binary formats, or portability, prefer fixed-width types from `
#include <iostream>
#include <cstdint>
#include <cstddef>
using namespace std;
int main() {
std::int32_t i32 = 42;
std::uint64_t u64 = 9000000000ULL;
std::size_t n = 10; // for sizes/indices
cout << i32 << "\n" << u64 << "\n" << n << "\n";
}
Character & Boolean Nuances
- `char` is distinct from `signed char` and `unsigned char`; the signedness of plain `char` is implementation-defined.
- Wide/Unicode characters: `wchar_t`, `char16_t`, and `char32_t` represent wider code units (encoding/size is implementation-defined).
- `bool` outputs as `1/0` by default; use `std::boolalpha` to print `true/false`.
Floating-Point Types
`float` (typically ~7 decimal digits), `double` (~15), and `long double` (implementation-defined, often >= `double`) trade precision for memory and speed. Avoid testing floating-point values with `==`; compare within a tolerance.
#include <iostream>
#include <cmath>
using namespace std;
int main() {
double a = 0.1 + 0.2; // not exactly 0.3 in binary FP
double b = 0.3;
cout << boolalpha << (fabs(a - b) < 1e-12) << '\n';
}
Literal Suffixes & Initialization
Use literal suffixes to control types: `1.0f` (float), `1.0` (double), `1.0L` (long double), `42u` (unsigned), `42LL` (long long). Prefer brace initialization to avoid narrowing.
#include <iostream>
using namespace std;
int main() {
float f = 1.0f; // ok, float literal
double d = 1.0; // double literal
long double ld = 1.0L; // long double literal
unsigned u = 42u; // unsigned literal
long long ll = 42LL; // long long literal
int x{42}; // brace init (prevents narrowing)
// int bad{1.5}; // ❌ narrowing, would not compile
cout << f << " " << d << " " << ld << "\n";
}
Best Practices
- Use `std::size_t` for sizes/indices and `
` fixed-width types for serialized/binary data. - Do not assume exact sizes of fundamental types; use `sizeof` and `
` when necessary. - Prefer `double` for general-purpose floating-point unless memory/perf dictates otherwise.
- Be careful with signed/unsigned mixing and integer overflow (signed overflow is undefined behavior).
- Use clear, self-documenting types that match your domain (e.g., `std::chrono` durations for time).