CommonLounge Archive

C++ Functions

September 11, 2018

In the tutorials so far in this course, you’ve used functions many times. In this tutorial, you’ll see how to create new ones!

Your own functions!

Remember functions like length() that you can execute in C++? Well, good news – you will learn how to write your own functions now!

A function is a sequence of instructions that C++ should execute. As an example, let’s define a function that just prints a line of text, and call it from the main function.

void greet() {
    cout << "Hi there!" << endl;
}
int main() {
    greet();
    return 0;
}

Okay, our first function is ready!

In lines 1-3, we define the function. That is, we are telling C++ what should happen when this function is called. In lines 5-8, we have our standard main() function. But notice that in line 6, we are calling function greet() that we defined earlier.

Note: C++ compiles the file from top to bottom. When C++ sees a function call, if it hasn’t yet found the function definition, it will throw an error. Hence, we define the function before calling it.

When you run the code above, it produces the following output:

Hi there!

As always, C++ execution starts at the beginning of the main() function. The main function just calls the greet() function. When the function is called, C++ goes and executes the function. Hence, it outputs Hi there!.

Functions with parameters

Let’s build our first function with parameters. We will use the previous example – a function that says ‘Hi there!’ to the person running it – with a name:

void greet(string name)

As you can see, we now gave our function a parameter that we called name:

void greet(string name) {
    cout << "Hi " << name << "!" << endl; 
}
int main() {
    greet();   
    return 0;
}

Within the function, a parameter works exactly the same as a variable. Let’s see how it works now:

error - too few arguments to function 'void greet(std::string)'

Oops, an error. Luckily, C++ gives us a pretty useful error message. It tells us that we are using too few arguments when calling the function greet() (the one we defined).

We’re using the words parameters and arguments interchangeably. We think parameters is much more clear to a beginner, but arguments is the word used conventionally.

Let’s fix it by replacing greet(); at the bottom of the file with:

greet("Commonlounge");

And run it again. We get the output:

Hi Commonlounge!

And if we change the name?

greet("Dave");

And run it:

Hi Dave!

This is awesome, right? This way you don’t have to repeat yourself every time you want to change the name of the person the function is supposed to greet. And that’s exactly why we need functions – you never want to repeat your code!

Multiple parameters and return values

So far, you have seen functions which take one parameter, and print something based on that. In fact, a function can take multiple inputs, and also return a value.


Below is the general syntax for defining a function in C++:

return_type function_name(parameter1, parameter2, ...) {
    ... function body ... 
}

So far, we have been using return_type void, because our functions have not been returning anything.

Here’s an example of a function that has two int parameters and returns an int.

int subtract(int x, int y) {
    int z = x - y;
    return z; 
}
int main() {
    int result = subtract(5, 2);
    cout << result << endl;       // Outputs: 3
}

The above subtract function takes two inputs, which it calls x and y. Both x and y are of type int. We can also see that the return_type of subtract is an int. All of this can be seen in line 1 itself.

In the function body (lines 2-3) the function calculates the difference between x and y and stores it in variable z. Then, it returns the value stored in z. Note that the type of this value must match the type in specified before the function name (in line 1).

Hence, when we call subtract(5, 2), the returned value is 3. You can assign this value to a variable, or use it any other way you want.

Also recall that you have already used functions before which return a value (length() ). Now you can write your own!

When evaluating an expression, functions evaluate to the value they return. For example:

subtract(5, 2) + 8
=> 3 + 8
=> 11 

Note that you can also return a string, bool, etc. Here’s an example of a function which returns a string.

string joinString(string x, string y) {
    return x + y;
}
int main() {    
    cout << joinString("Hello", "World") << endl;
    return 0;  
}
// Output - HelloWorld

More realistic example

So far, we’ve been using very simple examples just to learn what functions are. Here’s a more realistic example which calculates the maximum of 2 values

int maximum(int x, int y) {
    if (x > y) {
        return x; 
    } 
    else { 
        return y; 
    }
}
int main() {
    cout << maximum(2, 5) << endl;    // Outputs: 5
    cout << maximum(60, 40) << endl;  // Outputs: 60
    cout << maximum(17, 17) << endl;  // Outputs: 17
}

Default argument values

There’s one last thing about functions you should know - default argument values. Take a look at the function below:

void greet(string name="Commonlounge") {
    cout << "Hi " << name << "!" << endl; 
}
int main() { 
    greet();              // "Hi Commonlounge!"
    greet("Alice");       // "Hi Alice!"
    return 0;
}

In the above function, void hi(string name="Commonlounge") means that if the arguments name is not given when calling the function (line 6), the default value of name will be "Commonlounge". If the argument is given (line 7), the provided value will be used.

Default argument values come in specially handy when functions have a lot of arguments. Let’s see an example:

void greet(string name="Commonlounge", string greeting="Hi") {
    cout << greeting << " " << name << "!" << endl;
}
int main() {
    // Here are *ALL* the ways you can call the function.
    greet();                         // "Hi Commonlounge!"
    greet("Alice");                  // "Hi Alice!"
    greet("Alice", "Hello");         // "Hello Alice!"
    return 0;
}

In particular, notice that there’s no way to pass just the 2nd argument without passing the 1st argument.


Finally, you can also write functions where some arguments are mandatory and some optional. For example:

void greet(string name, string greeting="Hi") {
    cout << greeting << " " << name << "!" << endl;
}

In the above function, name is a mandatory argument, and greeting is optional. So you can call it in the following ways:

greet("Alice");                          // "Hi Alice"
greet("Alice", "Hello");                 // "Hello Alice"

In all cases, the first argument must be passed. Only the second argument is optional.

Pass by value vs Pass by reference

Let’s learn about pass by value vs pass by reference - two different ways of passing parameters to a function.

Pass by value

In pass by value (normal way), when you pass a variable to a function, it does not use the original variable for performing the task. It makes a copy of your original variable. Hence any modification on parameters inside the function will not reflect in the actual variable.

For example, let’s take an empty cup and try to fill it with tea, with the help of fillCup(int height) function, where argument represents the initial height, till where cup is filled.

#include <iostream>
using namespace std;
void fillCup(int height) {
    height = height + 5;
    cout << height << endl; 
}
int main() {
    int ht = 0;
    fillCup(ht);
    // Output - 5 (the copy inside the function changed)
    cout << ht << endl;
    // Output - 0 (original value unchanged)
    return 0;
}

As you can see the function didn’t change the value of ht variable. It modified its own copy of the variable and updated that one.

Pass by reference

In pass by reference, instead of passing the value of the variable, we pass its address. This allows us to modify the original variable.

This can be achieved by passing by reference. Hence any modification on parameters inside the function will reflect in the actual variable. To pass by reference, we add an ampersand & before the parameter name in the function declaration.

#include <iostream>
using namespace std;
// function accepts *REFERENCE* to the variable
void fillCup(int &height) {
    height = height + 5;
    cout << height << endl;
}
int main() {
    int ht = 0;
    fillCup(ht);      // pass the variable by reference
    // Output - 5 
    cout << ht << endl;
    // Output - 5 (original value modified)
    return 0;
}

Now we can see that ht has been updated to 5. Here’s an animation to recap what you just read:

Animation for pass by reference vs pass by value. Pass by value created a copy of the cup and modifies that. Pass by reference modifies the original cup, no copy is created.

Real-world practical examples using pass by reference

The earlier example was a toy example. Let’s see a couple of examples where passing by reference makes sense.

Swapping values

Let’s say we want a function to swap two values.

void swap(int &a, int &b) {
    int tmp = a; 
    a = b; 
    b = tmp; 
}
int main() {
    int x = 1, y = 2, z = 3; 
    swap(x, y); 
    cout << x << ' ' << y << ' ' << z << endl; // Output 2 1 3
    swap(y, z); 
    cout << x << ' ' << y << ' ' << z << endl; // Output 2 3 1
    swap(z, x);
    cout << x << ' ' << y << ' ' << z << endl; // Output 1 3 2 
    return 0; 
}

If we used pass by value, the original values would never get modified. In some sense, passing by reference is allowing us to return multiple values from a function.

Passing large variables

Another important use case for passing by reference is passing large variables, i.e. variables that are storing a lot of data. If we pass a lot of data by value, then a lot of computation is wasted copying the data. Hence, C++ arrays are always passed by reference, there’s no way to pass them by value. Here’s an example:

void modify_arr(int arr[]) {
    arr[0] = 100; 
}
int main() {
    int array[50]; 
    array[0] = 10; 
    cout << array[0] << endl; 
    // Output - 10 
    modify_arr(array); 
    cout << array[0] << endl; 
    // Output - 100 
    return 0; 
}

Note: When accepting an array as a function parameter, you have to be extra careful to not modify the array if the caller expects the array to remain unchanged.

But for a string which has thousands or millions of characters, C++ will still copy it if you pass the string to a function. Hence, you want to explicitly pass by reference. Here’s an example:

void modify_string(string &str) {
    str[0] = 'b'; 
}
void cant_modify_string(string str) {
    str[0] = 'c'; 
}
int main() {
    string s(10000, 'a'); // s = 'aaaaa...aaaaa' (length 10000)
    cout << s.substr(0, 5) << endl; 
    // Output - aaaaa
    modify_string(s); 
    cout << s.substr(0, 5) << endl; 
    // Output - baaaa
    cant_modify_string(s); 
    cout << s.substr(0, 5) << endl; 
    // Output - baaaa
    return 0; 
}

As you can see, passing by reference doesn’t create another copy, whereas passing by value does.

Note: If you are passing by reference just to avoid extra computation, then you have to careful to not modify the value of the original string by mistake.

Summary

In this chapter, you learnt about

  • functions - a set of instructions that are executed when a function is called
  • arguments and return values - passing values when calling a function, and getting a result
  • passing by value vs passing by reference - passing arguments so that they can be directly modified by the function and a copy of the argument is not made

That’s it. You totally rock! This was a tricky chapter, so you should feel proud of yourself. We’re definitely proud of you for making it this far!


© 2016-2022. All rights reserved.