Pointers and References in C++ Fifth Step in C++ Learning
Pointers and References in C++ Fifth Step in C++ Learning
First edition
References in C++
In C++, a reference is declared by using the ampersand
(&) operator before the name of the reference variable. Like
pointers, the type of the reference must be the same as the
referenced variable.
#include <iostream>
int main() {
int number = 10; // Integer variable
int* pointer; // Pointer variable declaration
return 0;
}
#include <iostream>
int main() {
int number = 42;
int* pointer = &number; // pointer holds the memory address
of 'number'
return 0;
}
Value of 'number': 42
Value stored at the memory address held by 'pointer': 42
#include <iostream>
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int* ptr = numbers; // Pointer to the first element of the
array
return 0;
}
#include <iostream>
int main() {
int* ptr = nullptr; // Assigning null value to the pointer
if (ptr == nullptr) {
std::cout << "Pointer is null." << std::endl;
} else {
std::cout << "Pointer is not null." << std::endl;
}
return 0;
}
#include <iostream>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr; // Pointer initialized to the start of the
array
return 0;
}
#include <iostream>
int main() {
int value = 42;
int* ptr = &value; // Pointer to an integer
int** ptrToPtr = &ptr; // Pointer to a pointer
return 0;
}
int rows = 3;
int cols = 4;
// Deallocate memory
for (int i = 0; i < rows; i++) {
delete[] array2D[i];
}
delete[] array2D;
#include <iostream>
int main() {
int original = 42;
int& ref = original;
return 0;
}
#include <iostream>
int main() {
int num1 = 5;
int num2 = 10;
return 0;
}
#include <iostream>
int main() {
int number = 5;
return 0;
}
#include <iostream>
int main() {
int value = 5;
int* ptr = &value; // Pointer to value
int& ref = value; // Reference to value
// Modify values
manipulateValue(ptr, ref);
return 0;
}
Now, let’s discuss the similarities and di erences between
pointers and references in C++:
Memory Usage:
Pointers: Pointers hold memory addresses as their
values. They require additional memory to store the
address they are pointing to.
References: References are aliases for variables. They do
not occupy additional memory because they refer
directly to the object they are referencing.
Syntax:
Pointers: Pointers are declared using the asterisk (*)
symbol. They need to be dereferenced using the asterisk
symbol to access the value they are pointing to.
References: References are declared using the
ampersand (&) symbol. They do not require any special
syntax to access the value they refer to.
Behavior:
Pointers: Pointers can be reassigned to point to di erent
objects or be set to a null value (nullptr). They can also
be used for pointer arithmetic.
References: References cannot be reseated to refer to a
di erent object once initialized. They must be initialized
with an object and cannot be null. References do not
support pointer arithmetic.
Nullability:
Pointers: Pointers can be null, which means they do not
point to any valid object.
References: References cannot be null and must be
initialized with a valid object.
Initialization:
Pointers: Pointers can be declared without initialization
or be initialized later. They can be assigned to the
address of an existing object using the address-of
operator (&) or by using the new keyword to dynamically
allocate memory.
References: References must be initialized at the time of
declaration. They cannot be assigned to a di erent
object after initialization.
Function Parameters:
Pointers: Pointers can be passed as function parameters
to allow modification of the original object or to achieve
pass-by-reference semantics.
References: References can also be passed as function
parameters to achieve pass-by-reference semantics.
They provide a more intuitive syntax and avoid the need
for pointer dereferencing.
In the provided example, the manipulateValue function
takes both a pointer and a reference as parameters. It
increments the values they point to/refer to. After calling the
function, you can observe that both the pointer and
reference modify the original value variable.
Overall, pointers and references share similarities in that
they both allow indirect access to objects and can be used to
modify the original object. However, pointers provide more
flexibility and allow for nullability and reassignment, while
references provide a simpler syntax and are more restricted
in their usage.
Pointer to Constant
#include <iostream>
int main() {
int value = 5;
const int* ptr = &value; // Pointer to a constant integer
return 0;
}
#include <iostream>
int main() {
int number = 5;
int* const ptr = &number; // Declare a constant pointer and
initialize it with the address of 'number'
return 0;
}
#include <iostream>
int main() {
int value = 10;
int anotherValue = 20;
// Pointer to a constant
const int* ptrToConst = &value;
std::cout << "Pointer to a constant:" << std::endl;
std::cout << "Memory Address: " << ptrToConst << std::endl;
std::cout << "Pointed Value: " << *ptrToConst << std::endl;
// Constant pointer
int* const constPtr = &value;
std::cout << "\nConstant pointer:" << std::endl;
std::cout << "Memory Address: " << constPtr << std::endl;
std::cout << "Pointed Value: " << *constPtr << std::endl;
return 0;
}
Explanation:
1. Pointer to a constant (const int* ptrToConst): This
means that the value being pointed to is constant and
cannot be modified through the pointer. The pointer
itself is not constant and can be reassigned to point to
di erent memory addresses. In the example, ptrToConst
points to the memory address of value. The pointed
value can be accessed but not modified through this
pointer. If you uncomment the assignment *ptrToConst
= 5;, it will result in a compilation error because it
attempts to modify the value.
2. Constant pointer (int* const constPtr): This means that
the pointer itself is constant and cannot be reassigned to
point to a di erent memory address. However, the value
being pointed to can be modified. In the example,
constPtr is a constant pointer that points to the memory
address of value. The pointed value can be accessed and
modified through this pointer. If you uncomment the
assignment constPtr = &anotherValue;, it will result in a
compilation error because it attempts to reassign the
constant pointer.
In summary, a pointer to a constant allows you to modify
the pointer itself but not the pointed value, while a constant
pointer allows you to modify the pointed value but not the
pointer itself.
Pointer as Func on Return Value
#include <iostream>
int main()
{
// Call the function to create a pointer to an integer
int* myPointer = createIntegerPointer();
#include <iostream>
int main() {
int value = 5;
std::cout << "Initial value: " << value << std::endl;
result = 10;
std::cout << "Modified value: " << value << std::endl;
return 0;
}
#include <iostream>
int main() {
// Dynamically allocate memory for an integer
int* dynamicInt = new int;
return 0;
}
#include <iostream>
int main() {
int intValue = 10;
float floatValue = 3.14;
std::string stringValue = "Hello, World!";
printValue(intValue);
printValue(floatValue);
printValue(stringValue);
return 0;
}
In this program, we have three overloaded functions named
printValue that take references as parameters. Each function
is responsible for printing the value of a specific data type.
When the printValue function is called with a particular
type of parameter, the compiler matches the function call to
the appropriate overloaded function based on the
parameter’s type. The references serve as a way for the
compiler to distinguish between the di erent data types.
In the main function, we declare variables intValue,
floatValue, and stringValue, which are of type int, float, and
std::string, respectively. We then call the printValue function
with each of these variables as arguments.
Since each function overload takes a reference as a
parameter, the references allow the overloaded functions to
receive the actual variables as arguments. This enables the
functions to operate directly on the original variables, rather
than creating copies, which can be more e cient for large
data types.
When the program is executed, each printValue function
overload is called with the appropriate parameter type, and
the corresponding value is printed to the console.
Output:
Integer value: 10
Float value: 3.14
String value: Hello, World!
#include <iostream>
// Function declaration
int Add(int a, int b)
{
return a + b;
}
int main()
{
// Declare a pointer to a function that takes two int
parameters and returns an int
int (*pFunc)(int, int);
return 0;
}
#include <iostream>
class MyClass {
public:
int myVariable;
};
int main() {
MyClass obj;
obj.myVariable = 42;
return 0;
}
In this program, we have a class called MyClass with a single
public member variable myVariable. Inside the main()
function, we create an instance of MyClass named obj and
set the value of myVariable to 42.
To declare a pointer to a member variable, we use the
syntax type_of_member Class::*ptr_name. In this case, we
declare int MyClass::*ptr, indicating that ptr is a pointer to
an integer member variable of the MyClass class.
To access the member variable directly, we use the object
name (obj) followed by the member variable name
(myVariable). This is the usual way of accessing a member
variable.
To access the member variable through the pointer, we
use the object name (obj) followed by the pointer name (ptr)
and the pointer-to-member operator .*. This syntax allows
us to access the member variable indirectly through the
pointer.
In the program, we demonstrate accessing and modifying
the member variable both directly and through the pointer.
Finally, we print the modified value of myVariable to verify
that the modification was successful.
The output of the program will be:
As you can see, both direct access and access through the
pointer yield the same result. The syntax di erence lies in
the use of the pointer-to-member operator .* when
accessing the member variable through the pointer.
Pointer to Member Func on
#include <iostream>
class MyClass {
public:
void myFunction() {
std::cout << "Hello from myFunction!" << std::endl;
}
};
int main() {
// Declare a pointer to member function
void (MyClass::*functionPtr)() = &MyClass::myFunction;
return 0;
}
return_type (Class::*pointer_name)(arguments);
functionPtr = &MyClass::myFunction;
(obj.*functionPtr)();
obj.myFunction();
#include <iostream>
class Base {
public:
virtual void display() {
std::cout << "This is the Base class" << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
return 0;
}
#include <iostream>
int main()
{
int value = 42;
return 0;
}
In this example, we define a typedef CallbackFunction which
represents a pointer to a function that takes an integer
parameter and returns void. The callback function is the
actual function that will be called when the callback is
invoked. It simply prints the value received as a parameter.
The performOperation function takes two parameters: an
integer value and a CallbackFunction pointer. It performs
some operation with the given value and then invokes the
callback function by using the pointer. In this case, we pass
the callback function as the callback function to be invoked.
In the main function, we define an integer value and call
performOperation with this value and the callback function
as the callback parameter. When performOperation is called,
it performs its operation and then invokes the provided
callback function, which results in the callback function
being called with the value as a parameter.
When you run this program, it will output:
#include <iostream>
int main() {
int size;
std::cout << "Enter the size of the array: ";
std::cin >> size;
#include <iostream>
using namespace std;
struct Person {
string name;
int age;
int* height;
int& weight;
};
int main() {
int heightVal = 170;
int weightVal = 65;
Person person;
person.name = "John";
person.age = 30;
person.height = &heightVal; // Assign the address of
heightVal to the pointer member
person.weight = weightVal; // Assign the reference of
weightVal to the reference member
return 0;
}
struct Person {
// ...
int* height; // Pointer member
int& weight; // Reference member
};
Person person;
cout << "Height: " << *(person.height) << endl; // Access the
value using the pointer
cout << "Weight: " << person.weight << endl; // Access the
value using the reference
#include <iostream>
class MyClass {
private:
int* pointer;
int& reference;
public:
// Constructor
MyClass(int value) : reference(value) {
pointer = new int(value);
}
// Destructor
~MyClass() {
delete pointer;
}
void displayValues() {
std::cout << "Pointer value: " << *pointer << std::endl;
std::cout << "Reference value: " << reference <<
std::endl;
}
};
int main() {
int value = 10;
MyClass obj(value);
obj.displayValues();
value = 20;
obj.displayValues();
return 0;
}
#include <iostream>
class Base {
public:
virtual void printMessage() {
std::cout << "This is the base class." << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
#include <iostream>
class MyClass {
public:
int myVariable;
int main() {
MyClass obj(42);
const MyClass* ptr = &obj; // Pointer to a constant object
return 0;
}
#include <iostream>
class MyClass {
public:
int myVariable;
};
int main() {
MyClass obj;
obj.myVariable = 42;
return 0;
}
#include <iostream>
class MyClass {
public:
void nonConstFunc() {
std::cout << "Non-const member function called." <<
std::endl;
}
int main() {
typedef void (MyClass::*FuncPtr)() const; // Define a type
for pointer to constant member function
FuncPtr ptr = &MyClass::constFunc; // Assign the address of
the constant member function
MyClass obj;
(obj.*ptr)(); // Call the constant member function through
the pointer
return 0;
}
#include <iostream>
class MyClass {
public:
void func() {
std::cout << "Regular member function." << std::endl;
}
int main() {
typedef void (MyClass::*MemberFuncPtr)() const;
MyClass obj;
(obj.*ptr)(); // Access the member function through the
constant pointer
return 0;
}
#include <iostream>
using namespace std;
// Base class
class Base {
public:
int baseVar;
void displayBase() {
cout << "Base variable: " << baseVar << endl;
}
};
// Derived class
class Derived : public Base {
public:
int derivedVar;
void displayDerived() {
cout << "Derived variable: " << derivedVar << endl;
}
};
int main() {
// Create objects
Base baseObj;
Derived derivedObj;
return 0;
}
#include <iostream>
class Base {
public:
virtual void display() {
std::cout << "Base class" << std::endl;
}
};
int main() {
// Casting between pointers
Derived derivedObj;
Base* basePtr = &derivedObj;
Derived* derivedPtr = static_cast<Derived*>(basePtr);
derivedPtr->display(); // Outputs "Derived class"
// static_cast
float f = 3.14;
int i = static_cast<int>(f); // Converts float to int
// dynamic_cast
Base* basePtr2 = new Derived();
Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2);
if (derivedPtr2 != nullptr) {
derivedPtr2->display(); // Outputs "Derived class"
}
// reinterpret_cast
int* intPtr = new int(10);
double* doublePtr = reinterpret_cast<double*>(intPtr);
std::cout << *doublePtr << std::endl; // Undefined behavior,
dangerous cast!
// const_cast
const int constant = 5;
int& nonConstRef = const_cast<int&>(constant);
nonConstRef = 10;
std::cout << constant << std::endl; // Outputs "10"
delete basePtr2;
delete intPtr;
return 0;
}
#include <iostream>
T* getPointer() const {
return ptr_;
}
private:
T* ptr_;
};
int main() {
// Template class with a pointer member
int* intValue = new int(42);
PointerClass<int> intPtrClass(intValue);
std::cout << "Pointer value: " << *(intPtrClass.getPointer())
<< std::endl;
delete intValue;
return 0;
}
Declare the lambda function using the auto keyword and the
lambda syntax. Make the lambda function constant by using
the const qualifier after the lambda’s argument list:
(*lambdaPtr)(42);
int main() {
auto myLambda = [](int x) const {
std::cout << "The value is: " << x << std::endl;
};
return 0;
}
#include <iostream>
int main() {
const auto& lambdaRef = [](int x, int y) {
return x + y;
};
return 0;
}
Result: 7
#include <iostream>
class MyClass {
public:
int myVariable;
void lambdaExample() {
int* ptr = &myVariable;
myVariable = 42;
lambda();
}
};
int main() {
MyClass obj;
obj.lambdaExample();
return 0;
}
In the above example, we have a class called MyClass with a
member variable myVariable. Inside the lambdaExample()
function, we create a pointer ptr and assign it the address of
myVariable.
We then define a lambda function lambda that captures
ptr by value using [ptr] in the lambda capture list. This
means the lambda function will have its own copy of ptr,
which points to myVariable outside the lambda.
Within the lambda function, we can access the member
variable myVariable by dereferencing the captured pointer
ptr. In this example, we print the value of myVariable
through the pointer using *ptr.
Finally, we set myVariable to 42 and invoke the lambda
function lambda(). This will output the value of myVariable
through the captured pointer, which is 42 in this case.
Note that capturing a member variable through a pointer
allows you to access and modify the variable within the
lambda function, just like capturing it by value or reference.
However, you need to ensure that the pointer remains valid
during the lifetime of the lambda function.
Reference to Member Variable in
Lambda Capture
#include <iostream>
class MyClass {
public:
MyClass(int value) : myMember(value) {}
void doOperation() {
int multiplier = 2;
auto lambda = [this, &multiplier]() {
myMember *= multiplier;
std::cout << "Inside lambda: myMember = " << myMember
<< std::endl;
};
lambda();
std::cout << "After lambda: myMember = " << myMember <<
std::endl;
}
private:
int myMember;
};
int main() {
MyClass obj(5);
obj.doOperation();
return 0;
}
As you can see, both within and after the lambda function,
we have access to the myMember member variable of the
MyClass instance and can modify it as needed.
Pointer to Member Func on in
Lambda Capture
#include <iostream>
class MyClass {
public:
void memberFunction(int value) {
std::cout << "Member function called with value: " <<
value << std::endl;
}
};
int main() {
MyClass obj;
return 0;
}
In this example, we have a MyClass with a member function
memberFunction that takes an integer argument. Inside the
main() function, we create an instance of MyClass called obj.
The lambda function lambda is defined with the capture
[&obj], which captures obj by reference. The lambda
function takes two parameters: a pointer to the member
function of MyClass (void (MyClass::*funcPtr)(int)) and an
integer value.
Within the lambda function, (obj.*funcPtr)(value) is used
to invoke the member function using the member function
pointer. The . operator is used to access the member
function of the object obj pointed to by funcPtr, and then it
is called with the () operator and the provided value.
Finally, we call the lambda function lambda by passing
the member function pointer &MyClass::memberFunction
and the integer argument 42.
When you compile and run this program, it will output:
#include <iostream>
#include <functional>
class MyClass {
public:
void memberFunction(int value) {
std::cout << "Member function called with value: " <<
value << std::endl;
}
};
int main() {
MyClass obj;
#include <iostream>
struct MyClass {
int value;
int main() {
MyClass obj(42);
return 0;
}
#include <iostream>
class MyClass {
public:
void memberFunction() {
std::cout << "Called memberFunction()" << std::endl;
}
};
int main() {
MyClass obj;
#include <iostream>
class MyClass {
private:
int myInt;
public:
MyClass(int num) : myInt(num) {}
void printInt() {
std::cout << "Member variable myInt: " << myInt << std::endl;
}
};
return 0;
}
#include <iostream>
class MyClass {
public:
void print() {
std::cout << "Printing from non-const member function" <<
std::endl;
}
int main() {
MyClass obj;
const MyClass& constObj = obj;
void (MyClass::*funcPtr)() = &MyClass::print;
void (MyClass::*constFuncPtr)() const = &MyClass::print;
return 0;
}
#include <iostream>
int main() {
try {
throwExceptionWithPointer();
}
catch (const MyException& e) {
std::cout << "Exception caught: " << e.getMessage() <<
std::endl;
}
try {
throwExceptionWithReference();
}
catch (const MyException& e) {
std::cout << "Exception caught: " << e.getMessage() <<
std::endl;
}
return 0;
}
#include <iostream>
#include <cstdarg>
va_list args;
va_start(args, num);
va_end(args);
return sum;
}
int main()
{
// Declare a pointer to a function with variable arguments
int (*sumFunc)(int, ...);
return 0;
}
Result: 15
This demonstrates how to use a pointer to a function with
the ellipsis (…) notation to invoke a function with a
variable-length argument list in C++.
Reference to Func on and Variable-
Length Argument Lists
#include <iostream>
#include <cstdarg>
int total = 0;
for (int i = 0; i < count; i++)
{
int num = va_arg(args, int);
total += num;
}
va_end(args);
return total;
}
va_end(args);
return result;
}
int main()
{
// Invoking the sum function using a reference to a function
int total = invokeFunction(5, sum, 1, 2, 3, 4, 5);
return 0;
}
Sum: 15
This program demonstrates how references to functions can
be used to invoke functions with a variable-length argument
list using the ellipsis notation.
Pointer to Member Func on and
Variable-Length Argument Lists
#include <iostream>
#include <functional>
class MyClass {
public:
void myFunction(int a, const char* format, ...) {
std::cout << "a: " << a << std::endl;
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
};
int main() {
MyClass obj;
return 0;
}
#include <iostream>
#include <cstdarg>
class Calculator {
public:
int add(int count, ...); // Member function with variable-
length argument list
};
class FunctionCaller {
public:
void callAdd(int count, ...);
};
va_end(args);
int main() {
FunctionCaller caller;
caller.callAdd(3, 1, 2, 3); // Invokes Calculator::add(3, 1,
2, 3)
return 0;
}
Result: 6
#include <iostream>
#include <thread>
#include <mutex>
int main() {
int counterPtr = 0;
int counterRef = 0;
std::thread thread1(incrementCounterWithPointer,
&counterPtr);
std::thread thread2(incrementCounterWithReference,
std::ref(counterRef));
thread1.join();
thread2.join();
return 0;
}
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "MyClass created" << std::endl;
}
~MyClass() {
std::cout << "MyClass destroyed" << std::endl;
}
void someMethod() {
std::cout << "Executing someMethod()" << std::endl;
}
};
int main() {
// Using std::unique_ptr
std::unique_ptr<MyClass> uniquePtr(new MyClass());
uniquePtr->someMethod();
// Using std::shared_ptr
std::shared_ptr<MyClass> sharedPtr(new MyClass());
sharedPtr->someMethod();
return 0;
}
#include <iostream>
#include <memory>
class MyClass {
public:
int value;
int main() {
// Create an instance of MyClass
auto myClass = std::make_unique<MyClass>(42);
#include <iostream>
#include <memory>
class MyClass {
public:
int myVariable;
void ExecuteLambda() {
// Create a smart pointer and capture the member variable
by reference
auto lambdaPtr = std::make_unique<decltype(myVariable)&>
(myVariable);
int main() {
MyClass obj;
obj.myVariable = 10;
obj.ExecuteLambda();
return 0;
}
Captured value: 10
Modified value: 42
#include <iostream>
#include <memory>
class MyClass {
public:
void memberFunction(int value) {
std::cout << "Member function called with value: " <<
value << std::endl;
}
};
int main() {
MyClass obj;
return 0;
}
In this program, we have a class MyClass with a member
function memberFunction that takes an integer argument.
We want to invoke this member function within a lambda
function wrapped in a smart pointer.
First, we create an instance of MyClass called obj.
Next, we create a smart pointer lambdaPtr using
std::make_unique<std::function<void(int)>>. This smart
pointer is used to store a lambda function that takes an
integer argument (int) and returns void. The lambda
function captures the obj variable by reference (&) and
invokes the memberFunction on it, passing the integer
argument.
Finally, we can invoke the lambda function by
dereferencing the smart pointer and passing the desired
argument (42 in this case). The (*lambdaPtr)(42) syntax is
used to call the lambda function through the smart pointer.
When you run this program, it will output:
#include <iostream>
int main()
{
try
{
double a = 10.0, b = 0.0;
double (*dividePtr)(double, double) = ÷ // Pointer
to divide function
return 0;
}
#include <iostream>
#include <functional>
int main()
{
try
{
// Declare a reference to a function
std::function<void()> errorFunc = errorHandler;
return 0;
}