Introduction
C and C++ unlike a lot of other languages provide the ability to directly interact with hardware memory - normally described as a middle-level or systems language - through the use of pointers. Every variable declared in any language has a memory location, and every memory location has an address used to locate it. C++ gives you the power to directly modify this memory.
Accessing Memory
In C++, the memory address (where a value lives) of a variable can be accessed through the
&
&
operator - see the example below.
std::string food = "Pizza";
cout << food; // outputs the value of food (Pizza)
cout << &food; // outputs the memory address of food (0x6dfed4)
std::string food = "Pizza";
cout << food; // outputs the value of food (Pizza)
cout << &food; // outputs the memory address of food (0x6dfed4)
Pointers
A pointer on the other hand, is a variable that stores the memory address of a variable as its value, and
is created using the *
operator. A pointer can exist for any data type, as it merely points to a memory address of the variable.
int x = 5;
int* y = &x;
// Equivalent to
int *y = &x;
cout << x; // outputs the value of x (5)
cout << y; // outputs the memory address of x (0x6dfed4)
cout << &y; // outputs the memory address of y (0x6ffd4)
cout << *y; // outputs the value of x (5)
int x = 5;
int* y = &x;
// Equivalent to
int *y = &x;
cout << x; // outputs the value of x (5)
cout << y; // outputs the memory address of x (0x6dfed4)
cout << &y; // outputs the memory address of y (0x6ffd4)
cout << *y; // outputs the value of x (5)
Arrow Operator
The arrow operator is a shorthand method for accessing the contents of a
struct
or class
pointer - normally the .
.
operator would be used for non-pointer
access. The example below shows the equivalences.
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area (void);
} rect;
Rectangle* p_rect = ▭ // creates a pointer to the rect declared above
p_rect->area;
// This is equivalent to
(*p_rect).area;
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area (void);
} rect;
Rectangle* p_rect = ▭ // creates a pointer to the rect declared above
p_rect->area;
// This is equivalent to
(*p_rect).area;
References and Pointers
By default, arguments to a function in C++ are passed by value, and the changes made to any arguments inside the function will not affect the original value, and instead are made to a copy. The 3 primary ways to pass arguments are shown below.
Call by value
int square1(int n)
{
cout << &n; // outputs the memory address of n (0x6dfed4)
n *= n;
// original n is unchanged - a copy of n has been created,
// only the n inside the function is altered
return n;
}
int square1(int n)
{
cout << &n; // outputs the memory address of n (0x6dfed4)
n *= n;
// original n is unchanged - a copy of n has been created,
// only the n inside the function is altered
return n;
}
Call by reference with a pointer argument:
int x = 5;
int *p_x = &x;
square(p_x);
void square(int *n)
{
cout << n; // outputs the memory address of n, which is the same as x (0x6dfed4)
*n *= *n; // deferences to get the value at the memory address of p_x,
// updating the value stored there
cout << *n; // outputs the squared value of x (25)
cout << x; // outputs the altered value of x (25)
}
int x = 5;
int *p_x = &x;
square(p_x);
void square(int *n)
{
cout << n; // outputs the memory address of n, which is the same as x (0x6dfed4)
*n *= *n; // deferences to get the value at the memory address of p_x,
// updating the value stored there
cout << *n; // outputs the squared value of x (25)
cout << x; // outputs the altered value of x (25)
}
Call by reference with reference argument:
int x = 5;
square(x);
void square(int &n)
{
// inside the function, the reference is used to access the
// actual argument used in the call
cout << &n; // outputs the memory address of x (0x6dfed4)
cout << n; // outputs the value of x (5)
// Implicit de-referencing (without '*')
n *= n;
cout << n; // outputs the altered value of n, which is equal to x (25)
cout << nx; // outputs the altered value of x (25)
}
int x = 5;
square(x);
void square(int &n)
{
// inside the function, the reference is used to access the
// actual argument used in the call
cout << &n; // outputs the memory address of x (0x6dfed4)
cout << n; // outputs the value of x (5)
// Implicit de-referencing (without '*')
n *= n;
cout << n; // outputs the altered value of n, which is equal to x (25)
cout << nx; // outputs the altered value of x (25)
}
Benefits of Pointers
There are a number of benefits to using pointers:
- Values that are passed in to a function do not have to be copied, especially useful with large objects.
- They allow more than one value to be returned from a function.
- Memory access through pointers is stated to be more efficient than through an array.
int x = 5;
int y = 55;
int *p_x, *p_y = &x;
edit_two(*x, *y);
void edit_two(int *x, int *y)
{
*x += 5;
*y += 5;
cout << *x; // edits the value at the memory address of x (10)
cout << y*; // edits the value at the memory address of x (60)
}
int x = 5;
int y = 55;
int *p_x, *p_y = &x;
edit_two(*x, *y);
void edit_two(int *x, int *y)
{
*x += 5;
*y += 5;
cout << *x; // edits the value at the memory address of x (10)
cout << y*; // edits the value at the memory address of x (60)
}
Hopefully this has cleared up any confusion surrounding pointers, and you can begin to understand those
insufferable nullptr
exceptions.