Variables & Data Types
Understanding how data is stored, typed, and manipulated in C++
What You Will Learn
- How to declare and initialize variables
- All fundamental data types in C++
- Type modifiers (signed, unsigned, short, long)
- How to write different types of literals
- Constants and the const/constexpr keywords
- Type inference with auto
- Implicit and explicit type conversion
- All C++ operators
- Reading input and writing output
Prerequisites
- Completed Chapter 01: Basics
- Ability to compile and run C++ programs
Variables
A variable is a named storage location in memory. It has a type (what kind of data it holds), a name (identifier), and a value.
Declaration
Declaring a variable reserves memory and gives it a name:
int age; // Declaration without initialization
double salary; // Declares a double
char grade; // Declares a character
Initialization
Initialization assigns an initial value. C++ offers several initialization syntaxes:
// Copy initialization
int a = 5;
// Direct initialization
int b(5);
// Uniform/Brace initialization (C++11, preferred)
int c{5};
int d = {5};
// Default initialization (value is undefined for fundamental types)
int e; // Contains garbage value!
Warning
Uninitialized variables contain garbage values. Always initialize your variables.
Brace Initialization Benefits
int x = 3.14; // Compiles! Silently truncates to 3
int y{3.14}; // Error: narrowing conversion not allowed
int arr[3]{1, 2, 3}; // Works for arrays too
std::vector<int> v{1, 2, 3}; // And containers
Brace initialization prevents accidental narrowing conversions and works consistently across all types.
Multiple Declarations
int x, y, z; // All are int
int a = 1, b = 2; // Both initialized
int *p, q; // p is pointer, q is int (confusing!)
int *p1, *p2; // Both are pointers
Variable Scope
Scope determines where a variable is accessible:
int global = 10; // Global scope (avoid if possible)
int main() {
int local = 20; // Local to main
{
int inner = 30; // Local to this block
// global, local, and inner are all accessible here
}
// inner is not accessible here
return 0;
}
// local is not accessible here
Fundamental Data Types
C++ has several built-in data types for different kinds of values.
Integer Types
| Type | Typical Size | Range (signed) |
|---|---|---|
short |
2 bytes | -32,768 to 32,767 |
int |
4 bytes | -2.1 billion to 2.1 billion |
long |
4-8 bytes | At least int range |
long long |
8 bytes | -9.2 quintillion to 9.2 quintillion |
short s = 100;
int i = 42;
long l = 1000000L;
long long ll = 9000000000000LL;
Fixed-Width Integers (C++11)
When you need guaranteed sizes, use <cstdint>:
#include <cstdint>
int8_t tiny = 127; // Exactly 8 bits
int16_t small = 32000; // Exactly 16 bits
int32_t medium = 100000; // Exactly 32 bits
int64_t large = 10000000000LL; // Exactly 64 bits
uint8_t utiny = 255; // Unsigned 8 bits
uint32_t umed = 4000000000U; // Unsigned 32 bits
Floating-Point Types
| Type | Size | Precision | Range |
|---|---|---|---|
float |
4 bytes | ~7 digits | ~3.4e-38 to ~3.4e38 |
double |
8 bytes | ~15 digits | ~1.7e-308 to ~1.7e308 |
long double |
8-16 bytes | ~18+ digits | Platform dependent |
float f = 3.14f; // Note the 'f' suffix
double d = 3.14159265; // Default for decimal literals
long double ld = 3.14159265358979L;
// Scientific notation
double sci = 1.5e10; // 1.5 x 10^10
double small = 2.5e-8; // 2.5 x 10^-8
Note
Prefer double over float for most applications. Modern CPUs handle double as fast as float, and you get more precision.
Character Types
char c = 'A'; // Single character (1 byte)
char newline = '\n'; // Escape sequence
char num = 65; // Same as 'A' (ASCII value)
wchar_t wc = L'A'; // Wide character (platform dependent)
char16_t c16 = u'A'; // UTF-16 character (C++11)
char32_t c32 = U'A'; // UTF-32 character (C++11)
char8_t c8 = u8'A'; // UTF-8 character (C++20)
Boolean Type
bool isValid = true;
bool isEmpty = false;
// Any non-zero value converts to true
bool b1 = 42; // true
bool b2 = 0; // false
bool b3 = -1; // true
Void Type
void represents the absence of a type. Used for:
void printMessage() { // Function returns nothing
std::cout << "Hello";
}
void* ptr; // Generic pointer (can point to any type)
Type Modifiers
Modifiers change the range or representation of a type.
signed and unsigned
signed int a = -10; // Can be negative (default)
unsigned int b = 10; // Only positive values
unsigned int u = 4294967295; // Max for 32-bit unsigned
// u + 1 would wrap around to 0 (overflow)
short and long
short int small = 100; // At least 16 bits
long int big = 100000L; // At least 32 bits
long long int huge = 10000000000LL; // At least 64 bits
// 'int' is optional
short s = 100;
long l = 100000L;
unsigned long ul = 100000UL;
Combining Modifiers
unsigned short us = 65535;
unsigned long long ull = 18446744073709551615ULL;
signed char sc = -128; // Explicitly signed
unsigned char uc = 255; // Often used for raw bytes
Literals
Literals are fixed values written directly in code.
Integer Literals
int decimal = 42; // Base 10
int octal = 052; // Base 8 (starts with 0)
int hex = 0x2A; // Base 16 (starts with 0x)
int binary = 0b101010; // Base 2 (C++14, starts with 0b)
// With digit separators (C++14)
int billion = 1'000'000'000;
int hexBytes = 0xFF'FF'FF'FF;
Floating-Point Literals
double d1 = 3.14;
double d2 = 3.14e10; // Scientific notation
double d3 = .5; // 0.5
double d4 = 5.; // 5.0
float f = 3.14f; // 'f' suffix for float
long double ld = 3.14L; // 'L' suffix for long double
// Hexadecimal float (C++17)
double hex_float = 0x1.2p3; // 1.125 * 2^3 = 9.0
Character Literals
char c1 = 'A';
char c2 = '\n'; // Newline
char c3 = '\t'; // Tab
char c4 = '\\'; // Backslash
char c5 = '\''; // Single quote
char c6 = '\0'; // Null character
char c7 = '\x41'; // Hex: 'A'
char c8 = '\101'; // Octal: 'A'
String Literals
const char* s1 = "Hello"; // C-style string
std::string s2 = "Hello"; // C++ string
// Raw string literals (C++11)
const char* path = R"(C:\Users\Name)"; // No escaping needed
const char* regex = R"(\d{3}-\d{4})";
// Multi-line raw string
const char* multi = R"(
Line 1
Line 2
Line 3
)";
Boolean Literals
bool t = true;
bool f = false;
Pointer Literal
int* ptr = nullptr; // C++11, preferred over NULL or 0
User-Defined Literals (C++11)
// Standard library examples
using namespace std::chrono_literals;
auto duration = 5s; // 5 seconds
auto ms = 100ms; // 100 milliseconds
using namespace std::string_literals;
auto str = "Hello"s; // std::string, not const char*
Constants
Constants are values that cannot be changed after initialization.
const
const int MAX_SIZE = 100;
const double PI = 3.14159265358979;
// Must be initialized
const int x; // Error: uninitialized const
// Can be initialized at runtime
const int size = getUserInput(); // OK
constexpr (C++11)
constexpr indicates that a value can be computed at compile time:
constexpr int SIZE = 10;
constexpr double PI = 3.14159;
// constexpr functions
constexpr int square(int x) {
return x * x;
}
constexpr int hundred = square(10); // Computed at compile time
// Can be used where compile-time constants are required
int arr[SIZE]; // OK with constexpr
std::array<int, square(5)> arr2; // Also OK
const vs constexpr
| Feature | const | constexpr |
|---|---|---|
| Compile-time evaluation | Not guaranteed | Guaranteed (if possible) |
| Runtime initialization | Allowed | Not allowed |
| Array sizes | Sometimes | Always |
| Template arguments | No | Yes |
#define Macros (Avoid)
// Old C-style, avoid in modern C++
#define MAX 100
#define PI 3.14159
// Problems: no type safety, no scope, hard to debug
// Use const or constexpr instead
enum and enum class
// C-style enum (values leak into enclosing scope)
enum Color { RED, GREEN, BLUE };
int x = RED; // OK, but maybe unintended
// C++11 enum class (scoped and type-safe)
enum class Status { OK, ERROR, PENDING };
Status s = Status::OK;
// With underlying type
enum class Size : uint8_t {
SMALL = 1,
MEDIUM = 2,
LARGE = 3
};
Auto Keyword (C++11)
auto lets the compiler deduce the variable's type from its initializer.
auto x = 42; // int
auto y = 3.14; // double
auto z = "Hello"; // const char*
auto s = std::string("Hello"); // std::string
// Useful with complex types
auto it = myVector.begin(); // std::vector<int>::iterator
// With modifiers
const auto& ref = x; // const int&
auto* ptr = &x; // int*
When to Use auto
// Good: complex iterator types
auto it = std::find(v.begin(), v.end(), target);
// Good: lambda expressions
auto lambda = [](int x) { return x * 2; };
// Good: avoiding repetition
auto p = std::make_unique<MyLongClassName>();
// Bad: when type isn't obvious
auto result = process(); // What type is this?
// Better: explicit when it aids readability
int count = getCount();
decltype
decltype gives you the type of an expression:
int x = 10;
decltype(x) y = 20; // y is int
// Useful for return type deduction
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
Type Conversion
Converting values from one type to another.
Implicit Conversion (Coercion)
The compiler automatically converts types when needed:
int i = 42;
double d = i; // int to double (safe, no data loss)
double pi = 3.14;
int truncated = pi; // double to int (loses .14)
char c = 'A';
int ascii = c; // char to int (65)
bool b = 42; // int to bool (true)
Promotion Rules
When mixing types in expressions, smaller types are "promoted":
int i = 10;
double d = 3.5;
auto result = i + d; // i promoted to double, result is double
short s = 5;
int sum = s + 10; // s promoted to int
Explicit Conversion (Casting)
// C-style cast (avoid)
double pi = 3.14;
int i = (int)pi;
// C++ function-style cast
int j = int(pi);
// C++ named casts (preferred)
int k = static_cast<int>(pi); // Safe conversions
C++ Cast Operators
| Cast | Purpose |
|---|---|
static_cast |
Safe, compile-time checked conversions |
dynamic_cast |
Safe downcasting in class hierarchies |
const_cast |
Add/remove const qualifier |
reinterpret_cast |
Low-level bit reinterpretation |
double d = 3.99;
int i = static_cast<int>(d); // 3
// const_cast example
const int x = 10;
int* p = const_cast<int*>(&x); // Remove const (dangerous!)
Operators
Operators perform operations on values.
Arithmetic Operators
int a = 10, b = 3;
int sum = a + b; // 13
int diff = a - b; // 7
int prod = a * b; // 30
int quot = a / b; // 3 (integer division)
int rem = a % b; // 1 (modulo)
double x = 10.0, y = 3.0;
double div = x / y; // 3.333...
Increment and Decrement
int i = 5;
i++; // Post-increment: use then increment
++i; // Pre-increment: increment then use
i--; // Post-decrement
--i; // Pre-decrement
int a = 5;
int b = a++; // b = 5, a = 6
int c = ++a; // a = 7, c = 7
Comparison Operators
int a = 5, b = 10;
bool eq = (a == b); // false
bool neq = (a != b); // true
bool lt = (a < b); // true
bool gt = (a > b); // false
bool le = (a <= b); // true
bool ge = (a >= b); // false
// Three-way comparison (C++20)
auto cmp = (a <=> b); // Returns ordering
Logical Operators
bool a = true, b = false;
bool and_result = a && b; // false (AND)
bool or_result = a || b; // true (OR)
bool not_result = !a; // false (NOT)
// Short-circuit evaluation
if (ptr != nullptr && ptr->isValid()) {
// ptr->isValid() only called if ptr != nullptr
}
Bitwise Operators
int a = 5; // 0101 in binary
int b = 3; // 0011 in binary
int and_r = a & b; // 0001 = 1 (AND)
int or_r = a | b; // 0111 = 7 (OR)
int xor_r = a ^ b; // 0110 = 6 (XOR)
int not_r = ~a; // Invert all bits
int left = a << 2; // 010100 = 20 (left shift)
int right = a >> 1; // 0010 = 2 (right shift)
Assignment Operators
int x = 10;
x += 5; // x = x + 5 = 15
x -= 3; // x = x - 3 = 12
x *= 2; // x = x * 2 = 24
x /= 4; // x = x / 4 = 6
x %= 4; // x = x % 4 = 2
x &= 1; // x = x & 1
x |= 2; // x = x | 2
x ^= 3; // x = x ^ 3
x <<= 1; // x = x << 1
x >>= 1; // x = x >> 1
Ternary Operator
int a = 10, b = 20;
int max = (a > b) ? a : b; // 20
// Equivalent to:
int max2;
if (a > b) {
max2 = a;
} else {
max2 = b;
}
Comma Operator
int a = (1, 2, 3); // a = 3 (evaluates all, returns last)
// Common in for loops
for (int i = 0, j = 10; i < j; i++, j--) {
// ...
}
Input/Output
C++ uses streams for I/O. The <iostream> header provides cout, cin, and cerr.
Output with cout
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
int age = 25;
std::cout << "Age: " << age << "\n";
// Chaining
std::cout << "A" << " " << "B" << " " << "C\n";
return 0;
}
Input with cin
#include <iostream>
#include <string>
int main() {
int age;
std::cout << "Enter your age: ";
std::cin >> age;
std::string name;
std::cout << "Enter your name: ";
std::cin >> name; // Reads until whitespace
// For full lines including spaces
std::string fullName;
std::cin.ignore(); // Clear newline from previous input
std::getline(std::cin, fullName);
return 0;
}
Error Output with cerr
std::cerr << "Error: File not found!" << std::endl;
Formatting Output
#include <iostream>
#include <iomanip>
double pi = 3.14159265358979;
// Fixed decimal places
std::cout << std::fixed << std::setprecision(2);
std::cout << pi << "\n"; // 3.14
// Field width
std::cout << std::setw(10) << 42 << "\n"; // " 42"
// Fill character
std::cout << std::setfill('0') << std::setw(5) << 42; // "00042"
// Hexadecimal
std::cout << std::hex << 255; // ff
// Boolean as text
std::cout << std::boolalpha << true; // "true"
sizeof Operator
The sizeof operator returns the size of a type or variable in bytes.
std::cout << sizeof(char) << "\n"; // 1
std::cout << sizeof(int) << "\n"; // Typically 4
std::cout << sizeof(double) << "\n"; // Typically 8
std::cout << sizeof(long long) << "\n"; // Typically 8
int arr[10];
std::cout << sizeof(arr) << "\n"; // 40 (10 * 4)
std::cout << sizeof(arr) / sizeof(arr[0]); // 10 (array size)
int* ptr;
std::cout << sizeof(ptr) << "\n"; // 4 or 8 (platform dependent)
Checking Limits
#include <limits>
std::cout << std::numeric_limits<int>::min() << "\n"; // -2147483648
std::cout << std::numeric_limits<int>::max() << "\n"; // 2147483647
std::cout << std::numeric_limits<double>::epsilon(); // Smallest diff
Common Mistakes
Integer Division
int a = 5, b = 2;
double result = a / b; // 2.0, not 2.5!
Solution: Cast at least one operand to double: double result = static_cast<double>(a) / b;
Integer Overflow
int big = 2147483647;
big++; // Undefined behavior! Wraps to -2147483648
Solution: Use a larger type or check before operations.
Floating-Point Comparison
double a = 0.1 + 0.2;
if (a == 0.3) { } // May be false due to precision!
Solution: Compare with tolerance: if (std::abs(a - 0.3) < 0.0001)
Using = Instead of ==
int x = 5;
if (x = 10) { } // Assignment, always true! Should be x == 10
Uninitialized Variables
int x;
std::cout << x; // Undefined behavior! Garbage value
Solution: Always initialize variables.
Practice Questions
- Write a program that declares variables of each fundamental type and prints their sizes.
- Create a program that reads two numbers and prints their sum, difference, product, quotient, and remainder.
- Write a temperature converter (Celsius to Fahrenheit and vice versa).
- Create a program using all comparison and logical operators.
- Write a program that demonstrates integer overflow.
- Use
std::numeric_limitsto print the ranges of all integer types. - Create a program that uses formatted output with
iomanip. - Write a program using
autoanddecltype. - Create constants using
const,constexpr, andenum class. - Experiment with different literal formats (binary, hex, with separators).