Previous Lecture lect11 Next Lecture

lect11, Thu 02/14

References, Pointers, Passing Parameters to functions

Announcements!

Lecture material

Readings for today’s lecture

Overview of Recent Readings

Pass by value

Let’s write a function that swaps the values of two integer variables.

// swap.cpp
#include <iostream>
using namespace std;

void swapValue(int x, int y){
    int tmp = x; //temporary variable tmp records the value of x before the swap
    x = y;
    y = tmp;   
    //after the last statement, x holds the value of y; y holds the previous value of x
}

int main() {
    int a=30, b=40;
    cout << a <<"  "<< b << endl;
    swapValue( a,  b);
    cout << a <<"  "<< b << endl;
}

However, when we run our main(), we notice that the output is:

30 40

30 40

The values of a and b remain unchanged!

What? How?

This is the reason for this unexpected behavior (important):

When a and b are passed into swapValues(), new local variables x and y are created as copies of a and b! The following assignments took place “under the hood” during the call to swapValue:

int x = a;
int y = b;

x and y are created inside the function, and are automatically deleted when the function returns. They are at different locations in memory than a and b, and, therefore, anything you do inside the function has no effect on a and b. Initially x was equal to 30, and y was equal to 40, then their values were swapped, but a and b remained unchanged!

Let’s look at how we can change this.

References

References can be thought of as nicknames/aliases for variables. For example:

int v = 500;
int& r = v; //r is a reference to v

The ampersand sign (&) indicates that r is a reference to v. This means that both v and r point to the same location in memory where value 500 is stored, and, essentially, r is just another name for v.

cout << v << " " << r << endl; //500 500
v++;
r++;
cout << v << " " << r << endl; //502 502

How can we apply this to our previous problem?

Pass by reference

Notice how the following code differs from the previous one: void swapValue(__int& x, int &y__) as opposed to void swapValue(int a, int b).

// swap-ref.cpp
#include <iostream>
using namespace std;

void swapValue(int& x, int& y){
    int tmp = x;

    cout << "x = " << x << endl;  // print the value stored inside of variable x
    cout << "y = " << y << endl;  // print the value stored inside of variable y
    cout<< "&x = " << &x <<"  "<< "&y = " << &y <<endl; 
    // print the reference/address where the variables x and y are stored
    // note that these addresses are the same as the addresses of a and b that we printed in main()
    x = y; // change the value of x
    y = tmp; // change the value of y
}

int main() {
    int a=30, b=40;
    cout << "=== Before the swap ===" << endl;
    cout<< a <<"  "<< b <<endl;
    swapValue( a,  b);
    cout << "=== After the swap ===" << endl;
    cout<< a <<"  "<< b <<endl;
    cout<< "&a = " << &a <<"  "<< "&b = " << &b <<endl;
    // print the reference/address where the variables a and b are stored
    // note that these addresses are the same as the addresses of x and y that we printed in swapValue()
}

The following assignments took place “under the hood” during the call to swapValue:

int& x = a;
int& y = b;

When references to variables are created inside the function, we call it a “pass by reference”.

As you’ve learned from last paragraph, x and y are references of a and b, and therefore all changes to x and y apply to a and b.

But what is &a and &x that are printed?

These are locations of a and x in memory. Locations in memory are represented in hexadecimals, so printing &a will result in something like 0x7ffc517b543a (note that this value will be different, when you run this code, because the compiler will assign variables to different memory locations).

You can get a location/address/reference of any variable using the ampersand sign (&).

This is the output:

=== Before the swap ===
30  40
&x = 0x7fffbea98426  &y = 0x7fffbea9841c
=== After the swap ===
40  30
&a = 0x7fffbea98426  &b = 0x7fffbea9841c

Let’s pause and review what we’ve learned:

Make sure the above code makes sense to you: both, the reference and the variable they are referencing are assigned to the same memory location.

Same functionality using pointers instead

Remember how we were able to get the memory address of any variable using the ampersand sign? As it turns out, we can store those locations in something called pointers.

Pointers are data types just like ints or chars, but instead of numbers and characters, a pointer stores a memory address.

Write this down somewhere you can see it while you are programming, and keep referencing this statement: a pointer stores a memory address (represented by a hexadecimal value).

To create a pointer, specify the type of an object that the pointer will point to and add an asterisk (*).

int a = 80;
cout << &a << endl; //0x2a8c64d78a0a
int* p; //p (a pointer to an integer) was created
p = &a; //p now stores the address of a.
cout << p << endl; //0x2a8c64d78a0a, same address as above

What can we do with pointers? Well, we can access the things they point to using the asterisk.

int a = 80;
int* p = &a;
cout << p << endl; //0x2a8c64d78a0a
cout << *p << endl; //80

This is very important

This process of getting the value stored in memory, which is pointed to by the address stored inside a pointer, is called dereferencing a pointer.

Look at the last example with a and p. *p is equivalent to a. Why? Because p == (&a), so *p == *(&a) This command is saying: “Get me the address of a (&a), and then get me what’s stored at that address (a)”

Let’s see how we can solve the same problem of swapping variables, this time, using pointers:

// swap-ptr.cpp
#include <iostream>
using namespace std;

void swapValue(int* x, int* y){
    cout << "x = " << x << endl;  // address of a
    cout << "*x = " << *x << endl; // value stored at a
    cout << "y = " << y << endl; // address of b
    cout << "*y = " << *y << endl; // value stored at b
    cout<< "&x = " << &x <<"  "<< "&y = " << &y <<endl;
    // the above line prints the address of the *pointers* x and y
    // note that their addresses are different from the *values* (which are addresses) stored at x and y
    // remember that the addresses of a and b that we printed are the *values*/addresses stored at x and y
    
    int tmp = *x; //tmp stores 30
    *x = *y;
    *y = tmp;
}

int main() {
    int a=30, b=40;
    cout << "=== Before the swap ===" << endl;
    cout<< a <<"  "<< b <<endl;
    swapValue( &a, &b); //passing LOCATIONS OF a and b
    cout << "=== After the swap ===" << endl;
    cout<< a <<"  "<< b <<endl;
    cout<< "&a = " << &a <<"  "<< "&b = " << &b <<endl;
}

The following assignments took place “under the hood” during the call to swapValue:

int* x = &a;
int* y = &b;

We accessed the values stored in a and b by dereferencing x and y pointers, which store the locations of a and b respectively.

Let’s pause and review what we’ve learned:

Make sure this also makes sense and you understand when to use & and *, and what vaue each would provide.

Pointers and arrays

When we learned about arrays, we promised to come back to them once we’ve leanred about pointers. Here we are. The variable declared as an array is actually just a pointer to the first element.

int arr[] = {5,4,3}; // arr points to the first element (currently 5)
cout << arr << endl; //0x2a8c64d78a14
cout << &(arr[0]) << endl; // same as `arr`, 0x2a8c64d78a14

Note that when you run the above code, you will get different values, because the compiler will assign variables to different memory locations.

Final notes

Pointers and references are confusing! They are arguably the hardest topic of the course, and are used a lot in C++. They are not intuitive, and may not make sense at first. That’s okay. We will be using them a lot, and it will get better. Use the resourses you have, including these notes and slides, and ask for help. Don’t wait until the week of the midterm.

Key concepts:

Next time: structs, more pointers, dynamic arrays

Read Section 10.1: Structures, since you will need to implement structs for the next lab (Lab 05).