User-Defined Functions
Chapter Thirty-Three
Syllabus topic 2, "Basics of functions. User defined and Library functions"
Pages 155 to 158 of 222
In one line
A user-defined function is one you write, and writing a good one is a matter of choosing what goes in, what comes out, and nothing else.
The shape of a definition
return-type name(parameter-list)
{
declarations and statements
return expression;
}Every part is a decision:
- The return type is what the caller gets.
voidif nothing. - The name says what the function does. A verb for an action, a noun for a value:
print_report,area_of_square,is_leap. - The parameter list is everything the function needs, each with its type.
(void)if nothing. - The body is a block, so everything chapter 21 said about blocks applies.
MU's Practical 4(a), and then the same program made general
#include <stdio.h>
double area_of_square(double side)
{
return side * side;
}
double perimeter_of_square(double side)
{
return 4.0 * side;
}
int main(void)
{
double sides[] = {2.5, 6.0, 10.0};
printf("%8s %10s %10s\n", "side", "area", "perimeter");
for (int i = 0; i < 3; i++) {
printf("%8.2f %10.2f %10.2f\n",
sides[i], area_of_square(sides[i]), perimeter_of_square(sides[i]));
}
return 0;
} side area perimeter
2.50 6.25 10.00
6.00 36.00 24.00
10.00 100.00 40.00Two functions, each with one job, each returning a value and printing nothing. That last point is the single most useful habit in this chapter: a function that computes should not print. Keep the calculation and the output apart and the function becomes testable, reusable and describable in one line.
Reading input, the way the practical wants it
#include <stdio.h>
double area_of_square(double side)
{
return side * side;
}
int main(void)
{
double side;
printf("Enter the side of the square: ");
if (scanf("%lf", &side) != 1) {
printf("\nThat was not a number.\n");
return 1;
}
if (side <= 0) {
printf("\nA side must be greater than zero.\n");
return 1;
}
printf("\nArea = %.4f\n", area_of_square(side));
return 0;
}7.5Enter the side of the square:
Area = 56.2500Notice where the validation is: in main, not in area_of_square. The function's job is arithmetic and it should work for any number it is given. Deciding what the user is allowed to type is the caller's job. Mixing the two is how a function becomes untestable.
Call by value, proved
#include <stdio.h>
void try_to_change(int n)
{
printf(" inside, n starts at %d\n", n);
n = 999;
printf(" inside, n is now %d\n", n);
}
void try_to_swap(int a, int b)
{
int t = a;
a = b;
b = t;
printf(" inside, a is %d and b is %d\n", a, b);
}
int main(void)
{
int value = 5;
printf("before try_to_change, value is %d\n", value);
try_to_change(value);
printf("after try_to_change, value is %d\n", value);
int x = 10, y = 20;
printf("\nbefore try_to_swap, x is %d and y is %d\n", x, y);
try_to_swap(x, y);
printf("after try_to_swap, x is %d and y is %d\n", x, y);
return 0;
}User-Defined Functions
before try_to_change, value is 5
inside, n starts at 5
inside, n is now 999
after try_to_change, value is 5
before try_to_swap, x is 10 and y is 20
inside, a is 20 and b is 10
after try_to_swap, x is 10 and y is 20Nothing changed in main. The parameters n, a and b are local variables of their functions, initialised from copies of the arguments. The swap worked perfectly on the copies and the copies were then thrown away.
This is not a defect. It is what makes a function safe to call: you know it cannot alter your variables behind your back. When you do want a function to change the caller's variable, you pass its address, which is chapter 40 and MU's Practical 7.
One exception, and it is the one that confuses everybody. An array argument is not copied. What is passed is the address of its first element, so a function can change the caller's array. Chapters 36 and 41 are that in full.
Returning more than one value, when you cannot
A function returns one value. The three ways round it, in the order you will meet them:
- A pointer parameter, so the function writes into the caller's variable. Chapter 40.
- A structure, returned by value, holding several members. Chapter 42.
- Two functions, each returning one thing, which is often the right answer.
#include <stdio.h>
int quotient(int a, int b)
{
return a / b;
}
int remainder_of(int a, int b)
{
return a % b;
}
int main(void)
{
int a = 47, b = 5;
printf("%d / %d is %d remainder %d\n",
a, b, quotient(a, b), remainder_of(a, b));
return 0;
}47 / 5 is 9 remainder 2remainder is the name of a function in <math.h>, so this one is called remainder_of. Shadowing a library name is legal and confusing; -Wall does not warn.
Rules of thumb that hold up
One job per function. If the name needs "and" in it, it is two functions.
Short. A function that does not fit on a screen is hard to check. There is no magic number; the test is whether you can say what it does in one sentence.
Few parameters. Beyond four, a reader cannot keep the order straight and neither can the writer. That is usually a sign the parameters belong together in a structure.
Do not print from a function that computes. Return the value and let the caller decide what to do with it.
Validate at the edge. The function that reads from the user checks the input; the functions that calculate assume it is already sensible.
User-Defined Functions
Name the return, not the mechanism. is_leap(year) reads better than check_year(year), and a function returning a truth value is best named as a question.
#include <stdio.h>
int is_leap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
int days_in_month(int month, int year)
{
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month < 1 || month > 12) {
return 0;
}
if (month == 2 && is_leap(year)) {
return 29;
}
return days[month];
}
int main(void)
{
printf("February 1900 has %d days\n", days_in_month(2, 1900));
printf("February 2024 has %d days\n", days_in_month(2, 2024));
printf("April 2024 has %d days\n", days_in_month(4, 2024));
printf("month 13 gives %d, which the caller must treat as an error\n",
days_in_month(13, 2024));
return 0;
}February 1900 has 28 days
February 2024 has 29 days
April 2024 has 30 days
month 13 gives 0, which the caller must treat as an errordays_in_month calls is_leap. That is the point of functions: each one is written once and the next one is built on it. is_leap was chapter 16's expression, now with a name.
What this does NOT mean
A function does not need parameters. (void) is a perfectly good parameter list.
A function does not need a return value. void is a perfectly good return type, and a function that only prints should have it.
A parameter is not the caller's variable. It is a local variable initialised from a copy.
You cannot define a function inside another. C has no nested definitions.
Two functions cannot have the same name. C has no overloading. area(double) and area(int) in one program is a duplicate definition.
A function is not slow. The call costs a few instructions, and a compiler often removes even that. Choosing one long function over four short ones for speed is a bad trade.
Quick revision
return-type name(parameters) { body }.(void)for no parameters;voidreturn type for no value.- One job per function; name it for what it gives, not how.
- Keep calculation separate from output: a computing function should not print.
- Validate input at the edge, in the function that reads it.
- C passes by value: parameters are local copies, so a swap by value does not swap.
- An array argument is the exception: the address is passed, so the function can change it.
- One return value; use a pointer parameter, a structure, or two functions.
- No nested function definitions, and no two functions with the same name.
User-Defined Functions
Test yourself
1. Write a function that returns 1 if its argument is even and 0 otherwise.
int is_even(int n) { return n % 2 == 0; }2. Why does a swap function taking two int parameters not swap the caller's variables?
Because the arguments are copied into the parameters, which are local variables. The function swaps its own copies and they are discarded when it returns.
3. Should a function that calculates an average also print it?
No. Return the average and let the caller print. Separating them makes the function testable and reusable.
4. Can C have two functions called area?
No. C has no overloading; two definitions of one name is an error. Give them different names.
5. Where should input validation go: in the calculating function or in main?
In whichever function reads the input, normally main. The calculating function should work for any value of its declared type.
6. How can a function give the caller two results?
By taking pointers and writing through them, by returning a structure, or by being split into two functions.
7. In days_in_month, why does month 13 return 0 rather than printing an error?
Because the function's job is to give the number of days; reporting to the user is the caller's job. Returning 0, a day count that cannot be real, lets the caller detect it.
What can be asked on this, and how to answer it
"What is a user-defined function? Write a program using one." Define it as a function written by the programmer rather than supplied by the library, give the definition syntax, and give this chapter's area-of-a-square program with a prototype, a definition and a call. Say what each part is.
"Explain call by value with an example." Say the argument's value is copied into the parameter, so the parameter is a local variable and changing it cannot affect the caller. Give the swap that fails, with its output. Then say that passing an address is the way to allow a change, and name it as call by reference.
"What are the advantages of using functions?" Avoiding repetition, giving a piece of work a name, letting a program be understood and tested a piece at a time, allowing reuse in other programs, and making the program shorter to change because a correction is made in one place.
"Write a program using a function to find the number of days in a month." Give days_in_month with is_leap beside it, and point out that one function calls the other, which is the answer to "why functions" in a single example.
"Can a function return more than one value?" Not directly: a return supplies one value. Use pointer parameters, return a structure, or split the work into two functions.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.