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 <iostream>
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:

ApplicationDescription
Passing Arguments by ReferencePointers allow call-by-reference, enabling modifications to be made directly to passed variables.
Accessing Array ElementsPointers are used internally by the compiler to manage array indexing.
Returning Multiple ValuesPointers allow functions to return multiple results, like a value and its square.
Dynamic Memory AllocationPointers can be used to allocate and manage memory at runtime, providing flexibility.
Implementing Data StructuresPointers are essential in creating dynamic data structures like linked lists, trees, and graphs.
System-Level ProgrammingThey are heavily used in system-level programming to manipulate memory addresses directly.
Features of Pointers
FeatureDescription
Memory EfficiencyPointers reduce memory usage by directly accessing variables without duplicating them.
Dynamic AllocationMemory can be dynamically allocated and managed via pointers.
File HandlingPointers 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 <iostream>
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:

FeaturePointersReferences
Syntaxint *ptr;int& ref = var;
NullabilityCan be set to nullptr.Must always refer to an existing object.
ReassignmentCan be reassigned to point to another variable.Cannot be reassigned after initialization.
Memory AddressCan store the address of a variable.Acts as an alias for a variable.
DereferencingRequires 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 <iostream>
using namespace std;

int main() {
    int num = 42;  // Declare an integer variable
    int* ptr;      // Declare a pointer

    ptr = &num;    // 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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <stdio.h>
#include <stdlib.h>

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 <stdio.h>

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 <stdio.h>

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 <stdio.h>

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 <stdio.h>

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 <stdio.h>

int main() {
    int* ptr = NULL;  // Pointer initialized to NULL
    
    if (ptr == NULL) {
        printf("Pointer is NULL\n");
    }
    
    return 0;
}

				
			

Output:

				
					#include <stdio.h>

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 <stdio.h>

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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
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 <iostream>
#include <vector>

using namespace std;

int main() {
    vector<int> 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 <iostream>
#include <vector>

using namespace std;

int main() {
    vector<string> 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.

ReassignmentOnce assigned, a reference cannot be made to reference another object. Pointers, however, can be reassigned.

Null ValuesPointers 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 <iostream>
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 <iostream>
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 NULL or hold invalid addresses.

References cannot be NULL and must always refer to a valid object.

Usage

Useful when you need pointer arithmetic or NULL handling.

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 <iostream>
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,