Chapter 02

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

  1. Write a program that declares variables of each fundamental type and prints their sizes.
  2. Create a program that reads two numbers and prints their sum, difference, product, quotient, and remainder.
  3. Write a temperature converter (Celsius to Fahrenheit and vice versa).
  4. Create a program using all comparison and logical operators.
  5. Write a program that demonstrates integer overflow.
  6. Use std::numeric_limits to print the ranges of all integer types.
  7. Create a program that uses formatted output with iomanip.
  8. Write a program using auto and decltype.
  9. Create constants using const, constexpr, and enum class.
  10. Experiment with different literal formats (binary, hex, with separators).