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:
| Type | Size (bytes) | Range / Values | Use Case |
|---|---|---|---|
bool | 1 | true or false | Boolean flags |
char | 1 | -128 to 127 (signed) | Single ASCII character |
unsigned char | 1 | 0 to 255 | Raw byte, pixel values |
short | 2 | -32,768 to 32,767 | Small integers |
int | 4 | approx -2.1B to 2.1B | General integer work |
unsigned int | 4 | 0 to approx 4.3B | Non-negative counts |
long | 4 or 8 | Platform-dependent | Varies |
long long | 8 | approx -9.2×10^18 to 9.2×10^18 | Large integers, competitive programming |
unsigned long long | 8 | 0 to approx 1.8×10^19 | Very large non-negative integers |
float | 4 | ~6–7 significant digits | Approximate decimal |
double | 8 | ~15–16 significant digits | Precise decimal (default for floating-point) |
long double | 10 or 16 | ~18–19 significant digits | High-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
double—floatloses precision too quickly. - For characters, use
char; for strings, usestd::string(covered in a later chapter). - For true/false flags, use
bool— do not useintwith 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
const | constexpr | |
|---|---|---|
| Value cannot change | Yes | Yes |
| Evaluated at compile time | Not guaranteed | Guaranteed |
| Can be used for array sizes | Only if initialiser is constexpr | Yes |
| Can apply to functions | No (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:
| Operator | Meaning | Example (a=5, b=3) |
|---|---|---|
== | Equal | a == b → false |
!= | Not equal | a != b → true |
< | Less than | a < b → false |
> | Greater than | a > b → true |
<= | Less than or equal | a <= b → false |
>= | Greater than or equal | a >= b → true |
Logical Operators
| Operator | Meaning | Example |
|---|---|---|
&& | 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:
| Operator | Meaning | Example (a=6=0110, b=3=0011) |
|---|---|---|
& | Bitwise AND | a & b = 0010 = 2 |
| | Bitwise OR | a | b = 0111 = 7 |
^ | Bitwise XOR | a ^ b = 0101 = 5 |
~ | Bitwise NOT | ~a = ...11111001 = -7 (2's complement) |
<< | Left shift | a << 1 = 1100 = 12 (multiply by 2) |
>> | Right shift | a >> 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):
| Precedence | Operators | Associativity |
|---|---|---|
| 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: int → long → long long → float → double → long 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:
| Cast | Use Case |
|---|---|
static_cast | Safe, compile-time-checked conversions between related types |
dynamic_cast | Downcasting in class hierarchies (requires virtual functions) |
const_cast | Remove or add const qualifier |
reinterpret_cast | Reinterpret 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:
constexprfor compile-time constantsMAX_MARKSandSUBJECTS.static_cast<double>to avoid integer division when computing the percentage.std::stringfor 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
-
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.
-
Declare a
long longvariable and assign it the value of 10^18 (one quintillion). Print it. Now try storing the same value in anintand observe what happens. -
Write a program to convert temperature from Celsius to Fahrenheit using the formula
F = (C * 9.0 / 5.0) + 32. Make sure you usedoublefor the result. -
Compute the area and perimeter of a rectangle. Use
constexpr double PI = 3.14159265358979and compute the area of a circle with the same perimeter (i.e., circumference equals the rectangle's perimeter). -
Using only bitwise operators, write expressions to: (a) check if an integer
nis even; (b) set the k-th bit ofn; (c) clear the k-th bit ofn. Verify with several test values. -
Extend the student grade calculator to accept marks via
std::cinfor 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
sizeofto inspect the byte size of any type at compile time. autodeduces the type from the initialiser — saves typing, preserves compile-time type safety.constprevents modification;constexpradditionally guarantees compile-time evaluation.- Arithmetic operators include
+,-,*,/,%— watch out for integer division truncation. - Relational operators (
==,!=,<,>,<=,>=) returnbool. - 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_castover C-style casts for explicit, safe numeric conversions. - Always initialise variables at declaration to avoid undefined behaviour.