Functions
There are situations when we want to use the same piece of code in several places in a program. In general programming languages provide facilities to allow code to be written only once to be repeated and its invocation as many times as desired, possibly parameterized. In the C/C++ language this is done by through functions.
With the development of the language came libraries with a lot of code already written and that the programmer can use only by including the corresponding library in the program (#include ...).
We can also think of functions as an extension of the operator set. Thus, if language already offers signs for the usual operations (+, -, % ...) how could we perform other operations such as radical, raising to power, etc., knowing we don't have a dedicated sign for it? The answer comes from functions.
Functions are therefore small programs, with their data, with their code, that perform a specific task. In C/C++ functions can be classified in several ways, but primarily by the return value, namely, if return (give out directly through them) or not any value. We have thus:
- Operand functions – those that return a value and so can be used (called) within some expressions;
- Procedural functions – those that do not return values and are called as a separate instruction;
Operand Function
I said that language libraries are increasingly offering code already written and which can then be used. We therefore say that we have predefined functions in this mode. In the present material we do not we focus on predefined functions but on how we write new ones. It will always be needed regardless of how wide the offer of predefined functions is, particular situations often appearing in any program.
#include <iostream>
#include <cmath>
using namespace std;
int n;
double aux;
int main () {
cin >> n;
cout << sqrt(n) << "\n";
cout << sqrt(10) << "\n";
aux = sqrt(12 + sqrt(14));
cout << 2 * sqrt(aux) << "\n";
return 0;
}
If it is read from the keyboard 144 will appear in the console window
144
12
3.16228
3.98375
We notice that there are five calls to the code that calculates the radical, made where use is allowed of real data (within an assignment expression, within a display statement, etc.)
The sqrt function can therefore be regarded as a unary operator (since it applies to a single data) and which provides a value of type real. From the above example we can already draw some conclusions about the way of calling a functions (even if it is predefined – the principle is the same), namely: the name of the function, the type must be known return values as well as what kind of data it can be applied to. The data written in parentheses on the call are therefore data passed to the function - a kind of input data for it - they are called parameters, and the value returned is the output date
What would happen if we replaced all sqrt calls with sumDigits calls. I mean, we would write in code sumDigits everywhere sqrt appears:
#include <iostream>
#include <cmath>
using namespace std;
int n;
double aux;
int main () {
cin >> n;
cout << sumDigit(n) << "\n";
cout << sumDigit(10) << "\n";
aux = sumDigit(12 + sumDigit(14));
cout << 2 * sumDigit(aux) << "\n";
return 0;
}
If we are lucky enough to have a predefined function with this name in one of the included libraries, and which one also accept a single parameter and return a value that can be used in the above expressions up, then the program will run. But most likely it is not so we will get compile error
What if we want to make the above work? That is, instead of radicals, let's do the sum numbers? We complete the code as below (I added it before main):
#include <iostream>
using namespace std;
int n;
double aux;
int sumDigit(int n) {
int r;
r = 0;
while (n != 0) {
r += n % 10;
n /= 10;
}
return r;
}
int main () {
cin >> n;
cout << sumDigit(n) << "\n";
cout << sumDigit(10) << "\n";
aux = sumDigit(12 + sumDigit(14));
cout << 2 * sumDigit(aux) << "\n";
return 0;
}
In this case things work, and if I enter 144, the values will be displayed one by one: 9, 1, 16.
Notice that there was no need to include the cmath library since we didn't use anything from there. At the same time, if we left that line of code too, nothing would have happened, only that it would have been useless and might grow and the size of the executable file.
So I wrote the calculation code for the sum of the digits of a number only once (I defined the function) and I have called her five times.
Let's focus on how to define the function (we mentioned before and we will do it again, the definition se happens once, is the time of constructing the function, and the call, which is made as many times as we want, is the time using the function).
int sumaCifre(int n) {
int r;
r = 0;
while (n != 0) {
r += n % 10;
n /= 10;
}
return r;
}
This is done outside the main function (generally outside another function) and follows the rule required by identifiers, to be defined before the place where they will be used.
- The word int at the beginning indicates the type of the result of the function. This is very important, the call and can then only be done where the use of a value of this type is allowed.
- Next comes sumDigits, so an identifier that represents the function name. It will be used at call.
- Then there are the parentheses, the primary hallmark of functions. The parameters are written between them, but we will see that we can also have functions without parameters but nevertheless the round brackets are mandatory both in definition and in appeal
- The parameters of the function are written between the round brackets. They are indicated in the form of value type, separate with a comma if there are more than one. As I mentioned before, through them they can pass data to functions. We will see in a subsequent subchapter that we also have the variant as by parameters to return values other than the one the function returns.
The above forms the function header. Its knowledge is necessary for those who will further use of the function.
Between the braces follows the actual code of the function, also called the body of the function, where we observe:
- We can declare local data as is r. This data is only visible inside the function and le we use in the process of transforming the parameters into the desired result. These local data, immediately after declaration is not initialized to 0 by default (as in the case of global data - those declared outside other functions). I have separately initialized r to 0.
- We then have the instructions by which we carry out the desired processing. They work with local data and with parameters, but we will see that global data can also appear (this is usually avoided).
- We also have a separate return statement. Generally this is used in the form: return phrase; The expression must be of the function result type. Executing return causes it to terminate the function code immediately and return to the calling program with the value of the expression as the returned by the function.
In more general terms, the way to define a function is:
type_result name_function (list of parameters) {
code...
}
Here is another example from which we will draw more conclusions.
#include <iostream>
using namespace std;
int n, i;
int prim(int n) {
int i;
if (n < 2)
return 0;
for (i = 2;i <= n / i; i++)
if (n % i == 0)
return 0;
return 1;
}
int main () {
for (i = 1;i <= 100; i++)
if (prim(i) == 1)
cout << i << " ";
return 0;
}
The above program displays the prime natural numbers less than 100. Analyzing the definition of the prime function we observe:
- It was no longer necessary to use a logical variable, ok, in which to write down the status of the check: we return directly 0 if we find any divisor that makes the number not prime. We can write return 1 at the end, without any condition as it would not get there if divisors were found
- We notice that we get rid of dealing with the special cases with numbers less than 2 that are not receive everything with a return, at the beginning. So we can avoid writing the rest of the code on else, something similar to using break/continue in repetitive structures.
- We make the function call as part of the if condition, which is fine because it compares two numbers, one being the int value returned by the function.
- The function has a local variable called i, and its call is made in a for which has a counter called tot i. What actually happens? Things are fine, the global variable i is no longer visible in the function as long as inside I declared another with the same name. The local variable i will be used in the function. If we would have omitted the first line; from inside the function we would not have received a compile error since i was used in function would have been the global variable itself, but things would have gone wrong, the global variable i ending up being counter in two forums that reach each other through the function call.
Although we haven't discussed it in detail, we have already used function calls. Now is the time to say more clearly how to do this:
- The call occurs anywhere a date of the function result type can occur.
- The name of the function is indicated on the call and the parameters of the call are indicated between round brackets. These are expressions of the corresponding date type from the function definition, the values of these expressions evaluating to and being the initial values of the parameters in the function body.
Solved Problems:
1. Let the following function be:
int f(int n) {
if (n % 2 == 0)
return 2 * n;
}
What is the result of calling f(4) ?
What is the result of calling f(3) ?
Solution:
In subpoint a) in the body of the function return with the value 8 will be executed, so this is the answer. If subpoint b) no return is executed in the body of the function, so in the place where the calling program will look for the value returned nothing is put and, as we saw above (in the table with the explanation of the memory model from example with the cmmdc function), there the area is uninitialized, so we can expect any result. Must so take care that in the case of operand functions something is always returned
2. Write a function that takes a natural number n as a parameter and returns the largest value that can be obtained with the digits of n placed in a convenient order.
Solution:
int cmmnr(int n) {
int v[12];
int k = 0;
while (n != 0) {
k++;
v[k] = n%10;
n /= 10;
}
for (int i = 1;i < k; i++)
for (int j = i + 1;j <= k; j++)
if (v[i] > v[j]) {
int aux = v[i];
v[i] = v[j];
v[j] = aux;
}
int r = 0;
for (int i = k;i >= 1; i--)
r = r * 10 + v[i];
return r;
}
The strategy followed above is to put all the digits in a vector, sort it in descending order, and construct the value to return with the digits in that order.
Notice that we can declare a local vector to a function, which is allocated in memory at the time of the call to a function like other local variables. It also frees memory on termination performance of the function.
int cmmnr(int n) {
int f[10];
for (int i = 0;i <= 9; i++)
f[i] = 0;
while (n != 0) {
f[n % 10] ++;
n /= 10;
}
int r = 0;
for (int i = 9;i >= 0; i--)
while (f[i] != 0) {
r = r * 10 + i;
f[i]--;
}
return r;
}
The above solution uses a frequency vector to sort the digits of the number to be processed. As we see, we can declare a local vector to a function. Since in its components we count the number of occurrences for each digit, it is essential to initialize its values with 0.
Procedural functions
We initially classified functions into operand and procedural. The operands return a value and their call se do in expressions. Procedurals do not return a value. Then what are they useful to us? And how do we use them?
here is an example:
#include <iostream>
using namespace std;
int n, m;
void afiseaza(int n, int m) {
cout << n << "/" << m << "\n";
}
int main () {
cin >> n >> m; /// input 26 20
afiseaza(n, m);
afiseaza(12, 34);
afiseaza(2 * 7, 4 * 9);
return 0;
}
It will display:
26 / 20
12 / 34
14 / 36
We can imagine that we want to display in a relevant way a fraction. The above function is what we want. Even if it doesn't return a value, the procedural function has its purpose, in this case to print something on the screen in a certain format
Everything we discussed in the operand functions applies here as well. But here are the differences:
When defining, void is written instead of the result type. It is a keyword that here indicates that the function is procedural, does not return a value.
- The call to such a function is done as a separate statement, as can be seen above, and not in the within an expression.
- The return statement can be present in procedural functions, but without being followed by an expression, but only by the semicolon character. If its execution is reached, the function ends immediately
A common use case for procedural functions is to organize code into sections. For example, in contests, we often find the main function written like this:
int main() {
read();
solve();
write();
}
Here's a full schedule:
#include <iostream>
using namespace std;
int n, v[100];
void read() {
cin >> n;
for (int i = 1;i <= n; i++)
cin>>v[i];
}
void solve() {
for (int i = 1, j = n; i < j; i++, j--) {
int aux = v[i];
v[i] = v[j];
v[j] = aux;
}
}
void write() {
for (int i = 1;i <= n; i++)
cout << v[i] << " ";
cout << "\n";
}
int main () {
read();
solve();
write();
return 0;
}
The program above asks the keyboard for the size and elements of a vector, mirrors it, and then displays it on the screen in the new configuration. The functions have no parameters, they work with the vector v and the number of elements n as global variables.
Functions with parameters passed by reference
Let's analyze the following program:
#include <iostream>
using namespace std;
int n, m;
void change(int a, int b) {
int aux;
aux = a;
a = b;
b = aux;
}
int main () {
n = 2;
m = 3;
change(n, m);
cout << n << " " << m;
return 0;
}
When asked what will be displayed, many people are quick to say 3 and then 2. That is not the case, they will patterns 2 and 3, in that order and separated by space since the interchange is between variables a and b, which, as we analyzed above, is allocated in the stack and exists only during the execution of the function. The modification lor in function has no effect on n and m.
The method of passing parameters to functions used from the beginning of the chapter until now can be called de passing parameters by value.
We have another tool that we will explain further: passing parameters by reference. These are the parameters that are written preceded by the & character when defining the function. We will modify, for the previous example both parameters indicating that we are passing them by reference.
#include <iostream>
using namespace std;
int n, m;
void change(int &a, int &b) {
int aux;
aux = a;
a = b;
b = aux;
}
int main () {
n = 2;
m = 3;
change(n, m);
cout << n << " " << m;
return 0;
}
When calling the function, a and b will not be new variables but they will be a kind of nicknames (aliases) for the parameters that are in their right to appeal. So writing a inside the function makes it work directly with the value in the area of memory associated with n, so using b in the function makes us work directly with m.
Obviously, function assignments to a and b cause n and m to change directly. In this case the subroutine will therefore exchange the values of the two global variables, becoming after the call: n with the value 3 and m with value 2.
A consequence of the fact that reference parameters must be given an area of memory in which to keep them the value is that at the call, according to a reference parameter, a variable must be written and not otherwise of expression (such as constants or expressions that also contain operators)
Another important thing to keep in mind: parameters passed by reference actually representing areas of memory of some variables outside the function, they have as initial value the very value of that variable in the time of the call. So if we want to independently calculate a function value in them, we have to initialize them as it suits us.
Now we can say that parameters passed by value represent input data for functions and those passed by reference are both input and output data.s
Solved Problems:
1. What is the effect of the following program?
#include <iostream>
using namespace std;
int n;
void change(int &a, int &b) {
int aux;
aux = a;
a = b;
b = aux;
}
int main () {
n = 2;
change(n, 3);
cout << n;
return 0; }
The answer is: compilation error. This occurs because of the call with the constant 3 on the right reference parameter a
2. Write a function that receives a natural number as a parameter and two other parameters returns the highest digit and lowest digit of the received number.
void calcul(int n, int &maxi, int &mini) {
if (n == 0) {
maxi = mini = 0;
return;
}
maxi = 0;
mini = 9;
while (n != 0) {
if (n % 10 > maxi)
maxi = n % 10;
if (n % 10 < mini)
mini = n % 10;
n /= 10;
}
}
This is the example where we see how we can return two calculated values in a function. May we also notice the use of return (without expression in the case of void functions) to handle the case more compactly particular of the number 0.