Skip to content

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;
}
Output
marks = 90
a = 10, b = 20, c = 30
x + y + z = 6
Ravi scored 91.5% grade A
passed = 1
passed = true

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 + 5 reads the old value (85), adds 5 and stores 90. In C++ = means assignment, not mathematical equality (equality is ==).
  • A bool prints as 1 or 0 by default; std::boolalpha makes it print true/false.
  • Declaring several variables on one line works, but one per line is usually clearer.

Declaration vs definition vs initialisation vs assignment

TermExampleMeaning
Declarationextern int count;Tells the compiler a name and its type exist
Definitionint count;Declares and reserves memory. Most local declarations are definitions
Initialisationint count = 0;Gives the first value at the moment the variable is created
Assignmentcount = 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
Output
copyInit = 9

(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
Compiler output
narrowing.cpp:6:19: error: narrowing conversion of '9.9900000000000002e+0' from 'double' to 'int' [-Wnarrowing]
    6 |     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):

Compiler output
uninit.cpp:11:41: warning: 'count' is used uninitialized [-Wuninitialized]

Running the same executable twice gave:

Output (run 1, then run 2)
total = 0
items = 0
count = 31933
total = 0
items = 0
count = 31534

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: total and Total are different variables.
int 2ndPlace = 2;       // starts with a digit
int total-marks = 90;   // '-' is the minus operator
Compiler output
names.cpp:3:9: error: expected unqualified-id before numeric constant
    3 |     int 2ndPlace = 2;
      |         ^~~~~~~~
names.cpp:4:14: error: expected initializer before '-' token
    4 |     int total-marks = 90;
      |              ^

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, not tm or x1.
  • Pick one style and stick to it: camelCase (studentName) or snake_case (student_name, used by the standard library).
  • Constants are often UPPER_CASE (MAX_USERS) or kMaxUsers.
  • 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;
}
Output
local counter  = 5
global counter = 100
blockValue     = 42
  • 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 -Wshadow warns about this: declaration of 'counter' shadows a global declaration.

Using a block variable after its block ends is an error:

Compiler output
scope_error.cpp:8:18: error: 'blockValue' was not declared in this scope
    8 |     std::cout << blockValue << '\n';
      |                  ^~~~~~~~~~

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;
}
Output
Area of circle: 19.635
Days in 3 weeks: 21
price = 499.99, initial = S

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
Compiler output
const_error.cpp:4:14: error: assignment of read-only variable 'maxUsers'
    4 |     maxUsers = 60;
      |     ~~~~~~~~~^~~~
const_error.cpp:5:15: error: uninitialized 'const limit' [-fpermissive]
    5 |     const int limit;
      |               ^~~~~
constconstexpr
MeaningRead-only after initialisationA constant whose value is computed at compile time
Value knownAt compile time or at run time (for example from user input)Must be known at compile time
Exampleconst double area = PI * r * r; with r read from the userconstexpr int DAYS_IN_WEEK = 7;
Use forValues that shouldn't change once setFixed 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 const or constexpr for values that must not change; auto deduces the type from the initialiser.

Next lesson: C++ data types: sizes, ranges, overflow, floating-point precision and conversions.