Functions are the building blocks of any programming language. In C++, a function is a block of code that performs a specific task. Think of it as a mini-program within your main program, designed to execute a particular operation. Functions are like building blocks that help break down complex problems into manageable chunks, making your code more readable and modular.
Understanding functions is like unlocking the door to a world of organized and efficient coding. In this article, we’ll take a closer look at what functions are, why they are essential, and how you can leverage them to enhance your C++ programming skills.
Table of Contents
- Declaration of a Function
- Anatomy of a Function
- Pass by Reference
- The Default Parameters
- Variable Arguments to a Function
- Function Name Overloading
Declaration of a Function
The standard form of declaration of a function is:
1 2 3 4 | return_type function_name(parameters list) { //body of the function } |
Anatomy of a Function
There are two main parts of the function, function header and the function body. Look at the following sa
1 2 3 4 5 | // Return_type function_name(Parameter_list) { // // Function body // // Code to perform the task // return result; // (optional, depends on the return type) // } |
Function Header
In the first line of the C++ code below has three main parts which comprises a function header.
- The name of the function i.e. function_name()
- The parameters of the function enclosed in parenthesis ( Parameter_list )
- Return value type i.e. int
Function Body
Function body is the actual code that performs the specified task. This is enclosed within curly braces {}
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <iostream> // Function declaration int add(int a, int b) { return a + b; } int main() { // Function call int result = add(5, 7); // Output the result std::cout << "The sum is: " << result << std::endl; return 0; } |
Function Prototype
A function prototype in C++ is a declaration of a function that tells the compiler about the function’s name, return type, and the types of its parameters. It provides essential information to the compiler which allows it to understand how the function should be called and what values it expects.
The general syntax of a function prototype is:
Using function prototypes is important in larger programs or when functions are defined after they are called. By providing the compiler with a prototype, you inform it about the existence and signature of the function, enabling it to catch potential errors related to function calls and parameter mismatches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <iostream> // Function prototype int add(int a, int b); int main() { // Function call int result = add(5, 7); // Output the result std::cout << "The sum is: " << result << std::endl; return 0; } // Function definition int add(int a, int b) { return a + b; } |
In this example, the function prototype int add(int a, int b);
informs the compiler about the add
function’s signature before it’s actually defined in the code. This allows the main
function to call add
without any issues.
How does Prototyping Work?
Prototyping is a powerful technique in C++ that involves declaring the structure of a function before its actual implementation. To understand its impact, let’s experiment with a simple program. Take a look at the code snippet below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <iostream> // Function prototype void printValues(double a, int b, long c); int main() { double x = 12.2; int y = 13; long z = 12345; // Try changing the parameters and observe the compiler's response printValues(x, y, z); // Original parameters // Experiment with different parameter changes // printValues(12.0, 13); // Error: Not enough arguments // printValues(&x, y, z); // Error: Type mismatch with the address of 'x' return 0; } // Function definition void printValues(double a, int b, long c) { std::cout << "Values: " << a << ", " << b << ", " << c << std::endl; } |
In this example, the printValues
function prototype is declared before the main
function. The parameters (double, int, long) signify the types of values the function expects. By experimenting with different parameter changes, you can observe how the compiler responds. For instance, providing too few or mismatched types of arguments will result in error messages, highlighting the importance of accurate function prototypes.
To reinforce this understanding, try changing the return type in the prototype and notice how the compiler guides you through adjusting both the prototype and the function header.
Further Explanation on Prototyping
Let’s examine the next example program for a little more information on prototyping.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream> void do_stuff(int, float, char); int main() { int arm = 2; float foot = 1000.0; char lookers = 65; // Function calls with different arguments do_stuff(3, 12.0, 67); do_stuff(arm, foot, lookers); return 0; } // Function definition with explanatory comments void do_stuff(int wings, // Number of wings float feet, // Number of feet char eyes) // Number of eyes { // Output information based on function parameters std::cout << "There are " << wings << " wings." << "\n"; std::cout << "There are " << feet << " feet." << "\n"; std::cout << "There are " << eyes << " eyes." << "\n\n"; } |
In this example, the function prototype in line 4 lacks variable names. While comments alongside parameters in the function header can improve clarity, it’s crucial to rely on well-chosen variable names for effective communication within your code. Strive to use comments judiciously and prioritize clear and meaningful variable naming conventions for optimal code readability.
Pass by Reference
Passing variables to functions usually involves creating copies, leaving the original values untouched. However, there exists a powerful construct called “pass by reference,” a feature not available in ANSI-C. Let’s explore this concept through an example program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> #include <stdio.h> void fiddle(int in1, int &in2); int main() { int count = 7, index = 12; // Display initial values std::cout << "The values are " << count << " " << index << "\n"; // Call function with pass by reference fiddle(count, index); // Display values after function call std::cout << "The values are " << count << " " << index << "\n"; return 0; } // Function definition using pass by reference void fiddle(int in1, int &in2) { in1 = in1 + 100; in2 = in2 + 100; // Display values within the function std::cout << "The values are " << in1 << " " << in2 << "\n"; } |
Upon executing this program, you’ll notice that the values of the first variable (count
) get changed within the function but revert to their original state upon returning to the main program. On the other hand, the second variable (index
) undergoes a transformation in the function, and this change is reflected back into the main program.
To achieve pass by reference, observe the function prototype in line 4, where the second variable is denoted with an ampersand (&
). This instructs the compiler to treat the variable as if a pointer to the original variable were passed, making modifications directly in the main program’s variable. The function itself uses the referenced variable (in2
) just like any other variable, but it manipulates the original variable from the main program.
If you prefer a cleaner prototype without variable names, you can write it as follows:
The Default Parameters
In C++, default parameters offer a convenient way to enhance the flexibility of function calls. Let’s explore an example program to understand how default parameters work and how they can simplify code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream.h> #include <stdio.h> int get_volume(int length, int width = 2, int height = 3); int main() { int x = 10, y = 12, z = 15; // Displaying box data with different parameter combinations cout << "Some box data is " << get_volume(x, y, z) << "\n"; cout << "Some box data is " << get_volume(x, y) << "\n"; cout << "Some box data is " << get_volume(x) << "\n"; cout << "Some box data is "; cout << get_volume(x, 7) << "\n"; cout << "Some box data is "; cout << get_volume(5, 5, 5) << "\n"; return 0; } // Function definition with default parameters int get_volume(int length, int width, int height) { printf("%4d %4d %4d ", length, width, height); return length * width * height; } |
In this example, the get_volume
function has default values assigned to the width
and height
parameters in its prototype. This means that if these parameters are not explicitly provided during a function call, the default values (2 for width and 3 for height) will be used. Let’s break down the function calls in the main
function:
- Line 11: All three parameters specified.
- Line 12: Only two parameters specified, using the default value for height.
- Line 13: Only one parameter specified, defaulting both width and height.
The output reflects the values passed to the function, with the reversed order explained later in the program.
Some rules to keep in mind:
- Once a parameter is given a default value, all subsequent parameters must also have default values.
- Default values must match the correct types.
- Default values can be specified in either the prototype or the function header, but not both.
It’s advisable to provide default values in the prototype for clarity and consistency, especially when venturing into object-oriented programming techniques. Default parameters are a powerful tool, enabling you to create more flexible and readable code by allowing some parameters to be omitted during function calls. Happy coding!
Variable Arguments to a Function
There are occasions when your C++ function needs to accept a variable number of arguments. While this practice should be approached with caution due to its potential to introduce complexity, it can be a powerful tool when used judiciously. The following example program illustrates the use of a variable number of arguments in a function call:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream.h> #include <stdarg.h> void display_var(int number, ...); int main() { int index = 5; int one = 1, two = 2; // Displaying variable number of arguments display_var(one, index); display_var(3, index, index + two, index + one); display_var(two, 7, 3); return 0; } void display_var(int number, ...) { va_list param_pt; va_start(param_pt, number); // Call the setup macro cout << "The parameters are "; for (int index = 0; index < number; index++) cout << va_arg(param_pt, int) << " "; // Extract a parameter cout << "\n"; va_end(param_pt); // Closing macro } |
In this example, the display_var
function is declared with the first parameter as an integer (number
) indicates the number of subsequent arguments to expect. The ellipsis (...
) in the parameter list allows for a variable number of arguments without enforcing strong type checking beyond the first parameter.
The main
function showcases three different calls to display_var
, each with a different number of parameters. The system accommodates these differences, and the function successfully processes the arguments provided.
Keep in mind that while this capability offers flexibility, it also relinquishes the robust type checking normally enforced by C++. Therefore, it’s imperative to ensure that the correct types are used in the function call, as the compiler will not perform type checking beyond the first parameter.
Function Name Overloading
In C++, function name overloading empowers programmers to create multiple functions with the same name, each tailored to handle different types and numbers of parameters. The example program below illustrates the versatility of function name overloading:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include <iostream.h> // Keyword 'overload' (optional in C++ version 1.2) overload do_stuff; // Function prototypes with overloading int do_stuff(const int); int do_stuff(float); float do_stuff(const float, float); int main() { int index = 12; float length = 14.33; float height = 34.33; // Displaying results of overloaded function calls cout << "12 squared is " << do_stuff(index) << "\n"; cout << "24 squared is " << do_stuff(2 * index) << "\n"; cout << "Three lengths is " << do_stuff(length) << "\n"; cout << "Three heights is " << do_stuff(height) << "\n"; cout << "The average is " << do_stuff(length, height) << "\n"; return 0; } // Overloaded function: squares an integer int do_stuff(const int in_value) { return in_value * in_value; } // Overloaded function: triples a float and returns an integer int do_stuff(float in_value) { return (int)(3.0 * in_value); } // Overloaded function: averages two floats float do_stuff(const float in1, float in2) { return (in1 + in2) / 2.0; } |
In this example, three functions coexist with the same name, do_stuff
, each serving a unique purpose based on the types and number of parameters. The program’s output demonstrates the successful execution of overloaded functions, producing results that align with the input provided.
A crucial point to note is that the selection of which function to call is determined at compile time, not runtime. The compiler identifies the appropriate function based on the number and types of parameters specified during the function call. The return type does not play a role in this selection process.
The optional keyword overload
(used in line 4) is a vestige from C++ version 1.2. In more recent versions, this keyword is not required but can be used for compatibility with older code.