Lesson 7. Organizing Code with Functions

So far in this book you have seen simple programs where all programming effort is contained in main(). This works well for really small programs and applications. The larger and more complex your program gets, the longer the contents of main() become, unless you choose to structure your program using functions.

Functions give you a way to compartmentalize and organize your program’s execution logic. They enable you to split the contents of your application into logical blocks that are invoked sequentially.

A function is hence a subprogram that optionally takes parameters and returns a value, and it needs to be invoked to perform its task. In this lesson you learn

Image The need for programming functions

Image Function prototypes and function definition

Image Passing parameters to functions and returning values from them

Image Overloading functions

Image Recursive functions

Image C++11 lambda functions

The Need for Functions

Think of an application that asks the user to enter the radius of a circle and then computes the circumference and area. One way to do this is to have it all inside main(). Another way is to break this application into logical blocks: in particular two that compute area and circumference given radius, respectively. See Listing 7.1.

LISTING 7.1 Two Functions That Compute the Area and Circumference of a Circle Given Radius


  0: #include <iostream>
  1: using namespace std;
  2:
  3: const double Pi = 3.14159265;
  4:
  5: // Function Declarations (Prototypes)
  6: double Area(double radius);
  7: double Circumference(double radius);
  8:
  9: int main()
 10: {
 11:    cout << "Enter radius: ";
 12:    double radius = 0;
 13:    cin >> radius;
 14:
 15:    // Call function "Area"
 16:    cout << "Area is: " << Area(radius) << endl;
 17:
 18:    // Call function "Circumference"
 19:    cout << "Circumference is: " << Circumference(radius) << endl;
 20:
 21:    return 0;
 22: }
 23:
 24: // Function definitions (implementations)
 25: double Area(double radius)
 26: {
 27:    return Pi * radius * radius;
 28: }
 29:
 30: double Circumference(double radius)
 31: {
 32:    return 2 * Pi * radius;
 33: }


Output Image

Enter radius: 6.5
Area is: 132.732
Circumference is: 40.8407

Analysis Image

main(), which is also a function, is compact and delegates activity to functions such as Area() and Circumference() that are invoked in Lines 16 and 19, respectively.

The program demonstrates the following artifacts involved in programming using functions:

Image Function prototypes are declared in Lines 6 and 7, so the compiler knows what the terms Area and Circumference are when used in main() mean.

Image Functions Area() and Circumference() are invoked in main() in Lines 16 and 19.

Image Function Area() is defined in Lines 25–28, Circumference() in Lines 30–33.

Compartmentalizing the computation of area and circumference into different functions can potentially help reuse as the functions can be invoked repeatedly, as and when required.

What Is a Function Prototype?

Let’s take a look at Listing 7.1 again—Lines 6 and 7 in particular:

double Area(double radius);
double Circumference(double radius);

Figure 7.1 shows what a function prototype is comprised of.

Image

FIGURE 7.1 Parts of a function prototype.

The function prototype basically tells what a function is called (the name, Area), the list of parameters the function accepts (one parameter, a double called radius), and the return type of the function (a double).

Without the function prototype, on reaching Lines 16 and 19 in main() the compiler wouldn’t know what the terms Area and Circumference are. The function prototypes tell the compiler that Area and Circumference are functions; they take one parameter of type double and return a value of type double. The compiler then recognizes these statements as valid and the job of linking the function call to its implementation and ensuring that the program execution actually triggers them is that of the linker.


Note

A function can have multiple parameters separated by commas, but it can have only one return type.

When programming a function that does not need to return any value, specify the return type as void.


What Is a Function Definition?

The actual meat and potatoes—the implementation of a function—is what is called the definition. Analyze the definition of function Area():

 25: double Area(double radius)
 26: {
 27:    return Pi * radius * radius;
 28: }

A function definition is always comprised of a statement block. A return statement is necessary unless the function is declared with return type void. In this case, Area() needs to return a value because the function has been declared as one that returns as double. The statement block contains statements within open and closed braces ({...}) that are executed when the function is called. Area() uses the input parameter radius that contains the radius as an argument sent by the caller to compute the area of the circle.

What Is a Function Call, and What Are Arguments?

Calling a function is the same as invoking one. When a function declaration contains parameters, the function call needs to send arguments. Arguments are values the function requests within its parameter list. Let’s analyze a call to Area() in Listing 7.1:

16:    cout << "Area is: " << Area(radius) << endl;

Here, Area(radius) is the function call, wherein radius is the argument sent to the function Area(). When invoked, execution jumps to function Area() that uses the radius sent to compute the area of the circle.

Programming a Function with Multiple Parameters

Assume you were writing a program that computes the surface area of a cylinder, as shown in Figure 7.2.

Image

FIGURE 7.2 A cylinder.

The formula you use would be the following:

Area of Cylinder = Area of top circle + Area of bottom circle + Area of Side
                 = Pi *  radius^2 + Pi * radius ^2 + 2 * Pi * radius * height
                 = 2 * Pi * radius^2 + 2 * Pi * radius * height

Thus, you need to work with two variables, the radius and the height, in computing the area of the cylinder. In such cases, when writing a function that computes the surface area of the cylinder, you specify at least two parameters in the parameter list, within the function declaration. You do this by separating individual parameters by a comma as shown in Listing 7.2.

LISTING 7.2 Function That Accepts Two Parameters to Compute the Surface Area of a Cylinder


  0: #include <iostream>
  1: using namespace std;
  2:
  3: const double Pi = 3.14159265;
  4:
  5: // Declaration contains two parameters
  6: double SurfaceArea(double radius, double height);
  7:
  8: int main()
  9: {
 10:    cout << "Enter the radius of the cylinder: ";
 11:    double radius = 0;
 12:    cin >> radius;
 13:    cout << "Enter the height of the cylinder: ";
 14:    double height = 0;
 15:    cin >> height;
 16:
 17:    cout << "Surface area: " << SurfaceArea(radius, height) << endl;
 18:
 19:    return 0;
 20: }
 21:
 22: double SurfaceArea(double radius, double height)
 23: {
 24:    double area = 2 * Pi * radius * radius + 2 * Pi * radius * height;
 25:    return area;
 26: }


Output Image

Enter the radius of the cylinder: 3
Enter the height of the cylinder: 6.5
Surface Area: 179.071

Analysis Image

Line 6 contains the declaration of the function SurfaceArea() with two parameters: radius and height, both of type double, separated by a comma. Lines 22–26 show the definition—that is, the implementation of SurfaceArea(). As you can see, the input parameters radius and height are used to compute the value stored in the local variable area that is then returned to the caller.


Note

Function parameters are like local variables. They are valid within the scope of the function only. So in Listing 7.2, parameters radius and height within function SurfaceArea() are copies of variables with similar names within main().


Programming Functions with No Parameters or No Return Values

If you delegate the task of saying “Hello World” to a function that does only that and nothing else, you could do it with one that doesn’t need any parameters (as it doesn’t need to do anything apart from say “Hello”), and possibly one that doesn’t return any value (because you don’t expect anything from such a function that would be useful elsewhere). Listing 7.3 demonstrates one such function.

LISTING 7.3 A Function with No Parameters and No Return Values


  0: #include <iostream>
  1: using namespace std;
  2:
  3: void SayHello();
  4:
  5: int main()
  6: {
  7:    SayHello();
  8:    return 0;
  9: }
 10:
 11: void SayHello()
 12: {
 13:    cout << "Hello World" << endl;
 14: }


Output Image

Hello World

Analysis Image

Note that the function prototype in Line 3 declares function SayHello() as one with return value of type void—that is, SayHello() doesn’t return a value. Consequently, in the function definition in Lines 11–14, there is no return statement. Some programmers prefer to insert a symbolic empty return statement at the end:

void SayHello()
{
   cout << "Hello World" << endl;
   return; // an empty return
}

Function Parameters with Default Values

In samples thus far, you assumed the value of Pi, fixed it as a constant, and never gave the user an opportunity to change it. However, the user may be interested in a less or more accurate reading. How do you program a function that would use a default value of Pi of your choosing unless another one is supplied?

One way of solving this problem is to supply an additional parameter in function Area() for Pi and supply a value chosen by you as a default one. Such an adaptation of function Area() from Listing 7.1 would look like the following:

double Area(double radius, double pi = 3.14);

Note the second parameter pi is assigned a default value of 3.14. This second parameter is therefore an optional parameter for the caller. The function Area() can be invoked as if the second parameter didn’t exist:

Area(radius);

In this case, the second parameter defaults to the value of 3.14. However, when required, the same function can be invoked using two arguments:

Area(radius, 3.14159); // more precise pi

Listing 7.4 demonstrates how you can program functions that contain default values for parameters that can be overridden with a user-supplied value, if available and desired.

LISTING 7.4 Function That Computes the Area of a Circle, Using Pi as a Second Parameter with Default Value 3.14


  0: #include <iostream>
  1: using namespace std;
  2:
  3: // Function Declarations (Prototypes)
  4: double Area(double radius, double pi = 3.14);
  5:
  6: int main()
  7: {
  8:    cout << "Enter radius: ";
  9:    double radius = 0;
 10:    cin >> radius;
 11:
 12:    cout << "pi is 3.14, do you wish to change this (y / n)? ";
 13:    char changePi = 'n';
 14:    cin >> changePi;
 15:
 16:    double circleArea = 0;
 17:    if (changePi == 'y')
 18:    {
 19:       cout << "Enter new pi: ";
 20:       double newPi = 3.14;
 21:       cin >> newPi;
 22:       circleArea = Area (radius, newPi);
 23:    }
 24:    else
 25:       circleArea = Area(radius); // Ignore 2nd param, use default value
 26:
 27:    // Call function "Area"
 28:    cout << "Area is: " << circleArea << endl;
 29:
 30:    return 0;
 31: }
 32:
 33: // Function definitions (implementations)
 34: double Area(double radius, double pi)
 35: {
 36:    return pi * radius * radius;
 37: }


Output Image

Enter radius: 1
Pi is 3.14, do you wish to change this (y / n)? n
Area is: 3.14

Next run:

Enter radius: 1
Pi is 3.14, do you wish to change this (y / n)? y
Enter new Pi: 3.1416
Area is: 3.1416

Analysis Image

In the two runs, the radius entered by the user was the same—1. In the second run, however, the user opted for the choice to change the precision of Pi, and hence the area computed is slightly different. Note that in both cases, as seen in Lines 22 and 25, you invoke the same function. Line 25 invokes Area() without the second parameter pi. In this case, the parameter pi in Area() contains value 3.14, supplied as default in the declaration in Line 4.


Note

You can have multiple parameters with default values; however, these should all be at the tail end of the parameter list.


Recursion—Functions That Invoke Themselves

In certain cases, you can actually have a function call itself. Such a function is called a recursive function. Note that a recursive function should have a very clearly defined exit condition where it returns without invoking itself again.


Caution

In the absence of an exit condition or in the event of a bug in the same, your program execution gets stuck at the recursive function that won’t stop invoking itself, and this eventually stops when the “stack overflows,” causing an application crash.


Recursive functions can be useful when determining a number in the Fibonacci series as shown in Listing 7.5. This series starts with two numbers, 0 and 1:

F(0) = 0
F(1) = 1

And the value of a subsequent number in the series is the sum of the previous two numbers. So, the nth value (for n > 1) is determined by the (recursive) formula:

Fibonacci(n) = Fibonacci(n - 1) + Fibonacci(n - 2)

As a result the Fibonacci series expands to

F(2) = 1
F(3) = 2
F(4) = 3
F(5) = 5
F(6) = 8, and so on.

LISTING 7.5 Using Recursive Functions to Calculate a Number in the Fibonacci Series


  0: #include <iostream>
  1: using namespace std;
  2:
  3: int GetFibNumber(int fibIndex)
  4: {
  5:    if (fibIndex < 2)
  6:       return fibIndex;
  7:    else // recursion if fibIndex >= 2
  8:       return GetFibNumber(fibIndex - 1) + GetFibNumber(fibIndex - 2);
  9: }
 10:
 11: int main()
 12: {
 13:    cout << "Enter 0-based index of desired Fibonacci Number: ";
 14:    int index = 0;
 15:    cin >> index;
 16:
 17:    cout << "Fibonacci number is: " << GetFibNumber(index) << endl;
 18:    return 0;
 19: }


Output Image

Enter 0-based index of desired Fibonacci Number: 6
Fibonacci number is: 8

Analysis Image

The function GetFibNumber() defined in Lines 3–9 is recursive as it invokes itself at Line 8. The exit condition programmed in Lines 5 and 6 ensures that the function will return without recursion if fibIndex is less than two. Thus, GetFibNumber() invokes itself recursively with ever reducing values of fibIndex. It ultimately reaches a state where the exit condition is satisfied, the recursion ends, and a Fibonacci value is determined and returned to main().

Functions with Multiple Return Statements

You are not restricted to having only one return statement in your function definition. You can return from any point in the function, and multiple times if you want, as shown in Listing 7.6. Depending on the logic and the need of the application, this might or might not be poor programming practice.

LISTING 7.6 Using Multiple Return Statements in One Function


  0: #include <iostream>
  1: using namespace std;
  2: const double Pi = 3.14159265;
  3:
  4: void QueryAndCalculate()
  5: {
  6:    cout << "Enter radius: ";
  7:    double radius = 0;
  8:    cin >> radius;
  9:
 10:    cout << "Area: " << Pi * radius * radius << endl;
 11:
 12:    cout << "Do you wish to calculate circumference (y/n)? ";
 13:    char calcCircum = 'n';
 14:    cin >> calcCircum;
 15:
 16:    if (calcCircum == 'n')
 17:        return;
 18:
 19:    cout << "Circumference: " << 2 * Pi * radius << endl;
 20:    return;
 21: }
 22:
 23: int main()
 24: {
 25:    QueryAndCalculate ();
 26:
 27:    return 0;
 28: }


Output Image

Enter radius: 1
Area: 3.14159
Do you wish to calculate circumference (y/n)? y
Circumference: 6.28319

Next run:

Enter radius: 1
Area: 3.14159
Do you wish to calculate circumference (y/n)? n

Analysis Image

The function QueryAndCalculate() contains multiple return statements: one at Line 17 and the next one at Line 20. If the user presses 'n' for calculating circumference, the program quits by using the return statement. For all other values, it continues with calculating the circumference and then returning.


Caution

Use multiple returns in a function with caution. It is a lot easier to understand and follow a function that starts at the top and returns at the bottom than one that returns at multiple points in-between.

In Listing 7.6, use of multiple returns could’ve been avoided simply by changing the if condition to testing for 'y' or yes:

if (calcCircum == 'y')
   cout << "Circumference: " << 2*Pi*radius << endl;


Using Functions to Work with Different Forms of Data

Functions don’t restrict you to passing values one at a time; you can pass an array of values to a function. You can create two functions with the same name and return value but different parameters. You can program a function such that its parameters are not created and destroyed within the function call; instead, you use references that are valid even after the function has exited so as to allow you to manipulate more data or parameters in a function call. In this section you learn about passing arrays to functions, function overloading, and passing arguments by reference to functions.

Overloading Functions

Functions with the same name and return type but with different parameters or set of parameters are said to be overloaded functions. Overloaded functions can be quite useful in applications where a function with a particular name that produces a certain type of output might need to be invoked with different sets of parameters. Say you need to be writing an application that computes the area of a circle and the area of a cylinder. The function that computes the area of a circle needs a parameter—the radius. The other function that computes the area of the cylinder needs the height of the cylinder in addition to the radius of the cylinder. Both functions need to return the data of the same type, containing the area. So, C++ enables you to define two overloaded functions, both called Area, both returning double, but one that takes only the radius as input and another that takes the height and the radius as input parameters as shown in Listing 7.7.

LISTING 7.7 Using an Overloaded Function to Calculate the Area of a Circle or a Cylinder


  0: #include <iostream>
  1: using namespace std;
  2:
  3: const double Pi = 3.14159265;
  4:
  5: double Area(double radius);   // for circle
  6: double Area(double radius, double height); // for cylinder
  7:
  8: int main()
  9: {
 10:    cout << "Enter z for Cylinder, c for Circle: ";
 11:    char userSelection = 'z';
 12:    cin >> userSelection;
 13:
 14:    cout << "Enter radius: ";
 15:    double radius = 0;
 16:    cin >> radius;
 17:
 18:    if (userSelection == 'z')
 19:    {
 20:       cout << "Enter height: ";
 21:       double height = 0;
 22:       cin >> height;
 23:
 24:       // Invoke overloaded variant of Area for Cyclinder
 25:       cout << "Area of cylinder is: " << Area (radius, height) << endl;
 26:    }
 27:    else
 28:       cout << "Area of cylinder is: " << Area (radius) << endl;
 29:
 30:    return 0;
 31: }
 32:
 33: // for circle
 34: double Area(double radius)
 35: {
 36:    return Pi * radius * radius;
 37: }
 38:
 39: // overloaded for cylinder
 40: double Area(double radius, double height)
 41: {
 42:    // reuse the area of circle
 43:    return 2 * Area (radius) + 2 * Pi * radius * height;
 44: }


Output Image

Enter z for Cylinder, c for Circle: z
Enter radius: 2
Enter height: 5
Area of cylinder is: 87.9646

Next run:

Enter z for Cylinder, c for Circle: c
Enter radius: 1
Area of cylinder is: 3.14159

Analysis Image

Lines 5 and 6 declare the prototype for the overloaded forms of Area(): The first overloaded variant accepts a single parameter—radius of a circle. The next one accepts two parameters—radius and height of a cylinder. The function is called overloaded because there are two prototypes with the same name, Area(); same return types, double; and different sets of parameters. The definitions of the overloaded functions are in Lines 34–44, where the two functions determine the area of a circle given the radius and the area of a cylinder given the radius and height, respectively. Interestingly, as the area of a cylinder is comprised of the area of the two circles it contains (one on top and the other on the bottom) in addition to the area of the sides, the overloaded version for cylinder was able to reuse Area() for the circle, as shown in Line 43.

Passing an Array of Values to a Function

A function that displays an integer can be represented like this:

void DisplayInteger(int Number);

A function that can display an array of integers has a slightly different prototype:

void DisplayIntegers(int[] numbers, int Length);

The first parameter tells the function that the data being input is an array, whereas the second parameter supplies the length of the array such that you can use the array without crossing its boundaries. See Listing 7.8.

LISTING 7.8 Function That Takes an Array as a Parameter


  0: #include <iostream>
  1: using namespace std;
  2:
  3: void DisplayArray(int numbers[], int length)
  4: {
  5:    for (int index = 0; index < length; ++index)
  6:       cout << numbers[index] << " ";
  7:
  8:    cout << endl;
  9: }
 10:
 11: void DisplayArray(char characters[], int length)
 12: {
 13:    for (int index = 0; index < length; ++index)
 14:       cout << characters[index] << " ";
 15:
 16:    cout << endl;
 17: }
 18:
 19: int main()
 20: {
 21:    int myNums[4] = {24, 58, -1, 245};
 22:    DisplayArray(myNums, 4);
 23:
 24:    char myStatement[7] = {'H', 'e', 'l', 'l', 'o', '!', ''};
 25:    DisplayArray(myStatement, 7);
 26:
 27:    return 0;
 28: }


Output Image

24 58 -1 245
H e l l o !

Analysis Image

There are two overloaded functions called DisplayArray() here: one that displays the contents of elements in an array of integers and another that displays the contents of an array of characters. In Lines 22 and 25, the two functions are invoked using an array of integers and an array of characters, respectively, as input. Note that in declaring and initializing the array of characters in Line 24, you have intentionally included the null character—as a best practice and a good habit—even though the array is not used as a string in a cout statement or the like (cout << myStatement;) in this application.

Passing Arguments by Reference

Take another look at the function in Listing 7.1 that computed the area of a circle given the radius:

24: // Function definitions (implementations)
25: double Area(double radius)
26: {
27:    return Pi * radius * radius;
28: }

Here, the parameter radius contains a value that is copied into it when the function is invoked in main():

15:    // Call function "Area"
16:    cout << "Area is: " << Area(radius) << endl;

This means that the variable radius in main() is unaffected by the function call, as Area() works on a copy of the value radius contains, held in radius. There are cases where you might need a function to work on a variable that modifies a value that is available outside the function, too, say in the calling function. This is when you declare a parameter that takes an argument by reference. A form of the function Area() that computes and returns the area as a parameter by reference looks like this:

// output parameter result by reference
void Area(double radius, double& result)
{
   result = Pi * radius * radius;
}

Note how Area() in this form takes two parameters. Don’t miss the ampersand (&) next to the second parameter result. This sign indicates to the compiler that the second argument should NOT be copied to the function; instead, it is a reference to the variable being passed. The return type has been changed to void as the function no longer supplies the area computed as a return value, rather as an output parameter by reference. Returning values by references is demonstrated in Listing 7.9, which computes the area of a circle.

LISTING 7.9 Fetching the Area of a Circle as a Reference Parameter and Not as a Return Value


  0: #include <iostream>
  1: using namespace std;
  2:
  3: const double Pi = 3.1416;
  4:
  5: // output parameter result by reference
  6: void Area(double radius, double& result)
  7: {
  8:    result = Pi * radius * radius;
  9: }
 10:
 11: int main()
 12: {
 13:    cout << "Enter radius: ";
 14:    double radius = 0;
 15:    cin >> radius;
 16:
 17:    double areaFetched = 0;
 18:    Area(radius, areaFetched);
 19:
 20:    cout << "The area is: " << areaFetched << endl;
 21:    return 0;
 22: }


Output Image

Enter radius: 2
The area is: 12.5664

Analysis Image

Note Lines 17 and 18 where the function Area() is invoked with two parameters; the second is one that should contain the result. As Area() takes the second parameter by reference, the variable result used in Line 8 within Area points to the same memory location as the double areaFetched declared in Line 17 within the caller main(). Thus, the result computed in function Area() at Line 8 is available in main() and displayed on the screen in Line 20.


Note

A function can return only one value using the return statement. So, if your function needs to perform operations that affect many values required at the caller, passing arguments by reference is one way to get a function to supply those many modifications back to the calling module.


How Function Calls Are Handled by the Microprocessor

Although it is not extremely important to know exactly how a function call is implemented on a microprocessor level, you might find it interesting. Understanding this helps you understand why C++ gives you the option of programming inline functions, which are explained later in this section.

A function call essentially means that the microprocessor jumps to executing the next instruction belonging to the called function at a nonsequential memory location. After it is done with executing the instructions in the function, it returns to where it left off. To implement this logic, the compiler converts your function call into a CALL instruction for the microprocessor. This instruction is accompanied by the address in memory the next instruction needs to be taken from—this address belongs to your function routine. When the microprocessor encounters CALL, it saves the position of the instruction to be executed after the function call on the stack and jumps to the memory location contained in the CALL instruction.

This memory location contains instructions belonging to the function. The microprocessor executes them until it reaches the RET statement (the microprocessor’s code for return programmed by you). The RET statement results in the microprocessor popping that address from the stack stored during the CALL instruction. This address contains the location in the calling function where the execution needs to continue from. Thus, the microprocessor is back to the caller and continues where it left off.

Inline Functions

A regular function call is translated into a CALL instruction, which results in stack operations and microprocessor execution shift to the function and so on. This might sound like a lot of stuff happening under the hood, but it happens quite quickly—for most of the cases. However, what if your function is a very simple one like the following?

double GetPi()
{
   return 3.14159;
}

The overhead of performing an actual function call on this might be quite high for the amount of time spent actually executing GetPi(). This is why C++ compilers enable the programmer to declare such functions as inline. Keyword inline is the programmers’ request that these functions be expanded inline where called.

inline double GetPi()
{
   return 3.14159;
}

Functions that perform simple operations like doubling a number are good candidates for being inlined, too. Listing 7.10 demonstrates one such case.

LISTING 7.10 Using an Inline Function That Doubles an Integer


  0: #include <iostream>
  1: using namespace std;
  2:
  3: // define an inline function that doubles
  4: inline long DoubleNum (int inputNum)
  5: {
  6:    return inputNum * 2;
  7: }
  8:
  9: int main()
 10: {
 11:    cout << "Enter an integer: ";
 12:    int inputNum = 0;
 13:    cin >> inputNum;
 14:
 15:    // Call inline function
 16:    cout << "Double is: " << DoubleNum(inputNum) << endl;
 17:
 18:    return 0;
 19: }


Output Image

Enter an integer: 35
Double is: 70

Analysis Image

The keyword in question is inline used in Line 4. Compilers typically see this keyword as a request to place the contents of the function DoubleNum() directly where the function has been invoked—in Line 16—which increases the execution speed of the code.

Classifying functions as inline can also result in a lot of code bloat, especially if the function being inline does a lot of sophisticated processing. Using the inline keyword should be kept to a minimum and reserved for only those functions that do very little and need to do it with minimal overhead, as demonstrated earlier.


Note

Most modern C++ compilers offer various performance optimization options. Some, such as the Microsoft C++ Compiler, offer you to optimize for size or speed. Optimizing for size may help in developing software for devices and peripherals where memory may be at a premium. When optimizing for size, the compiler might often reject many inline requests as that might bloat code.

When optimizing for speed, the compiler typically sees and utilizes opportunities to inline code where it would make sense and does it for you—sometimes even in those cases where you have not explicitly requested it.


Automatic Return Type Deduction

You learned about the keyword auto in Lesson 3, “Using Variables, Declaring Constants.” It lets you leave variable type deduction to the compiler that does so on the basis of the initialization value assigned to the variable. Starting with C++14, the same applies also to functions. Instead of specifying the return type, you would use auto and let the compiler deduce the return type for you on the basis of return values you program.

Listing 7.11 demonstrates the usage of auto in a function that computes the area of a circle.

LISTING 7.11 Using auto as Return Type of Function Area()


  0: #include <iostream>
  1: using namespace std;
  2:
  3: const double Pi = 3.14159265;
  4:
  5: auto Area(double radius)
  6: {
  7:     return Pi * radius * radius;
  8: }
  9:
 10: int main()
 11: {
 12:     cout << "Enter radius: ";
 13:     double radius = 0;
 14:     cin >> radius;
 15:
 16:     // Call function "Area"
 17:     cout << "Area is: " << Area(radius) << endl;
 18:
 19:     return 0;
 20: }


Output Image

Enter radius: 2
Area is: 12.5664

Analysis Image

The line of interest is Line 5, which uses auto as the return type of function Area(). The compiler deduces the return type on the basis of the return expression that uses double. Thus, in spite of using auto, Area() in Listing 7.11 compiles to no different code than Area() in Listing 7.1 with return type double.


Note

Functions that rely on automatic return type deduction need to be defined (i.e., implemented) before they’re invoked. This is because the compiler needs to know a function’s return type at the point where it is used. If such a function has multiple return statements, they need to all deduce to the same type. Recursive calls need to follow at least one return statement.


Lambda Functions

This section is just an introduction to a concept that’s not exactly easy for beginners. So, skim through it and try to learn the concept without being disappointed if you don’t grasp it all. Lambda functions are discussed in depth in Lesson 22, “Lambda Expressions.

Lambda functions were introduced in C++11 and help in the usage of STL algorithms to sort or process data. Typically, a sort function requires you to supply a binary predicate. This is a function that compares two arguments and returns true if one is less than the other, else false, thereby helping in deciding the order of elements in a sort operation. Such predicates are typically implemented as operators in a class, leading to a tedious bit of coding. Lambda functions can compact predicate definitions as shown in Listing 7.12.

LISTING 7.12 Using Lambda Functions to Display Elements in an Array and to Sort Them


  0: #include <iostream>
  1: #include <algorithm>
  2: #include <vector>
  3: using namespace std;
  4:
  5: void DisplayNums(vector<int>& dynArray)
  6: {
  7:    for_each (dynArray.begin(), dynArray.end(),
  8:            [](int Element) {cout << Element << " ";} );
  9:
 10:    cout << endl;
 11: }
 12:
 13: int main()
 14: {
 15:    vector<int> myNums;
 16:    myNums.push_back(501);
 17:    myNums.push_back(-1);
 18:    myNums.push_back(25);
 19:    myNums.push_back(-35);
 20:
 21:    DisplayNums(myNums);
 22:
 23:    cout << "Sorting them in descending order" << endl;
 24:
 25:    sort (myNums.begin(), myNums.end(),
 26:         [](int Num1, int Num2) {return (Num2 < Num1); } );
 27:
 28:    DisplayNums(myNums);
 29:
 30:    return 0;
 31: }


Output Image

501 -1 25 -35
Sorting them in descending order
501 25 -1 -35

Analysis Image

The program contains integers pushed into a dynamic array provided by the C++ Standard Library in the form of a std::vector in Lines 15–19. The function DisplayNums() uses the STL algorithm for_each to iterate through each element in the array and display its value. In doing so, it uses a lambda function in Line 8. std::sort used in Line 25 also uses a binary predicate (Line 26) in the form of a lambda function that returns true if the second number is smaller than the first, effectively sorting the collection in an ascending order.

The syntax of a lambda function is the following:

[optional parameters](parameter list){ statements; }


Note

Predicates and their use in algorithms such as sort are discussed at length in Lesson 23, “STL Algorithms.Listing 23.6 in particular is a code sample that uses a lambda and a nonlambda variant in an algorithm, thereby allowing you to compare the programming efficiency introduced by lambda functions.


Summary

In this lesson, you learned the basics of modular programming. You learned how functions can help you structure your code better and also help you reuse algorithms you write. You learned that functions can take parameters and return values, parameters can have default values that the caller can override, and parameters can also contain arguments passed by reference. You learned how to pass arrays, and you also learned how to program overloaded functions that have the same name and return type but different parameter lists.

Last but not the least, you got a sneak preview into what lambda functions are. Completely new as of C++11, lambda functions have the potential to change how C++ applications will be programmed henceforth, especially when using STL.

Q&A

Q What happens if I program a recursive function that doesn’t end?

A Program execution doesn’t end. That might not be bad, per se, for there are while(true) and for(;;) loops that do the same; however, a recursive function call consumes more and more stack space, which is finite and runs out, eventually causing an application crash due to a stack overflow.

Q Why not inline every function? It increases execution speed, right?

A That really depends. However, inlining every function results in functions that are used in multiple places to be placed at the point where they’re called, and this results in code bloat. That apart, most modern compilers are better judges of what calls can be inlined and do so for the programmer, depending on the compiler’s performance settings.

Q Can I supply default parameter values to all parameters in a function?

A Yes, that is definitely possible and recommended when that makes sense.

Q I have two functions, both called Area. One takes a radius and the other takes height. I want one to return float and the other to return double. Will this work?

A Function overloading needs both functions with the same name to also have the same return types. In this case, your compiler shows an error as the name has been used twice in what it expects to be two functions of different names.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E, and be certain that you understand the answers before continuing to the next lesson.

Quiz

1. What is the scope of variables declared in a function’s prototype?

2. What is the nature of the value passed to this function?

int Func(int &someNumber);

3. I have a function that invokes itself. What is such a function called?

4. I have declared two functions, both with the same name and return type but different parameter lists. What are these called?

5. Does the stack pointer point to the top, middle, or bottom of the stack?

Exercises

1. Write overloaded functions that calculate the volume of a sphere and a cylinder. The formulas are the following:

Volume of sphere = (4 * Pi * radius * radius * radius) / 3
Volume of a cylinder = Pi * radius * radius * height

2. Write a function that accepts an array of double as input.

3. BUG BUSTERS: What is wrong with the following code?

#include <iostream>
using namespace std;
const double Pi = 3.1416;

void Area(double radius, double result)
{
   result = Pi * radius * radius;
}

int main()
{
   cout << "Enter radius: ";
   double radius = 0;
   cin >> radius;

   double areaFetched = 0;
   Area(radius, areaFetched);

   cout << "The area is: " << areaFetched << endl;
   return 0;
}

4. BUG BUSTERS: What is wrong with the following function declaration?

double Area(double Pi = 3.14, double radius);

5. Write a function with return type void that still helps the caller calculate the area and circumference of a circle when supplied the radius.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.137.76