C++ Variables: Declare, Initialize and Use
A variable is a named piece of memory that holds a value your program can read and change. A student's marks, a product's price and a user's name are all stored in variables. In C++ every variable has a name, a type that decides what it can store and how much memory it uses, and a value.
This lesson covers how to declare and initialise variables (including the modern brace style), the naming rules, scope, the danger of uninitialised variables, and constants with const, constexpr and auto. All outputs are from real runs with g++ 14.2. The types themselves are covered in the next lesson.
Picture a variable as a labelled box in memory. The label is the name, the size and shape of the box is the type (an int box holds whole numbers, a double box holds decimals), and whatever is inside is the current value.
Declaring and initialising variables
The basic form is type name; to declare, or type name = value; to declare and give a starting value in one step:
#include <iostream>
#include <string>
int main()
{
// 1. Declaration, then assignment
int marks;
marks = 85;
// 2. Declaration with initialization: three styles
int a = 10; // copy initialization
int b(20); // direct initialization
int c{30}; // brace (uniform) initialization - recommended
// 3. Several variables of the same type
int x = 1, y = 2, z = 3;
// 4. Changing a value
marks = marks + 5;
std::string student = "Ravi";
double percentage = 91.5;
char grade = 'A';
bool passed = true;
std::cout << "marks = " << marks << '\n';
std::cout << "a = " << a << ", b = " << b << ", c = " << c << '\n';
std::cout << "x + y + z = " << x + y + z << '\n';
std::cout << student << " scored " << percentage << "% grade " << grade << '\n';
std::cout << "passed = " << passed << '\n';
std::cout << std::boolalpha << "passed = " << passed << '\n';
return 0;
}
Try it yourself: edit and run the same program. Change marks, add your own variables, or try int c{30.5}; to see the narrowing error.
Notes on the output:
marks = marks + 5reads the old value (85), adds 5 and stores 90. In C++=means assignment, not mathematical equality (equality is==).- A
boolprints as1or0by default;std::boolalphamakes it printtrue/false. - Declaring several variables on one line works, but one per line is usually clearer.
Declaration vs definition vs initialisation vs assignment
| Term | Example | Meaning |
|---|---|---|
| Declaration | extern int count; | Tells the compiler a name and its type exist |
| Definition | int count; | Declares and reserves memory. Most local declarations are definitions |
| Initialisation | int count = 0; | Gives the first value at the moment the variable is created |
| Assignment | count = 5; | Replaces the value of an existing variable |
Three ways to initialise, and why braces are safest
int a = 10;, int b(20); and int c{30}; all produce the same result for simple values. The difference shows up when the value doesn't fit the type. Copy initialisation silently loses data:
int copyInit = 9.99; // compiles: the decimal part is silently dropped
(g++ warns about this only if you add -Wconversion: warning: conversion from 'double' to 'int' changes value from '9.9900000000000002e+0' to '9'.) Brace initialisation refuses to compile it:
int braceInit{9.99}; // error: narrowing conversion
A narrowing conversion loses information, like double to int or a large number to a small type. Braces turn that silent bug into a compile error, which is why brace initialisation (introduced in C++11) is recommended in modern C++. Notice also that 9.99 is really stored as 9.9900000000000002: more on that in data types.
Always initialise: uninitialised variables
int count; // declared but never given a value
int total = 0; // always initialise
int items{}; // empty braces: value-initialised to 0
std::cout << "total = " << total << '\n';
std::cout << "items = " << items << '\n';
std::cout << "count = " << count << '\n'; // undefined behaviour!
g++ warns (with -Wall):
Running the same executable twice gave:
And compiled with optimisation (-O2), count printed 0. Three runs, three different values, from the same source code. A local variable without an initialiser holds whatever bits were already in that memory, often called a "garbage value". Reading it is undefined behaviour: the program may print rubbish, appear to work, or behave differently in release builds. Unlike C# or Java, C++ doesn't zero local variables for you and doesn't stop you from using them. Always initialise, and {} is an easy way to get zero.
Global variables and static variables are automatically zero-initialised. Only local (function) variables of built-in types start with an indeterminate value.
Naming rules (identifiers)
Rules (the compiler enforces these):
- Use letters (
a-z,A-Z), digits (0-9) and underscore (_) only. No spaces or symbols like-,$or@. - The first character can't be a digit.
- A name can't be a keyword (
int,class,return,new,double...). - Names are case sensitive:
totalandTotalare different variables.
int 2ndPlace = 2; // starts with a digit
int total-marks = 90; // '-' is the minus operator
The messages don't say "bad name", so recognise the pattern: an error right at a variable name usually means an illegal identifier. Using a keyword is just as confusing: int class = 10; produced error: expected primary-expression before 'int'.
Conventions (not enforced, but expected):
- Use meaningful names:
totalMarks, nottmorx1. - Pick one style and stick to it:
camelCase(studentName) orsnake_case(student_name, used by the standard library). - Constants are often
UPPER_CASE(MAX_USERS) orkMaxUsers. - Avoid names starting with an underscore followed by a capital letter, or containing a double underscore: they're reserved for the compiler and library.
Scope: where a variable can be used
#include <iostream>
int counter = 100; // global variable: visible in the whole file
int main()
{
int counter = 5; // local variable with the same name hides the global one
std::cout << "local counter = " << counter << '\n';
std::cout << "global counter = " << ::counter << '\n';
{
int blockValue = 42; // exists only inside these braces
std::cout << "blockValue = " << blockValue << '\n';
}
// blockValue is not available here
return 0;
}
- Local variables are declared inside a block
{ }and live only until its closing brace. - Global variables are declared outside all functions and are visible everywhere in the file. Use them sparingly: any code can change them, which makes bugs hard to find.
- When a local name shadows a global one,
::counter(scope resolution with nothing before it) reaches the global. Compiling with-Wshadowwarns about this:declaration of 'counter' shadows a global declaration.
Using a block variable after its block ends is an error:
Constants: const and constexpr
#include <iostream>
constexpr double PI = 3.14159265358979; // known at compile time
constexpr int DAYS_IN_WEEK = 7;
int main()
{
const double radius = 2.5; // cannot change after initialisation
const double area = PI * radius * radius;
auto weeks = 3; // auto deduces int
auto price = 499.99; // auto deduces double
auto initial = 'S'; // auto deduces char
std::cout << "Area of circle: " << area << '\n';
std::cout << "Days in " << weeks << " weeks: " << weeks * DAYS_IN_WEEK << '\n';
std::cout << "price = " << price << ", initial = " << initial << '\n';
return 0;
}
A const variable must be initialised and can never be changed. Both mistakes are caught at compile time:
const int maxUsers = 50;
maxUsers = 60; // trying to change a constant
const int limit; // constant without a value
| const | constexpr | |
|---|---|---|
| Meaning | Read-only after initialisation | A constant whose value is computed at compile time |
| Value known | At compile time or at run time (for example from user input) | Must be known at compile time |
| Example | const double area = PI * r * r; with r read from the user | constexpr int DAYS_IN_WEEK = 7; |
| Use for | Values that shouldn't change once set | Fixed values like limits and mathematical constants; usable as array sizes |
Prefer named constants to "magic numbers": weeks * DAYS_IN_WEEK explains itself, weeks * 7 doesn't. Older code uses #define PI 3.14. Avoid it in C++, because a macro has no type and no scope.
auto: let the compiler deduce the type
With auto the compiler picks the type from the initialiser: 3 gives int, 499.99 gives double and 'S' gives char. The type is still fixed at compile time: auto isn't a "dynamic" or "var-anything" type, and an auto variable must be initialised. It's most useful with long type names you'll meet later (iterators, lambdas). For simple numbers, writing the type explicitly is often clearer for beginners.
Interview questions
What is the default value of an uninitialised local variable in C++?
There's none: its value is indeterminate, and reading it is undefined behaviour. Global and static variables are zero-initialised. Always initialise locals, for example with {}.
What is the difference between int a = 5;, int a(5); and int a{5};?
Copy, direct and brace (uniform) initialisation. They're equivalent for simple values, but braces reject narrowing conversions such as int a{9.99};, so they're the safest choice.
What is the difference between const and constexpr?
Both make a variable read-only. constexpr also guarantees the value is computed at compile time; a const can be initialised with a value known only at run time.
What is variable shadowing?
A variable in an inner scope with the same name as one in an outer scope hides the outer one. Use ::name to reach a shadowed global, and -Wshadow to detect it.
Summary
- A variable has a name, a type and a value:
int marks{85}; - Prefer brace initialisation: it rejects narrowing conversions.
- Never read an uninitialised local variable; its value is undefined.
- Names use letters, digits and
_, can't start with a digit or be a keyword, and are case sensitive. - Variables live in the block where they're declared; avoid globals.
- Use
constorconstexprfor values that must not change;autodeduces the type from the initialiser.
Next lesson: C++ data types: sizes, ranges, overflow, floating-point precision and conversions.