Functions in C++

Functions in C++

Functions in C++

Functions in C++: The Key Piece for Writing Clear and Reusable Code

Have you noticed that as a program grows, its code becomes harder to understand and maintain? If you have ever felt that your code looks like a tangled labyrinth, it is because you are not yet fully leveraging functions in C++. These act as building blocks that allow you to break a program into manageable parts, making it easier to read, maintain, and optimize. In this lesson, you will learn how to use them effectively to improve your code organization, write more structured programs, and make your C++ development more professional and efficient.

Learning Objectives

By the end of this lesson, you will have learned to:

  • Understand the purpose of functions and why they are essential in C++.
  • Create functions correctly, ensuring structured code.
  • Invoke functions within a program and understand how they execute.
  • Distinguish between functions that return values and those that simply execute instructions.
  • Compare different ways to define functions and choose the best approach depending on the situation.

CONTENT INDEX
Declaration, Invocation, and Definition of Functions
Approach: Declare – Invoke – Define
Approach: Declare and Implement Before Invoking
Return Value Propagation
Recursion: Functions That Call Themselves
Multiple Return in Functions
Function Overloading
Inline Functions in C++
Final Reflection on Functions in C++

Function Declaration, Invocation, and Definition

In C++, functions are reusable blocks of code that allow structuring a program in a modular and organized way. Each function encapsulates a specific task, which helps improve code clarity and maintainability. To use a function in a program, we must follow three fundamental steps: declaration, invocation, and definition.

These three concepts are essential, and each serves a particular purpose in the code structure. Let’s examine each one in detail.

  1. Function Declaration

    Before a function can be used in the code, the compiler must be informed of its existence. This is done through a function declaration or prototype.

    The function declaration tells the compiler three fundamental things:

    • The data type that the function will return (or void if it returns nothing).
    • The function’s name.
    • The parameters it accepts (if any), along with their types.

    The general syntax for a function declaration is:

    return_type function_name (parameter_list);
    

    The function declaration is usually placed before main() or in a header file (.h) when working with multiple files.

  2. Function Invocation

    After declaring a function, we can invoke it, meaning we call it within the code so it executes.

    When a function is invoked:

    • The code within its definition is executed.
    • If the function returns a value, it can be stored in a variable or used directly in an expression.
    • If the function is of type void, it simply executes its instructions without returning anything.

    The syntax for invoking a function is simply writing its name followed by parentheses with arguments (if needed):

    function_name(arguments);
    
  3. Function Definition

    Finally, the function definition is the part where its behavior is implemented. Here, the instructions that will be executed when the function is invoked are specified.

    The general syntax for a function definition is:

    return_type function_name (parameter_list) {
        // Function body: instructions to execute
        return value; // (if the function returns a value)
    }
    

    Each function definition must follow these rules:

    • It must match the declaration (if previously declared).
    • If the function returns a value (e.g., int), it must include a return statement with the value to return.
    • If the function does not return anything (void), it simply executes its instructions and does not need a return statement.

Execution Flow

When the program runs, functions are called in the order they appear in main(). The execution flow is as follows:

  1. The compiler recognizes the function declaration.
  2. In main(), when a function call is encountered, the program control is transferred to the function definition.
  3. The function executes its instructions.
  4. If the function has a return value, it is returned to the line where it was called.
  5. The program flow returns to main() or to the function that made the call.

Importance of Prior Declaration

Declaring functions before using them is crucial because the C++ compiler processes the code from top to bottom. If we try to call a function before it has been defined or declared, we will get an error.

There are two main ways to handle this:

  1. Declare the function before main() and define it afterward (as we have seen so far).
  2. Define the function before main(), avoiding the need for a prior declaration.

Both approaches are valid, but the first is more useful in large programs where functions are in different files.

Approach: Declare – Invoke – Define

One of the most commonly used approaches for structuring functions in C++ is declare – invoke – define. Following this logic, we organize our code into three fundamental stages:

  1. Declaration: The compiler is informed about the existence of the function before its use, specifying its name, return type, and parameters (if any).
  2. Invocation: The function is called within the main code (main() in most cases), executing its content.
  3. Definition: The function’s implementation is specified, detailing the instructions it will execute when invoked.

This structure improves code organization, making maintenance and scalability easier. Let’s review an example where we apply this approach:

Example: consoledisplay() Function

In the following code, we follow the declare – invoke – define sequence:

#include <iostream>
using namespace std;
// First, we declare the function
void consoledisplay();
int main() {
    // We invoke the function
    consoledisplay();
    return 0;
}
// We define the previously declared function, detailing its internal behavior
void consoledisplay() {
    cout << "This is a simple string of characters or literals." << endl;
    cout << "Now I show you the number five. Here it is: " << 5 << endl;
    cout << "Let's see what result we get if we do 10/5. The result is: " << 10/5 << endl;
    cout << "A typical way to approximate the number Pi is by doing 22/7. The result is: " << 22/7 << endl;
    cout << "In C++, writing 22/7 is not the same as 22.0/7, the treatment is different." << endl;
    cout << "With this simple change, we can see that 22.0/7 equals " << 22.0/7 << endl;
    cout << "Doesn't this seem like a better approximation?" << endl;
}

The expected output of this code is:

This is a simple string of characters or literals.
Now I show you the number five. Here it is: 5
Let's see what result we get if we do 10/5. The result is: 2
A typical way to approximate the number Pi is by doing 22/7. The result is: 3
In C++, writing 22/7 is not the same as 22.0/7, the treatment is different.
With this simple change, we can see that 22.0/7 equals 3.14286
Doesn't this seem like a better approximation?

To better understand the declare – invoke – define approach, let’s focus on three key parts of the code:

  1. Line 5: Function Declaration
    • void consoledisplay(); informs the compiler that at some point in the code, a function called consoledisplay() will exist.
    • It specifies that its return type is void, meaning it will not return any value.
    • Although the implementation of consoledisplay() is not yet known, this declaration allows the compiler to recognize it when used later.
  2. Line 9: Function Invocation
    • Inside the main() function, the line consoledisplay(); executes the function.
    • At this moment, the compiler already recognizes the existence of consoledisplay() thanks to its prior declaration.
    • When the function is invoked, program control is transferred to its definition, where its content is executed.
  3. Lines 14 to 22: Function Definition
    • This is where the detailed implementation of consoledisplay() is found, specifying its instructions.
    • In this case, the function prints several messages to the console, including numbers and mathematical operations.
    • One important thing is the difference between 22/7 and 22.0/7. When using 22/7, both numbers are integers, resulting in 3 due to integer division. However, when writing 22.0/7, the operation is forced into floating-point arithmetic, yielding 3.14286.

Approach: Declare and Implement Before Invoking

In C++, besides the declare – invoke – define approach, there is another valid way to structure our functions: declare and implement before invoking. This method combines the declaration and definition in a single step, before the function is used in main().

In this approach, instead of making a prior declaration of the function and defining it after main(), we directly declare and define it in the same place before invoking it. This has the advantage of avoiding a separate declaration and making the code more compact and easier to read in small programs.

The general structure of this approach is as follows:

// Define the function before main()
return_type function_name(parameter_list) {
    // Function body
    return value; // if necessary
}
int main() {
    // Function invocation
    function_name(arguments);
}

Since it is defined before main(), the compiler already knows it when invoked, so a prior declaration is not necessary.

Example: consoledisplay() Function Without Prior Declaration

Now let’s see a practical example where we apply this approach:

#include <iostream>
using namespace std;
// Define the function before invoking it
void consoledisplay() {
    cout << "This is a simple string of characters or literals." << endl;
    cout << "Now I show you the number five. Here it is: " << 5 << endl;
    cout << "Let's see what result we get if we do 10/5. The result is: " << 10/5 << endl;
    cout << "A typical way to approximate the number Pi is by doing 22/7. The result is: " << 22/7 << endl;
    cout << "In C++, writing 22/7 is not the same as 22.0/7, the treatment is different." << endl;
    cout << "With this simple change, we can see that 22.0/7 equals " << 22.0/7 << endl;
    cout << "Doesn't this seem like a better approximation?" << endl;
}
int main() {
    // Invoke the function
    consoledisplay();
    return 0;
}

In this code, we can see that:

  1. Between lines 5 and 13, declaration and definition are combined

    The function consoledisplay() is defined directly before main(), without needing a separate declaration.

  2. On line 17, the function is invoked within main()

    Since the function has already been defined before, the compiler recognizes it and allows its execution without issues.

  3. The result is exactly the same

    Functionally, this approach generates the same behavior as the declare – invoke – define approach, but with a more compact structure.

✅ Advantages:

  • More direct and compact code in small programs.
  • No need for a prior declaration, reducing the number of code lines.
  • Easier readability in short scripts where all functions are in the same file.

❌ Disadvantages:

  • In large programs, it can make organization difficult if there are many functions defined before main().
  • Less useful when working with multiple files (.h and .cpp), as it is preferable to keep the declaration in a separate file.

Return Value Propagation

So far, we have worked with functions that simply execute instructions without returning any results. However, in many cases, a function needs to return a value so that it can be used in other calculations or stored in variables. This process is known as return value propagation.

In this section, we will learn how functions that return values work, how they differ from void functions, and how we can take advantage of this mechanism in C++.

Practical Example: Function That Calculates the Area of a Rectangle

To illustrate return value propagation, we will implement a function that receives the base and height of a rectangle and calculates its area.

#include <iostream>
using namespace std;
// Function that calculates the area of a rectangle and returns the result
double calculateArea(double base, double height) {
    return base * height;
}
int main() {
    double base, height;
    
    // Ask the user to enter the values
    cout << "Enter the base of the rectangle: ";
    cin >> base;
    cout << "Enter the height of the rectangle: ";
    cin >> height;
    // Call the function and store its result
    double area = calculateArea(base, height);
    // Display the result
    cout << "The area of the rectangle is: " << area << endl;
    
    return 0;
}
  1. The function calculateArea() returns a value
    • Receives two values (base and height) as parameters.
    • Calculates the area using the multiplication base * height.
    • Uses return to send the result of the operation back to the part of the program that invoked it.
  2. Using the return value in main()
    • The values of base and height are entered by the user.
    • The function calculateArea() is called, and its result is stored in the variable area.
    • Finally, the result is displayed in the console.
  3. Key Difference from a void Function

    If calculateArea() were of type void, we would have to display the result directly inside the function instead of returning it to main() for later use.

Example: Function That Determines If a Number Is Even or Odd

#include <iostream>
using namespace std;
bool isEven(int number) {
    return number % 2 == 0;
}
int main() {
    int number;
    cout << "Enter a number: "; cin >> number;
    if (isEven(number)) {
        cout << "The number is even." << endl;
    } else {
        cout << "The number is odd." << endl;
    }
    return 0;
}

Here, the function isEven() returns true if the number is even and false if it is odd, allowing main() to use the result to decide which message to display.

Recursion: Functions That Call Themselves

Recursion is a technique where a function calls itself to solve problems by breaking them down into smaller versions of themselves. It is particularly useful in algorithms such as factorial calculation, the Fibonacci series, and traversing data structures like trees.

Example: Factorial of a Number

The factorial of a number n is n!=n\cdot(n-1)\cdot(n-2) \cdots 3 \cdot2 \cdot 1. This formulation has a recursive structure that we can mathematically represent as follows:

\begin{array}{rl} 0! &=1\\ n! &= n\cdot(n-1)!\\ \end{array}

With this in mind, we can program this function in C++ as follows:

#include <iostream>
using namespace std;
 
int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1;
    }
    return n * factorial(n - 1);
}
 
int main() {
    int number;
    cout << "Enter a number: "; cin >> number;
    cout << "The factorial of " << number << " is " << factorial(number) << endl;
    return 0;
}

In this code:

  • The function factorial(n) calls itself with n-1 until it reaches the base case (n == 0 or n == 1).
  • The function resolves recursively, multiplying the values until it finds the result.

Example: Fibonacci Numbers

The Fibonacci numbers are those included in the sequence 1, 1, 2, 3, 5, 8, 13, \cdots. This sequence is characterized by the fact that each number is equal to the sum of the previous two.

Mathematically, if fibo(n) is the function whose results are the Fibonacci numbers, then it has the following mathematical structure:

\begin{array}{rl} fibo(0) &= 1\\ fibo(1) &= 1 \\ fibo(n) &= fibo(n-1) + fibo(n-2) \end{array}

A C++ code example that displays Fibonacci numbers is the following:

#include <iostream>
 
using namespace std;
 
int fibo(int number) {
    if (number == 0 || number == 1) {
        return 1;
    }
    return fibo(number - 1) + fibo(number - 2);
}
int main() {
    int x = 0, i = 0;
    cout << "Enter a number: "; cin >> x;
     
    while (i < x) {
        cout << "The Fibonacci number at position " << i + 1 << " is: " << fibo(i) << endl;
        i = i + 1;
    }   
}

Multiple Return Values in Functions

In C++, a function can return more than one value using structures such as std::pair, std::tuple, or references to variables.

Example: Function That Returns Two Values Using std::pair

#include <iostream>
#include <utility> // To use std::pair
using namespace std;
 
pair<int, int> divide(int a, int b) {
    return make_pair(a / b, a % b);
}
 
int main() {
    int numerator = 0, denominator = 1;
     
    cout << "Enter the numerator: "; cin >> numerator;
    cout << "Enter the denominator: "; cin >> denominator;
     
    pair<int, int> result = divide(numerator, denominator);
 
    cout << "Quotient: " << result.first << endl;
    cout << "Remainder: " << result.second << endl;
 
    return 0;
}

Here, the function divide() returns two values: the quotient and the remainder of an integer division.

Example: Function That Returns Multiple Values Using std::tuple

#include <iostream>
#include <tuple>
using namespace std;
tuple<int, int, int> operations(int a, int b) {
    return make_tuple(a + b, a - b, a * b);
}
int main() {
    int sum = 0, difference = 0, product = 0;
    int a = 0, b = 0;
    
    cout << "Enter a number: "; cin >> a;
    
    cout << "Enter another number: "; cin >> b;
    
    std::tie(sum, difference, product) = operations(a, b);
    cout << "Sum: " << sum << ", Difference: " << difference << ", Product: " << product << endl;
    return 0;
}

Function Overloading

Function overloading allows defining multiple functions with the same name but different types or numbers of parameters. This improves code readability and reusability.

#include <iostream>
#include <string> // Required to use std::string
#include <cmath>
using namespace std;
// Area of a square or circle (shapes with one parameter)
double area(double side) {
    return side * side;
}
// Area of a rectangle (or shapes with two parameters)
double area(double base, double height) {
    return base * height;
}
// Area of a triangle (or shapes with three parameters)
double area(double a, double b, double c) {
    return 0.25 * sqrt((a + b + c) * (a + b - c) * (a - b + c) * (-a + b + c));
}
int main() {
    string shape;
    double result = 0;
    double l1 = 0, l2 = 0, l3 = 0;
    // Ask for the shape
    cout << "What shape is it? (square, circle, rectangle, or triangle): ";
    cin >> shape;
    // Evaluate the shape using if-else
    if (shape == "square") {
        cout << "How long is its side? "; cin >> l1;
        result = area(l1);
        cout << "The area of the square is: " << result << endl;
    } 
    else if (shape == "rectangle") {
        cout << "How long is the base? "; cin >> l1;
        cout << "How long is the height? "; cin >> l2;
        result = area(l1, l2);
        cout << "The area of the rectangle is: " << result << endl;
    } 
    else if (shape == "circle") {
        l1 = 3.141592653;
        cout << "How long is the radius? "; cin >> l2;
        result = area(l1, l2);
        cout << "The area of the circle is: " << result << endl;
    } 
    else if (shape == "triangle") {
        cout << "How long are its sides?" << endl;
        cout << "Side 1: "; cin >> l1;
        cout << "Side 2: "; cin >> l2;
        cout << "Side 3: "; cin >> l3;
            
        if ((l1 + l2 + l3) * (l1 + l2 - l3) * (l1 - l2 + l3) * (-l1 + l2 + l3) < 0) {
            cout << "The triangle is impossible";
        }    
        else {
            result = area(l1, l2, l3);
            cout << "The area of the triangle is: " << result << endl;
        }             
    }
    else {
        cout << "Invalid shape." << endl;
    }
    return 0;
}

Inline Functions in C++

Inline functions in C++ provide a mechanism to optimize program performance by reducing function call overhead. Instead of executing a conventional call, the compiler attempts to expand the function’s code directly in every place where it is invoked.

Syntax of an Inline Function

inline return_type function_name(parameter_list) {
    // Function body
    return value; // If necessary
}

By using inline, we eliminate the need to jump to another memory address to execute the function, which can reduce execution time.

Differences Between an Inline Function and a Conventional Function

FeatureConventional FunctionInline Function
Function CallA call with execution jump is performed.The code is copied directly where it is used.
Execution TimeCan be slower due to function call overhead.Can be faster in small functions.
Memory UsageA single copy of the function is stored in memory.Can increase binary size if the function is used many times.

Example of an inline Function

#include <iostream>
using namespace std;
inline int square(int x) {
    return x * x;
}
int main() {
    cout << "The square of 5 is: " << square(5) << endl;
    return 0;
}

🔍 Compiler Process:

  1. The compiler replaces the call square(5) directly with 5 * 5.
  2. There is no execution jump.
  3. The calculation is performed in the same line where the function was invoked.

Advantages and Disadvantages of inline

✅ Advantages

  • Eliminates function call overhead: Reduces execution time in short and frequently called functions.
  • Facilitates compiler optimization: Can improve performance by avoiding CPU register and stack usage.
  • Ensures function code is available at compile time.

❌ Disadvantages

  • Increases binary size: If the inline function is used many times in a large program, the code will be duplicated at every invocation point. This happens when used in long or highly repeated functions in the code.
  • Does not always guarantee inline expansion: The compiler may ignore the inline request if it deems it non-optimal.

Final Reflection on Functions in C++

Functions in C++ are an essential tool for writing modular, reusable, and maintainable code. Throughout this lesson, we have explored everything from the basics of declaration, invocation, and definition to more advanced techniques such as return value propagation, recursion, function overloading, and the use of inline functions. We have also compared different strategies for organizing code and how to choose the best one depending on the context.

Understanding and correctly applying functions will not only make your code clearer and more efficient but also allow you to tackle more complex problems with well-structured solutions. Now you have the foundation to develop C++ programs with a more professional and scalable approach. The best way to reinforce these concepts is through practice, so I encourage you to experiment with different types of functions and apply them in your own projects. Keep exploring and taking your C++ skills to the next level!

Views: 1

Leave a Reply

Your email address will not be published. Required fields are marked *