Data Types and Variables in C++
A variable is a name for a memory location that stores data, while a data type determines the kind and size of data that can be stored in the variable. Understanding data types and variables is a primary foundation in C++ programming, because this language is a statically typed language — a data type must be declared before use.
Primitive Data Types
C++ provides several primitive (built-in) data types that are the basic building blocks:
| Data Type | Size (typical) | Value Range | Usage |
|---|---|---|---|
bool |
1 byte | true or false |
True/false values |
char |
1 byte | -128 to 127 or 0 to 255 | Single character |
unsigned char |
1 byte | 0 to 255 | Unsigned character |
short |
2 byte | -32,768 to 32,767 | Small integer |
unsigned short |
2 byte | 0 to 65,535 | Small positive integer |
int |
4 byte | -2,147,483,648 to 2,147,483,647 | Standard integer |
unsigned int |
4 byte | 0 to 4,294,967,295 | Positive integer |
long |
4-8 byte | At least the same as int | Long integer |
unsigned long |
4-8 byte | 0 to 4,294,967,295+ | Positive long integer |
long long |
8 byte | -9.2×10¹⁸ to 9.2×10¹⁸ | Very long integer |
float |
4 byte | ±3.4×10³⁸ (6-7 digit precision) | Single-precision floating point |
double |
8 byte | ±1.7×10³⁰⁸ (15-16 digit precision) | Double-precision floating point |
long double |
8-16 byte | Larger than double | Extended-precision floating point |
void |
0 byte | No value | Function return type, generic pointer |
Note: The actual size of a data type can vary between platforms. Use the
sizeofoperator to find the actual size on your system.
Code Example
#include <iostream>
#include <limits>
#include <string>
int main() {
// Basic data types
bool active = true;
char letter = 'A';
int age = 25;
float height = 1.75f;
double weight = 68.5;
wchar_t wideCharacter = L'Ω';
// Output data types and their sizes
std::cout << "=== Data Type Sizes ===" << std::endl;
std::cout << "bool: " << sizeof(bool) << " byte" << std::endl;
std::cout << "char: " << sizeof(char) << " byte" << std::endl;
std::cout << "short: " << sizeof(short) << " byte" << std::endl;
std::cout << "int: " << sizeof(int) << " byte" << std::endl;
std::cout << "long: " << sizeof(long) << " byte" << std::endl;
std::cout << "long long: " << sizeof(long long) << " byte" << std::endl;
std::cout << "float: " << sizeof(float) << " byte" << std::endl;
std::cout << "double: " << sizeof(double) << " byte" << std::endl;
std::cout << "long double: " << sizeof(long double) << " byte" << std::endl;
std::cout << "wchar_t: " << sizeof(wchar_t) << " byte" << std::endl;
// Value ranges with <limits>
std::cout << "\n=== Value Ranges ===" << std::endl;
std::cout << "int min: " << std::numeric_limits<int>::min() << std::endl;
std::cout << "int max: " << std::numeric_limits<int>::max() << std::endl;
std::cout << "float min: " << std::numeric_limits<float>::min() << std::endl;
std::cout << "float max: " << std::numeric_limits<float>::max() << std::endl;
// Declaration and initialization
int x; // Declaration only (indeterminate value)
int y = 10; // Copy initialization
int z(20); // Direct initialization
int w{30}; // Brace initialization (C++11, recommended)
auto autoType = 42; // Auto: type is deduced automatically
std::cout << "\n=== Variable Values ===" << std::endl;
std::cout << "x: " << x << std::endl;
std::cout << "y: " << y << std::endl;
std::cout << "z: " << z << std::endl;
std::cout << "w: " << w << std::endl;
std::cout << "autoType: " << autoType << std::endl;
std::cout << "active: " << std::boolalpha << active << std::endl;
std::cout << "letter: " << letter << std::endl;
std::cout << "height: " << height << std::endl;
return 0;
}
Code Explanation
1. bool
The boolean data type stores a value of true or false. Its size is 1 byte.
bool finished = false;
bool valid = (10 > 5); // true
bool isEmpty = (text == ""); // true if text is empty
2. char
The char data type stores a single character in 1 byte.
char letter = 'A';
char digit = '7';
char newline = '\n'; // Character escape sequence
// ASCII value
int asciiValue = letter; // 65
char fromAscii = 65; // 'A'
Common escape characters:
| Escape | Description |
|---|---|
\n |
Newline (new line) |
\t |
Horizontal tab |
\\ |
Backslash |
\' |
Single quote |
\" |
Double quote |
\0 |
Null character |
\r |
Carriage return |
\b |
Backspace |
3. int
The integer data type is the most commonly used type for storing numbers without a decimal point.
int positive = 42;
int negative = -17;
int zero = 0;
4. float and double
Floating-point types for decimal numbers. float uses single precision, while double uses double precision.
float pi_float = 3.14159f; // 'f' suffix for float
double pi_double = 3.14159; // The default floating-point type in C++ is double
Tip: Use
doubleas the default for floating point unless you have a specific reason to usefloat(for example, saving memory for a large array).
Data Type Modifiers
Modifiers are used to change the range or size of a basic data type:
signed and unsigned
// signed (default for int) — can be positive and negative
int temperature = -15;
// unsigned — can only be positive (or zero)
unsigned int population = 1500000;
short and long
short int smallNumber = 32000;
long int bigNumber = 2000000000L;
long long int veryBigNumber = 9000000000000000000LL;
Modifier Combinations
unsigned short us; // Unsigned short integer
unsigned long ul; // Unsigned long integer
unsigned long long ull; // Unsigned long long integer
The sizeof Operator
The sizeof operator returns the size of a data type in bytes.
#include <iostream>
int main() {
std::cout << "Data type sizes on your system:" << std::endl;
std::cout << "char: " << sizeof(char) << " byte" << std::endl;
std::cout << "int: " << sizeof(int) << " byte" << std::endl;
std::cout << "float: " << sizeof(float) << " byte" << std::endl;
std::cout << "double: " << sizeof(double) << " byte" << std::endl;
std::cout << "bool: " << sizeof(bool) << " byte" << std::endl;
// sizeof on a variable
int data = 42;
std::cout << "data: " << sizeof(data) << " byte" << std::endl;
// sizeof on an array
int arr[10];
std::cout << "arr: " << sizeof(arr) << " byte" << std::endl;
std::cout << "element: " << sizeof(arr[0]) << " byte" << std::endl;
std::cout << "count: " << sizeof(arr) / sizeof(arr[0]) << std::endl;
return 0;
}
The auto Keyword (C++11)
The auto keyword allows the compiler to automatically deduce the data type from the given expression.
auto x = 42; // int
auto y = 3.14; // double
auto z = 'A'; // char
auto name = "John"; // const char*
auto valid = true; // bool
auto number = 42LL; // long long
// Very useful for long data types
#include <vector>
#include <map>
std::vector<int>::iterator it = vec.begin();
// Can be replaced with:
auto it = vec.begin();
std::map<std::string, int>::iterator iter = myMap.begin();
// Can be replaced with:
auto iter = myMap.begin();
// Auto with reference
int score = 10;
auto& ref = score; // ref is int&
// Auto with pointer
int* ptr = &score;
auto ptr2 = ptr; // ptr2 is int*
// Auto with const
const int constant = 5;
auto k = constant; // k is int (const is removed)
auto& kr = constant; // kr is const int& (const is preserved)
Note:
autocannot be used without initialization. The compiler needs an initial value to deduce the type.
auto x; // ❌ Error: no initialization
auto y = 10; // ✅ Correct
Data Type Limits (<limits>)
The <limits> header provides detailed information about the limits of each data type.
#include <iostream>
#include <limits>
int main() {
std::cout << "=== Data Type Limits ===" << std::endl;
std::cout << "char:" << std::endl;
std::cout << " min: " << (int)std::numeric_limits<char>::min() << std::endl;
std::cout << " max: " << (int)std::numeric_limits<char>::max() << std::endl;
std::cout << "\nint:" << std::endl;
std::cout << " min: " << std::numeric_limits<int>::min() << std::endl;
std::cout << " max: " << std::numeric_limits<int>::max() << std::endl;
std::cout << "\nunsigned int:" << std::endl;
std::cout << " min: " << std::numeric_limits<unsigned int>::min() << std::endl;
std::cout << " max: " << std::numeric_limits<unsigned int>::max() << std::endl;
std::cout << "\nlong long:" << std::endl;
std::cout << " min: " << std::numeric_limits<long long>::min() << std::endl;
std::cout << " max: " << std::numeric_limits<long long>::max() << std::endl;
std::cout << "\ndouble:" << std::endl;
std::cout << " min: " << std::numeric_limits<double>::min() << std::endl;
std::cout << " max: " << std::numeric_limits<double>::max() << std::endl;
std::cout << " epsilon: " << std::numeric_limits<double>::epsilon() << std::endl;
return 0;
}
Variable Scope
Scope determines where in a program a variable can be accessed.
Local Scope
A variable declared inside a function or code block can only be accessed within that function or block.
void function() {
int x = 10; // Local variable — can only be accessed here
std::cout << x << std::endl;
}
int main() {
function();
// std::cout << x; // ❌ Error: x is not recognized here
return 0;
}
Global Scope
A variable declared outside all functions can be accessed from anywhere in the file.
#include <iostream>
int global_var = 100; // Global variable
void display() {
std::cout << "From function: " << global_var << std::endl;
}
int main() {
display();
std::cout << "From main: " << global_var << std::endl;
global_var = 200; // Change the global value
std::cout << "After change: " << global_var << std::endl;
return 0;
}
Note: The use of global variables should be avoided as much as possible because it can make code difficult to maintain and lead to hard-to-track bugs.
Block Scope
A variable declared inside a { } block is only valid within that block.
#include <iostream>
int main() {
int x = 10;
if (x > 5) {
int y = 20; // Block variable — only valid inside the if
std::cout << "x = " << x << ", y = " << y << std::endl;
}
std::cout << "x = " << x << std::endl;
// std::cout << "y = " << y; // ❌ Error: y is not recognized
for (int i = 0; i < 5; i++) {
int temp = i * 2; // temp is only valid inside the loop
}
// std::cout << temp; // ❌ Error: temp is not recognized
return 0;
}
Shadowing (Variable Overriding)
When a local variable has the same name as a variable in a broader scope, the local variable "shadows" that variable within its own scope.
#include <iostream>
int x = 100; // Global variable
int main() {
std::cout << "Global: " << x << std::endl; // 100
int x = 50; // Shadows the global variable
std::cout << "Local: " << x << std::endl; // 50
if (true) {
int x = 10; // Shadows the local variable
std::cout << "Block: " << x << std::endl; // 10
}
std::cout << "Local again: " << x << std::endl; // 50
return 0;
}
Variable Initialization
C++ supports several ways to initialize a variable:
int a = 10; // Copy initialization (C-style)
int b(20); // Direct initialization
int c{30}; // Brace/Uniform initialization (C++11, recommended)
int d = {40}; // Copy list initialization
// Why is brace initialization recommended?
int x{3.14}; // ❌ Error: narrowing conversion not allowed
int y = {3.14}; // ❌ Error: narrowing conversion not allowed
int z = 3.14; // ⚠️ Compilation succeeds, but the data is truncated (3)
// Initialization with auto
auto a = 10; // int
auto b = 10.0; // double
Everyday Use Cases
Simple Program: Calculating an Employee's Salary
#include <iostream>
#include <string>
int main() {
std::string employeeName;
int workHours;
double ratePerHour;
double baseSalary;
double taxPercent;
double netSalary;
// Input
std::cout << "Employee name: ";
std::getline(std::cin, employeeName);
std::cout << "Work hours: ";
std::cin >> workHours;
std::cout << "Rate per hour ($): ";
std::cin >> ratePerHour;
std::cout << "Tax percentage: ";
std::cin >> taxPercent;
// Calculation
baseSalary = workHours * ratePerHour;
double tax = baseSalary * (taxPercent / 100.0);
netSalary = baseSalary - tax;
// Output
std::cout << "\n=== Pay Slip ===" << std::endl;
std::cout << "Name: " << employeeName << std::endl;
std::cout << "Work hours: " << workHours << " hours" << std::endl;
std::cout << "Rate/hour: $ " << ratePerHour << std::endl;
std::cout << "Base salary: $ " << baseSalary << std::endl;
std::cout << "Tax (" << taxPercent << "%): $ " << tax << std::endl;
std::cout << "Net salary: $ " << netSalary << std::endl;
return 0;
}
Conclusion
C++ provides various primitive data types (bool, char, int, float, double, void, wchar_t) with modifiers (signed, unsigned, short, long) to adjust size and value range. Variables must be declared with a data type before use, and the sizeof operator can be used to find the actual size on a particular system. The auto keyword makes declarations easier with automatic type inference. It is important to understand variable scope (local, global, block) and to avoid unintended shadowing. Use <limits> to see the value limits of each data type.