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
    └── typedef

3. 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                   nullptr

4. 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 bits

8. 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;  // false

20. 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 byte

24. 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 long

29. 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 integer

34. 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 nullptr

36. 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 use
char c = '5';
int value = c;
std::cout << value << '\n';
unsigned int x = 0;
--x;
std::cout << x << '\n';