Chapter 3 of 23

Variables, Data Types and Operators

Master C++ fundamental types, the auto keyword, constants, all operator categories, type casting, and build a working student grade calculator.

Meritshot15 min read
C++VariablesData TypesOperatorsType CastingConstants
All C++ Chapters

Variables, Data Types and Operators

Every C++ program manipulates data. Before the processor can do anything useful — add two numbers, compare strings, decide a branch — it needs somewhere to store that data. That storage is a variable, and every variable has a type that tells the compiler how many bytes to reserve and how to interpret those bytes. Operators are the symbols that act on variables to produce new values.

Understanding types deeply is what separates a programmer who writes code that "seems to work" from one whose code is correct, efficient, and portable. This chapter covers everything you need.


Variables: Declaration and Initialisation

A variable declaration in C++ has the form:

type variable_name = initial_value;

For example:

int age = 21;
double salary = 85000.50;
char grade = 'A';
bool isPlaced = true;

Declaration vs Initialisation

Declaration tells the compiler the variable's name and type. Initialisation gives it a first value. In C++, you can declare without initialising, but reading an uninitialised variable produces undefined behaviour — one of the most common bugs in C++ programs.

int x;        // declared but NOT initialised — value is garbage
int y = 10;   // declared AND initialised — always prefer this
int z{15};    // uniform (brace) initialisation — C++11, preferred in modern C++

Brace initialisation {} has a bonus: it prevents narrowing conversions (e.g., fitting a double into an int silently) and signals intent clearly.


Fundamental Data Types

C++ provides a set of built-in types. Their exact sizes can vary by platform, but on virtually every modern 64-bit system you will encounter:

TypeSize (bytes)Range / ValuesUse Case
bool1true or falseBoolean flags
char1-128 to 127 (signed)Single ASCII character
unsigned char10 to 255Raw byte, pixel values
short2-32,768 to 32,767Small integers
int4approx -2.1B to 2.1BGeneral integer work
unsigned int40 to approx 4.3BNon-negative counts
long4 or 8Platform-dependentVaries
long long8approx -9.2×10^18 to 9.2×10^18Large integers, competitive programming
unsigned long long80 to approx 1.8×10^19Very large non-negative integers
float4~6–7 significant digitsApproximate decimal
double8~15–16 significant digitsPrecise decimal (default for floating-point)
long double10 or 16~18–19 significant digitsHigh-precision scientific computing

Checking Sizes with sizeof

The sizeof operator returns the size of a type or variable in bytes:

#include <iostream>

int main() {
    std::cout << "bool:        " << sizeof(bool)        << " byte(s)\n";
    std::cout << "char:        " << sizeof(char)        << " byte(s)\n";
    std::cout << "int:         " << sizeof(int)         << " byte(s)\n";
    std::cout << "long long:   " << sizeof(long long)   << " byte(s)\n";
    std::cout << "float:       " << sizeof(float)       << " byte(s)\n";
    std::cout << "double:      " << sizeof(double)      << " byte(s)\n";
    return 0;
}

sizeof is evaluated at compile time, so there is zero runtime cost.

When to Use Which Type

  • For general integer work, use int.
  • For competitive programming where numbers can exceed 2 billion (e.g., sums over 10^9), use long long.
  • For decimal values, use doublefloat loses precision too quickly.
  • For characters, use char; for strings, use std::string (covered in a later chapter).
  • For true/false flags, use bool — do not use int with 0/1 as Python habits might suggest.

The auto Keyword

C++11 introduced auto, which lets the compiler deduce the type of a variable from its initialiser:

auto x = 42;          // deduced as int
auto pi = 3.14159;    // deduced as double
auto flag = true;     // deduced as bool
auto name = std::string("Priya");  // deduced as std::string

auto is not dynamic typing — the type is fixed at compile time. It simply saves you from typing long type names, which becomes valuable with complex STL types:

// Without auto:
std::vector<std::pair<int, std::string>>::iterator it = v.begin();

// With auto (identical type, just shorter):
auto it = v.begin();

Caution: Do not over-use auto at the expense of readability. When the type is not obvious from the right-hand side, be explicit. Use auto when the type is long or when you are working with iterators and template return types.


Constants: const and constexpr

const

A const variable cannot be modified after initialisation:

const double PI = 3.14159265358979;
const int MAX_STUDENTS = 60;

Attempting to assign a new value to a const variable causes a compile-time error — exactly the kind of safety net you want. Use const for values that should never change: physical constants, configuration limits, lookup table entries.

constexpr

constexpr (C++11) goes one step further: it guarantees the value is computed at compile time, not at runtime. This means it can be used in contexts that require compile-time constants (like array sizes):

constexpr int BOARD_SIZE = 8;
int chessboard[BOARD_SIZE][BOARD_SIZE];  // valid because BOARD_SIZE is constexpr

constexpr double circleArea(double r) {
    return 3.14159265 * r * r;
}
constexpr double area = circleArea(5.0);  // computed at compile time
constconstexpr
Value cannot changeYesYes
Evaluated at compile timeNot guaranteedGuaranteed
Can be used for array sizesOnly if initialiser is constexprYes
Can apply to functionsNo (use constexpr function)Yes

Rule of thumb: Prefer constexpr for truly constant values known at compile time; use const for runtime values that should not be modified.


Operators

Arithmetic Operators

int a = 17, b = 5;

int sum  = a + b;   // 22
int diff = a - b;   // 12
int prod = a * b;   // 85
int quot = a / b;   // 3  (integer division — truncates toward zero)
int rem  = a % b;   // 2  (modulo — remainder)

double x = 17.0, y = 5.0;
double fdiv = x / y;  // 3.4  (floating-point division)

Integer division truncates — 17 / 5 is 3, not 3.4. This catches many beginners off guard. If you need the decimal result, ensure at least one operand is double or cast it: (double)a / b.

The modulo operator % is indispensable in competitive programming for wrapping indices, checking divisibility, and working in modular arithmetic (e.g., answers modulo 10^9 + 7).

Increment and Decrement

int n = 5;
n++;   // post-increment: use n (5), then increment to 6
++n;   // pre-increment: increment to 7, then use 7
n--;   // post-decrement: use n (7), then decrement to 6
--n;   // pre-decrement: decrement to 5, then use 5

In isolation, n++ and ++n have the same effect on n. The difference matters when used inside an expression: int m = n++ gives m the old value, while int m = ++n gives m the new value.

Relational Operators

These return bool:

OperatorMeaningExample (a=5, b=3)
==Equala == bfalse
!=Not equala != btrue
<Less thana < bfalse
>Greater thana > btrue
<=Less than or equala <= bfalse
>=Greater than or equala >= btrue

Logical Operators

OperatorMeaningExample
&&Logical AND (both true)(a > 0) && (b > 0)true
||Logical OR (at least one true)(a > 10) || (b > 2)true
!Logical NOT!(a == b)true

C++ uses short-circuit evaluation: in A && B, if A is false, B is never evaluated. In A || B, if A is true, B is never evaluated. This is important for safety checks like ptr != nullptr && ptr->value > 0.

Bitwise Operators

Essential in competitive programming, embedded systems, and optimisation:

OperatorMeaningExample (a=6=0110, b=3=0011)
&Bitwise ANDa & b = 0010 = 2
|Bitwise ORa | b = 0111 = 7
^Bitwise XORa ^ b = 0101 = 5
~Bitwise NOT~a = ...11111001 = -7 (2's complement)
<<Left shifta << 1 = 1100 = 12 (multiply by 2)
>>Right shifta >> 1 = 0011 = 3 (divide by 2)

Assignment Operators

Compound assignment operators combine an operation with assignment:

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 = 4
x >>= 1;  // x = x >> 1 = 2

Operator Precedence

When an expression contains multiple operators, precedence determines the order of evaluation (higher precedence = evaluated first):

PrecedenceOperatorsAssociativity
1 (highest)() [] . -> ++ -- (postfix)Left-to-right
2++ -- (prefix) ! ~ + - (unary)Right-to-left
3* / %Left-to-right
4+ -Left-to-right
5<< >>Left-to-right
6< <= > >=Left-to-right
7== !=Left-to-right
8&Left-to-right
9^Left-to-right
10|Left-to-right
11&&Left-to-right
12||Left-to-right
13?: (ternary)Right-to-left
14 (lowest)= += -= etc.Right-to-left

Advice: When in doubt, use parentheses. (a + b) * c is always clearer than relying on precedence rules, and parentheses have zero runtime cost.


Type Casting

Implicit (Automatic) Conversion

C++ automatically converts between compatible types in expressions:

int i = 7;
double d = i;      // i is promoted to double: d = 7.0
double result = i / 2;   // DANGER: integer division happens first → result = 3.0
double correct = (double)i / 2;  // now result = 3.5

The rule is: in a mixed arithmetic expression, the "smaller" type is promoted to the "larger" type. The hierarchy is roughly: intlonglong longfloatdoublelong double.

Explicit Casting

C-style cast (avoid in modern C++):

double x = 9.7;
int n = (int)x;  // n = 9, truncates — does not round

C++ named casts (preferred):

double x = 9.7;
int n = static_cast<int>(x);  // n = 9, same effect, but explicit and searchable

The four C++ named casts are:

CastUse Case
static_castSafe, compile-time-checked conversions between related types
dynamic_castDowncasting in class hierarchies (requires virtual functions)
const_castRemove or add const qualifier
reinterpret_castReinterpret the raw bytes of one type as another (use with caution)

For all the numeric casting you will do day-to-day, static_cast is the right tool.


Worked Example: Student Percentage and Letter Grade

Let us put types, operators, constants, and casting together in a realistic program. Imagine a student at a Delhi University college with marks in five subjects:

#include <iostream>
#include <string>

int main() {
    // Student marks out of 100 in five subjects
    int maths    = 88;
    int physics  = 74;
    int chemistry = 81;
    int english  = 92;
    int hindi    = 67;

    constexpr int MAX_MARKS = 500;
    constexpr int SUBJECTS  = 5;

    int totalMarks = maths + physics + chemistry + english + hindi;

    // Cast to double before dividing to get a decimal percentage
    double percentage = (static_cast<double>(totalMarks) / MAX_MARKS) * 100.0;

    // Determine letter grade
    std::string letterGrade;
    if (percentage >= 90.0) {
        letterGrade = "O (Outstanding)";
    } else if (percentage >= 75.0) {
        letterGrade = "A+ (Excellent)";
    } else if (percentage >= 60.0) {
        letterGrade = "A (Very Good)";
    } else if (percentage >= 50.0) {
        letterGrade = "B (Good)";
    } else if (percentage >= 40.0) {
        letterGrade = "C (Average)";
    } else {
        letterGrade = "F (Fail)";
    }

    std::cout << "Total Marks    : " << totalMarks << " / " << MAX_MARKS << "\n";
    std::cout << "Percentage     : " << percentage << "%\n";
    std::cout << "Letter Grade   : " << letterGrade << "\n";

    return 0;
}

Output:

Total Marks    : 402 / 500
Percentage     : 80.4%
Letter Grade   : A+ (Excellent)

Notice:

  • constexpr for compile-time constants MAX_MARKS and SUBJECTS.
  • static_cast<double> to avoid integer division when computing the percentage.
  • std::string for the grade label (a future chapter covers strings in depth).

Common Pitfalls

Integer overflow. int holds up to roughly 2.1 billion. If you sum 10^5 numbers each up to 10^6, the total can reach 10^11 — far beyond int. Use long long and declare literals with LL: long long x = 3000000000LL;.

Integer division surprises. 5 / 2 is 2, not 2.5. Always check whether both operands are integers before dividing, and cast if needed.

Floating-point equality. Never compare double values with ==. Due to rounding errors, 0.1 + 0.2 == 0.3 is false in most floating-point implementations. Instead, check abs(a - b) < 1e-9 (epsilon comparison).

Uninitialised variables. Reading an uninitialised variable is undefined behaviour — the value is garbage from whatever was previously in that memory location. Always initialise on declaration.

Signed vs unsigned mismatch. Comparing a signed int with an unsigned int can yield surprising results because the signed value is implicitly converted to unsigned. Use -Wsign-compare (included in -Wall) to detect this.

char arithmetic. char may be signed or unsigned depending on the platform. If you do arithmetic on character codes, cast to unsigned char or int first to avoid sign-extension issues.


Practice Exercises

  1. Write a program that reads two integers from the user and prints their sum, difference, product, quotient, and remainder. Handle the case where the second number is zero.

  2. Declare a long long variable and assign it the value of 10^18 (one quintillion). Print it. Now try storing the same value in an int and observe what happens.

  3. Write a program to convert temperature from Celsius to Fahrenheit using the formula F = (C * 9.0 / 5.0) + 32. Make sure you use double for the result.

  4. Compute the area and perimeter of a rectangle. Use constexpr double PI = 3.14159265358979 and compute the area of a circle with the same perimeter (i.e., circumference equals the rectangle's perimeter).

  5. Using only bitwise operators, write expressions to: (a) check if an integer n is even; (b) set the k-th bit of n; (c) clear the k-th bit of n. Verify with several test values.

  6. Extend the student grade calculator to accept marks via std::cin for all five subjects. Add a check that each mark is between 0 and 100 and print an error message if not.


Summary

  • C++ has rich fundamental types: bool, char, int, long long, float, double, and their variants.
  • Use sizeof to inspect the byte size of any type at compile time.
  • auto deduces the type from the initialiser — saves typing, preserves compile-time type safety.
  • const prevents modification; constexpr additionally guarantees compile-time evaluation.
  • Arithmetic operators include +, -, *, /, % — watch out for integer division truncation.
  • Relational operators (==, !=, <, >, <=, >=) return bool.
  • Logical operators &&, ||, ! use short-circuit evaluation.
  • Bitwise operators (&, |, ^, ~, <<, >>) are essential for competitive programming and embedded work.
  • Use parentheses liberally to make operator precedence explicit and code readable.
  • Prefer static_cast over C-style casts for explicit, safe numeric conversions.
  • Always initialise variables at declaration to avoid undefined behaviour.