Hunter Liu's Website

12. Week 6 Thursday: Function Scope and Passing by Reference

≪ 11. Week 6 Tuesday: Functions | Table of Contents | 13. Week 7 Tuesday: Vectors! ≫

Earlier this week, we discussed the basics of functions and their syntax in C++. To recap, a function is a set of instructions that “teaches” C++ how to perform a certain task, such as taking the squareroot of a double or finding the divisor sum of an int.

These tasks may or may not have inputs, and the way we want a function to handle these inputs may vary depending on the scenario. To illustrate what I mean, consider the following real-world scenario:

You’re working as a secretary at a huge public unversity now, and you need to do a bunch of data processing on the database of student records. You have a very helpful assistant to help you perform some of this data processing, but there is a dilemma. On one hand, you can give them your login to the school database and have them directly do their job. While convenient, this has some security flaws: do you really want a lowly assistant to have unfettered access to all of this sensitive information? What if your assistant screws up and irreversibly messes up decades of student records?

The alternative is to print out or digitally create a copy of the entire school’s database. This fixes the security issues we addressed above, and as a bonus, even if your assistant is a moron and screws everything up, at least you’ll have a backup. However, there are some clear drawbacks as well. Any changes made to the copy of the database must be copied over to the school’s actual database after your assistant is done. This is quite redundant! Moreover, making the copy itself might take a long time, especially if you’re low-tech and want to print it all out on paper!

This is the dilemma we face in C++ when “passing” data into a function. By default, function parameters are passed “by value” — a copy of the inputs are created for the function, so that changes to the inputs from within the function are not reflected outside of the function. For instance, the following program prints the number 5:

 1#include <iostream> 
 2
 3using namespace std; 
 4
 5void increment(int n) {
 6    n = n + 1; 
 7}
 8
 9int main() {
10    int n = 5;
11    increment(n); 
12
13    cout << n << endl; 
14
15    return 0; 
16} 

The n in increment is a copy of the n in main; the two variables are “decoupled”. Changes made to the n in increment do not affect the n in main.

Passing “by reference” is the alternative, where you give a function direct access to a variable that you’re giving it. This is denoted by an ampersand & between the type and name of an input parameter. For instance, consider the following code:

 1#include <iostream> 
 2
 3using namespace std; 
 4
 5void increment(int& n) {
 6    n = n + 1; 
 7}
 8
 9int main() {
10    int n = 5;
11    increment(n); 
12
13    cout << n << endl; 
14
15    return 0; 
16} 

This prints out 6 instead of 5! The n inside increment now “points to” the n inside of main. C++ understands that the two variables represent the same thing, even if they’re in separate functions, and even if you give them different names.

Some Background on Computer Memory, and When Passing by Reference Can Fail

We can sometimes use “literals” — explicit values that could be stored inside variables — as arguments to a function. The 6.25 in sqrt(6.25) is a literal, for instance, and the number returned (2.5) behaves a lot like a literal as well. What happens if I use a literal in a pass-by-reference argument? Will the function have direct access to a literal number, specified only by my code? For instance, what happens in the following code?

 1#include <iostream>
 2
 3using namespace std; 
 4
 5void increment(int& n) {
 6    n = n + 1; 
 7} 
 8
 9int main() {
10    increment(5); 
11    cout << "Hello world!" << endl; 
12
13    return 0;
14} 

The answer is, you get a build error! To understand why, I think it’s a good idea to take a closer look at how C++ programs are represented in computer memory. The following explanation is simplified significantly, but understanding how programs interact with computer memory is paramount to future topics (namely, pointers).

When your computer runs a C++ program, it sections off a little bit of RAM for the main program, just enough space for each of the variables that you use throughout your program. Whenever you call a function, such as sqrt or increment, your computer allocates another chunk of space to hold all the variables in that function.

In addition to the main function and any other functions that get called, your computer also createsa a little chunk of memory to hold the program’s actual instructions! This chunk of memory cannot be modified by the program. This chunk of memory is used to remember all the constants in your code, including the 5 on line 10. So when me write the line increment(5), the compiler understands that we’re asking for direct access to something we shouldn’t ever have access to, thereby resulting in a compilation error.

Practise Problems

In the following, predict the output of the code. Some of these programs have build errors in them; in the case that they do, determine the error.

Problem 1.

 1#include <iostream> 
 2#include <string>
 3
 4using namespace std; 
 5
 6void foo(int& n, int m, string s) {
 7    while(n < m) {
 8        cout << s << endl; 
 9
10        // deletes the last character of s 
11        s.pop_back(); 
12        n++; 
13    } 
14} 
15
16int main() {
17    int a = 5, b = 7; 
18    string s = "Zoo wee mama!"; 
19
20    foo(a, b, s); 
21    cout << a << endl; 
22    cout << s << endl; 
23    return 0; 
24} 

Problem 2.

 1#include <iostream>
 2
 3using namespace std; 
 4
 5void gah(int a, int& b) {
 6    if(a != 0) {
 7        b = b % a; 
 8    } 
 9
10    a += b; 
11} 
12
13void zot(int& c, int d) {
14    gah(c, d); 
15    gat(d, c); 
16} 
17
18int main() {
19    int e = 17, f = 94; 
20    zot(e, f); 
21
22    cout << e << endl; 
23    cout << f << endl; 
24
25    return 0; 
26} 

Problem 3.

 1#include <cmath> 
 2#include <iostream> 
 3#include <string> 
 4
 5using namespace std; 
 6
 7string bar(double& d, string s) {
 8    while(d > 0) {
 9        s = s + s; 
10        d = d - 1; 
11    }
12
13    return s; 
14} 
15
16int main() {
17    string s1 = "abc"; 
18    string s2 = bar(sqrt(47), s1); 
19
20    cout << s1 << endl; 
21    cout << s2 << endl;
22    return 0; 
23}

As a final remark, we don’t quite yet have the background necessary to demonstrate an application of passing variables by reference. However, once we begin discussing object-oriented programming and the concept of a “class” in C++, we’ll begin seeing more and more appearances of these ideas, especially in the form of pointers!