Introduction to functions
It's not really avoidable, functions in a program are analogous to functions in mathematics. During high (secondary) school we learn that [math]\displaystyle{ f(x) = x^2 }[/math] is a function. Each [math]\displaystyle{ x }[/math] is associated with [math]\displaystyle{ x^2 }[/math]. You should be familiar that a function can only be a function if, for each [math]\displaystyle{ x }[/math], there is only one corresponding [math]\displaystyle{ y }[/math]. From the mathematical definition: a function can have different inputs and return the same result (example, a constant function). But a function cannot, for the same input, return two different results. That is not a function. Therefore, it's impossible for a function to receive an input and return a different result in different calls or more than one different result at the same time. Using computing terminology: much like a variable, a function has an address and a name. A function can assume one value at a time, the same for variables.
Suppose that you pick up a quadratic equation and write an algorithm to find the roots of it. In your program, if you need that same algorithm more than once, your code is going to be confusing and long. Let's use a function to simplify that. Whenever you need the formula, call the function. You can think about functions as formulas to do something, you reuse the formula whenever you need it.
In this course it's highly recommended that you practice with lots of exercises that require functions, because the mathematical idea is not hard, but there are lots of small technical details that require a lot of attention and practice. Initially, you just study how to use functions, without having to worry about the technical aspects of memory load. Nevertheless, you are already instructed to use functions as a mean to organize code.
Errors of logic:
- Confuse the definition of a function with calling a function. Call the function with arguments in the wrong order or with wrong values;
- The function causes the program to "hang" with or without an error message. It must be something wrong that you did in the algorithm that you wrote in the function;
- The function should have calculated the square root of 4, but the output was 5? One of the two errors above;
- If everything else fails: the algorithm might be completely correct, but the output on screen might be wrong. The printf() call contains some error.
It's required to have a good understanding about the concept of declaring a variable and local variables to avoid trouble here
- A function that calculates xn:
/* receives x and n and calculates x^n returns the value accumulated in x */
int x_power_n(int x, int n) {
int i;
max = n;
n = x;
for (i = 1; i < max; i++) { x = x * n; }
return x;
}
[math]\displaystyle{ x^0 }[/math] ou [math]\displaystyle{ 0^0 }[/math]. The algorithm does not work in both cases, but it's rather easy to add an IF /ELSE to fix that.
- The sum of the first n terms of an arithmetic progression, implemented as a function
int sum(int n) { return (n*(1 + n))/2; }
The formula is simple, there is no need to store the result in a variable to return the value of that variable. The function itself is already occupying some memory address.
Try picking up the program that checks if a number is odd or even from the other chapter and rewrite it with a function.
- The algorithm that does the sum of the first n prime numbers from the chapter of iteration structures, this time with functions:
#define YES 1
#define NO 0
/* Receives a number and checks its divisibility, returning the value of is_prime. If a divisor is found, is_prime's value is changed to NO If no divisor is found, is_prime's values remains YES */
int remainder, divisor, is_prime = YES;
for (divisor = 3; divisor < prime / 2; divisor += 2) {remainder = prime % divisor;
if (remainder == 0) is_prime = NO;
}
return is_prime;
}
/* If the number of primes to be summed is one, then the result is direct, 2 without calculating anything
Else, count numbers and check if it's prime or not
The number 3 is prime and can skip the prime number check */
int prime, found_one = 0, sum = 0;
if (number == 1) return 2;
else {found_one = 1;
sum = 2;
}
else { check_prime(prime); }
found_one++;
sum = sum + prime;
}
}
}
return sum;
}
Functions are used to put order in your program. One of the concepts is to keep your functions simple, to allow them to server in a broad range of different cases without having to be modified. If the function is too specific, it'll have a much narrower range of applications.
Be careful with the confusion between function's parameter x argument! It's becomes pretty clear when a function is called inside another. Can a value be passed directly to the inner function without having to pass through the outer one? This kind of doubt arises when there is incorrect understanding about the difference of calling and defining a function.
- Sum of the terms of the series: [math]\displaystyle{ 2 + \frac{2^2}{3!} + \frac{2^4}{4!} + ... + \frac{2^n}{n!} }[/math]
Version with separated functions:
/* the function to calculate the power x^n can be the same as the one made earlier in this chapter */
/* receives n and calculates the factorial n! */
int i, fact = 1;
if (n == 0 || n == 1) return 1;
for (i = n; i > 1; i--) fact = fact * i;
return fat;
}
/* calculates the sum of the series 2^n/n! up to the precision value received by the function */
float series (int precision) {int i, term = 0, sum = 0;
for (i = 1; term > precision; i++) term = x_power_n(2, i)/factorial(i);
sum += term;
return sum;
}
Which one grows faster: [math]\displaystyle{ 2^n }[/math] or [math]\displaystyle{ n! }[/math] ? For n = 10, 10! > 210. Therefore, the terms to be summed are progressively smaller. That's why the conditional is not a limit of iterations but a limit of precision. It's good to solve this kind of exercise in steps. First the power or the factorial, then the other. Leaving the sum of the series for the last.
If you noticed, the algorithm has a flaw. It never stores the calculations in each step. For each term in the sequence the algorithm goes all the way back to the number 1. It's a huge waste of performance to repeat the same computation over for each term.
If the precision is constant, then we have a typical case of a function that doesn't require any parameter. But in this case, if the result is always the same, it's much easier to define the calculated value as a constant. The program's main function is a classical example of a function with no parameters. All variables used by your program are local to the main function. The operational system doesn't send any value to your program.
Version without using functions to calculate factorial and power:
/* calculates the sum of of the series 2^n/n! up to a certain precision */
int i, num = 2, den = 1, sum = 2;
for (i = 1; num/den > precision; i++) {num = num * 2;
den = den * i;
sum += num/den;
}
}
Factorial and power are sequences in which the n of one matches the n of the other, therefore we can calculate both in the same loop's iteration, without having to rely on one separated loop for each one.