Skip to content

C++ Data Types

Variables are named storage locations in memory used to hold data that your program can manipulate. When you declare a variable in C++, you specify its data type. The data type tells the compiler:

  • How much memory to allocate for the variable.
  • The range or kind of values that can be stored in that memory.
  • The set of operations that can be performed on the variable.

C++ offers a set of fundamental data types, often called primitive types. Here are the most common ones:

Note: For modern Unicode support, C++11 introduced char16_t (for UTF-16) and char32_t (for UTF-32), and C++20 added char8_t (for UTF-8). However, std::string often handles UTF-8 encoded text using char.

Most integer and character types can be modified using these keywords:

  • signed: Explicitly states the type can hold positive and negative values (usually the default for int, etc.).
  • unsigned: Restricts the type to hold only non-negative values (0 and positive), effectively doubling the maximum positive value compared to its signed counterpart.
  • short: Hints that the integer type should use less memory than int (at least 16 bits).
  • long: Hints that the integer type should use at least as much memory as int (at least 32 bits).
  • long long: (C++11) An integer type guaranteed to be at least 64 bits wide.

The exact size (in bytes) and range of values for these types are implementation-defined, meaning they can vary between compilers, operating systems, and hardware architectures. However, the standard guarantees minimum ranges. The table below shows typical values on common 32-bit and 64-bit systems. Do not rely on these exact sizes for portable code. Use <limits> header for precise information.

You can use the sizeof() operator to determine the exact size of a type or variable on your specific system:

#include <iostream>
#include <string> // Include for std::string
int main() {
std::cout << "Size of bool: " << sizeof(bool) << " bytes" << std::endl;
std::cout << "Size of char: " << sizeof(char) << " bytes" << std::endl;
std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
std::cout << "Size of short int: " << sizeof(short int) << " bytes" << std::endl;
std::cout << "Size of long int: " << sizeof(long int) << " bytes" << std::endl;
std::cout << "Size of long long int: " << sizeof(long long int) << " bytes" << std::endl;
std::cout << "Size of float: " << sizeof(float) << " bytes" << std::endl;
std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;
std::cout << "Size of long double: " << sizeof(long double) << " bytes" << std::endl;
std::cout << "Size of wchar_t: " << sizeof(wchar_t) << " bytes" << std::endl;
std::cout << "Size of std::string: " << sizeof(std::string) << " bytes" << std::endl; // Note: Size of object, not stored text!
return 0;
}

The output will vary depending on your system. Note that sizeof(std::string) gives the size of the string object itself (which manages the text), not the length of the text it might contain.

You can create a new name (an alias) for an existing type using the using keyword (preferred in modern C++) or the older typedef keyword. This can improve readability for complex types.

Syntax (using):

using NewTypeName = ExistingType;

Example:

using Distance = double; // 'Distance' is now an alias for 'double'
using Counter = unsigned long long; // 'Counter' is an alias for 'unsigned long long'
Distance earthToMoon = 384400.0; // Units might be kilometers
Counter worldPopulation = 8000000000;

Syntax (typedef - older style):

typedef ExistingType NewTypeName;

Example (typedef):

typedef int ErrorCode;
ErrorCode fileNotFound = 404;

Enumerations allow you to define a new type with a restricted set of named integer constants (enumerators). They improve code clarity by using meaningful names instead of arbitrary numbers.

1. Unscoped Enumerations (enum) (Traditional C-style)

enum Color {
RED, // Defaults to 0
GREEN, // Defaults to 1
BLUE // Defaults to 2
};
Color myColor = GREEN; // Variable 'myColor' holds the value associated with GREEN (1)
// Enumerators pollute the surrounding scope:
// int RED = 5; // Error! RED is already defined.
// Implicit conversion to int:
int colorValue = myColor; // OK: colorValue becomes 1

You can assign specific integer values:

enum Status {
PENDING = 10,
PROCESSING = 20,
COMPLETE = 30,
FAILED // Defaults to 31 (previous + 1)
};

2. Scoped Enumerations (enum class) (Modern C++ - Recommended)

enum class (or enum struct) introduced in C++11 provides stronger type safety and avoids polluting the surrounding scope.

enum class SignalColor {
RED, // Value is 0
YELLOW, // Value is 1
GREEN // Value is 2
};
// Must use scope resolution operator (::)
SignalColor trafficLight = SignalColor::GREEN;
// Names are scoped, no conflict:
int RED = 100; // OK, this is different from SignalColor::RED
// No implicit conversion to int:
// int lightValue = trafficLight; // Error!
// Explicit conversion required if needed:
int lightValue = static_cast<int>(trafficLight); // OK: lightValue becomes 2
if (trafficLight == SignalColor::GREEN) {
std::cout << "Go!" << std::endl;
}

enum class is generally preferred in modern C++ because its enumerators don’t leak into the surrounding scope, and it prevents accidental implicit conversions to integer types, leading to safer and more maintainable code.