References in C++
A reference is an alias (another name) for an existing variable. References are declared with the & operator after the data type. Once initialized, a reference cannot be changed to point to another variable. References are safer and more intuitive than pointers because they do not require dereferencing and cannot be null.
References are needed for: pass by reference to functions without copying data, returning references from functions, and simplifying data access syntax. Many C++ programmers prefer references over pointers because of the cleaner syntax.
Key differences between references and pointers:
- A reference must be initialized at declaration; a pointer does not have to be.
- A reference cannot be null; a pointer can.
- A reference cannot be reassigned to another variable; a pointer can.
- A reference is dereferenced automatically; a pointer needs the * operator.
Const References
A const reference allows passing data without the ability to modify it. This is very common for function parameters that only read the data, because it avoids the cost of copying while also protecting the data's integrity.
Code Example
#include <iostream>
#include <string>
using namespace std;
struct Student {
string name;
int score;
};
void swapValues(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void printStudent(const Student& m) {
cout << "Name: " << m.name << ", Score: " << m.score << endl;
}
void addPoints(Student& m, int points) {
m.score += points;
}
string& largestChar(string& text) {
char& largest = text[0];
for (size_t i = 1; i < text.length(); i++) {
if (text[i] > largest) {
largest = text[i];
}
}
return largest;
}
int main() {
// Declaring a reference
int original = 10;
int& ref = original; // ref is an alias for original
cout << "original = " << original << endl;
cout << "ref = " << ref << endl;
ref = 30; // changing ref is the same as changing original
cout << "\nAfter ref = 30:" << endl;
cout << "original = " << original << endl;
cout << "ref = " << ref << endl;
// Reference to a string
string name = "John";
string& nameRef = name;
nameRef += " Smith";
cout << "\nName: " << name << endl;
// Pass by reference
int x = 5, y = 15;
cout << "\nBefore swap: x=" << x << ", y=" << y << endl;
swapValues(x, y);
cout << "After swap: x=" << x << ", y=" << y << endl;
// Const reference
const string message = "Hello World";
const string& messageRef = message;
cout << "\nMessage: " << messageRef << endl;
// messageRef += "!"; // ERROR: cannot modify a const reference
// Const reference to non-const (read-only view)
int number = 42;
const int& readonly = number;
cout << "Readonly: " << readonly << endl;
// readonly = 100; // ERROR: cannot modify
number = 100; // POSSIBLE: the original can still be changed
cout << "After the original is changed: " << readonly << endl;
// Reference with a struct
Student student = {"Mike", 85};
cout << "\n";
printStudent(student);
addPoints(student, 10);
cout << "After adding points: ";
printStudent(student);
// Reference to an array element
int arr[] = {10, 20, 30};
int& element = arr[1];
cout << "\nArray[1] via reference: " << element << endl;
element = 25;
cout << "After modification: " << arr[1] << endl;
// Return by reference
string text = "abcdef";
cout << "\nLargest character in '" << text << "': ";
largestChar(text);
cout << text[0] << " (index 0)" << endl;
// Reference to a pointer
int val = 100;
int* ptr = &val;
int*& refPtr = ptr; // refPtr is an alias for ptr
cout << "\nThrough ref to pointer: " << *refPtr << endl;
int val2 = 200;
refPtr = &val2; // changes ptr
cout << "After ptr is changed: " << *ptr << endl;
// Looping with a const reference
int numbers[] = {1, 2, 3, 4, 5};
int total = 0;
for (const int& a : numbers) {
total += a;
}
cout << "\nTotal: " << total << endl;
// Comparing references vs pointers
cout << "\n--- Reference vs Pointer Comparison ---" << endl;
int value = 50;
int& refA = value;
int* ptrA = &value;
cout << "Reference dereferences automatically: " << refA << endl;
cout << "Pointer needs dereference: " << *ptrA << endl;
cout << "Sizeof reference: " << sizeof(refA) << endl; // same as sizeof(int)
cout << "Sizeof pointer: " << sizeof(ptrA) << endl; // 8 bytes (64-bit)
return 0;
}
Code Explanation
int& ref = original;— Declares a reference.refis another name fororiginal. Anything done torefaffectsoriginaland vice versa.ref = 30;— Changingrefis the same as changingoriginal. Output:original = 30.string& nameRef = name;— References can refer to any data type, including classes such asstd::string.void swapValues(int& a, int& b)— Parameters by reference. The function receives the addresses of the original variables without copying. Operations onaandbmodify the original variables.const string& messageRef = message;— A const reference. You cannot modify the value through this reference. Useful for reading data without the risk of modification.const int& readonly = number;— A const reference to a non-const variable. Creates a read-only view: you cannot modify throughreadonly, but the original variablenumbercan still be changed from elsewhere.printStudent(const Student& m)— Passing a struct by const reference. It avoids copying the entire struct while also guaranteeing that the function does not modify the data. This is best practice for large object parameters.addPoints(Student& m, int points)— Passing by non-const reference because the function needs to modifym.score.int& element = arr[1]— A reference can point to a specific array element. Useful for directly modifying a particular element.largestChar(string& text)— Return by reference. Returns a reference to the largest character in the string. Because it returns a reference, the caller can directly modify the result.int*& refPtr = ptr— A reference to a pointer.refPtris an alias forptr. ChangingrefPtrmeans changing the pointerptritself.for (const int& a : numbers)— A range-based for with a const reference. Efficient for large data types because it does not copy, and safe because it is const.sizeof(refA)vssizeof(ptrA)— A reference has no memory overhead of its own. The sizeof of a reference is the same as the sizeof of the referenced type. A pointer is always 4 or 8 bytes depending on the architecture.
Everyday Use Cases
Swap function: In a sorting algorithm such as bubble sort, you need to swap two array elements. With references, a swapValues(a, b) function directly modifies the original variables without complicated pointers.
Reading large objects: When calling a function printStudent(const Student& m), the Student data is not copied. For a large struct with many fields, this saves significant time and memory.
Other examples:
- Iterators: for (auto& item : collection) processes collection elements directly without copying.
- Builder pattern: A method returns *this as a reference for method chaining: builder.setName("X").setAge(25).
- Callbacks: References make it easy to pass data to callback functions without copy overhead.
Conclusion
References are a C++ feature that simplifies the use of pointers. References must be initialized, cannot be null, and cannot be reassigned. A const reference is best practice for function parameters that read large data without copying while also protecting it. Use references as the default; use pointers only when you need nullable or reassignable behavior.