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:
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
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
- Create a program that tests
signum()
function - Develop and test a function that calculates product of three arguments
- Develop and test a function that calculates product of n first odd values
- Develop and test a function that calculates ex using sum
- Develop and test a function that calculates factorial
- Develop and test a function that prints all even values in a given range
- Develop and test a function and prints product of n first even values
- Develop and test a function that calculates greatest common divisor of two integers
5 Quiz
- What is function?
- What is function header?
- What is function body?
- How is a function prototype?
- Are names of formal arguments always obligatory in function header?
- How to create a local function?
- What is the difference between actual arguments and formal arguments?
- Is return statement always obligatory?
- How to use result of a function with void returning type?
- What is a local variable?
- What is scope?
- What is global scope?
- How to access names from global scope?
- Can local variables hide global variables?
- What is the difference between static and non-static local variables?
- What is life cycle of static local variable?
- How to use static local variables?
- What is recursion?
- What is inline function?
- How to overload function name?
- Can you create two global functions with the same name and parameter list, but different return types?
- How to declare default arguments?
- Can you redefine default arguments of a function?
- What are references?
- How to initialize reference?
- What is the usage of references?
- Can you return a reference from a function?
- Can you return a reference to non-static local variable?