Laboratory Training 3

Use of Functions

1 Training Tasks

1.1 Static Local Variables

Write a program that calculates and shows the minimum and maximum of integers as the user inputs those integers. Use static local variables.

1.2 Recursion

Write a program that reads x and n and calculates y using recursive function:

y = (x + 1)(x + 2)(x + 3)(x + 4) ... (x + n)

 

1.3 Default Arguments

Create a function that returns 1, argument, and product of arguments, depending on arguments count. Test this function in main() function. Implement program in two ways: using function overloading and using default arguments.

1.4 Quadratic Equation

Create a function for solving of quadratic equation. Function should return the number of roots (0, 1, or 2) or -1 if the equation has an infinite number of roots. The function should get the coefficients as arguments and return the roots as reference type arguments.

1.5 Individual Assignment

You should create a program that implements an individual assignment of previous laboratory training. Program should be split into several functions. Function y() should obtain values of x and n as arguments and return value calculated using formula given in an individual assignment. Create a separate function for reading data. Do not use global variables.

2 Instructions

2.1 Function Declaration and Definition

A function is a subprogram that can act on data and return a value. Each function has its own name. When that name with the list of arguments is encountered, the execution of the program branches to the body of that function. This is called function call, or function invocation. When the function returns, execution resumes on the next line of the calling function. Complicated tasks should be broken down into multiple functions, and then each can be called in turn.

The main() function is called by the operating system.

When calling functions, all their local variables, function addresses, as well as parameter values and other local data are placed in a special memory area called call stack. After return from a function, the area occupied by its local data is freed.

Function definition supplies a function body - the code that makes up the function.

The declaration of a function without definition is called its prototype. The function prototype consists of the function's return type, name, parameter list, and semicolon. The parameter list is a list of all the parameters and their types, separated by commas.

int sum(int a, int b); // prototype

The function prototype and the function definition must agree exactly about the return type, the name, and the parameter list. The function prototype does not need to contain the names of the parameters:

int sum(int, int); // prototype

The function definition consists of the function header and its body. The function body is a set of statements enclosed in braces.

int sum(int a, int b)
{
    return a + b;
}

All functions have a return type.

The return statement terminates the function and returns control to the previous function from which the call was made. The result of the main() function can be used by the operating system as an error code (0 - no errors).

The value of expression in return statement is returned to the calling function. Functions of all types (except void) must specify an expression in the return statement.

The return statement always terminates the function.

You can't define another function from within a function. There are no local functions in C++.

Although there is no limit to the size of a function in C++, well-designed functions tend to be small. A smaller function is easier to understand and maintain.

The arguments are passed in to the function in the order in which they are declared and defined. Any valid C++ expression can be a function argument, including constants, mathematical and logical expressions, and other functions that return a value.

int main()
{
    int x, y;
    cin >> x >> y;
    int z = sum(x, y);             // function call
    int k = sum(z, x + y);         // function call
    int m = sum(k, sum(x + y, z)); // function call
    cout << z << ' ' << k << ' ' << m;
    return 0;
}

The arguments passed into the function are local to the function. Changes made to the arguments do not affect the values in the calling function. This is known as passing by value, which means a local copy of each argument is made in the function.

Calling programs pass information to called functions in actual arguments. The called functions access the information using corresponding formal arguments.

2.2 The void Returning Type

If a function does not return a value, its return type will be void.

void print(double a)
{
    cout << a << endl;
}

The void type is syntactically a fundamental type. It can, however, be used only as part of a more complicated type; there are no objects of type void.

If a function returns void, the body of function is executed until the end of programming block. Functions of type void cannot specify expressions in the return statement. In such functions you can use return statement without expression to terminate the execution of a function. For example

void printReciprocal(double a)
{
    if (a == 0)
    {
        return;
    }
    cout << 1 / a << endl;
}

A function with the void result type can only be called using a separate statement (not inside an expression).

2.3 Scope

A variable has scope, which determines how long it is available to the program and where it can be accessed.

You can declare variables within the body of the function, or other program block. This can be done using local variables. Such variables exist only locally within the function itself. The parameters passed in to the function are also considered local variables.

Global variables have global scope. They are available anywhere within your program. The global variables defined outside of any function are available from any function in the program, including main(). Local variables with the same name as global variables do not change the global variables. A local variable with the same name as a global variable hides the global variable. However, you can use scope resolution operator (::) for access to global variable. For example

int k = 1;

void f()
{
    int k = 2;
    cout << k;   // 2
    cout << ::k; // 1
}

In C++, global variables are legal, but their usage is not recommended. Global variables are dangerous because they are shared data. One function can change a global variable in a way that is invisible to another function. This can produce bugs that are difficult to find.

2.4 Static Local Variables

Normally, variables defined local to a function disappear at the end of the function scope. When you call the function again, storage for the variables is created anew and the data is reinitialized. If you want the data to be extant throughout the life of the program, you can define that variable to be static and give it an initial value. The initialization is only performed when the function invokes for the first time, and the data retains its value between function calls. The following example demonstrates the difference between normal (automatic) and static local variables:

#include <iostream>

using namespace std;

void localVariables()
{
    int m = 0;
    static int n = 0;
    m++;
    n++;і
    cout << m << " " << n << endl;
}

int main()
{
    localVariables(); // 1 1
    localVariables(); // 1 2
    localVariables(); // 1 3
    return 0;
}

Static data are unavailable outside the scope of the function, so they can't be inadvertently changed. This localizes errors.

2.5 Recursion

A function can call itself. This is called recursion.

Recursion can be direct or indirect. It is direct when a function calls itself:

void f() {
    ...
    f();
    ...
}

Indirect recursion when a function calls another function that then calls the first function. In this case, it is necessary to create a prototype of at least one of the functions:

void f(); // prototype
void g() {
    ...
    f();
    ...
}
void f() {
    ...
    g();
    ...
}

It is important to note that when a function calls itself, a new copy of that function is run. The local variables in the second version are independent of the local variables in the first, and they cannot affect one another directly.

The misuse of recursion can cause stack overflow error.

Sometimes you can use recursion instead of looping. For example, you can calculate sum

y = 12 + 22 + 32 + ... + n2

without any loop:

#include <iostream>

using namespace std;

double sum(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * n + sum(n - 1);
    }
}

int main()
{
    cout << sum(5);
    return 0;
}

2.6 Inline Functions

When you call the function, execution of the program jumps to those instructions, and when the function returns, execution jumps back to the next line in the calling function.

If a function is declared with the keyword inline, the compiler does not create a real function: it copies the code from the inline function directly into the calling function. No jump is made; it is just as if you had written the statements of the function right into the calling function.

inline int signum(double x)
{
    return x < 0 ? -1 : (x == 0 ? 0 : 1);
}

The inline specifier instructs the compiler to insert a copy of the function body into each place the function is called. If the function is very small, no more than a line or two, and won't be called from many places in your program, it is a candidate for inlining. An inline function cannot be recursive.

2.7 Function Name Overloading

C++ enables you to create more than one function with the same name. This is called function name overloading. Overloaded functions enable programmers to supply different semantics for a function, depending on the types and number of arguments.

The functions must differ in their parameter list, with a different type of parameter, a different number of parameters, or both. Here's an example:

int sum(int a, int b)
{
    return a + b;
}

double sum(double a, double b)
{
    return a + b;
}

double sum(double a, double b, double c)
{
    return a + b + c;
}

int main()
{
    cout << sum(1, 2) << endl;      // First  sum() function
    cout << sum(1.0, 2.5) << endl;  // Second sum() function
    cout << sum(1, 2, 2.6) << endl; // Third  sum() function
    return 0;
}

You should note that two functions with the same name and parameter list, but different return types, generate a compiler error:

int f(double x);
double f(double x); // Error

2.8 Default Arguments

In many cases, functions have arguments that are used so infrequently that a default value would suffice. To address this, the default-argument facility allows for specifying only those arguments to a function that are meaningful in a given call. Here's an example:

int sum(int x, int y = 0, int z = 0)
{
    return x + y + z;
}

int main()
{
    cout << sum(5) << endl;       // 5, y = 0, z = 0
    cout << sum(1, 2) << endl;    // 3, z = 0
    cout << sum(1, 2, 5) << endl; // 8
    return 0;
}

Default arguments will be used in calls where trailing arguments are missing. Any or all of the function's parameters can be assigned default values. The one restriction is this: If any of the parameters does not have a default value, no previous parameter may have a default value. Default arguments must be the last arguments:

void f(double x, int y = 0, int h); // Error. 

A default argument cannot be redefined in later declarations even if the redefinition is identical to the original. Therefore, the following code produces an error:

void f(double x, int y = 1);
void f(double x, int y = 1) { } // Error. 
  // Must be void f(double x, int y) { }

2.9 References

C++ introduces a new data type called reference. A reference can be defined as a second name (alias) of an existing variable (object).

You can declare a reference by writing the type, followed by the reference operator (&), followed by the reference name.

int i = 10; 
int &j = i; // reference to i
j = 11;
cout << i;  // 11 

As an alias cannot exist without its corresponding real part, you cannot define single references. References must be initialized at the time of creation.

int &k; // Syntax error!

There are no constant references, but you can declare references to constant objects:

int m = 2; 
const int &n = m; // reference to i
double x = n + 1; // OK
m = 11;           // OK 
n = 12;           // error!

References can be used as function arguments. Function that swaps two integers can be as follows:

void swap(int &x1, int &x2)
{
    int x = x1;
    x1 = x2;
    x2 = x;
}

We can invoke swap() from main() function:

void main()
{
    int a = 1;
    int b = 2;
    swap(a, b);
    cout << a << endl; // 2
    cout << b << endl; // 1
}

References can be used as return values. It is very important to ensure that the reference is bound with the variable that exists after leaving a function:

int& f()
{
    int k;
    return k; // Error; k is destroyed after leaving a function
}

That's correct:

int& f()
{
    static int k;
    return k; // OK
}

void main()
{
    f() = 10;
    cout << f(); // 10
}

A function returns an alias to static variable that is not destroyed after leaving a function. Therefore, we can assign

f() = 10;

3 Sample Programs

3.1 The Sum of Successive Powers of a Number

In the following program the user enters the value of x and the integer n. It is necessary to calculate the sum of successive integer powers x from 1 to n.

#include <iostream>

using namespace std;

double sumOfPowers(double x, int n)
{
    double sum = 0;
    double power = 1;
    // Powers and the sum can be calculated in a single loop:
    for (int i = 0; i < n; i++)
    {
        power *= x;
        sum += power;
    }
    return sum;
}

int main()
{
    double x;
    int n;
    cout << "Enter x and n:";
    cin >> x >> n;
    cout << sumOfPowers(x, n);
    return 0;
}

3.2 Static local variable

This program calculates and shows the sum of integers as the user inputs those integers.

#include <iostream>

using namespace std;

int add(int i)
{
    static int sum = 0;
    sum += i;
    return sum;
}

int main()
{
    int i;
    do
    {
        cin >> i;
        cout << add(i) << endl;
    }
    while (i);
    return 0;
}

3.3 Division of Program into Independent Parts

Almost every program can be divided into relatively independent parts. Program that calculates some values usually contains parts responsible for input, calculation, and output. Assume that we need to implement a program that calculates sum of second powers of integers:

y = 12 + 22 + 32 + ... + n2

The first implementation is not divided into parts:

#include <iostream>

using namespace std;

int main()
{
    int n;
    cout << "Input n:";
    cin >> n;
    int y = 0;
    for (int i = 1; i <= n; i++)
    {
        y += i * i;
    }
    cout << "y = " << y;
    return 0;
}

The simplest solution of dividing into separate functions consists in use of global variables:

#include <iostream>

using namespace std;

int n;
int y = 0;

void read()
{
    cout << "Input n:";
    cin >> n;
}

void calc()
{
    for (int i = 1; i <= n; i++)
    {
        y += i * i;
    }
}

void write()
{
    cout << "y = " << y;
}

int main()
{
    read();
    calc();
    write();
    return 0;
}

Use of global variables is not a good idea. The better approach consists in use of arguments:

#include <iostream>

using namespace std;

int read()
{
    int n;
    cout << "Input n:";
    cin >> n;
    return n;
}

int calc(int n)
{
    int y = 0;
    for (int i = 1; i <= n; i++)
    {
        y += i * i;
    }
    return y;
}

void write(int y)
{
    cout << "y = " << y;
}

int main()
{
    int n = read();
    int y = calc(n);
    write(y);
    return 0;
}

The main() function can be implemented without any variables.

...

int main()
{
    write(calc(read()));
    return 0;
}

3.4 Linear Equation

Function solve() in the following example finds root of a linear equation (the argument passed by reference) and returns the number of roots. If the number of roots is infinite, the function returns -1. Inside the main() function, it is advisable to create a switch that analyzes the number of roots and performs the appropriate output

#include <iostream>

using namespace std;

int solve(double a, double b, double& x)
{
    if (a == 0)
    {
        if (b == 0)
        {
            return -1;
        }
        else
        {
            return 0;
        }
    }
    x = -b / a;
    return 1;
}

int main()
{
    double a, b, x;
    cin >> a >> b;
    int count = solve(a, b, x);
    switch (count)
    {
    case -1:
        cout << "Infinite number of roots";
        break;
    case 0:
        cout << "No roots";
        break;
    case 1:
        cout << "x = " << x;
    }
    cout << endl;
    return 0;
}

3.5 Function Values Table

Suppose we need to create a program that calculates the values of a function on a given range with a certain step

Individual parts of the program should be implemented as independent functions. The program can be as follows:

#include <iostream>
#include <cmath>
using namespace std;

// Data input from the keyboard, parameters are transferred by the reference
bool readData(double& from, double& to, double& step)
{
    cout << "Enter the beginning, end of the interval, and step: ";
    cin >> from >> to >> step;
    if (from >= to || step <= 0)
    {
        cerr << "Wrong data" << endl;
        return false;
    }
    return true;
}

// The function defined in the task
double y(double x)
{
    if (x < 0)
    {
        return sin(x);
    }
    else
    {
        return sqrt(x);
    }
}

// Output of values of arguments and functions on an interval with the certain step
void printInALoop(double from, double to, double step)
{
    cout << "x\ty" << endl; // table header
    for (double x = from; x <= to; x += step)
    {
        cout << x << "\t" << y(x) << endl;
    }
}

int main()
{
    double from, to, step;
    if (readData(from, to, step))
    {
        printInALoop(from, to, step);
        return 0; // OK
    }
    return -1; // data input error
}

The location of functions in the program is determined by the call mechanism. The function that is called must precede the function that calls it. This order is not always convenient in terms of human perception of the code. If we first place function prototypes, we can change the order:

#include <iostream>
#include <cmath>
using namespace std;

// Function prototypes:
bool readData(double &, double &, double &);
void printInALoop(double, double, double);
double y(double);

int main()
{
    double from, to, step;
    if (readData(from, to, step))
    {
        printInALoop(from, to, step);
        return 0; // OK
    }
    return -1; // data input error
}

// Data input from the keyboard, parameters are transferred by the reference
bool readData(double& from, double& to, double& step)
{
    cout << "Enter the beginning, end of the interval, and step: ";
    cin >> from >> to >> step;
    if (from >= to || step <= 0)
    {
        cerr << "Wrong data" << endl;
        return false;
    }
    return true;
}

// Output of values of arguments and functions on an interval with the certain step
void printInALoop(double from, double to, double step)
{
    cout << "x\ty" << endl; // table header
    for (double x = from; x <= to; x += step)
    {
        cout << x << "\t" << y(x) << endl;
    }
}

// The function defined in the task
double y(double x)
{
    if (x < 0)
    {
        return sin(x);
    }
    else
    {
        return sqrt(x);
    }
}

4 Exercises

  1. Create a program that tests signum() function
  2. Develop and test a function that calculates product of three arguments
  3. Develop and test a function that calculates product of n first odd values
  4. Develop and test a function that calculates ex using sum
  5. Develop and test a function that calculates factorial
  6. Develop and test a function that prints all even values in a given range
  7. Develop and test a function and prints product of n first even values
  8. Develop and test a function that calculates greatest common divisor of two integers

5 Quiz

  1. What is function?
  2. What is function header?
  3. What is function body?
  4. How is a function prototype?
  5. Are names of formal arguments always obligatory in function header?
  6. How to create a local function?
  7. What is the difference between actual arguments and formal arguments?
  8. Is return statement always obligatory?
  9. How to use result of a function with void returning type?
  10. What is a local variable?
  11. What is scope?
  12. What is global scope?
  13. How to access names from global scope?
  14. Can local variables hide global variables?
  15. What is the difference between static and non-static local variables?
  16. What is life cycle of static local variable?
  17. How to use static local variables?
  18. What is recursion?
  19. What is inline function?
  20. How to overload function name?
  21. Can you create two global functions with the same name and parameter list, but different return types?
  22. How to declare default arguments?
  23. Can you redefine default arguments of a function?
  24. What are references?
  25. How to initialize reference?
  26. What is the usage of references?
  27. Can you return a reference from a function?
  28. Can you return a reference to non-static local variable?

 

up