C++ Data Types
C++ Fundamental Data Types
Section titled “C++ Fundamental 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.
Primitive (Built-in) Types
Section titled “Primitive (Built-in) Types”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.
Type Modifiers
Section titled “Type Modifiers”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 forint, etc.).unsigned: Restricts the type to hold only non-negative values (0 and positive), effectively doubling the maximum positive value compared to itssignedcounterpart.short: Hints that the integer type should use less memory thanint(at least 16 bits).long: Hints that the integer type should use at least as much memory asint(at least 32 bits).long long: (C++11) An integer type guaranteed to be at least 64 bits wide.
Typical Sizes and Ranges
Section titled “Typical Sizes and Ranges”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.
Type Aliases (using)
Section titled “Type Aliases (using)”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 kilometersCounter worldPopulation = 8000000000;Syntax (typedef - older style):
typedef ExistingType NewTypeName;Example (typedef):
typedef int ErrorCode;ErrorCode fileNotFound = 404;Enumerated Types (enum, enum class)
Section titled “Enumerated Types (enum, enum class)”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 1You 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.