5. Functions#

As with most other programming languages, C++ also has the concept of functions, which are named reusable code blocks. They can take any number of parameters to send data into the function. Functions can then return a result to the calling block. Functions exist to perform a specific task within the construct of a larger program. As you use functions within your programs, two fundamental concepts exist:

  1. Defining (declaring) the function. This provides the function’s definition: the name, parameters, and functionality. Unlike Python, you will need to explicitly define functionʼs return type and parameter.  A function’s name, the return type, and the parameter types determine the function’s signature.  Many consider the four parts of a function to be the name, return type, parameters, and the function body.

  2. Calling the function. As we call (execute) a function, we pass a specific list of arguments as the values for a function’s parameters. C++ uses the follow syntax to define a function:

returnType functionName(parameterList) {
    functionBody
}

Unlike Python, you must define both the parameter type and the name. Parameters are separated by commas. In C++, if your function does not return a value, you should declare the function to return void. Otherwise, this is considered a compiler warning. If a function does not have any parameters, you can either place the keyword void in between the parenthesis or leave that space blank.

 1//filename: function.cpp
 2//complile: g++ -std=c++17 -o function function.cpp
 3//execute: ./function
 4#include <iostream>
 5using std::cout;
 6
 7int max(int num1, int num2) {
 8   if (num1 > num2) {
 9       return num1;
10   }
11   else {
12       return num2;
13   }
14}
15
16int main(int argc, char *argv[]) {
17    int num1 = 122, num2 = 22;
18    int result = max(num1,num2);
19    cout << "Maximum of (" << num1 << ", " << num2 << "): " << result << "\n";
20}

Visualize execution

5.1. Passing Parameters#

By default, C++ passes parameters to functions by value. In other words, a copy of a value is placed in the stack frame for the called object. Without using pointers or references, any changes made to a parameter are limited to the scope of that function. The following incorrect implementation of swap demonstrates _call by value _in that a and b remain unchanged after the call to swap.

 1//filename: badswap.cpp
 2//complile: g++ -std=c++17 -o badswap badswap.cpp
 3//execute: ./badswap
 4#include <iostream>
 5using std::cout;
 6
 7void swap(int a, int b) {
 8    int t = a;
 9    a = b;
10    b = t;
11    cout << "in swap: (a, b) = (" << a << ", " << b << ")" << "\n";
12}
13
14int main() {
15    int a = 10;
16    int b = 15;
17    cout << "before swap: (a, b) = (" << a << ", " << b << ")" << "\n";
18    swap(a,b);
19    cout << "after swap: (a, b) = (" << a << ", " << b << ")" << "\n";
20}

The following code prints the address of the variable a - this demonstrates that a is a different object is a different object between main and bad implementation of swap. Remember, that in C++, an object is “a region of data storage in the execution environment, the contents of which can represent values”. The address points (refers) to that data storage in memory. The unary operator & is called the “address-of” operator. This operator provides the address of a variable in memory address. These addresses returned by the address-of operator are known as pointers because they “point” to the variable in memory.The address-of operator can only be applied to lvalues.

 1//filename: swap_address.cpp
 2//complile: g++ -std=c++17 -o swap_address swap_address.cpp
 3//execute: ./swap_address
 4#include <iostream>
 5using std::cout;
 6
 7void swap(int a, int b) {
 8    int t = a;
 9    a = b;
10    b = t;
11    cout << "in swap: a address " << &a << "\n";
12}
13
14int main(int argc, char *argv[]) {
15    int a = 10;
16    int b = 15;
17    cout << "before swap: a address " << &a << "\n";
18    swap(a,b);
19    cout << "after swap: a address " << &a << "\n";
20}

Below, we demonstrate the proper way to pass arguments to a function using C++’s pass-by reference mechanism. We place the “address-of” operator in the function’s parameter definition. Rather than passing the value of the parameter (such as 10 for a), C++ passes the value of a’s address. We can then use the variable as we normally would, and updates are applied to a’s original address - in other words, ais the same object in memory for both main and for swap.

 1//filename: swap_references.cpp
 2//complile: g++ -std=c++17 -o swap_references swap_references.cpp
 3//execute: ./swap_references
 4#include <iostream>
 5using std::cout;
 6
 7void swap(int &a, int &b) {
 8    int t = a;
 9    a = b;
10    b = t;
11
12    cout << "in swap: a address " << &a << "\n";
13    cout << "in swap: (a, b) = (" << a << ", " << b << ")" << "\n";
14}
15
16int main() {
17    int a = 10;
18    int b = 15;
19
20    cout << "before swap: a address " << &a << "\n";
21    cout << "before swap: (a, b) = (" << a << ", " << b << ")" << "\n";
22
23    swap(a,b);
24
25    cout << "after swap: a address " << &a << "\n";
26    cout << "after swap: (a, b) = (" << a << ", " << b << ")" << "\n";
27
28}

The following code demonstrates the C++ use of call by value and return by value with classes. You can see that C++ copies the return value from squarePoint into p2.

 1//filename: class_return.cpp
 2//complile: g++ -std=c++17 -o class_return class_return.cpp
 3//execute: ./class_return
 4#include <iostream>
 5using std::cout;
 6
 7class point {
 8public:
 9    int x;
10    int y;
11};
12
13point squarePoint(point t) {
14    cout << "in call: t address " << &t << "\n";
15    point result;
16    result.x = t.x * t.x;
17    result.y = t.y * t.y;
18    cout << "in call: result address " << &result << "\n";
19    return result;
20}
21
22int main(int argc, char *argv[]) {
23    point p;
24    
25    p.x = 1;
26    p.y = 5;
27
28    cout << "(x,y): (" << p.x << ", " << p.y << ")" << "\n";
29    point p2;
30    cout << "before call: p2 address " <<  &p2 << "\n";
31    p2 = squarePoint(p);
32    cout << "(x,y): (" << p2.x << ", " << p2.y << ")" << "\n";
33    cout << "after call: p2 address " << &p2 << "\n";
34
35    return EXIT_SUCCESS;
36}

To avoid copying large objects, we can also use the reference mechanism, but apply the const keyword to prevent any changes to the original object. Often, this can be a good practice to prevent unintended side effects. You should try altering t in squarePoint (e.g., add the line    t.x = 10;) to see how the compiler handles this situation.

 1//filename: point_const.cpp
 2//complile: g++ -std=c++17 -o point_const point_const.cpp
 3//execute: ./point_const
 4#include <iostream>
 5using std::cout;
 6
 7class point {
 8public:
 9    int x;
10    int y;
11};
12
13point squarePoint(const point &t) {
14    cout << "in call: t address " << &t << "\n";
15    point result;
16    result.x = t.x * t.x;
17    result.y = t.y * t.y;
18    cout << "in call: result address " << &result << "\n";
19    return result;
20}
21
22int main(int argc, char *argv[]) {
23    point p;
24    
25    p.x = 1;
26    p.y = 5;
27
28    cout << "(x,y): (" << p.x << ", " << p.y << ")" << "\n";
29    point p2;
30    cout << "before call: p address " <<  &p << "\n";
31    p2 = squarePoint(p);
32    cout << "(x,y): (" << p2.x << ", " << p2.y << ")" << "\n";
33    cout << "after call: p2 address " << &p2 << "\n";
34
35    return EXIT_SUCCESS;
36}

Note: After attempting to modify t and compiling the program, the compiler should produce the error message “error: assignment of member ‘point::x’ in read-only object”.

5.1.1. Scope#

Scope is pretty much the same between C++ and Python. The biggest difference is that once a code block ends for C++, any variable declared within the scope goes out of scope, even if you are still in the same function. You can think that every { } block forms a level of scope, and you can access variables defined by other blocks that enclose that block.

 1//filename: scope.cpp
 2//complile: g++ -std=c++17 -o scope scope.cpp
 3//execute: ./scope
 4#include <iostream>
 5using std::cout;
 6
 7void swap(int a, int b) {
 8    int t = a;
 9    a = b;
10    b = t;
11    cout << "in swap: (a, b) = (" << a << ", " << b << ")" << "\n";
12}
13
14int main(int argc, char *argv[]) {
15    int a = 10;
16    int b = 15;
17    cout << "before swap: (a, b) = (" << a << ", " << b << ")" << "\n";
18    swap(a,b);
19    cout << "after swap: (a, b) = (" << a << ", " << b << ")" << "\n";
20    if (1) {
21        int a = 33;
22        int c = 50;
23        cout << "Within if: a=" << a << "\n";
24        cout << "Within if: b=" << b << "\n";
25        cout << "Within if: c=" << c << "\n";
26    }
27    cout << "After if: a=" << a << "\n";
28    cout << "Value of c: " << c << "\n";  // This line causes a compilation error. 
29                                          // Try running to see the error, comment out the line, and run again 
30                                          // Note: This is an exeption, to always work top-down with errors
31                                          //       However, note that the first message is a warning, not an error.
32}

5.1.2. Other Notes#

  • C++ does support function overloading as long as the parameter types differ. With Python, the second function definition replaces the first definition.

  • Function naming rules follow that for variables: must start with a letter or underscore followed by any number of letters, numbers, or underscores.  C++ does not have a universal guideline for function names.  Both camelCase and underscores (as in Python) are used to distinguish words.

  • While it will not be covered in this class, functions can have a variable number of arguments in C++. 

  • Unlike Python, C++ cannot nest functions.

5.2. Review Questions#

  1. Compare and constrast functions in Python and C++.

  2. Explain how C++ uses call by value. Why did this create issues with the swap function?