|
~ Introduction to Programming: C ~ Session 19 - Passing variables by reference |
In This Session...
In this session we will be covering the following topics:-
In the last session, we covered:-
In session 11, we talked about good coding practice. One practice that was mentioned was the indentation of commands within functions.
This holds true for any functions that you write, not just the main() function. So, when you put in the opening curly brace - { - of your function, all the lines between this and the closing curly brace - } - should start with a few (typically 2 or 3) spaces to make them all line up slightly in from the curly brace.
Similarly, whenever you use loops, if statements etc. that use curly braces to group together what is going to conditionally be done, then indent anything between these curly braces another few spaces.
The below example gives an example of a badly-indented function:-
int power(int x, int n)
{
int i, tot;
tot = 1;
for ( i = 1; i<=n; i++ )
{
tot = tot * x;
printf("%d to the power of %d is %d\n", x, i, tot);
}
return tot;
}
The purpose of this function is to raise the first parameter (x) to the power of the second parameter (n). This means that for every n, the value 1 is multiplied by the value of x.
So 2 to the power of 0 is 1, 2 to the power of 1 is 2 (1x2) 2 to the power of 2 (2 squared) is 4 (1x2x2), 2 to the power of 3 (2 cubed) is 8 (1x2x2x2), 2 to the power of 4 is 16 (1x2x2x2x2) etc. etc.
It also shows how the result was worked out. In practice, you might comment this out (put comments around the print line) to stop it from executing, and only take them out if the program doesn't seem to work properly, and you suspect it may be due to how the power is being calculated. This is a common debugging technique.
Exercise 59 - Well-indented function
Take the badly indented function above, and turn it into a well-indented function.
When you have done this:-
The output from a typical session should look something like this:-
Enter number,power: 2,5 2 to the power of 1 is 2 2 to the power of 2 is 4 2 to the power of 3 is 8 2 to the power of 4 is 16 2 to the power of 5 is 32 2 raised to 5 is 32_
or if you are unsure how to take input in the format 2,5 the output can look something like this:-
Enter number: 2
Enter power: 5 2 to the power of 1 is 2 2 to the power of 2 is 4 2 to the power of 3 is 8 2 to the power of 4 is 16 2 to the power of 5 is 32 2 raised to 5 is 32_
A solution is given at the end of these session notes.
We had a brief look at global variables in the last session. Let's take a more in-depth look so we understand the concept of scope as applied to local and global variables.
A variable only exists for as long as the function in which it was created is in scope. This term in scope means "is in between the curly brackets for" the function in which the variable was created. For example:-
main()
{ |
float f; |
scanf("%f",&f); | this is the scope of the f variable
one_over(); |
printf(" i is %f", f ); |
}
void one_over()
{
float j; |
if (f==0) f==1; | this is the scope of the j variable
j = 1 / f; |
}
will not work. This is because the f variable is created within the main() function, and so is only available within the curly braces of the main() function.
You may be forgiven for thinking that as we call the one_over() function from the main() function, then its data must be available to the one_over() function. This does not happen. The reason is that the one_over() variable can be called from any function - and another function may not have a f variable defined for it, in which case the one_over() function would not be able to do its calculation - e.g.
void badfunc()
{ |
float x; |
scanf("%f",&x); | this is the scope of the x variable
one_over(); |
printf(" x is %3.2f", x ); |
}
void one_over()
{
float j; |
if (f==0) f==1; | this is the scope of the j variable
j = 1 / f; |
}
Similarly, the one_over() variable has a local variable called j which is only available between the curly braces of the one_over() function. So, the following example would not work:-
main()
{ |
float f; |
scanf("%f",&f); | this is the scope of the f variable
one_over(); |
printf(" j is %3.2f", j ); |
}
void one_over()
{
float j; |
if (f==0) f==1; | this is the scope of the j variable
j = 1 / f; |
}
The value of j is not available from the main() function as it has ceased to exist once the one_over() function has finished its processing.
Of course, this means we cannot pass information into or out of a function, as data is held separately for each function. There are three possible solutions for this problem:-
Pass values using function parameters
The first is to pass data into the function using input parameters, as we looked at a few sessions back. Our program would look something like this:-
main()
{ |
float f,res; | this is the scope of the f variable
scanf("%f",&f); |
res = one_over(f); |
printf(" res is %3.2f", res ); |
}
float one_over(float amt)
{
float j; |
if (amt==0) amt==1; | this is the scope of the j variable
j = 1 / amt; | and the amt parameter variable
return j; |
}
We can see that we are passing the i variable into the one_over function. This gets copied into the amt local variable inside the function. This can then be changed if necessary (as it is here to ensure that we never divide by zero, although this is not a very good solution to the problem!).
The result is then returned from the function (of type float as specified at the start of the function's definition line and prototype line), which has been calculated to be one divided by the original value.
Control is passed back to the original program, where one_over(f) is replaced with the return value - e.g. if f contained 2 then the value returned would be 1/2 (i.e. 1 divided by 2) - i.e. 0.5, so one_over(f) is replaced with 0.5, and the line in main() becomes res = 0.5. So res gets filled with the value 0.5 in this example.
Pass values using global variables
Another method, which is generally frowned upon by many, is to use variables that are available to every function in your program. These are called global variables.
To declare a global variable and make it available to all functions, just to the definition out of a specific function, and put it towards the top of your program - just above the main() function should be fine. This implies that the scope of the variable has changed to be outside all functions, and therefore available to all functions. Our program can now look like this:-
float f,res;
main() |
{ |
scanf("%f",&f); |
one_over; | this is the scope
printf(" res is %3.2f", res ); | of the res and f variables
} |
|
void one_over; |
{ |
float amt; | |
amt = f; | this is the scope |
if (amt==0) amt==1; | of the amt variable |
res = 1 / amt; | |
} |
In this case, we have placed both the input (variable f) and output (variable res) as global variables. By doing so, we must always make sure we set variable f before we call the one_over function, as it is now providing the input. We must also make sure that the variables res is set before the function ends, as this is now providing the output.
It would be possible to provide input as shown in the previous section, by passing a parameter, and providing the ouput as the res variable, and then we could keep the res variable local to the main() function. This would mean that we could re-use the one_over function in different functions without the fear of overwriting the result.
We still run the risk of losing our input, if the one_over function is used in several different places - we risk mixing the inputs and outputs up of if we use it several different times. This is the advantage of keeping variables local, and passing parameters - this keeps the risk of overwriting something unintentionally to a minimum, and is the reason why you should not use global variables unless really necessary.
A typical example of where it would be okay to use a global variable might be a variable that is truly only ever used for one purpose throughout a program - for example score or high_score in a game, or userid in a password-protected application.
Passing parameters by reference
The third option is to pass the original variable itself to the function rather than making a copy, so that any changes made to the parameter inside the function makes changes to the original variable automatically. This is called passing by reference because you are passing a reference to the variable rather than the value itself.
In the above example, this doesn't make sense to do, because we have exactly one result, and we may want to use the function as part of another calculation - e.g. res = one_over(f) + one_over(g)
So how do we pass a variable into a function in order to read it and possibly change it? To understand this, you need to understand a little about how variables are stored on your computer.
Every variable is stored in the computer's memory, or RAM (Random Access Memory). This type of memory only lasts as long as the power is supplied to your computer, and is thus known as volatile memory. It is much faster to access than your hard disk, so it is used to store data that your computer needs to access quickly - e.g. programs, data you are working on etc. When you save a document, you are infact copying data from RAM to your disk, so that you can retrieve it after the power has been switched off to your computer.
Each variable is stored in RAM at a specific location. The memory is arranged so that every piece of data is numbered sequentially (rather like an array in C - there is a connection).

When a variable is declared, it is allocated a piece of this memory, and the starting location of the variable is stored in a list against the variable name:-

When you look at a variable, the computer is looking up the name in that list, reading the location (known as a memory address) of the variable, and then looking in that location to retrieve the data.
This means that the variable could be set to point at a different location in memory if you so wished, and the same named variable could be used to look at a different piece of data.
This proves to be useful when we wish to pass a variable into a function. All we have to do is pass the location (or address) of the variable instead of taking a copy of the data, and that address can be put against the variable name in the list of local variables. This then means that when you access the variable, you access the original variable passed into the function, whichever variable that may have been.
The advantage of this is that we can pass any variable to the function to have it changed.
Clearly, we have to be careful what we pass to a function. If we are expecting string variable to be passed into our function, and we pass an integer variable, then what is passed is the address of the variable. Both are valid addresses, but when you try to read from the variable, you will find that the four bytes (memory locations) that the integer takes up in memory may not have a zero within the four bytes, so when reading or writing from/to the string, we read past the end of the variable into another piece of memory that could contain anything. This could cause serious errors in your programs and somewhat unpredictable results - you may even end up writing over your program in memory!
In C, if we wish to refer to the address at which a variable is held, we just put an & in front of the variable - the & looks a bit like the letter A - A for Address.
So, if we wanted to pass the address of a variable to a function, so that the contents of the variable can be changed from within the function, we pass the variable with an & in front of it
Inside the function, the prototype or function definition is expecting an actual variable to be passed rather than the address of the variable. For this reason, we need to identify that we are expected an address to be passed, for which we use the "dereferencing" operator * (also used for multiplication, confusingly!)
So all we do, is add a * symbol before the name of any variables that actually hold addresses, in the prototype.
This turns the variables into special variables known as pointers that actually contain the address of data - i.e. they point to the data being held.
If we now made any changes to the pointer variable, this would change the address rather than the data being pointed to - e.g. adding 1 would mean that the variable would now point to a data held one place further on in memory. This can actually be useful if we wish to look through something like a character array (string) one character-at-a-time. Otherwise the results can be unpredictable and potentially damaging to the data in your program, as you may end up writing over a different piece of data.
So how do we access the data held at the address in the pointer variable? We use the dereferencing - * - operator to extract the data at the address given by the pointer variable. As we defined the pointer with a specific type, C knows the type of data that should be held at this location, and so how much data to read.
All we do to dereference a pointer is to place a star - * - before the variable when we use it in our program - remember that this gives us the data itself, rather than the pointer.
It's worth mentioning that you will probably need to read over these notes several times (I would suggest reading it a few times initially, leave it a week, and read it again). Pointers are a topic that most students have difficulty getting to grips with initially, and even experienced programmers often shy away from them, due to the potential hazards of getting it wrong. However, once you understand them, they are a very powerful tool, and well worth knowing.
The following example gives the previous example using a "pass by reference" technique where we copy the address of the variable into the function, to a local pointer variable. We then use the dereferencing operator to read and then change the value of this variable. Note that we do not need to use the return instruction as we are not passing a value back as part of a formula, but rather passing data back through the original variable itself. We can therefore make the return type of the function void
#include <stdio.h>
#include <conio.h>
void one_over(float * amt);
main()
{
float f;
printf("Enter value: "); scanf("%f",&f);
one_over(&f);
printf("Result is: %3.2f", f );
getch();
}
void one_over(float * amt)
{
float j;
if (*amt==0) *amt=1;
*amt = 1 / *amt;
}
You can see that the prototype has a void return type, indicating that it can no longer be used as part of a calculation. Instead, the parameter (f) has been turned into a reference parameter, meaning that it expects to have a pointer to a float (i.e. the memory address of a float) variable passed to it.
If we look at the listing, we can see that we no longer set the res variable to be equal to the result of the one_over function. We now pass the f variable with an & before it - i.e. we pass the address of the f variable.
So the local amt variable inside the one_over function contains the address of the f variable in the main() function.
To access the data pointed to by the amt pointer variable inside the function, we need to put a star - * - before the amt variable to dereference it. You can read this as "contents of" in the context.
Thus, in the one_over function, we read the value, and if it is not zero, set it to be one divided by itself. Note that there is no return statement. None is required when the return type is void - no value needs to be returned, as the variable has been changed directly.
Exercise 60 - Stepping through a reference parameter
Type out the program listing given above. Run it using the F8 key to step, and step through a line at a time. When the execution point highlighter is over the line that calls the power function, press F7 instead to trace into the function.
The value of the amt parameter should be an address (given as something like :0012FF80 - this is hexadecimal - another way of showing a number - you don't need to worry about this).
The value of de-referenced *amt variable will be the value you typed in.
If you wish to see a value, you can either hover your cursor over the variable in the program listing, or use the Ctrl+F9 Evaluate option, and type in the name of the variable (or any other calculation or formula) to test in the Expression: text box. Press the Enter key or click on the Evaluate button to view the expression. Note, you can also change the value of a variable from here by evaluating it first, typing out a new value in the New Value text-box, and clicking on the Modify button.
This can on occasion be quite useful (e.g. if you are stuck in an infinite loop and need to set the loop variable to something that will cause the loop to exit.
Exercise 61 - Changing the power function
Change the power function we created earlier so that the x parameter variable receives a variable by reference. Remember to pass an address to the variable, and to make the function have a return type of void. You may wish to set the result variable to the value of the number variable and pass the results variable to the power function. This avoids the problem of losing the original value of the number variable.
If you wish to pass an array variable to a function, the process is slightly different.
An array is actually held as an address (or pointer) to an area of memory. This means that when you come to pass it into a function, it can only be passed in as a reference. For this reason, you do not need to use the & operator when passing a string to a function.
Also, when you define the parameter to read in the data in the function prototype, you declare it as though you were reading in a single-item variable instead of an array. It is then up to the function to read the first item from the array, move the pointer along to the next item, and then carry on until it finds the end of the array - thus, it has to somehow know how long the array is.
In the function prototype / heading, we have a problem. How long is the array that is being passed into the function? We have no idea, as different sized arrays could be passed in.
With strings - i.e. character arrays - we have one advantage: we know that the end of the array is marked by a zero character, so as long as we know where the beginning of the array is, we can find the end of the string of characters by moving from the beginning of the array to the end one-character at a time.
Look at the following example: It defines a function to convert the whole of a character array to upper case. This is actually very useful, as standard C doesn't have such a function:-
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <string.h>
void upper(char * s);
main()
{
char name[21];
printf( "Enter name: " ); gets(name);
upper( name );
printf("Name is: %s", name);
getch();
}
void upper(char * s)
{
while (*s>0)
{
*s = toupper(*s);
s++;
}
}
What is happening here?
The character array name is passed by reference into the upper function. Only the address of the first character is really passed, and this is stored in the local s variable in the upper function.

So, we do a loop which looks at the character pointed to by the s character pointer, and if the character is 0 (i.e. the end of the string), then the loop terminates (i.e. no more characters to turn to upper case).
Otherwise, we take the character pointed to by dereferencing it (first character to start with), convert it to upper case, and store it back in its original character position.
Next, we add one to the string pointer variable. Note we are not adding one to the data. We are adding one to the address at which the pointer variable is looking - i.e. the next character in the array.

Then we go around the loop and carry on until a zero character is reached.
Another method is to refer to the character pointer as though it were an array. Therefore, s[0] is the same a *s, s[1] is the same as *(s+1), s[2] is the same as *(s+2) etc. So for our purposes, we can pretend that we are working with a known-length array. Our program can then become:-
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <string.h>
void upper(char * s);
main()
{
char name[21];
printf( "Enter name: " ); gets(name);
upper( name );
printf("Name is: %s", name);
getch();
}
void upper(char * s)
{
int i;
for ( i=0; i<strlen(s); i++ )
s[i] = toupper(s[i]); }
This may be easier to remember. In general, you will mostly use passing-by-reference only with strings, and as you do not need to use an & to pass the array, or use a * to dereference the local variable pointer, the only thing you need to remember to do is to include the * before the variable name when passing an array to a function, and that you are able to change the contents of the string.
Exercise 62 - Passing strings to a function
Type in one of the two listings above and try it out to make sure it works okay.
Then create a second function that converts to lower case called lower - the C function to return the lower case version of a single character is called tolower and is found in the ctype.h library, the same as toupper.
Don't forget to create a prototype for this function too.
Test that this works okay by changing your program to call lower instead of upper.
If you have time, you may like to create a function called title which will convert the first letter in a string to upper case, and the rest to lower case. Test this in your program too.
#include <stdio.h>
#include <conio.h>
int power(int x, int n);
main()
{
int number, to_power, result;
printf( "Enter number,power: " );
scanf( "%d,%d", &number, &to_power);
result = power( number, to_power );
printf( "%d raised to %d is %d", number, to_power, result );
getch();
}
int power(int x, int n)
{
int i, tot;
tot = 1;
for ( i = 1; i<=n; i++ )
{
tot = tot * x;
printf("%d to the power of %d is %d\n", x, i, tot);
}
return tot;
}
#include <stdio.h>
#include <conio.h>
void power(int * x, int n);
main()
{
int number, to_power, result;
printf( "Enter number,power: " );
scanf( "%d,%d", &number, &to_power);
result = number;
power( &result, to_power );
printf( "%d raised to %d is %d", number, to_power, result );
getch();
}
void power(int * x, int n)
{
int i, tot;
tot = 1;
for ( i = 1; i<=n; i++ )
{
tot = tot * *x;
printf("%d to the power of %d is %d\n", *x, i, tot);
}
*x = tot;
}
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <string.h>
void lower(char * s);
void title(char * s);
main()
{
char name[21];
printf( "Enter name: " ); gets(name);
lower( name );
printf("Name as lower case is: %s", name);
title( name );
printf("Name as title case is: %s", name);
getch();
}
void lower(char * s)
{
int i;
for ( i=0; i<strlen(s); i++ )
s[i] = tolower(s[i]); }
void title(char * s)
{
int i;
if (s[0] != 0)
s[0] = toupper(s[0]);
for ( i=1; i<strlen(s); i++ )
s[i] = tolower(s[i]); }
Here is an example output from executing the program:-
Enter name: sImOn Name as lower case is: simon Name as title case is: Simon_
If you wish to make every word capitalised, then replace the title function with the following C code:-
void title(char * s)
{
int i;
if (s[0] != 0)
s[0] = toupper(s[0]);
for ( i=1; i<strlen(s); i++ )
{
if (s[i-1]==' ')
s[i] = toupper(s[i]);
else
s[i] = tolower(s[i]);
} }
Here is an example output from executing the program with the amended function:-
Enter name: sIMoN huggINs Name as lower case is: simon huggins Name as title case is: Simon Huggins_
In the next session we will be covering the following topics:-
Exercise: Implement the 'pairs' game - we will (that's all of us!) - take a basic concept of a 'pairs' game, think about what could be involved, come up with some specifications, look at what is feasible for us to do, scale it down accordingly, come up with a JSP diagram and pseudocode for the specification, and then look at how we could test the program based on this specification. We will then create a test plan that would effectively test our program to make sure it works to our specification.
![]()
(c) Copyright
2003-4 Simon Huggins. All Rights Reserved.
If you have any issues or questions regarding the content of this web
site, please contact the
author by clicking here.
Alternatively, you can leave a voice message on 00 44 (0)7050-618-297 or fax
on 00 44 (0)7050-618-298
This Page was last updated: 29 January 2004 13:07