As mentioned in Part I of the tutorial series, what is passed to a function by its
caller is passed "by value". This means that a copy of the argument is
made prior to the call to the function, and it is that copy which
is provided to the function. As a result, any actions taken with that
variable within the function only affect the copy, they do not affect the
variable in the caller. For example:
#include <stdio.h>
int squareIt(int toBeSquared);
int main(void)
{
int valueToSquare = 5;
int squaredValue = squareIt(valueToSquare);
printf("%d squared is %d\n", valueToSquare, squaredValue);
return 0;
}
int squareIt(int toBeSquared)
{
/* Operate directly on the passed-in value
and return it */
toBeSquared = toBeSquared * toBeSquared;
return toBeSquared;
}
which yields:
./sq 5 squared is 25
demonstrating that the valueToSquare variable which was passed was not
in fact modified by the function.
So, how to get around this? Well, think about it this way: if you're
going to the coffee maker to get some nice, fresh nectar of the gods to
bring back to your programming session, you need a container in which to
carry it, right? Well in C and C++ the container is that pointer you
know and love!
A pointer variable consists the address in memory of the variable to which
it's pointing. Memory is a container of sorts, isn't it? So, if we
wanted to make that squareIt function above actually operate on
the value passed-in, we can change the function prototype to return
void, as it will be returning the modified variable passed
instead, and change the argument to be a pointer to an int,
like so:
void squareIt(int *toBeSquared);
Where we've changed the prototype, we also have to change the
implementation of the function, which we do like so:
void squareIt(int *toBeSquared)
{
/* Remember, we are passed the CONTAINER
for the variable, so in order to actually work
with the variable's contents, we must get the
content from the container. This is done through
the pointer dereference operator, * */
*toBeSquared = (*toBeSquared) * (*toBeSquared);
}
And with the change in prototype comes the need to change how the
function is called. To provide a pointer to a memory location, you
prefix the variable with the address-of operator, & in the
function call, so our program above would now look like:
#include <stdio.h>
void squareIt(int *toBeSquared);
int main(void)
{
int valueToSquare = 5;
/* Because we're changing the value of the variable in the
function, we need to print its initial value before we call
the function */
printf("%d squared is ", valueToSquare);
/* Pass the address of the variable to the function.
Note we have no = in this function call; the function
does not return anything directly to the caller, so there's
no need; in fact, it should cause a compilation error. */
squareIt(&valueToSquare);
/* And print the result */
printf("%d\n", valueToSquare);
return 0;
}
void squareIt(int *toBeSquared)
{
/* Operate directly on the passed-in value, which
means there's nothing to explcitly return. */
*toBeSquared = (*toBeSquared) * (*toBeSquared);
}
Running this program results in the identical output:
./sq2 5 squared is 25
C++ Pass-By-Reference
Now C++ -- not C -- has a nifty little feature called
pass-by-reference, which hides that icky pointer notation from you,
making your code somewhat easier to read. To use this in C++, you will
use that same & operator, only this time it's part of the
arguments in the function signature, rather than on the argument
provided by the caller.
void squareIt(int &toBeSquared);
So the equivalent program in C++ (note, the
program above is still valid C++) would be:
#include <iostream>
// Pass the variable by reference
void squareIt(int &toBeSquared);
int main()
{
int valueToSquare = 5;
std::cout << valueToSquare << " squared is ";
// Note, we pass the variable AS IS, no & necessary!
squareIt(valueToSquare);
std::cout << valueToSquare << std::endl;
}
void squareIt(int &toBeSquared)
{
// Here we get rid of the icky pointer notation
// Note that using *= is the same as the above
// toBeSquared = toBeSquared * toBeSquared;
toBeSquared *= toBeSquared;
}
Again, running this program the result is the same:
./sq3 5 squared is 25
Functions and Passing Arrays
Arrays would seem to be a special case when passing these to a
function; in fact, arrays are automatically passed by pointer. When
passed to a function, an array "decays" to a pointer. In other words, if
you have a function which is declared like this:
void printArray(int myArray[], int size)
the compiler is actually implementing it for you like this:
void printArray(int *myArray, int size)
Now, notice in both of these function prototypes I have passed a second
variable, size. This is required when passing arrays
because the array is being treated as a pointer within the
function. If we're going to operate on the array within the function, we
need to know the bounds of the array, else we're going to go off into
memory we don't own and cause the program to crash.
Also, because the array is treated as a pointer within the function,
then any changes you make to the array will be reflected in the variable
passed by the calling routine.
Here's how you would implement and call the function above:
#include <stdio.h>
void printArray(int myArray[], int size);
int main(void)
{
/* Initialize the array with our numbers */
int arrayOfInts[5] = { 2, 4, 6, 8, 10 };
/* Notice we do NOT use the array brackets here when passing
the array! */
printArray(arrayOfInts, 5);
return 0;
}
void printArray(int myArray[], int size)
{
int i = 0;
/* Remember, valid array indexes are from 0 to size - 1! */
while (i < size)
{
printf("Element %d: %d\n", i, myArray[i]);
/* Don't forget to increment i, or you'll have an infinite loop! */
++i;
}
}
Compiling and running this program yields:
./arrfunc Element 0: 2 Element 1: 4 Element 2: 6 Element 3: 8 Element 4: 10
So now we can demonstrate that you can modify the array within the
function and have it reflected in the calling function. We'll keep the
printArray function for its utility, and add a new function,
squareArray, which will square each of the elements within the
array.
#include <stdio.h>
void printArray(int myArray[], int size);
void squareArray(int myArray[], int size);
int main(void)
{
/* Initialize the array with our numbers */
int arrayOfInts[5] = { 2, 4, 6, 8, 10 };
/* Notice we do NOT use the array brackets here when passing
the array! */
printf("Array before:\n");
printArray(arrayOfInts, 5);
squareArray(arrayOfInts, 5);
printf("Array after:\n");
printArray(arrayOfInts, 5);
return 0;
}
void printArray(int myArray[], int size)
{
int i = 0;
/* Remember, valid array indexes are from 0 to size - 1! */
while (i < size)
{
printf("Element %d: %d\n", i, myArray[i]);
/* Don't forget to increment i, or you'll have an infinite loop! */
++i;
}
}
void squareArray(int myArray[], int size)
{
int i;
for (i = 0; i < size; ++i)
{
myArray[i] *= myArray[i];
}
}
Compiling and running this yields:
Array before: Element 0: 2 Element 1: 4 Element 2: 6 Element 3: 8 Element 4: 10 Array after: Element 0: 4 Element 1: 16 Element 2: 36 Element 3: 64 Element 4: 100
Functions and Returning Arrays
But what if you don't want to modify the passed-in array, instead
generating a different array? Well, another gotcha of sorts with
functions and arrays is that there is no way to return an array -- as
such -- from a function; instead, you either pass in another array which
you populate in the function, or you return a pointer to the first
element of the array you create in the function.
In the second case, keep in mind what we said in Part I of
this tutorial: variables created within the function only exist for the
scope of the function in which they're created. Therefore, if you wish
to use the second case of returning a pointer, the pointer must point to
dynamically-allocated memory -- memory allocated through the use of
malloc or calloc in C, or new in C++ -- which
must therefore be de-allocated when you no longer need it through the
use of free in C or delete [] in C++, or a pointer to
a static array created within the function.
First we'll demonstrate passing a second array to the function, again
using our squareArray function, only modified to support
passing in the second array. Its prototype will look like this:
void squareArray(int toBeSquared[], int size, int arraySquared[]);
We will create a second, uninitialized array in the main
function to receive the squared data:
#include <stdio.h>
void printArray(int myArray[], int size);
void squareArray(int toBeSquared[], int size, int squaredArray[]);
int main(void)
{
/* Initialize the array with our numbers */
int arrayOfInts[5] = { 2, 4, 6, 8, 10 };
/* An uninitialized array */
int arrayOfSquaredInts[5];
/* Notice we do NOT use the array brackets here when passing
the array! */
printf("Initial Array before:\n");
printArray(arrayOfInts, 5);
/* Pass the second array to the function */
squareArray(arrayOfInts, 5, arrayOfSquaredInts);
printf("Initial Array after:\n");
printArray(arrayOfInts, 5);
printf("Squared array:\n");
printArray(arrayOfSquaredInts, 5);
return 0;
}
void printArray(int myArray[], int size)
{
int i = 0;
/* Remember, valid array indexes are from 0 to size - 1! */
while (i < size)
{
printf("Element %d: %d\n", i, myArray[i]);
/* Don't forget to increment i, or you'll have an infinite loop! */
++i;
}
}
void squareArray(int toBeSquared[], int size, int squaredArray[])
{
int i;
for (i = 0; i < size; ++i)
{
squaredArray[i] = toBeSquared[i] * toBeSquared[i];
}
}
Compiling and running this yields:
Initial Array before: Element 0: 2 Element 1: 4 Element 2: 6 Element 3: 8 Element 4: 10 Initial Array after: Element 0: 2 Element 1: 4 Element 2: 6 Element 3: 8 Element 4: 10 Squared array: Element 0: 4 Element 1: 16 Element 2: 36 Element 3: 64 Element 4: 100
Next, let's demonstrate returning the new array as a
dynamically-allocated pointer to the first element. We will change the
prototype to return an int * to the caller, like so:
int *squareArray(int toBeSquared[], int size);
#include <stdio.h>
#include <stdlib.h> /* Required for malloc/free */
void printArray(int myArray[], int size);
int *squareArray(int toBeSquared[], int size);
int main(void)
{
/* Initialize the array with our numbers */
int arrayOfInts[5] = { 2, 4, 6, 8, 10 };
/* An uninitialized pointer */
int *arrayOfSquaredInts;
/* Notice we do NOT use the array brackets here when passing
the array! */
printf("Initial Array before:\n");
printArray(arrayOfInts, 5);
/* Pass the second array to the function */
arrayOfSquaredInts = squareArray(arrayOfInts, 5);
printf("Initial Array after:\n");
printArray(arrayOfInts, 5);
printf("Squared array:\n");
printArray(arrayOfSquaredInts, 5);
/* We used malloc to create the array, so we must
use free to return the memory we not longer need */
free(arrayOfSquaredInts);
return 0;
}
void printArray(int myArray[], int size)
{
int i = 0;
/* Remember, valid array indexes are from 0 to size - 1! */
while (i < size)
{
printf("Element %d: %d\n", i, myArray[i]);
/* Don't forget to increment i, or you'll have an infinite loop! */
++i;
}
}
int *squareArray(int toBeSquared[], int size)
{
/*
Dynamically allocate an array of the appropriate size.
Note that the size we need MUST take into account the
size of the variable to which we're pointing.
sizeof(*squaredArray) gets the size of the data type to
which squaredArray is pointing.
*/
int *squaredArray = malloc(size * sizeof(*squaredArray));
int i;
for (i = 0; i < size; ++i)
{
squaredArray[i] = toBeSquared[i] * toBeSquared[i];
}
return squaredArray;
}
If you compile and run this, you will notice the output is the same as
the above.
I'll provide the equivalent C++ solution here as well.
#include <iostream>
void printArray(int myArray[], int size);
int *squareArray(int toBeSquared[], int size);
int main(void)
{
/* Initialize the array with our numbers */
int arrayOfInts[5] = { 2, 4, 6, 8, 10 };
/* An uninitialized pointer */
int *arrayOfSquaredInts;
/* Notice we do NOT use the array brackets here when passing
the array! */
std::cout << "Initial Array before:" << std::endl;
printArray(arrayOfInts, 5);
/* Pass the second array to the function */
arrayOfSquaredInts = squareArray(arrayOfInts, 5);
std::cout << "Initial Array after" << std::endl;
printArray(arrayOfInts, 5);
std::cout << "Squared array:" << std::endl;
printArray(arrayOfSquaredInts, 5);
// Again, because we allocated memory with new in the
// function, we must have a correponding delete to
// return the no longer needed memory to the system.
// Note the use of delete[], because we allocated an array
delete [] arrayOfSquaredInts;
return 0;
}
void printArray(int myArray[], int size)
{
int i = 0;
/* Remember, valid array indexes are from 0 to size - 1! */
while (i < size)
{
std::cout << "Element " << i << ": " << myArray[i] << std::endl;
/* Don't forget to increment i, or you'll have an infinite loop! */
++i;
}
}
int *squareArray(int toBeSquared[], int size)
{
// Dynamically allocate an array of the appropriate size.
int *squaredArray = new int[size];
int i;
for (i = 0; i < size; ++i)
{
squaredArray[i] = toBeSquared[i] * toBeSquared[i];
}
return squaredArray;
}
Finally, there's the static variable solution. This is not
particularly helpful in most situations, as a static variable means that
the memory for the variable is created at compile time -- so we must know
the array's size when creating the variable -- and the variable exists for
the duration of the program. The need for this actually removes the need
to pass the size of the array to the function, but I will leave that in.
#include <stdio.h>
/* We need to know the size of the array throughout in order
to create the static variable in the function */
#define SIZE_OF_ARRAY 5
void printArray(int myArray[], int size);
int *squareArray(int toBeSquared[], int size);
int main(void)
{
/* Initialize the array with our numbers */
int arrayOfInts[SIZE_OF_ARRAY] = { 2, 4, 6, 8, 10 };
/* An uninitialized pointer */
int *arrayOfSquaredInts;
/* Notice we do NOT use the array brackets here when passing
the array! */
printf("Initial Array before:\n");
printArray(arrayOfInts, SIZE_OF_ARRAY);
/* Pass the second array to the function */
arrayOfSquaredInts = squareArray(arrayOfInts, SIZE_OF_ARRAY);
printf("Initial Array after:\n");
printArray(arrayOfInts, SIZE_OF_ARRAY);
printf("Squared array:\n");
printArray(arrayOfSquaredInts, SIZE_OF_ARRAY);
return 0;
}
void printArray(int myArray[], int size)
{
int i = 0;
/* Remember, valid array indexes are from 0 to size - 1! */
while (i < size)
{
printf("Element %d: %d\n", i, myArray[i]);
/* Don't forget to increment i, or you'll have an infinite loop! */
++i;
}
}
int *squareArray(int toBeSquared[], int size)
{
/* The memory for this set aside at compile time */
static int squaredArray[SIZE_OF_ARRAY];
int i;
for (i = 0; i < size; ++i)
{
squaredArray[i] = toBeSquared[i] * toBeSquared[i];
}
return squaredArray;
}
Again, the output is as shown above.
This post has been edited by JackOfAllTrades: 04 January 2012 - 08:11 AM
Reason for edit:: Thanks for jimblumberg for catching the glaring error noted below!





MultiQuote




|