Skip to content

Functions

Function Declaration

A function in programming is generally a collection of several statements. We can also call it a subroutine. In programming, if there are repetitive processes, we can extract them to form a function. A function can receive several values, which are called the function's parameters. A function can also return a value, which is called the function's return value.

To declare a function, we need the return type, the function's name, and the parameter list.

1
2
3
4
// Return type int
// Function name some_function
// Parameter list int, int
int some_function(int, int);

As shown above, we declared a function named some_function, which takes two parameters of type int and returns a value of type int. It can be understood that this function will perform some operations on the two integers passed in and return a result of the same type.

Implementing Functions: Writing Function Definitions

Only having the function declaration is not enough; it only lets us know the interface type of the function when calling it (i.e., what data it receives and what data it returns), but it lacks the specific internal implementation, which is the function's definition. We can write code to implement this function elsewhere after the declaration (it can also be implemented in another file, but the separately compiled files need to be provided together during linking).

If a function has a return value, it needs to use the return statement to return the value to the caller. Once the function executes the return statement, it immediately ends the current function and does not execute subsequent statements.

1
2
3
4
5
6
7
8
9
int some_function(int, int);  // Declaration

/* some other code here... */

int some_function(int x, int y) {  // Definition
  int result = 2 * x + y;
  return result;
  result = 3;  // This statement will not be executed
}

In the definition, we give names to the variables in the function's parameter list. This way, we can use these variables within the function definition.

If it is in the same file, we can also directly combine the declaration and definition, in other words, complete the definition at the time of declaration.

1
int some_function(int x, int y) { return 2 * x + y; }

If a function does not need to return a value, mark the function's return type as void; if the function does not need parameters, the parameter list can be left empty. Similarly, a function with no return value will also end execution when it reaches the return; statement.

1
2
3
4
5
6
7
void say_hello() {
  cout << "hello!\n";
  cout << "hello!\n";
  cout << "hello!\n";
  return;
  cout << "hello!\n";  // This statement will not be executed
}

Function Calls

Like variables, functions must be declared before they can be used. The act of using a function is called a "call". We can call other functions inside any function, including the function itself. The act of a function calling itself is called recursion.

In most languages, the syntax for calling a function is the function name followed by a pair of parentheses (), such as foo(). If the function requires parameters, we fill in the required parameters in order within the parentheses, separated by commas, such as foo(1, 2). A function call is also an expression, and the function's return value is the value of the expression.

The parameters written in the function declaration can be understood as variables that can be used inside the current call of the function. These variables are initialized with the values passed in from the call site. Consider the following example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void foo(int, int);

/* ... */

void foo(int x, int y) {
  x = x * 2;
  y = y + 3;
}

/* ... */

a = 1;
b = 1;
// Before call: a = 1, b = 1
foo(a, b);  // Call foo
            // After call: a = 1, b = 1

In the above example, foo(a, b) is a call to foo. During the call, the variables x and y in foo are initialized with the values from the call site a and b, respectively. Therefore, modifications to variables x and y within foo do not affect the values of the variables at the call site.

If we need to modify the value of a variable within a function (subroutine), we need to use "pass by reference".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void foo(int& x, int& y) {
  x = x * 2;
  y = y + 3;
}

/* ... */

a = 1;
b = 1;
// Before call: a = 1, b = 1
foo(a, b);  // Call foo
            // After call: a = 2, b = 4

In the above code, we see that an "& (ampersand)" is added after "int" in the function parameter list, which indicates a reference to an int type. When calling foo, the variables a and b at the call site initialize the two references x and y to int types in foo. In foo, x and y can be understood as "aliases" for the variables a and b at the call site, meaning operations on x and y in foo are operations on a and b at the call site.

The main Function

Specifically, every C/C++ program needs to have a function named main. Any program will start execution from the main function.

The main function can also have parameters. Through the parameters of the main function, we can obtain instructions passed to the program from the outside (i.e., "command-line arguments") to react differently.

Below is a piece of code that calls a function (subroutine):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// hello_subroutine.cpp

#include <iostream>

void say_hello() {
  std::cout << "hello!\n";
  std::cout << "hello!\n";
  std::cout << "hello!\n";
}

int main() {
  say_hello();
  say_hello();
}