C++ Data Types
Every C++ object is shaped by its type — what values it can hold, how it's stored, and which operations make sense. This page covers every fundamental type, sizes, signedness, fixed-width integers, and how types relate to derived and user-defined types.
Data types are one of the fundamental building blocks of C++. A data type tells the compiler what kind of value an object can store, how that value is interpreted, and which operations are valid for it. Fundamental types such as int, double, char, and bool form the foundation that derived types, user-defined types, and type aliases all build on.
Overview: A First Look
Consider four objects of different types side by side:
int, double, char, and bool are four different data types, each storing a different kind of value.
int age = 25;
double temperature = 36.5;
char grade = 'A';
bool isRunning = true;1. What Is a Data Type?
A data type defines the category of data associated with an object or expression.
Here int is the data type, count is the variable/object name, and 10 is the initial value.
The compiler uses the type of count to determine things such as: what values can be represented, how operations on the value are interpreted, which conversions are allowed, how much storage is required, and which functions/operators can be used with the object.
Because a and b are int, the + operation is integer addition.
int count = 10;int a = 10;
int b = 20;
int result = a + b;2. Categories of C++ Data Types
C++ data types can broadly be organized into four groups.
This topic focuses primarily on fundamental C++ data types and how C++ categorizes types. Pointers, references, arrays, classes, structures, unions, and enumerations are separate concepts and should be studied in their own dedicated topics.
C++ Data Types
│
├── Fundamental Types
│ ├── Integer types
│ ├── Floating-point types
│ ├── Character types
│ ├── Boolean type
│ ├── void
│ └── nullptr type
│
├── Derived Types
│ ├── Pointers
│ ├── References
│ ├── Arrays
│ └── Function types
│
├── User-Defined Types
│ ├── class
│ ├── struct
│ ├── union
│ └── enum
│
└── Type Aliases
├── using
└── typedef3. Fundamental Data Types
Fundamental types are types provided directly by the C++ language.
Type General purpose Example
-------------- ------------------------------- --------------
bool Boolean values true
char Character / small integer 'A'
wchar_t Wide character L'A'
char8_t UTF-8 code unit u8'A'
char16_t UTF-16 code unit u'A'
char32_t UTF-32 code unit U'A'
short Small integer short x = 10;
int General integer int x = 100;
long Integer long x = 1000;
long long Large integer long long x = 100000;
float Single-precision floating point 3.14f
double Double-precision floating point 3.14
long double Extended floating point 3.14L
void No value/type void func();
std::nullptr_t Type of nullptr nullptr4. Integer Types
Integer types represent whole-number values. Common integer types include short, int, long, and long long. They can also be qualified with signed or unsigned.
int temperature = 25;
unsigned int count = 100;
long distance = 100000L;
long long population = 8000000000LL;5. int
int is the most commonly used integer type.
It can represent both positive and negative values when it is a signed type. The exact size of int is implementation-defined.
A common modern implementation uses int -> 32 bits, but portable C++ code should not assume this universally. The language guarantees that sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) in terms of their relative storage sizes.
int age = 25;
int count = 100;
int temperature = -10;6. short
short is an integer type that is at least 16 bits wide.
It is useful when a smaller integer range is sufficient and the implementation's representation makes that choice meaningful. However, using short does not automatically guarantee better performance or memory savings in every context.
short temperature = 25;7. long
long is another signed integer type.
Its size is platform-dependent. For example, common systems may have:
Therefore, portable code should avoid assuming that long is always 32 or 64 bits.
long distance = 1000000L;Windows long -> 32 bits
Many Unix-like 64-bit systems long -> 64 bits8. long long
long long provides an integer type with at least 64 bits.
The suffix LL indicates a long long integer literal. It is commonly used when a range larger than int is required.
long long value = 9223372036854775807LL;9. Signed and Unsigned Integers
Integer types can be signed or unsigned.
Signed — can represent negative and positive values.
Unsigned — represents non-negative values only.
For an unsigned integer type with N value bits, its range is 0 to 2^N - 1. For example, a 32-bit unsigned integer commonly represents 0 to 4,294,967,295.
int temperature = -20;unsigned int count = 20;10. Important Unsigned Behavior
Unsigned arithmetic has defined modulo behavior.
If unsigned int has 32 value bits, the result wraps around to 4,294,967,295.
This is different from signed integer overflow, which has undefined behavior.
Be careful when comparing signed and unsigned values:
The usual arithmetic conversions can convert the signed value to an unsigned type before the comparison happens.
unsigned int x = 0;
--x;int a = -1;
unsigned int b = 1;
if (a < b)
{
// May not behave as expected at first glance
}11. char
char represents a character type.
Character literals use single quotes: 'A', 'B', '0', '\n'.
A char is also an integer type in C++, so it can participate in arithmetic:
value receives the numeric encoding of 'A'; the exact numeric value depends on the character encoding used by the implementation.
Important distinction: char c = '5'; does not store the integer value 5. It stores the character '5'.
char grade = 'A';
char symbol = '#';char c = 'A';
int value = c;12. Character Types
C++ provides several character-related types: char, signed char, unsigned char, wchar_t, char8_t, char16_t, and char32_t. They serve different purposes.
char8_t, char16_t, and char32_t are particularly relevant to Unicode encoding and are distinct types.
char c = 'A';
char16_t c16 = u'A';
char32_t c32 = U'A';13. signed char and unsigned char
These are distinct character/integer types.
Unlike plain char, the signedness of plain char is implementation-defined. Therefore char c; may behave as either a signed or unsigned character type depending on the implementation. If signedness matters, explicitly use signed char or unsigned char.
signed char a = -10;
unsigned char b = 200;14. Floating-Point Types
Floating-point types represent values containing a fractional component. C++ provides float, double, and long double.
float temperature = 36.5f;
double distance = 12345.678;
long double preciseValue = 3.141592653589793238L;15. float
float is commonly a single-precision floating-point type.
The f suffix explicitly makes the literal a float. Without the suffix, float x = 3.14; means 3.14 is a double literal that is then converted to float.
Prefer float x = 3.14f; when you specifically want a float literal.
float temperature = 36.5f;16. double
double is commonly used when more floating-point precision is needed than float provides.
A decimal floating-point literal without a suffix is normally of type double: 3.14 is a double literal.
double pi = 3.141592653589793;
double voltage = 12.75;17. long double
long double provides an extended floating-point type.
Its actual representation and precision are implementation-dependent. Do not assume that long double always means a particular number of bits or a specific floating-point format.
long double value = 3.141592653589793238L;18. Floating-Point Precision
Floating-point types generally cannot represent every decimal value exactly.
The stored representation is typically an approximation of 0.1, not the exact value.
Avoid assuming 0.1 + 0.2 == 0.3 will always be true.
For numerical comparisons, an appropriate tolerance may be needed:
Instead of comparing floating-point values with ==, check whether their difference is smaller than a small tolerance.
The correct tolerance depends on the numerical problem.
double x = 0.1;#include <cmath>
if (std::abs(a - b) < 1e-9)
{
// Treat a and b as approximately equal
}19. bool
bool represents a logical value: true or false.
A bool is commonly used in conditions:
C++ also allows conversion between boolean and other arithmetic types:
In a boolean context, 0 converts to false, and any nonzero value converts to true.
bool isReady = true;
bool isError = false;if (isReady)
{
// Execute when true
}bool a = 10; // true
bool b = 0; // false20. void
void represents the absence of a value. It is commonly used for functions that do not return a value:
A function returning void cannot return a value:
void also has other language uses, but it is not a normal object type that you instantiate directly.
void printMessage()
{
// ...
}void func()
{
return; // valid
}void func()
{
return 10; // invalid
}21. nullptr and std::nullptr_t
C++ provides nullptr as the null pointer literal. Its type is std::nullptr_t.
nullptr represents a null pointer value.
It is different from 0 and NULL. nullptr is specifically designed for pointer-related use and provides better type safety. Detailed pointer behavior is covered separately in the Pointers topic.
int* ptr = nullptr;22. Type Size and sizeof
C++ provides the sizeof operator to determine the size of an object or type in bytes.
Prints the size, in bytes, of int, double, and char on the current implementation.
The result is implementation-dependent. However, sizeof(char) is always 1, because C++ defines one byte as the size of a char.
One byte is not necessarily 8 bits. The number of bits in a byte is available through:
#include <iostream>
int main()
{
std::cout << sizeof(int) << '\n';
std::cout << sizeof(double) << '\n';
std::cout << sizeof(char) << '\n';
}#include <climits>
std::cout << CHAR_BIT;23. Typical Type Sizes
A common 64-bit environment might have values similar to these:
These are common examples, not universal guarantees. Never write portable code based solely on assumptions such as int = 4 bytes, long = 8 bytes, or double = 8 bytes unless the target platform explicitly guarantees them.
Type Common size
------------ -----------------------
char 1 byte
short 2 bytes
int 4 bytes
long 4 or 8 bytes
long long 8 bytes
float 4 bytes
double 8 bytes
long double implementation-dependent
bool commonly 1 byte24. Fixed-Width Integer Types
When an exact integer width is important, C++ provides fixed-width integer types through <cstdint>.
Examples: std::int8_t, std::int16_t, std::int32_t, std::int64_t, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t.
These are useful when the program requires a specific integer width. They are especially valuable when data representation must be precisely controlled.
#include <cstdint>std::uint32_t counter = 100;
std::int16_t temperature = -20;25. Minimum Width vs Exact Width
Fixed-width types such as std::uint32_t are available when the implementation provides an unsigned integer type with exactly 32 bits. There are also minimum-width types such as std::uint_least32_t and fast types such as std::uint_fast32_t. These serve different portability/performance requirements.
For general C++ programming, use the ordinary types such as int unless a specific width or range is actually required.
26. std::size_t
std::size_t is an unsigned integer type used to represent sizes and object-related counts. It is commonly used with sizeof.
You will frequently encounter it when working with sizes, array lengths, and standard-library interfaces.
std::size_t size = sizeof(int);27. Type Modifiers
C++ allows certain modifiers to be applied to fundamental types. Common modifiers include signed, unsigned, short, and long.
Some forms can be abbreviated:
For example, unsigned int x; and unsigned x; represent the same type.
unsigned int count;
long int distance;
long long int value;
short int smallValue;unsigned count;
long distance;
long long value;
short smallValue;28. Integer Type Hierarchy
Conceptually, the standard provides a family of standard signed integer types: signed char, short, int, long, long long. Each provides at least as much range as the preceding type.
Unsigned counterparts exist for the standard unsigned integer types: unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long.
signed char -> short -> int -> long -> long long29. Type Aliases
C++ allows an existing type to be given another name. Modern C++ generally prefers using:
Counter is an alias for unsigned int; it does not create a new distinct type.
Another form is:
typedef is still valid, but using is generally preferred in modern C++. Detailed type aliases are covered separately.
using Counter = unsigned int;Counter count = 100;typedef unsigned int Counter;30. Fundamental Types vs User-Defined Types
int is a fundamental type.
Employee is a user-defined class type.
Point is a user-defined type.
The distinction is: fundamental types are provided directly by C++; user-defined types are created by the programmer. Classes, structures, unions, and enumerations are covered in their own topics.
int age = 25;class Employee
{
// ...
};struct Point
{
int x;
int y;
};31. Type Determines Valid Operations
The type of an object influences which operations make sense.
The compiler uses type information during compilation to check whether expressions and operations are valid.
int a = 10;
int b = 20;
int result = a + b;double a = 10.5;
double b = 2.5;
double result = a + b;bool ready = true;char c = 'A';32. Type and Memory Representation
A type is more than simply a number of bytes.
The type determines how the stored bits are interpreted as an integer value and what operations are applicable.
Uses a floating-point representation instead.
Therefore storage, representation, allowed operations, and value/range rules together form important aspects of a type.
33. Data Type Selection
Choose a type based on what the value represents.
Do not choose a type solely because it happens to occupy fewer bytes on one platform.
int count = 100; // whole-number quantity
long long population = 8000000000LL; // large integer
double temperature = 36.75; // fractional value
bool enabled = true; // true/false state
char grade = 'A'; // character
std::uint32_t id = 100; // exact-width integer34. Best Practices
1. Choose types based on meaning:
Meaningful types make code easier to understand.
2. Initialize objects. Prefer int count{}; double value{}; bool enabled{}; over leaving local fundamental objects uninitialized.
3. Use fixed-width integers when width matters: std::uint32_t status; instead of assuming unsigned int status; is always 32 bits.
4. Be careful with signed/unsigned mixing. Avoid unnecessary mixing of int and unsigned in arithmetic and comparisons.
5. Do not assume platform-specific sizes. Use sizeof(T) or appropriate <cstdint> types when requirements demand it.
6. Use double when ordinary floating-point precision is needed (double result = 3.14159;); use float when its smaller representation or a specific API/interface requirement is appropriate.
bool isConnected;
int retryCount;
double temperature;
char grade;35. Quick Reference
bool Logical true/false
char Character / small integer type
signed char Signed character/integer type
unsigned char Unsigned character/integer type
short Small signed integer
unsigned short Small unsigned integer
int General-purpose signed integer
unsigned int General-purpose unsigned integer
long Larger signed integer type
unsigned long Larger unsigned integer type
long long At least 64-bit signed integer
unsigned long long At least 64-bit unsigned integer
float Single-precision floating point
double Double-precision floating point
long double Extended floating point
wchar_t Wide character type
char8_t UTF-8 code unit
char16_t UTF-16 code unit
char32_t UTF-32 code unit
void No value
std::nullptr_t Type of nullptr36. Mental Model
Think of a C++ data type as a contract for a value:
For example: int count = 10; means int -> integer representation -> integer value -> integer operations.
Whereas: double temperature = 36.5; means double -> floating-point representation -> approximate numeric value -> floating-point operations.
Why is sizeof(char) always guaranteed to be 1, even though a byte is not necessarily 8 bits?
C++ defines one byte as exactly the size of a char, by definition — sizeof(char) is always 1 by the standard's own terms, regardless of how many bits that byte actually contains on a given platform. The number of bits per byte is a separate, implementation-defined fact, available through CHAR_BIT in <climits>.
Explain why int a = -1; unsigned int b = 1; if (a < b) does not do what someone might expect at first glance.
In a comparison between a signed and an unsigned operand, the usual arithmetic conversions convert the signed value to the unsigned type before comparing. -1 converted to unsigned int becomes a very large positive value (close to the type's maximum), so a < b evaluates to false rather than true.
What is the practical difference between using int and using std::uint32_t from <cstdint>?
int's exact size is implementation-defined — it is commonly 32 bits but a program should not rely on that universally. std::uint32_t guarantees an unsigned integer type of exactly 32 bits wherever the implementation provides one, which matters when data representation, binary layout, or a specific range must be precisely controlled.
Why does float x = 3.14; not necessarily give you exactly the float you might expect, and what should you write instead if you want a float literal?
Without a suffix, 3.14 is parsed as a double literal, which is then implicitly converted to float when stored in x — an extra conversion step. Writing float x = 3.14f; uses the f suffix so the literal itself is a float from the start, avoiding the implicit double-to-float conversion.
Give one example each of a fundamental type, a derived type, and a user-defined type.
A fundamental type: int (provided directly by the language). A derived type: int* (a pointer, built from an existing type). A user-defined type: a class or struct such as struct Point { int x; int y; }; (created by the programmer).
What does this print, and why doesn't it print 5?
It prints the numeric encoding of the character '5' (53 under ASCII), not the integer 5. char c = '5'; stores the character '5', and converting it to int yields its underlying character-encoding value, not the digit it visually represents.
Write a program that prints the size, in bytes, of int, double, char, and bool using sizeof.
The exact numbers are implementation-dependent, but a typical 64-bit environment prints int: 4, double: 8, char: 1, bool: 1 (sizeof(char) is always guaranteed to be 1).
What does this print on a system where unsigned int has 32 value bits, and why?
It prints 4294967295. Unsigned arithmetic wraps around using defined modulo behavior instead of triggering undefined behavior, so decrementing 0 wraps to the maximum representable value, 2^32 - 1.
Write a program that declares a std::uint32_t counter from <cstdint>, initializes it to 4000000000, and prints it alongside sizeof(counter).
It prints 4000000000 followed by 4 (the exact-width type guarantees 4 bytes / 32 bits regardless of platform, unlike plain unsigned int, whose width is not guaranteed).
Data Type
│
┌────────────┼────────────┐
↓ ↓ ↓
Representation Values Operations
│ │ │
storage range valid usechar c = '5';
int value = c;
std::cout << value << '\n';unsigned int x = 0;
--x;
std::cout << x << '\n';