Contents
Pointers and References
Pointers and References in C++
In C++, pointers and references are mechanisms to manage memory and access data using memory addresses. Both serve different purposes in dealing with memory, addressing, and data manipulation within a program.Pointers are symbolic representations of addresses. They allow a program to implement call-by-reference and handle dynamic data structures. A pointer stores the memory address of a variable.
Syntax:
datatype *ptr;
For example:
int *ptr; // ptr is a pointer to an integer variable
Example of Pointers in C++
#include
using namespace std;
int main() {
int x = 15; // Declare an integer variable
int* myptr; // Declare a pointer variable
// Store the address of x in the pointer
myptr = &x;
// Print the value of x
cout << "Value of x: " << x << endl;
// Print the address stored in the pointer
cout << "Address stored in myptr: " << myptr << endl;
// Print the value of x using dereferencing
cout << "Value of x using *myptr: " << *myptr << endl;
return 0;
}
Output:
Value of x: 15
Address stored in myptr: 0x7ffeefbff5cc
Value of x using *myptr: 15
Applications of Pointers in C++
Pointers are useful in various ways, such as:
Application | Description |
---|---|
Passing Arguments by Reference | Pointers allow call-by-reference, enabling modifications to be made directly to passed variables. |
Accessing Array Elements | Pointers are used internally by the compiler to manage array indexing. |
Returning Multiple Values | Pointers allow functions to return multiple results, like a value and its square. |
Dynamic Memory Allocation | Pointers can be used to allocate and manage memory at runtime, providing flexibility. |
Implementing Data Structures | Pointers are essential in creating dynamic data structures like linked lists, trees, and graphs. |
System-Level Programming | They are heavily used in system-level programming to manipulate memory addresses directly. |
Features of Pointers
Feature | Description |
---|---|
Memory Efficiency | Pointers reduce memory usage by directly accessing variables without duplicating them. |
Dynamic Allocation | Memory can be dynamically allocated and managed via pointers. |
File Handling | Pointers are used for efficient file handling and buffer management. |
‘this’ Pointer in C++ | The this pointer is implicitly passed to non-static member functions to access the current object instance. |
References in C++
References provide an alias for another variable. A reference allows a function to operate on the original variable rather than a copy.
Syntax:
datatype &ref_var = original_var;
Example:
#include
using namespace std;
int main() {
int y = 25;
// Create a reference to y
int& myref = y;
// Modify y
y = 40;
cout << "Value of y: " << y << endl;
cout << "Value of myref after modifying y: " << myref << endl;
return 0;
}
Output:
Value of y: 40
Value of myref after modifying y: 40
Pointers vs. References
Both pointers and references can modify variables in other functions and avoid unnecessary copying of large objects. However, they have key differences:
Feature | Pointers | References |
---|---|---|
Syntax | int *ptr; | int& ref = var; |
Nullability | Can be set to nullptr . | Must always refer to an existing object. |
Reassignment | Can be reassigned to point to another variable. | Cannot be reassigned after initialization. |
Memory Address | Can store the address of a variable. | Acts as an alias for a variable. |
Dereferencing | Requires explicit dereferencing (*ptr ). | Automatically dereferenced. |
C++ Pointers
Pointers are variables that hold the address of another variable. They enable operations such as call-by-reference and are useful for managing dynamic memory and creating complex data structures.
Syntax
datatype *pointer_name;
Example of Pointers in C++
The following program demonstrates basic pointer usage:
#include
using namespace std;
int main() {
int num = 42; // Declare an integer variable
int* ptr; // Declare a pointer
ptr = # // Store the address of 'num' in 'ptr'
// Output the value of 'num'
cout << "Value of num: " << num << endl;
// Output the memory address stored in 'ptr'
cout << "Address stored in ptr: " << ptr << endl;
// Output the value of 'num' by dereferencing 'ptr'
cout << "Value of num using ptr: " << *ptr << endl;
return 0;
}
Output:
Value of num: 42
Address stored in ptr: 0x7ffd3c942a24
Value of num using ptr: 42
Applications of Pointers in C++
1. Passing arguments by reference: Enables modification of variables inside a function without copying data.
2. Accessing array elements: Internally, arrays are accessed using pointers.
Returning multiple values: Pointers can be used to return more than one value from a function.
3. Dynamic memory allocation: Pointers allow for dynamic allocation of memory, which remains allocated until explicitly released.
4. Implementing data structures: Pointers are used in data structures like linked lists, trees, and graphs.
5. System-level programming: Pointers enable direct manipulation of memory addresses.
Features and Uses of Pointers
- Efficient memory usage and dynamic allocation.
- Essential for file handling operations.
- Enable direct access to memory locations, improving performance in certain cases.
The ‘this’ Pointer in C++
In C++, the this
pointer is automatically passed to non-static member functions, allowing access to the calling object’s members. Static member functions, however, do not have access to this
since they can be called without an object.
Declaration:
this->x = x;
References in C++
A reference is simply another name for an existing variable, created using the &
operator in a declaration. It allows for more intuitive manipulation of variables without needing explicit pointers.
There are three ways to pass arguments to functions in C++:
1. Call-by-value
2. Call-by-reference using pointers
3. Call-by-reference using references
Example of References in C++
#include
using namespace std;
int main() {
int value = 50; // Declare an integer
// Create a reference to 'value'
int& ref = value;
// Modify the value using the reference
ref = 100;
// Output the value of 'value' and 'ref'
cout << "Value of 'value' is: " << value << endl;
cout << "Value of 'ref' is: " << ref << endl;
return 0;
}
Output:
Value of 'value' is: 100
Value of 'ref' is: 100
Pointers and Arrays
Arrays and pointers are closely related in C++. An array name holds the address of its first element, making it similar to a constant pointer.
Example of Using a Pointer with an Array:
#include
using namespace std;
void demonstrateArrayPointer() {
int arr[3] = { 2, 4, 6 };
int* ptr = arr; // Points to the first element
cout << "Array elements accessed through pointer: ";
for (int i = 0; i < 3; i++) {
cout << *(ptr + i) << " ";
}
cout << endl;
}
int main() {
demonstrateArrayPointer();
return 0;
}
Output:
Array elements accessed through pointer: 2 4 6
Pointer Arithmetic
Pointer arithmetic allows us to navigate arrays by incrementing and decrementing pointers. However, this should be done cautiously and only within the bounds of an array.
Example of Pointer Arithmetic:
#include
using namespace std;
void pointerArithmetic() {
int values[3] = { 1, 5, 9 };
int* ptr = values;
for (int i = 0; i < 3; i++) {
cout << "Pointer at: " << ptr << " Value: " << *ptr << endl;
ptr++; // Move to the next element
}
}
int main() {
pointerArithmetic();
return 0;
}
Output:
Pointer at: 0x7ffe08c583e0 Value: 1
Pointer at: 0x7ffe08c583e4 Value: 5
Pointer at: 0x7ffe08c583e8 Value: 9
Void Pointers
Void pointers (type void*
) are generic pointers that can point to any data type, but they must be cast to the correct type before dereferencing.
Example of Void Pointer:
#include
using namespace std;
void manipulate(void* ptr, int size) {
if (size == sizeof(char)) {
char* charPtr = (char*)ptr;
*charPtr = *charPtr + 1;
} else if (size == sizeof(int)) {
int* intPtr = (int*)ptr;
*intPtr = *intPtr + 1;
}
}
int main() {
char c = 'A';
int num = 10;
manipulate(&c, sizeof(c));
manipulate(&num, sizeof(num));
cout << "New char value: " << c << endl;
cout << "New int value: " << num << endl;
return 0;
}
Output:
New char value: B
New int value: 11
Dangling, Void , Null and Wild Pointers in C
A dangling pointer arises when a pointer continues to reference a memory location that has been freed or deallocated. Accessing such pointers can lead to unexpected behavior and bugs.
Common scenarios leading to dangling pointers:
1. Deallocation of Memory: When memory allocated using malloc
or similar functions is freed, the pointer becomes a dangling pointer unless explicitly set to NULL.
Example:
#include
#include
int main() {
int* ptr = (int*)malloc(sizeof(int));
// Memory is freed, ptr is now a dangling pointer
free(ptr);
printf("Memory has been freed\n");
// Avoid dangling pointer by setting it to NULL
ptr = NULL;
return 0;
}
Output:
Memory has been freed
2. Returning Local Variables from a Function: A pointer to a local variable becomes a dangling pointer after the function call ends because local variables are destroyed when the function returns.
Example:
#include
int* getPointer() {
int x = 10; // Local variable
return &x; // Returning address of local variable (invalid)
}
int main() {
int* p = getPointer();
printf("%d", *p); // Undefined behavior, p is a dangling pointer
return 0;
}
Output:
0 (or unpredictable value)
Fix: Declare the local variable as static
to extend its lifetime.
#include
int* getPointer() {
static int x = 10; // Static variable has global lifetime
return &x;
}
int main() {
int* p = getPointer();
printf("%d", *p); // Correct behavior
return 0;
}
3. Out-of-Scope Variables: A pointer to a variable declared in a block becomes a dangling pointer once the block ends.
Example:
#include
int main() {
int* ptr;
{
int a = 50;
ptr = &a; // Pointer becomes dangling after block ends
}
// Accessing dangling pointer leads to undefined behavior
printf("%d\n", *ptr);
return 0;
}
Output:
Some undefined value
Void Pointer in C
A void pointer (void *
) is a type of pointer that points to a memory location without specifying the data type. Since it has no type, it can point to any data but cannot be directly dereferenced without casting.
Syntax:
void *ptr;
Example:
#include
int main() {
int x = 5;
float y = 3.14;
void* ptr;
ptr = &x; // Void pointer points to an integer
printf("Integer value: %d\n", *((int*)ptr)); // Typecasting to int*
ptr = &y; // Void pointer points to a float
printf("Float value: %.2f\n", *((float*)ptr)); // Typecasting to float*
return 0;
}
Output:
Integer value: 5
Float value: 3.14
NULL Pointer in C
A null pointer points to “nothing” and is used to indicate that a pointer has not been assigned any valid memory address.
Syntax:
int *ptr = NULL;
Example:
#include
int main() {
int* ptr = NULL; // Pointer initialized to NULL
if (ptr == NULL) {
printf("Pointer is NULL\n");
}
return 0;
}
Output:
#include
int main() {
int a = 9;
int b = 9;
printf("Using Prefix Decrement on a = %d\n", --a);
printf("Using Postfix Decrement on b = %d", b--);
return 0;
}
Wild Pointer in C
A wild pointer is a pointer that has not been initialized to a specific memory address. Accessing memory through a wild pointer leads to undefined behavior.
Example:
#include
int main() {
int* p; // Wild pointer (not initialized)
// Attempting to dereference wild pointer can lead to crash or error
// printf("%d", *p); // Uncommenting this would cause undefined behavior
return 0;
}
Understanding nullptr in C++
nullptr
is a keyword introduced in C++11 to address the issues associated with the traditional NULL
. The primary problem with NULL
is its definition and its behavior when passed to overloaded functions, which can lead to ambiguity.
Consider the following C++ program that demonstrates the issue with NULL
and the need for nullptr
:
// C++ program demonstrating the problem with NULL
#include
using namespace std;
// Function that takes an integer argument
void fun(int N) {
cout << "fun(int)" << endl;
}
// Overloaded function that takes a char pointer argument
void fun(char* s) {
cout << "fun(char*)" << endl;
}
int main() {
// This should ideally call fun(char*),
// but it leads to an ambiguous compiler error.
fun(NULL);
}
Output:
error: call of overloaded 'fun(NULL)' is ambiguous
Why is this a problem?
NULL
is often defined as (void*)0
, and its conversion to integral types is allowed. Therefore, when the function call fun(NULL)
is made, the compiler is unable to decide between fun(int)
and fun(char*)
, resulting in ambiguity.
Example of the problem with NULL:
#include
using namespace std;
int main() {
int x = NULL; // This compiles but may produce a warning
cout << "x = " << x << endl;
return 0;
}
How does nullptr
solve the problem?
By using nullptr
in place of NULL
, the ambiguity is resolved. nullptr
is a keyword that can be used in all places where NULL
was expected. It is implicitly convertible to any pointer type but is not implicitly convertible to integral types, which avoids the ambiguity issue.
Example: if we modify the previous program to use nullptr
instead of NULL
, it compiles and behaves as expected:
// C++ program using nullptr
#include
using namespace std;
// Function that takes an integer argument
void fun(int N) {
cout << "fun(int)" << endl;
}
// Overloaded function that takes a char pointer argument
void fun(char* s) {
cout << "fun(char*)" << endl;
}
int main() {
// Using nullptr resolves the ambiguity
fun(nullptr);
return 0;
}
Output:
fun(char*)
nullptr
and Integral Types:
Unlike NULL
, nullptr
cannot be assigned to an integral type. Attempting to do so results in a compilation error:
#include
using namespace std;
int main() {
int x = nullptr; // This will produce a compiler error
return 0;
}
Output:
Compiler Error
Side Note: nullptr
is convertible to bool
nullptr
can be compared in boolean expressions. If a pointer is assigned nullptr
, it behaves like a false
value when evaluated in a condition.
#include
using namespace std;
int main() {
int* ptr = nullptr;
// This will compile and check if ptr is null
if (ptr) {
cout << "Pointer is not null" << endl;
} else {
cout << "Pointer is null" << endl;
}
return 0;
}
Output:
Pointer is null
Side Note: nullptr
is convertible to bool
nullptr
can be compared in boolean expressions. If a pointer is assigned nullptr
, it behaves like a false
value when evaluated in a condition.
// C++ program to demonstrate comparisons with nullptr
#include
using namespace std;
int main() {
// Variables of nullptr_t type
nullptr_t np1, np2;
// Comparison using <= and >= returns true
if (np1 >= np2) {
cout << "Comparison is valid" << endl;
} else {
cout << "Comparison is not valid" << endl;
}
// Initialize a pointer with nullptr
char* x = np1; // Same as x = nullptr
if (x == nullptr) {
cout << "x is null" << endl;
} else {
cout << "x is not null" << endl;
}
return 0;
}
Output:
Comparison is valid
x is null
References in C++
A reference in C++ is essentially an alias for an existing variable. Once a reference is initialized to a variable, it becomes an alternative name for that variable. To declare a reference, the &
symbol is used in the variable declaration.
Syntax:
data_type &reference_variable = original_variable;
Example:
// C++ program demonstrating references
#include
using namespace std;
int main() {
int x = 10;
// ref is a reference to x.
int& ref = x;
// Modifying x through the reference
ref = 20;
cout << "x = " << x << endl;
// Modifying ref will modify x as well
x = 30;
cout << "ref = " << ref << endl;
return 0;
}
Output:
x = 20
ref = 30
Applications of References in C++
There are several practical uses of references in C++, some of which are:
1. Modifying Function Parameters: Passing arguments by reference allows the function to modify the actual arguments.
2. Avoiding Copies of Large Objects: References allow passing large objects without copying them, saving time and memory.
3. Modifying Elements in Range-Based Loops: References can be used in for loops to modify container elements directly.
4. Avoiding Object Copies in Loops: Using references in loops avoids copying large objects, improving efficiency.
Modifying Passed Parameters in a Function
When a function receives a reference to a variable, it can modify the original variable’s value.
Example:
// C++ program showing reference as function parameters
#include
using namespace std;
void swap(int& first, int& second) {
int temp = first;
first = second;
second = temp;
}
int main() {
int a = 2, b = 3;
// Swapping variables using references
swap(a, b);
// Values of a and b are swapped
cout << a << " " << b << endl;
return 0;
}
Output:
3 2
Avoiding Copies of Large Structures
Passing large structures without references creates unnecessary copies. Using references avoids this.
Example:
struct Student {
string name;
string address;
int rollNo;
};
// Passing large object by reference
void print(const Student &s) {
cout << s.name << " " << s.address << " " << s.rollNo << endl;
}
Modifying Elements in Range-Based Loops
By using references in a loop, you can modify all elements in a collection.
Example:
// C++ program demonstrating modifying vector elements using references
#include
#include
using namespace std;
int main() {
vector vect {10, 20, 30, 40};
// Modifying vector elements by reference
for (int& x : vect) {
x += 5;
}
// Printing modified elements
for (int x : vect) {
cout << x << " ";
}
cout << endl;
return 0;
}
Output:
15 25 35 45
Avoiding Object Copies in Loops
In some cases, especially when objects are large, avoiding copies in loops enhances performance.
Example:
// C++ program avoiding copy of objects using references
#include
#include
using namespace std;
int main() {
vector vect {"example 1", "example 2", "example 3"};
// Using reference to avoid copying
for (const auto& str : vect) {
cout << str << endl;
}
return 0;
}
Output:
example 1
example 2
example 3
Differences Between References and Pointers
While both references and pointers allow modifying variables in other functions and avoiding unnecessary copies, they have key differences:
Void Pointers vs. Void References: Pointers can be declared as void, but references cannot.
int a = 10;
void* ptr = &a; // Valid
// void& ref = a; // Invalid
Multiple Levels of Indirection: Pointers can have multiple levels of indirection (e.g., pointers to pointers), whereas references only have one level of indirection.
Reassignment: Once assigned, a reference cannot be made to reference another object. Pointers, however, can be reassigned.
Null Values: Pointers can be NULL
, but references must always refer to a valid object.
int num = 4;
printf("%d\n", num << 1); // Multiplies 4 by 2, result: 8
printf("%d\n", num >> 1); // Divides 4 by 2, result: 2
Advantages of Using References
Safer:
Since references must be initialized, they avoid issues like wild pointers.Easier to Use:
References don’t require dereferencing to access the value, making them more intuitive to use.Efficiency:
Copy constructors and operator overloads often require references to avoid unnecessary copying.
Example:
#include
using namespace std;
int& fun() {
static int x = 10;
return x;
}
int main() {
fun() = 30;
cout << fun();
return 0;
}
Output:
30
Pointers vs References in C++
C++ is unique in that it supports both pointers and references, unlike many other popular programming languages such as Java, Python, Ruby, and PHP, which only use references. At first glance, pointers and references seem to perform similar functions—both provide access to other variables. However, there are key differences between these two mechanisms that often cause confusion. Let’s explore these differences in detail.
Pointers:
A pointer is a variable that holds the memory address of another variable. To access the memory location a pointer is referring to, the dereference operator *
is used.
References:
A reference is essentially an alias for another variable. Just like pointers, references store the address of an object, but the compiler automatically dereferences them for you.
For example:
int i = 3;
// Pointer to variable i (stores the address of i)
int *ptr = &i;
// Reference (alias) for variable i
int &ref = i;
Key Differences
Initialization:
- A pointer can be initialized in multiple steps:
int a = 10;
int *p = &a; // or
int *p;
p = &a;
- A reference must be initialized when declared:
int a = 10;
int &r = a; // Correct
// However, the following is incorrect:
int &r;
r = a; // Error: references must be initialized during declaration
Reassignment:
Pointers can be reassigned to different variables:
int a = 5;
int b = 6;
int *p;
p = &a; // p points to a
p = &b; // Now p points to b
Passing by Reference
Passing by reference allows the function to modify the original variables without creating copies. It works by using reference variables, which act as aliases for the original variables. This method is referred to as “call by reference.”
Example of passing by reference:
// C++ program to swap two numbers using pass by reference
#include
using namespace std;
void swap(int &x, int &y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 10, b = 20;
cout << "Before Swap: a = " << a << ", b = " << b << endl;
swap(a, b);
cout << "After Swap using pass by reference: a = " << a << ", b = " << b << endl;
return 0;
}
Comparison: Pass by Pointer vs. Pass by Reference
Feature |
Pass by Pointer |
Pass by Reference |
---|---|---|
Passing Arguments |
The memory address of the argument is passed to the function. |
The argument itself is passed, not its memory address. |
Accessing Values |
Values are accessed using the dereference operator |
Values can be accessed directly using the reference. |
Reassignment |
The pointer can be reassigned to point to different memory. |
References cannot be reassigned after initialization. |
NULL Values |
Pointers can be |
References cannot be |
Usage |
Useful when you need pointer arithmetic or |
Typically used when a function does not need to change what it refers to. |
Differences Between Reference Variables and Pointer Variables
-
A reference is essentially another name for an object, and must always refer to an actual object. References cannot be
NULL
, making them safer than pointers. -
Pointers can be reassigned, while references must be initialized when declared and cannot be changed afterward.
-
A pointer holds the memory address of a variable, while a reference shares the memory address with the object it refers to.
-
Pointer arithmetic is possible (e.g., incrementing a pointer to traverse arrays), but references do not support this.
-
Pointers use the
->
operator to access class members, whereas references use the.
operator. -
You need to explicitly dereference a pointer with the
*
operator to access its value, while references do not require dereferencing.
Updated Example Demonstrating Differences
// C++ program to demonstrate differences between pointers and references
#include
using namespace std;
struct Example {
int value;
};
int main() {
int x = 10;
int y = 20;
Example obj;
// Using a pointer
int* ptr = &x;
ptr = &y; // Pointer reassignment is allowed
// Using a reference
int& ref = x;
// ref = &y; // Compile Error: References can't be reassigned
ref = y; // Changes the value of 'x' to 20
ptr = nullptr; // Pointer can be assigned to NULL
// ref = nullptr; // Compile Error: References can't be NULL
cout << "Pointer address: " << ptr << endl; // Pointer points to memory address
cout << "Reference address: " << &ref << endl; // Reference shares the same address as 'x'
Example* ptrObj = &obj;
Example& refObj = obj;
ptrObj->value = 42; // Accessing member using pointer
refObj.value = 42; // Accessing member using reference
return 0;
}
Which to Use: Pass by Pointer or Pass by Reference?
- Use references when you don’t need to reseat the reference and want safer, cleaner code. For example, references are ideal for function parameters and return values when you know the object being passed will never be
NULL
. - Use pointers when you need to handle
NULL
values or perform pointer arithmetic. Pointers are essential for more complex data structures like linked lists and trees,