Skip to content

C++ Variable Types

C++ Variables: Definition, Declaration, and Initialization

Section titled “C++ Variables: Definition, Declaration, and Initialization”

A variable is a fundamental concept in programming, representing a named region of memory that stores a value. Our programs use variables to hold and manipulate data during execution. In C++, every variable must have a specific data type, which dictates its size, the kind of values it can hold, and the operations applicable to it.

Variable names (identifiers) in C++ can consist of letters, digits, and underscores (_). They must start with a letter or an underscore. C++ is case-sensitive, meaning myVariable and myvariable are treated as distinct variables.

As covered previously, C++ has several fundamental built-in types:

Beyond these, C++ allows defining more complex types like enumerations (enum, enum class), pointers, arrays, references, structures (struct), unions (union), and classes (class).

A definition introduces a variable’s name and type to the compiler and, crucially, allocates memory for it. A definition serves as a declaration as well.

Syntax:

DataType variableName1, variableName2, /* ..., */ variableNameN;

Here, DataType is a valid C++ type (like int, double, std::string, or a user-defined type), and variableName1, etc., are the identifiers you choose. Examples:

#include <string>
int score; // Defines an integer variable named score
double temperature; // Defines a double-precision floating-point variable
int x, y, z; // Defines three integer variables
std::string message; // Defines a string object (requires <string> header)
bool isFinished; // Defines a boolean variable

You can (and often should) give a variable an initial value when you define it. This is called initialization.

C++ offers several initialization syntaxes:

  • Copy Initialization (=): Uses the equals sign. Historically common.
  • Direct Initialization (()): Uses parentheses. Often used for calling constructors.
  • Uniform Initialization / Brace Initialization ({}) (C++11 onwards): Uses curly braces. Generally the most recommended form as it’s unambiguous and prevents narrowing conversions (e.g., trying to initialize an int with a double that would lose precision).

Examples:

#include <string>
int score = 0; // Copy initialization
double temperature = 20.5; // Copy initialization
int x(10); // Direct initialization
std::string name("Alice"); // Direct initialization (calls string constructor)
// Brace initialization (Modern C++ - Preferred)
int count {0}; // Initializes count to 0
double pi {3.14159};
std::string greeting {"Hello"};
int value {}; // Value initialization: initializes to 0 for fundamental types
bool enabled {true};
// Brace initialization prevents narrowing conversions:
// int narrowing_error {3.14}; // Compile-time error! Cannot narrow double to int.

Important: Variables defined inside a function (local variables) that are not explicitly initialized have indeterminate values (garbage). Always initialize local variables before using them. Variables defined outside functions (global variables) or as static are typically zero-initialized by default, but explicitly initializing is still good practice.

A declaration introduces a variable’s name and type to the compiler without allocating memory. It essentially tells the compiler, “This variable exists somewhere else, trust me.” Declarations are primarily used when code is split across multiple files.

The extern keyword is used to declare a variable without defining it:

extern int globalCounter; // Declaration: globalCounter is defined elsewhere
extern const double PI; // Declaration: PI is a constant defined elsewhere
extern std::string systemName; // Declaration: systemName defined elsewhere

A variable can be declared multiple times (e.g., in different header files), but it must be defined exactly once in one source file. The definition is where the storage is actually allocated.

globals.h (Header File):

#ifndef GLOBALS_H
#define GLOBALS_H
#include <string>
// Declarations of global variables
extern int sharedCounter;
extern const std::string appName;
#endif // GLOBALS_H

globals.cpp (Source File):

#include "globals.h"
// Definitions of the global variables (memory allocated here)
int sharedCounter = 0; // Initialized definition
const std::string appName = "My Awesome App";

main.cpp (Another Source File):

#include <iostream>
#include "globals.h" // Include declarations
void incrementCounter(); // Assume this function is defined elsewhere
int main() {
std::cout << "Application: " << appName << std::endl; // Use declared global const
std::cout << "Initial counter: " << sharedCounter << std::endl; // Use declared global
sharedCounter++;
incrementCounter(); // Another function might modify it too
std::cout << "Final counter: " << sharedCounter << std::endl;
return 0;
}

In this setup, main.cpp knows about sharedCounter and appName via the declarations in globals.h, but the actual memory and initial values are established in globals.cpp. The linker connects the uses in main.cpp to the definitions in globals.cpp.

Expressions in C++ can be categorized based on whether they represent memory locations or just data values. The two fundamental categories are:

  • lvalue (locator value): An expression that refers to a memory location (an object) that potentially persists beyond a single expression. Variables are typically lvalues. An lvalue can usually appear on the left-hand side of an assignment operator (=). Think of it as something that has an address.
  • rvalue (read value): An expression that refers to a temporary data value that doesn’t necessarily have a persistent memory location (or whose location cannot be easily obtained or relied upon). Literals (like 10, 3.14, "hello") are rvalues. An rvalue can typically appear on the right-hand side of an assignment, but not usually on the left.

Examples:

int x = 10; // 'x' is an lvalue, '10' is an rvalue
int y = 20; // 'y' is an lvalue, '20' is an rvalue
x = y; // OK: 'x' (lvalue) = 'y' (lvalue - implicitly converted to rvalue here)
x = 5; // OK: 'x' (lvalue) = '5' (rvalue)
// The following are invalid because the left side is not an lvalue:
// 10 = x; // Error! Cannot assign to a literal (rvalue).
// (x + y) = 30; // Error! (x + y) produces a temporary result (rvalue).

Understanding lvalues and rvalues becomes more important with advanced C++ features like references, move semantics (C++11), and operator overloading.