Pointers
Python is a programming language lacking a notion of pointers. It will therefore be impossible to show the Python version of many of the C++ examples presented this week.
Up to this point, our variables have been working exclusively with data.
This is familiar and straight-forward: if you want to make a copy of a value then use the assignment (=
) operator.
Often, however, it is more convenient to work with addresses rather than with data.
Declaring a Pointer Variable
To declare a pointer variable, use the asterisk (*
) operator between the data-type and the variable name.
Note that one never declares a pointer.
Instead, one declares a pointer to an integer, a pointer to a double, or a pointer to some other data-type.
In other words, it is necessary to tell the compiler what data-type we are pointing to.
C++ |
---|
#include <iostream> using namespace std; /************************* * MAIN *************************/ int main() { // Declare a pointer to an integer. Leave it uninitialized. int * pInteger; // Declare a pointer to a character. Leave it uninitialized. char * pCharacter; // Declare a pointer to a double. Initialize it to the NULL address. double * pDouble = nullptr; return 0; } |
There are a few things to notice about this.
First, it is customary to put the p
prefix
before the pointer variable.
This reminds us we are working with a pointer rather than with data.
The second thing to notice is we can initialize the pointer to nullptr
.
This is a special address which says, "This pointer is not referring to anything in particular."
Try it yourself! Declare a variable that is a pointer to a Boolean.
Address-Of
Every variable in C++ resides in some memory location.
With the address-of operator (&
), it is possible to query a variable for its address at any point in the program.
Note that even though Python does not expose pointers to the user directly,
the id()
function provides similar functionality.
Python | C++ |
---|---|
# Create a variable with some value. x = 42 # Display the "identity" of the value, # the address of the object in memory. print(id(x)) |
#include <iostream> using namespace std; /************************* * MAIN *************************/ int main() { // Create a variable with some value. int x = 42; // Display the "identity" of the value, // the address of the object in memory. cout << &x << endl; return 0; } |
The most common use of the address-of operator is to initialize a pointer.
It is important that the data-type of the pointer
matches the data-type of object from which we retrieve the address.
For example, a pointer to an integer (int *
) variable
is needed when getting the address of an integer (int
).
C++ |
---|
#include <iostream> using namespace std; /************************* * MAIN *************************/ int main() { // A bunch of normal data variables. int valueInteger; // integer float valueFloatingPoint; // floating point number bool valueBoolean; // Boolean char valueCharacter; // character // A bunch of pointer variables, none of which are initialized. int * pInteger; // pointer to integer float * pFloatingPoint; // pointer to a floating point number bool * pBoolean; // pointer to a Boolean value char * pCharacter; // pointer to a character // Make the pointers refer to the normal data variables. pInteger = &valueInteger; // both sides of = are pointers to integers pFloatingPoint = &valueFloatingPoint; // both sides are pointers to floats pBoolean = &valueBoolean; // both sides are pointers to Booleans pCharacter = &valueCharacter; // both sides are pointers to characters return 0; } |
In the first assignment (pInteger = &valueInteger
), the data-type of valueInteger
is int
.
When the address-of operator (&
) is added to the expression, the data-type becomes "pointer to int."
The left-side of the assignment is pInteger
which is declared as a "pointer to an int."
Since both sides are the same data-type (pointer to an int), then there is not a problem with the assignment.
If we tried to assign &valueInteger
to pFloatPoint
, we would get the following compile error:
example.cpp: In function “int main()”: example.cpp:20: error: cannot convert “int*” to “float*” in assignment
Note that we can display a pointer on the screen,
but it will just give us nonsense.
It will tell us where a variable happens to be located in memory
during this execution.
The next time the program is executed, the operating system will likely
assign the program a different location in memory so the address will change.
A pointer address looks something like this:
0xbfff0921
.
Try it yourself!
Declare a variable that is a pointer to a double and make it point to a variable that is a double
.
Dereference
We can always retrieve the data from a given address using the dereference operator (*
),
also known as the "indirection operator."
Python offers functionality similar to the dereference operator in the form of the ctypes
object.
Python | C++ |
---|---|
# One needs the ctypes library # to directly access memory. import ctypes # Create a variable and get the id. x = 42 p = id(x) # Display the contents of # the location in memory. value = ctypes.cast(id, ctypes.py_object).value print(value) |
#include <iostream> using namespace std; /************************* * MAIN *************************/ int main() { // Create a variable and get the id. int x = 42; int * p = &x; // Display the contents of // the location in memory. int value = *p; cout << value << endl; return 0; } |
For this to be accomplished the compiler must know the data-type of the location in memory and the address must be valid. Since the data-type is part of the pointer declaration, the first part is not a problem. It is up to the programmer to ensure the second constraint is met. In other words, the compiler ensures a data variable is always referring to a valid location in memory. However, with pointers, the programmer needs to set up the value. Consider the following example:
C++ |
---|
#include <iostream> using namespace std; /************************* * MAIN *************************/ int main() { // Declare two variables - one a data variable and one a pointer. int speed = 65; // the location in memory we will be pointing to int * pSpeed = nullptr; // Assign pSpeed to the address of speed. pSpeed = &speed; // Display the value that pSpeed is pointing to. This will be 65. cout << *pSpeed << endl; return 0; } |
If we removed the dereference operator (*
) from the cout
statement: cout << pSpeed << endl;
,
then we would pass a “pointer to an integer” to cout
.
This would not display the value 65
, but rather the location where that value exists in memory:
0x7fff9d235d74
.
If we skipped the initialization step in the above code (pSpeed = &speed
),
then the variable pSpeed
would remain un-initialized.
Thus, when we dereference it, it would refer to a location in memory (segment) the program does not own.
This would cause a segmentation fault (a.k.a “crash”) at run-time:
Segmentation fault (core dumped)
.
Try it yourself!
Create a double variable called gpa
.
Create a pointer to a double and call it pGpa
,
Make pGpa
point to gpa
.
Finally, display the data pGpa
points to.
Classes
It is possible to create a pointer to an object in much the same way we can create a pointer to any other data type.
Unfortunately the syntax of accessing member variables gets a bit complex.
We need to first dereference the structure pointer with the dereference operator (*
)
before we can access the member variables with the dot operator.
The problem arises from the fact the dot operator comes before the dereference operator in the order of operations.
To make our intentions clear, we need to use parentheses.
Consider the following example:
C++ |
---|
#include <iostream> using namespace std; /************************* * POSITION *************************/ class Position { public: int altitude; double latitude; double longitude; }; /************************* * MAIN *************************/ int main() { // Declare an object and a pointer to an object. Position pos; Position * pPos = &pos; // Access the member variable with the dot operator. pos.altitude = 4865; (*pPos).altitude = 5260; return 0; } |
To avoid this nasty syntax involving the dot, parentheses, and dereference operator,
a more convenient syntax was developed: the arrow operator (->
).
The arrow operator (also known as "member of pointer operator")
allows us to access member variables from a pointer to a structure without the unpleasant syntax.
C++ Dot Operator | C++ Arrow Operator |
---|---|
/************************* * MAIN *************************/ int main() { // Declare an object // and a pointer to an object. Position pos; Position * pPos = &pos; // Access the member variable // with the dot operator. pos.altitude = 4865; (*pPos).altitude = 5260; return 0; } |
/************************* * MAIN *************************/ int main() { // Declare an object // and a pointer to an object. Position pos; Position * pPos = &pos; // Access the member variable // with the dot operator. pos.altitude = 4865; pPos->altitude = 5260; return 0; } |
Try it yourself!
Create a class called Complex
with two member variables: real
and complex
.
Create an object (c
) and a pointer to an object (p
).
Set the member variables to p
with the dot operator and with the arrow operator.
Also, display the contents of the member variables with the dot operator and with the arrow operator.
Create a main()
function to test this class.
This
When writing a method within a class,
one has access to all the class's member variables.
With Python, one can access the member variables with the self
variable.
With C++, one needs to use the this
variable.
There is just one catch: this
is a pointer.
Python | C++ |
---|---|
class Gpa ''' Represent a GPA ''' # One member variable. gpa = 4.0 def set(self, gpa): ''' Set the GPA ''' self.gpa = gpa def get(self): ''' Return the GPA ''' return self.gpa |
/************************* * GPA: Represent a GPA *************************/ class Gpa { private: // one member variable float gpa; public: Gpa() : gpa(4.0) {} // Set the GPA void set(float gpa) { this->gpa = gpa; } // Return the GPA float get() const { return this->gpa; } }; |
Observe how Python requires an explicit self
parameter
to the member functions.
C++ does not.
The this
parameter is present but hidden.
The programmer does not need to place the this
parameter
in the parameter list, the compiler silently places it in there for you.
Second, Python allows the programmer to access the member variables
with the dot operator (.
).
Since this
is a pointer in C++,
it is necessary to use the arrow operator.
Try it yourself!
Create a class called Complex
with two member variables: real
and complex
.
Create three methods: set()
taking two parameters,
getReal()
, and getImaginary()
.
Create a main()
function to test this class.
Pointers and Functions
Up to this point in time, we have had two ways to pass a parameter: pass-by-value which makes a copy of the variable so the callee can’t change it, and pass-by-reference which links the variable from the callee to that of the caller so the callee can change it. Now, with pointers, we can use pass-by-pointer. Pass-by-pointer is sending a copy of the address of the caller’s variable to the callee. With this pointer, the callee can then make a change to be reflected in the caller’s data. To illustrate this point, consider the following code:
C++ |
---|
/******************************************************************** * FUNCTION * Demonstrate pass by-value, pass by-reference, and pass by-pointer *******************************************************************/ void function(int value, int &reference, int * pointer) { cout << "value: " << value << " &value: " << &value << endl; cout << "reference: " << reference << " &reference: " << &reference << endl; cout << "*pointer: " << *pointer << " pointer: " << pointer << endl; } /******************************************************************** * Just call function() ********************************************************************/ int main() { int number; cout << "Please give me a number: "; cin >> number; cout << "number: " << number << "\t&number: " << &number << endl << endl; function(number /*by value */, number /*by reference*/, &number /*by pointer */); return 0; } |
The output of this program is:
Please give me a number: 100 number: 100 &number: 0x7fff5fcbf07c value: 100 &value: 0x7fff5fcbf54c reference: 100 &reference: 0x7fff5fcbf07c *pointer: 100 pointer: 0x7fff5fcbf07c
Observe the value of number in main()
(100
) and the address in main()
(0x7fff5fcbf07c
).
The first parameter is pass-by-value.
Here, we would expect the value to be copied, which it is.
Since pass-by-value creates a new variable that is independent of the caller’s variable, we would expect that to be in a different address.
This is the case in the above example.
The address of value is 0x7fff5fcbf54c
, different than that of number
.
The second parameter is pass-by-reference.
The claim was the caller’s variable and the callee’s variable are linked.
Now we can see that the linking is accomplished by making sure both refer to the same location in memory.
In other words, because they have the same address (0x7fff5fcbf07c
), any change made to reference should also change number.
The final parameter is pass-by-pointer.
A more accurate way of saying this is we are passing a pointer by value (in other words, making a copy of the address).
Since the address is duplicated in the pointer variable, it follows that the value of *pointer
should be the same as that of number
.
The only difference between pass-by-pointer and pass-by-reference is notational.
With pass-by-pointer, we need to use the address-of operator (&
) when passing the parameter
and the dereference operator (*
) when accessing the value.
With pass-by-reference, we use the ampersand (&
) in the callee’s parameter.
Aside from these differences, they behave exactly the same.
This is why the C programming language does not have pass-by-reference; it doesn’t need it!
Try it yourself! Copy-Paste this code and run it a few times yourself. Observe how the address changes each execution, but the relationship between the addresses does not. In other words, the by-value address is always different than the by-reference or by-pointer address. Also, the by-reference and by-pointer addresses are always the same.
Arrays
It turns out that C++ arrays are simply pointers. The pointer refers to the first element in the array.
C++ | Output |
---|---|
#include <iostream> using namespace std; /**************************** * MAIN ****************************/ int main() { // declare an array and a pointer double values[3] = { 1.1, 2.2, 3.3 }; double *p = values; // The first value can be accessed // two ways: [] or * cout << values[0] << endl; cout << *p << endl << endl; // both "values" and "p" have // the same address cout << "values: " << values << endl; cout << "p: " << p << endl; return 0; } |
1.1 1.1 0x7fff5fcbf54c 0x7fff5fcbf54c |
When we pass an array to a function, we can pass it as an array or as a pointer.
C++ | Output |
---|---|
#include <iostream> using namespace std; /**************************** * FUNCTION ****************************/ void function(double array1[], double *array2) { cout << array1[0] << endl; cout << array2[0] << endl; } /**************************** * MAIN ****************************/ int main() { // declare an array double values[3] = { 1.1, 2.2, 3.3 }; // display the first element function(array1, array2); return 0; } |
1.1 1.1 |