|
~ Introduction to Programming: C ~ Session 4 - Functions & Scope |
We looked briefly at functions in session two. We'll be taking a closer look at the use of functions in this session, and at how Scoping Rules work.
As a brief reminder, a Function can serve a number of purposes:-
· A way of taking some information (input), processing it, and producing a result (output)
· A way of breaking up a program into smaller chunks (or tasks) and naming these tasks.
· A way of taking lines of code that you use often in your program or programs, and re-using them instead of repeating them. This means that if the code needs to change, you don't have to change it in many places – just the one.
It is possible to define variables that are available from any function, including main() – you can use this as a way of sharing information, rather than passing data explicitly into a function using parameters. The disadvantage of doing this is that you cannot then use different variables as input to the same function. In general, it is better to pass data into a function using parameters.
If you wish to use global variables, simply declare your variables outside of any functions - .e.g. above the main() function. E.g. in the example given later:-
/*
Global variables */
int
res,i1,i2,i3;
/*
Main function - entry point of program */
void
main() {
/* Variables available only in main() function */
int i4,i5,i6,i7,i8,i9;
...
}
Above the main() function appears a declaration for four variables – res, and i1 to i3. These are available from any function – their scope is global – i.e. they can be seen or changed from anywhere in the program. This means that any part of your program can share the same information without the need for passing parameters – this seems tempting at first, but over-use can quickly make your programs difficult to manage.
You can see that the main() function has variables i4 through to i9 defined. This means that these variables are only available from inside the main() function. The technical term for this is scope, which means the part of the program in which the variable is usable, or visible. In this case, i4 to i9 have scope within the main() variable. Thus, nothing outside the main() function (e.g. other functions) can see the contents of i4 to i9. This allows you to protect parts of your program from the influences of any changes that may be made later. For example, if we later created another function which used a variable called i4 then it's variable will have scope only within it's function, and so there should be no confusion between the two different variables in two different functions, which just happen to have the same name.
Below is an example of a function that simply places 80 dashes onto the screen to form a line that is the width of the console window.
The void before the function definition means that no value is returned from the function – i.e. there is no output returned to the place in the program where the function was called.
The divider part defines the name of the function, and the () part is the list of parameters, or inputs, or values passed into the function. There are none, hence the brackets with nothing between.
Finally, the { starts the C block of code which forms the function and the } ends the block. Everything between is what happens when the divider() function is called.
void divider() { for(int i=0;i<80;i++) printf("-"); }
To specify a function that returns a value, specify the type of value before the name of the function – e.g. instead of void use int if you wish to return a whole number.
Inside the function, use the return keyword followed by the value to be returned.
Notice that we have defined a variable inside get_int() called intval which is only visible from the get_int() function – its scope is the get_int() function. Therefore, the value gets created whenever the get_int() function is used, and destroyed again when the closing } of the function is reached.
int
get_int() {
int intval = 0;
printf("Type an integer: "); scanf("%d",&intval);
return intval;
}
It is possible to use globally defined variables (e.g. i1, i2 and i3) within functions, as they are globally available. In the below example, there are no parameters passed. The input is taken from the three variables.
Notice though, that if we wanted to sum other variables (e.g. i4, i5, and i6) we would be stuck. We would have to assign them to i1, i2 and i3 first, thus losing the original contents of these variables. You can see why passing values using parameters is preferred.
int
get_sum() {
int res = i1+i2+i3;
return res;
}
To take data in as input, we can define parameters inside the brackets () after the name of the function – we do this by giving each parameter a type and then name, separating each parameter with a comma.
In the example below, we will take three values as parameters (i1, i2, and i3) each of which are integer. These values are summed and divided by three to give a return value for a result.
The scope of the parameters is the life of the function. They are treated just like locally defined variables in this respect.
When the get_average() function is called, the values passed in are copied to these three variables to be used within the function. Thus changing any of these three variables would not change the original variables or values that were passed as parameters.
int
get_average(int i1, int i2, int i3) {
return (i1+i2+i3)/3;
}
Have you noticed that the names are the same as the global variable names. You might think there is a clash here, as the global variables should be visible from the local procedure, as they are global. However, we have locally defined variables in the form of parameters within the function. Which values are used within the function?
The answer is that the nearest scope is seen first, and hides any more global scope. Thus the parameters are used in preference to the global variables, and the global variables are effectively unavailable from within this function.
Sometimes, you want to change the value of a variable from within a function rather than just refer to its value. To do this, you need to pass the variable by reference, which means that a copy is not taken, and the original variable is used to look at and change values.
When you define a parameter as being passed by reference, you need to put an asterisk * before the parameter name. Inside your function, you must also remember to place a * before any occurrences of the name, otherwise strange things can happen.
void
make_negative(int *i) {
*i = -*i;
Finally, when calling the function, make sure you put an ampersand (&) before the variable name being passed in – e.g. make_negative(&i1)
The reasons for this will be explained further when we discuss pointers and strings.
We now make use of the above examples in a single program as a demonstration.
You may wish to try stepping through the program (use the F8 key repeatedly) to see what is happening line-by-line. Hover your cursor over variables in your program listing to see the values whilst doing this. Press F9 if you want the program to continue running as normal.
#include
<stdio.h>
#include
<conio.h>
#include
<stdlib.h>
/*
Pre-define functions using prototypes */
void
divider();
int
get_int();
int
get_sum();
int
get_average(int i1, int i2, int i3);
void
make_negative(int *i);
/*
Global variables */
int
res,i1,i2,i3;
/*
Main function - entry point of program */
void
main() {
/*
Variables available only in main() function */
int
i4,i5,i6,i7,i8,i9;
clrscr();
divider();
printf("Function testing\n"); divider();
printf("\nGet
Three Values\n");
i1=get_int();
i2=get_int(); i3=get_int();
res=get_sum();
printf("Sum is: %d\n",res);
printf("Average
is: %d\n",get_average(i1,i2,i3));
divider();
printf("Get
Three more Values\n");
i4=get_int();
i5=get_int(); i6=get_int();
printf("Average
is: %d\n",get_average(i4,i5,i6));
divider();
printf("Get
Three more Values\n");
i7=get_int();
i8=get_int(); i9=get_int();
printf("Average
is: %d\n",get_average(i7,i8,i9));
res=get_average(get_average(i1,i2,i3),get_average(i4,i5,i6),
get_average(i7,i8,i9));
printf("Average
of all: %d\n",res);
make_negative(&i1);
make_negative(&i2); make_negative(&i3);
printf("i1
is now %d, i2 is now %d, i3 is now %d\n",i1,i2,i3);
getch();
}
/*
Displays a line of dashes on the screen */
void
divider() {
for(int i=0;i<80;i++) printf("-");
}
/*
Gets a prompted integer value from user */
int
get_int() {
int intval = 0;
printf("Type an integer: "); scanf("%d",&intval);
return intval;
}
/*
Find the sum of three values using global variables */
int
get_sum() {
int res = i1+i2+i3;
return res;
}
/*
Finds the average of three values */
int
get_average(int i1, int i2, int i3) {
return (i1+i2+i3)/3;
}
/*
Makes a value negative */
void
make_negative(int *i) {
*i = -*i;
}
Try running the program. You should get an output similar to below : -
--------------------------------------------------------------------------------
Function
testing
--------------------------------------------------------------------------------
Get
Three Values
Type
an integer: 1
Type
an integer: 2
Type
an integer: 3
Sum
is: 6
Average
is: 2
--------------------------------------------------------------------------------
Get
Three more Values
Type
an integer: 4
Type
an integer: 5
Type
an integer: 6
Average
is: 5
--------------------------------------------------------------------------------
Get
Three more Values
Type
an integer: 7
Type
an integer: 8
Type
an integer: 9
Average
is: 8
Average
of all: 5
i1 is now -1, i2 is now -2, i3 is now -3
Change the get_sum() function so that it takes three parameters rather than using global variables.
Place the global variables with the other variables declared within the main() procedure, so they are no longer global.
Show sums for the second and third set of three numbers, now that this is possible, making use of the amended get_sum() function.
You will need to change the original get_sum() call from the first set of numbers too, as it now uses parameters.
Also: Remember to change the prototype for the get_sum() function at the top of the program.
Let's say we wish to read a start date from the user, and then an end date. We will be using this to calculate their biorhythms ... more on what this is later!
If we think about what we need, we are inputting the date twice – doing the same task twice – a good reason for putting the task into a function once to use in two different places.
Thus, to input the date, we will need the following as an input:-
Message saying what needs to be input
The user then inputs data. We cannot predict what that is going to be at the time of writing the program. The outputs however, will be:-
A day-of-month value (as an integer)
A month value (as an integer)
A year value (as an integer)
This can be represented diagrammatically as follows :-

A simple C program to request and record the two dates might be as follows:-
#include
<stdio.h>
#include
<conio.h>
/*
Prototype of function */
void
get_date(char *msg, int *day, int *month, int *year);
/*
Main program */
void
main() {
int day,month,year,this_day,this_month,this_year;
clrscr();
printf("Biorhythms
Program\n-------------------\n\n");
get_date("of birth",&day,&month,&year);
get_date("for today",&this_day,&this_month,&this_year);
printf("\nCalculating days from %d/%d/%d to %d/%d/%d...
",
day,month,year,this_day,this_month,this_year);
getch();
}
/*
Function takes date description as input, returns day, month and year
as outputs */
void
get_date(char *msg, int *day, int *month, int *year) {
printf("Enter date %s (d/m/yyyy): ",msg);
scanf("%d/%d/%d",day,month,year);
}
The parts referring to the get_date function are highlighted in bold text.
If (for example) we wanted to input date in American format instead (e.g. if we were tailoring the program for the American market), then instead of changing the program twice – once for the birth-date, and once for today – in the program, we change the format just once in the get_date function as follows:-
printf("Enter date %s (m/d/yyyy): ",msg);
scanf("%d/%d/%d",month,day,year);
Simple!
So how does this work? If we take a look at the prototype for the get_date function, we can see that each of parameters has a star before it:-
void
get_date(char *msg, int *day, int *month, int *year);
This means that the value that we are passing is not a copy of the variable, but a reference to the variable itself. Any variable that we pass must have an & placed before it, as in the following line:-
get_date("for today",&this_day,&this_month,&this_year);
The exception is the text string, which implicitly has an & before it, so we don't need to put it in. More on this when we look at strings.
Thus, if we change any of these variables within the function (called day, month and year within the procedure), we also change the original variables (this_day, this_month, and this_year.
The following diagram explains a little more about what is happening. Note the & means we are passing a reference to say where the variable is held, and the * in the parameter list says that the variable being passed is the location of the variable, and a copy shouldn't be taken, but rather the original variable should be used to store any data changes. It is what is known as a de-reference operator...

Thus "for today" gets read in and used as input as part of the printf statement to say "Enter date for today (d/m/yyyy):" as a prompt for the user to enter some data.
The variable this_day is passed by reference as the second parameter, and so storing anything into the parameter variable day will effectively be the same as storing the data into the variable this_day. Similarly, this_month is represented by month and this_year is represented by year.
This means that when the scanf statement reads in a date in the form d/m/y, the values are stored in the three variables this_day, this_month and this_year as output (i.e. results).
The void at the beginning of the functions means that no value is returned back explicitly – therefore, get_date cannot be used as part of a mathematical expression – e.g.
printf("Date is %s",get_date("for today",&day,&month,&year));
could not be done, as get_date does not explicitly return a value, and we would not know the type of data being returned, as we have specified this as being void. Thus, we would need to split the results as follows:-
get_date("for
today",&day,&month,&year);
printf("Date
is %d/%m/%y",day,month,year);
Okay, so how about another function that calculates the number of days between the two dates. In order to do this, we would need to give every date a specific value. For this we have to be mindful of the rules regarding dates.
Remember the rhyme:-
Thirty
days have September, April, June & November
All
the rest of Thirty-One, except February Alone, which has twenty-eight,
and twenty-nine each leap year.
That takes care of the number of days in each month. So what is a leap year?
·
A year which is divisible by four. This means that if we divide it
by four, there is nothing left over - i.e. it has a modulus of 0 – so in C terms:-
if ((year%4)==0) printf("A leap year!");
· UNLESS the year is a century (i.e. divisible by 100). In which case, it's not a leap year
· UNLESS the year is a millennium (i.e. divisible by 1000). In which case, it is a leap year.
In C terms, we can calculate the leap year as follows:-
if (
((year%4)==0)&&(((year%100)!=0)||((year%1000)==0) ) printf("Leap
Year!!");
Breaking this down, this says that if the year is divisible by 4 and the year isn't divisible by 100, or the year is divisible by 1000, then it's a leap year.
So the new procedure (called date_value) will find a unique number that gives us the date, counting from a value of 1 being 1st January 1900. Here's the entire program that we typed earlier, with a few additions (shown in bold type): -
#include
<stdio.h>
#include
<conio.h>
#include
<stdlib.h>
/*
Prototype of functions */
void
get_date(char *msg, int *day, int *month, int *year);
int
date_value(int day, int month, int year);
int
date_diff(int day1, int month1, int year1,
int day2, int month2, int year2);
/*
Main program */
void
main() {
int day,month,year,this_day,this_month,this_year,diff_days;
clrscr();
printf("Biorhythms
Program\n-------------------\n\n");
get_date("of birth",&day,&month,&year);
get_date("for today",&this_day,&this_month,&this_year);
printf("\nCalculating days from %d/%d/%d to %d/%d/%d...
",
day,month,year,this_day,this_month,this_year);
diff_days=date_diff(day,month,year,this_day,this_month,this_year);
printf("Result is %d days\n", diff_days);
getch();
}
/*
Function takes date description as input, returns day, month and year
as outputs */
void
get_date(char *msg, int *day, int *month, int *year) {
printf("Enter date %s (d/m/yyyy): ",msg);
scanf("%d/%d/%d",day,month,year);
}
/*
Finds an integer value to represent a date, where 1=01/01/1901 */
int
date_value(int day, int month, int year) {
year -= 1900;
int totval = year * 365;
totval += (year-1)/4;
totval -= (year-1)/100;
totval += (year+899)/1000;
switch(month) {
case(12): totval += 30;
case(11): totval += 31;
case(10): totval += 30;
case(9) : totval += 31;
case(8) : totval += 31;
case(7) : totval += 30;
case(6) : totval += 31;
case(5) : totval += 30;
case(4) : totval += 31;
case(3) : totval += 28;
if ( ((year%4)==0)&&(((year%100)!=0)||((year-100)%1000)==0) )
totval++;
case(2) : totval += 31;
}
totval +=day;
return totval;
}
/*
Find the number of days difference between two d/m/y values */
int
date_diff(int day1, int month1, int year1,
int day2, int month2, int year2) {
return
abs(date_value(day2,month2,year2)-date_value(day1,month1,year1));
}
As an aside, if you've looked a bit further into C, you may be aware that it has date and time functions built-in in the library time.h – these are fine if you are only dealing with dates from 1970 onwards, but many people are older than thirty (it's true!) so we really need to devise functions of our own.
Here's what the date_value function does: -
1. Takes three values (day, month and year) as input data.
2. Subtracts 1900 from the year to make this our "zero year" – avoids the result becoming unnecessarily large.
3. In a non-leap year, there are 365 days (try counting them up based on the poem given earlier). Thus times the number of years by 365 to give the number of days. I.e. for year 1900, the value would be 0*365 = 0, whereas for 1901, it would be 1*365 = 365 etc.
4. This just leaves the leap years to take care of. We need to add one extra day (February 29th) for every fourth year from 1900 to the present day, excluding the current year. Thus, we take one from the year and divide it by four.
5. But what about centuries? They aren't leap years. So take one to exclude the current year, and divide by 100. This gives the number of days in centuries to exclude, so take this amount from the total.
6. But what about millennia? Add 900 to our amount gives (for 2000) 2000-1900+900 = 1000 (i.e. still a multiple of 1000, but when divided by 1000, gives 1 day only since 1900). We add this on, because millennia years are leap years. However, remember that we aren't counting the current year, so our calculation must only work once we have reached 2001 – i.e. 2001-1900+899 = 1000.
7. So now we have to add up all the days for the current year. To do this, we count backwards from December to January. Thus, for December, we need to add the number of days from November back to January, and then add on the number of days in December. Thus, we add up the number of days in November (30) plus the number of days in October (31) etc. Note that we have achieved this by using a switch statement without the break;
8. In all months from March onwards, we need to check the number of days in February to count. This is 28 plus 1 if it's a leap year (note our calculation earlier).
9. Finally we add the number of days in the month to the total value. This is returned as an answer.
Another function has also been created, date_diff which takes the birth & current dates as parameters, and uses the date_value function to calculate the date-values for each date, and subtract them from each other to give the number of days apart.
Add a few lines to the main() function to show on two separate lines the date-values for both the birth date and current date. Answer at end of the session notes.
To understand better how the switch statement works, try putting a breakpoint at the switch(month) line, and enter a value of 01/06/1962 for the birth date and 01/02/2002 for the current date.
The computer will stop at the switch line. If you are using a later version of JBuilder, hover your mouse cursor over the word month in the highlighted line. The value will be displayed in a tooltip as 2 for February.
Now press the F8 key to step over to the next line that will be executed.
A blue line appears at the case(2) line to show where execution is currently resting. So you can see for February, only the days for January (31) are added.
Press F9 to resume running the program.
This time around, when the program stops, if you hover the cursor over month you will get a value of 6 for June.
Press F8 now, and you will see that the blue line shows that execution has resumed at the case(6) line. If you carry on pressing F8, you can see that execution continues line-by-line, adding up amounts for each month prior to the current one. If you hover your cursor over anywhere in the code that says totval you will see the value of this variable increasing with each step.
Do you know why the date_value function is called with the current date before the birth date? Answer at the back of the session if you're not sure. Clue: Where is the date_value function being called from?
So what are Biorhythms?
According to research, every human being has three basic cycles linked into their Physical, Emotional, and Intellectual well-being. The first half of each cycle is "positive" – i.e. good, peaking at a quarter-way through the cycle. The second half of each cycle is "negative" – worst three-quarters through the cycle. "Critical" days are exactly half-way through the cycle, where you are "off balance". If you have a day where all three cycles are "critical", worry. Actually, you just need to take extra care, apparently.
The Physical cycle is 23 days, the Emotional cycle is 28 days and the Intellectual cycle is 33 days.
So to calculate your Biorhythms, all you have to do is find the modulus of the difference in days between the current and birth date, against each of these three numbers. This should give you a value for each of the three biorhythms.
·
Calculate each of the values of
Biorhythms – create three new variables in main() for each of these
·
Create a function that takes two
parameters – the biorhythm value and the maximum value for the biorhythm,
which works out which phase the Biorhythm is in:-
o
If it is in the first half, display
"positive"
o
If it is in the second half,
display "negative"
o
If it is half way (11 or 12 days
for Physical, 14 days for Emotional, 16 or 17 days for Intellectual), show as
critical
If you have time, use a do...while loop, and check whether the user wishes to check for another current date (not birth date). The loop will be while the answer is 'Yes'. Answer given at end of session notes.
Exercise
1
int get_sum(int i1, int i2, int i3) {
int res = i1+i2+i3;
return res;
}
and
earlier in the program (in the main() function) –
void
main() {
int res,i1,i2,i3,i4,i5,i6,i7,i8,i9;
clrscr();
divider(); printf("Function testing\n"); divider();
printf("\nGet Three Values\n");
i1=get_int(); i2=get_int(); i3=get_int();
res=get_sum(i1,i2,i3); printf("Sum is: %d\n",res);
printf("Average is: %d\n",get_average(i1,i2,i3));
divider();
printf("Get Three more Values\n");
i4=get_int(); i5=get_int(); i6=get_int();
printf("Sum is: %d\n",get_sum(i4,i5,i6));
printf("Average is: %d\n",get_average(i4,i5,i6));
divider();
printf("Get Three more Values\n");
i7=get_int(); i8=get_int(); i9=get_int();