|
7. Multiple Return Values
Previous | Next We know how to hand several parameters into a function and how to get one value out of them. But it would be kind of boring if we couldn't get more than one parameter out of a function. But how do we do that, if C forbids accessing variables in the function that called the current function and we only have one return value? We already know the solution. Remember how scanf requires that funny ampersand before all the variables it's supposed to fill? This ampersand is the "address-of operator" we just heard about. You'll remember that, memory is kind of like a piece of graph paper. And each variable can take up one or more boxes on this sheet of paper. And each box has a number. One of these boxes is a byte, and an int on the Macintosh, for example, is always four bytes long. When you declare a variable of type int, C sets aside four bytes in sequence and remembers their number. This number is called the address. If it helps you, think of it as a building number, and your computer's memory being a very long street. The address-of operator "&", when used on a variable name, gives you the memory address of that variable. Now, a memory address is simply a number, like an int. You can hand it to another function as a parameter. And now, once you have that address in that other function, you can use the "follow-address" prefix operator "*", to access whatever is at that address in memory. So, if you have the address of the variable named a of the main() function in a variable named aAddress, you can change a by writing *aAddress = 15; Note that this "*" operator is different from the multiplication operator "*". The follow-address-operator is a unary prefix operator. That is, it goes right before the one variable containing the address you want to follow. The multiplication operator, on the other hand, is a binary infix operator, that is it goes in between the two variables or values that you want to multiply. Now, there's just one problem: Since an address is only a house number that points at one house, what about a bigger house that occupies several house numbers, what about a data type that uses several bytes? Like an int? Since C needs to know what type to treat an address as when you use the follow-address operator on it, they didn't use an int for storing addresses. Instead, they invented the "pointer" data type. Trouble is, some masochist decided not to call the pointer data type pointer, but instead it uses the "*" character (which is already doing double-duty as follow-address operator and multiplication operator). Triple shifts, oy! I guess I'll best give you an example: aAddress above is supposed to be a pointer to an int, so when you declare that variable, it's written as: int * aAddress; You can then put an address into this variable by writing e.g.: aAddress = &a; aAddress now contains a number, the house number of a. It's really pretty easy, only that there are a few pitfalls. One of them is declaring multiple pointer variables in one statement. What do you think are the types of bAddress, cAddress and dAddress below: int * bAddress, cAddress, dAddress; bAddress is a pointer to an int, just as you expected. But cAddress and dAddress are actually just ints (!) WTF?! Well, whoever had the bright idea to use "*" to mean "pointer" also had the bright idea that, for declaring new variables, this "*" belongs to the name, not to the type. i.e. this "*" goes with bAddress, not with "int". Consequently, the correct way to declare three int pointers is to repeat the asterisk each time: int * bAddress, * cAddress, * dAddress; Stupid, isn't it? But it gets worse. There's a nice handy shorthand notation where you can declare a variable and assign it a value right away. Fancy-schmancy people call this "initialising" the variable. You do that by writing: int num = 15; This is equivalent to: int num; num = 15; However, when you initialise a pointer, this would be kind of ambiguous: int * bAddress = &a; You could read this as equivalent to int * bAddress; // pointer to int. bAddress = &a; // assign house number to pointer variable. or equivalent to int bAddress; // int *bAddress = &a; // treat int as pointer and assign &a to the int on the other side. or even equivalent to int * bAddress; // pointer to int. *bAddress = &a; // follow the pointer and assign &a to the int on the other side. What is it? Well, luckily they went the sensible route and took the first option. The second option would be kind of pointless since an int is any integral number, not an address, and C wouldn't let you treat this int as an address for safety's sake. The third option is also kind of pointless, because an int* points to an int, and for the same reason C doesn't want you to assign an address to an int. C is very picky about having types match. Now that we have that confusing bit out of the way, back to something easier: Of course you can have pointers to other types. Take a char, for example: char a = 'X'; char * aAddress = &a; *aAddress = 'Y'; printf( "%c %c\n", a, *aAddress ); // Outputs "Y Y". And if you wanted to rewrite GetArgument to use pointers, you could: void GetArgument( bool isLeft, int * outNum ) { if( isLeft ) printf( "Enter the left argument: " ); else printf( "Enter the right argument: " ); scanf( "%d", outNum ); fpurge( stdin ); } There's one new thing in this code: void. void is the no-type. When you have a function that does not return a value, instead of writing int or bool you use void. Apart from that, void is also used for pointers that point to data whose type you don't know, but we'll have to cover that another time. One thing you should note is that there is no address-of operator ("&") in front of outNum in the call to scanf() this time. This is because outNum is declared as an int-pointer ("int *"). Since it already is an address, there's no need to use the address-of operator. But where does this address come from? Well, when you call this variant of GetArgument, you have to do it this way: GetArgument( true, &leftArgument ); If you left away the "&", your C compiler would complain that you're trying to give it an int where a pointer belonged. After all, you specified int* as the type when you wrote GetArgument and its commands. Also, this returns void, i.e. nothing, so for this function there is no return value to assign to leftArgument using the = operator. That would be confusing anyway, after all why change a variable through a pointer and then try to overwrite its value with your own return value? Previous | Next Colton writes: These last two lessons got really confusing, really fast.
|
Jason writes: I agree the last two lessons were tough, a little confusing, but after reading them over twice, I think I now get them. Real brain exercise, that's for sure. Thanks for doing this.
|
Marc Davidson writes: I believe "GetArgument( true, &leftArgument );" should read "GetArgument( true, &vFirstArg );
" to be consistent with the variable names used previously, right?
|
Josh writes: Marc is right, it should be &vFirstArg, not &leftArgument.
Regardless, thank you very much for writing these tutorials!
|
Jaxerell writes: This page is way confusing, didn't understand half of it.
Is the lowercase a in aAddress supposed to be a letter or replaced with a number?
|
John David Dunson writes: well, it took me three days to figure all of this out.
to jaxerell:
"a" and "aAddress" are two different variables.
"a" is a normal variable.
"aAddress" is a variable that stores the memory address of another variable (in this case, "a"). this type of variable is called a pointer.
so there are two ways to change "a".
you could go the normal route and say: a = 15;
or you could go the technical route and say: *aAddress = 15
the first route is saying: go to the place in memory where the variable named "a" is stored and change the value stored there to 15.
the second route is saying: go to "this specific address in memory" and change the value stored there to 15.
here's my explanation of his example:
char a = 'X'; // declare a character-type variable named "a" and initialize it to the value X.
char * aAddress = &a; /* declare a pointer-type variable named "aAddress" and initialize it to the address of the variable "a".
(a character-type can also hold an integer.)*/
*aAddress = 'Y'; /* go to the place in memory that "aAddress" points to and change the value stored there to Y.
in other words, change the variable "a" to the value Y (it used to be X and now it's Y).*/
printf("%c %c\n", a, *aAddress); /* print the value of the variable "a" to the console, then print the value stored at "aAddress" to the console.
basically, you're accessing the same sopt in memory using two different methods. Y Y would be the result.
if you left off the third line, then X X would be the result.
if you left off the asterisk in the last line, then Y 32 (or whatever the address of "a" is) would result.*/
what i don't get is why you taught us the ability to do more than one thing in a function, then you give us an example that will require the function to be called twice. so here's how it should look… although, this method is still not as efficient for this simple program as my original code was that i posted on page 4. A Crude Calculator… but here it is anyway, using pointers, address-of, and a function:
void GetArgument(int* outFirstArg, int* outSecondArg)
{
printf("Enter left argument: ");
scanf("%d", outFirstArg);
fpurge(stdin);
printf("Enter right argument: ");
scanf("%d", outSecondArg);
fpurge(stdin);
}
then the call would look like this:
GetArgument(&vFirstArg, &vSecondArg);
|
Uli Kusterer replies: ★ John, your code is certainly also correct. It's just a different level of re-use. You *can* use this notation to return several values, but I wanted to show that this is *completely equivalent* to returning a value. I'll think about ways to make this clearer.
|
TJ Leeland writes: I'm confused on a different point.
void GetArgument( bool isLeft, int * outNum ) //Creates a function(?) named
GetArgument using the void
data type that uses an
arguement that is creating
a boolean variable named
isLeft, then a creates a
integer of the pointer
data type named outNum
{
if( isLeft ) //here is where I am
confused. How does the
program know if
"if ( isLeft )" is indeed
isLeft?
printf( "Enter the left argument: " );
else
printf( "Enter the right argument: " );
scanf( "%d", outNum );
fpurge( stdin );
}
|
Uli Kusterer replies: ★ TJ, I have to admit I don't really understand your question. But I'll try:
"if" simply does the upper thing if the thing between the brackets ends up being "true". Otherwise it does the second thing. The things between the brackets are parameters, that is, they're local variables, but they get filled in when the function is called, right before any of the code between the { and } is run. If you call it as GetArgument( true, &vMyNumber );, isLeft will be set to true, if you call GetArgument( false, &vMyNumber );, isLeft will be set to false. Whatever we specify as the first parameter value in the call will end up in the first parameter here, because we wrote "bool isLeft" as the first item between the brackets. That's the magic of parameters. Regular local variables are declared lower down between { and }, but the parameters are special "mailboxes" through which data from the outside gets into our function, just like the return value (which we're not using here, hence the "void") can pass data back out again.
Now, when the code actually gets run, the "if( isLeft )" line does the same thing it would do in other cases: Compare what's in the brackets to true and do one thing or the other.
Did that help? Or did I misunderstand your problem?
|
Bruno Gätjens González writes: Hi!!!
First of all thanks for this amazing tutorial for us the C beginner, thank you for take the time to prepare and write all that. I am from Costa Rica and i was thinking if there is the possibility in the future to make it in Spanish too? :)
Thank you so much again.
Bruno
|
Uli Kusterer replies: ★ Bruno, thanks for the kind words. I'd love to do a Spanish version, but sadly I know about three words of Spanish, so this isn't very likely to happen.
|
Mk12 writes: I still don't get the point of the pointers (no pun intended). Why not just pass the actual variables and set those instead of using the pointers and addresses (except for the fact that scanf needs addresses)?
|
Gordon writes: Uli,
First thanks for the tutorials. This one definitely takes a few reads.
I think this particular chapter needs some more visuals. The part that is really confusing for me is the part about
int * bAddress = &a;
and the ambiguity. Some visuals illustrating pointer relationships and why this is ambiguous might be helpful to cement the problem in our minds.
But otherwise a fairly clear explanation.
|
Richard Howell writes: OK. I wrote this:
#include <stdio.h>
int main () {
int number;
int newNumber;
int * address = &number;
printf( "Give me a number to store in memory: " );
scanf( "%d", &number );
fpurge( stdin );
printf( "\nThe number %d can be found at memory location %d", number,* address);
newNumber = *address = +1;
printf( "\nThe new number stored in location %d is %d", * address, newNumber);
return 0;
}
And I had this output:
Give me a number to store in memory: 123
The number 123 can be found at memory location 123
The new number stored in location 1 is 1
I think I'm missing the point here. Shouldn't I be printing a memory location, and not just a repeat of what I typed in?
|
Uli Kusterer replies: ★ You're using the * operator in all your print statements. That operator means "follow the address to its value". That's why it's printing the value again, not the address.
|
steven writes: I have read these last two chapters twice, and now I am four times as confused.
^_^
Anyway, thanks for a really great tutorial.
So, A=15
and aAddress= the place in memory where I can find the location of A, which contains 15?
|
Richard Howell writes: OK. But when I replaced the misplaced '*'s in my printf statements...we get a new problem in that the compiler tells me:
Format 'd%' expects type 'int', but argument 'n' has type 'int *'.
When you used a char to store the address in your example, you were able to use '%c'. Why can't I use %d?
|
Uli Kusterer replies: ★ Well, an int* isn't really an int. it's a pointer to an int. It is a number, but marked specially. There's a special format for those, %p, but that prints the number in hexa-decimal. Alternately, you could typecast it, (see later chapters), and write
(int) address
This will work as long as you're on a 32-bit Mac. If your Mac runs in 64 bits, addresses are 64-bit long and won't fit in a shorter int. They do fit in a "long int" with format "%d" in that case, though.
|
College student writes: I also will admit the past two chapters of this tutorial were a tad confusing. I myself have taken two semesters of JAVA, and this C business isn't bad at all. To those of you out there that are learning this stuff without any prior experience to programming, I must give you kudos; this stuff isn't easy at first.
|
Roope writes: #include <stdio.h>
#include <stdbool.h>
int GetArgument( bool isLeft, int * outPut)
{
if( isLeft )
printf( "Enter the left argument: " );
else
printf( "Enter the right argument: " );
scanf( "%d", outPut );
fpurge( stdin );
}
int main()
{
int a;
int * aAddress = &a;
int b;
int * bAddress = &b;
int sum = *aAddress + *bAddress;
*aAddress = GetArgument( true, &a);
*bAddress = GetArgument( false, &b);
printf("%d plus %d equals %d", *aAddress, *bAddress, sum);
}
That's my code, what's wrong coz I get 0 plus 0 equals 0. ?
Great tutorial! Thanks!
|
Roope writes: This is what I have now, and I get 1+2=0?
#include <stdio.h>
#include <stdbool.h>
int GetArgument( bool isLeft, int * outPut)
{
if( isLeft )
printf( "Enter the left argument: " );
else
printf( "Enter the right argument: " );
scanf( "%d", outPut );
fpurge( stdin );
}
int main()
{
int a;
int * aAddress = &a;
int b;
int * bAddress = &b;
int sum;
int * sumAddress = ∑
sum = *aAddress+*bAddress;
GetArgument( true, &a);
GetArgument( false, &b);
printf("%d plus %d equals %d", *aAddress, *bAddress, *sumAddress);
}
|
Roope writes: Got it! It was before calls afterwards works a bit better :D
|
Aman writes: Roope, your code should be like this, else it doesnt add up
.....
GetArgument( true, &a);
GetArgument( false, &b);
sum = *aAddress+*bAddress;
printf("%d plus %d equals %d", *aAddress, *bAddress, *sumAddress);
}
|
Tristan Moriarty writes: Hi Uli, and thankyou again for a great set of tutorials! I also found this lesson trickier than what has gone before. Am I right in thinking that essentially what we learn here is a new way of changing a variable using a pointer, and that what makes this useful is that by putting a pointer into a parameter function, we can call that function with any variable we like (provided it is a variable of the same kind as the pointer type) and change it's value to one in line with our parameter function? Apologies if that seems like just a long winded way of saying something that should have made sense anyway, but that's how I'm thinking about it!
Thanks again for these articles- they really are very very helpful!
|
Uli Kusterer replies: ★ Not sure what you mean by a "parameter function"? But yes, the idea here is to change a variable that is *outside* the current function. Every function can only change the variables declared within its { and } brackets, (or return a value, which the function that called it can then assign to one of its variables).
To change any other variable, you need to use a pointer.
Oh, of course global variables can also be changed by any old function, that is their purpose, after all, and their curse.
|
Tristan Moriarty writes: By "parameter function", I just meant really the kind of function that we have here with GetArgument. I think about it like this because it was the first function we introduced with a parameter a couple of tutorials ago. Apologies, I'm new to programming and it seems quite easy to make up one's own jargon!
|
David Crooks writes: This is the best tutorial I've found for C on the mac. Thanks.
For what its worth, here's my version of the calculator,
#include <stdio.h>
#include <stdbool.h>
void GetArgument( int * leftNum,int * rightNum ) // read in user input, two integers
{
printf( "Enter the left argument: " );
scanf( "%d", leftNum );
fpurge( stdin );
printf( "Enter the right argument: " );
scanf( "%d", rightNum );
fpurge( stdin );
}
int main()
{
int vLeftArg, vRightArg;
char vOperation;
bool vFinished;
// Make sure our flag is initialized!
vFinished = false;
// Now loop until user doesn't want anymore:
while( vFinished != true )
{
printf( "What operation do you want to do? (+ ,/ ,* , q to quit)\n" ); // ask the user to select an operation
scanf( "%c", &vOperation );
fpurge( stdin );
switch( vOperation )
{
case '/': // test for division
GetArgument(&vLeftArg,&vRightArg); // get numbers from user
if (vRightArg != 0) //test for division by zero
{
printf( "\n%d / %d = %d\n",
vLeftArg,
vRightArg,
vLeftArg / vRightArg ); // if all is well, divide and display the result
}
else {
printf("infinity!!!!!! \n"); // bigger than you can possibly imagine
}
break;
case '+': //test for addition
GetArgument(&vLeftArg,&vRightArg); // get nunbers from user
printf( "\n%d + %d = %d\n",
vLeftArg,
vRightArg,
vLeftArg + vRightArg ); //add and display
break;
case '*': // test for multiplictaion
GetArgument(&vLeftArg,&vRightArg); // get nunbers from user
printf( "\n%d * %d = %d\n",
vLeftArg,
vRightArg,
vLeftArg * vRightArg ); // multiply and display
break;
case 'q': // test for quit
vFinished = true; // on q, quit
break;
default:
printf("I'm sorry, but I don't understand. Try again. \n"); // in case of gobbledygook
break;
}
}
printf( "Finished.\n" );
return 0;
}
|
Elijah writes: I LOVE THIS! I've been interested in learning how to program for a few years now, and this is the single best, straightforward and intelligible tutorial I have ever read!
One request though; please add tables to your text boxes! I can't read without scrolling left and right to the end of each line.
THANKS!
|
Uli Kusterer replies: ★ Elijah,
If you were talking about the line breaks in the code samples that had disappeared, that should be fixed again now.
|
Jack writes: Just to clarify, when you said: int * aAddress; that was declaring the pointer variable aAddress that points to an int variable that is currently unspecified, yes? And then after that when you say aAddress = &a; this then means that aAddress points to int variable a, is this also correct?
|
Uli Kusterer replies: ★ If by 'unspecified' you mean 'contains an arbitrary, random number that is not a valid address', then yes. That's exactly what's going on, yes :-)
|
hobs writes: Wow. I had to read through this twice, but like the previous commenters were saying, I was having lightbulb comments all over the place. Thank you so much for this article.
|
Scott writes: It would have been better to do an article on functions before the memory article. I think that's were I am going wrong, and a few others are getting lost.
|
ScottC writes: In an effort to understand this exercise, I tried to break it down to its simplest components. I'm curious if my thought process is correct. Here's the code:
#include <stdio.h>
void GetNumbers (int *ReturnFirst, int *ReturnSecond)
//reserves and assigns the memory address names ReturnFirst, and ReturnSecond
{
printf ( "nEnter first number: " );
scanf ( "%d", ReturnFirst);
fpurge (stdin);
//gets a number and assigns it to the memory address "ReturnFirst"
printf ( "nEnter second number: ");
scanf ( "%d", ReturnSecond);
fpurge (stdin);
//gets a number and assigns it to the memory address "ReturnSecond"
printf ( "nYou stored the numbers %d and %d in ReturnFirst and ReturnSecond. n", *ReturnFirst, *ReturnSecond );
//calls up the values in the memory with address ReturnFirst and ReturnSecond
}
int main ()
{
int vNumA,
vNumB;
//this sets up 2 variable numbers
GetNumbers (&vNumA, &vNumB);
//this calls the "GetNumbers" code above and gets values from the memory address "ReturnFirst" and "ReturnSecond", and assigns them to the ints "vNumA" and "vNumB"
printf ( "nThe number in vNumA is %d and vNumB is %d. n", vNumA, vNumB );
//print the two ints vNumA and vNumB
return 0;
}
|
Peter Nicholls writes: Hi, I;m working thru this a 2nd time (gave up a few years back!) and im confused at this point again. Are there any exercises to work through to make this theory more practical?
|
Isaac writes: This is greatly appreciated. A huge help in beginning to understand C and my first programming language. Very detailed elaboration. If someone could, tell me if I'm correct in assuming:
"*" is an operator that can be used to make a variable globally recognized in all functions, thus bypassing the C's restraints by pointing directly at an int's location in the computer's memory? Basically, its pointing to a variable that can be initialized under one function, and later, changed under a separate function by using the pointer operator? (in theory)
|
sahil goyal writes: great tutorial uli x thnx xxx
i don't get one thing. why do u use &leftArgument in this line GetArgument( true, &leftArgument );
leftArgument is neither a variable nor a pointer.
|
Uli Kusterer replies: ★ sahil, you are completely right. The line there is just an example, but you would need to define a variable named "leftArgument" somewhere for that one line to actually work. I'll think about this can be some more for the next revision of the tutorial. I'll have to determine if it's better to explain that you can use arbitrary names, or just use a name I've used before. |
|
Home | Admin | Edit
Last Change: 2012-02-04 @637, © 2003-2012 by M. Uli Kusterer, all rights reserved. |
|
|