By the end of Chapter 7 you should understand the following concepts:
Defining, prototyping, and calling functions
The interrelationships of type, parameter list, and address of a function
Passing and processing arrays with functions
A special case of arrays and functions: text strings
Preventing a function from changing data it is sent
Recursive functions and how they work
Using structures with functions
Using pointers to functions
Using functions in C++
We have already used quite a number of functions up to this point that were
supplies by the library files included with OpenWatcom. All we had to do is
#include the file and then call the functions. These parts:
Define the function
Prototype the function
have already been done for you when you use a library file. When we create our
own functions, we have to provide these steps ourselves before we can call the
function.
Function Arguments and Passing by Value
When I started to learn programming, I was totally confused with functions.
When a function was called, the argument might be a certain variable name, but
when I tried to follow the next step in the function itself, the variable name
wasn't there. I couldn't make the connection of how this mysterious processed
worked. Another case of making a simple process difficult in my mind.
Figure 7.2 in the book is a perfect example that blocked my learning.
You see a call to the cube() function:
double volume = cube(side);
I could easily see that the variable side was used as an argument.
But then follow this down to the cube() function:
double cube(double x)
What happened to side? I was lost already because side was
nowhere to be found in the function. I just didn't get it, not even in Rexx.
What I finally learned is that this magic was a simple assignment to another
variable name. Technically, x = side was being performed, but nothing
I read said this. I don't remember when the lights finally turned on, all I
know is this little roadblock stopped me from proceeding for a long time.
Passing by Value means that both variables, side and
x, each have the same value now, 5. When the cube()
function finishes, the value returned is assigned to the variable
volume, side is still 5 and x
vanishes from memory. In other words, the cube() function had no effect
on the value of side. A copy of its value, 5 , was passed
(assigned) to the cube() function variable x.
Functions and Arrays
If you recall, right after we learned about pointers and how to dereference a pointer to get the value stored at an address, we went on to learn about arrays. The array name is a pointer, meaning the array name holds the address of the first element of the array.
In Listing 7.4, arrfun1.cpp, the array cookies is an
argument for the sum_arr() function. Now stay with me here. Functions
work with copies of the variables passed to them. Since copies of variables are
passed into functions, and C++ specifies that an array name is really a pointer
(an address), a copy of the pointer (an address) is being passed into the
function.
What this means is that the function is going to be working with the data in the
original array. Why? Because all we did was tell the function where the array is
located, its address. We didn't pass into the function a copy of the array's
data. So basically there are now two pointers pointing to the same address in
memory, the array name cookies, and the array name
arr inside the function. When the function ends, any variables
inside the function cease to exist.
On page 258, you see this:
cookies == &cookies[0];
Don't let this confuse you, this isn't part of the code. It's just an
explanation to show that cookies is the same as the address of the array's first
element. Remember, & is the address-of operator.
The main point to remember, when dealing with arrays in function calls, is that
another way to declare a pointer is arr[ ], see figure 7.4.
Functions Using Array Ranges
Listing 7.8, arrfun4.cpp, may look odd. The function
sum_arr() sums the total cookies eaten in the cookies array, but
there's not the usual array element syntax using the [ ]. The function
is simply using pointer arithmetic to access each array element, and then
dereferencing the pointer to get the value in the element. pt
is the pointer to the array. When you add 1 to pt,
pt + 1, you are now pointing to the next element address in the
array. This is what the for loop is doing, stepping through the
array elements by adding 1 to the pointer. Each time pt is
incremented, the next element in the array is being pointed to, and to get the
value at that memory address, the pointer is dereferenced, *pt.
Functions and Two-Dimensional Arrays
This section looks rather confusing. I've had to reread it several times. A
two-dimensional array is actually an array of arrays. One dimensional arrays are
usually an array of int or an array of char,
etc. On page 272, we have the data array which has 3 elements.
Each of these 3 elements is pointing to an array of 4 int values.
Bottom line: the array name is a pointer to the first element of an array, we
just learned this. There are 3 arrays containing 4 int values,
therefore there are 3 memory addresses which point to the first element of each
array. These 3 memory addresses are stored in an array called the
data array.
If you were to look at the actual memory locations, you would simply see a
section of memory with 12 addresses in sequence. The first location is pointed
to by the array name data. This is also the address of the first
4 element array. To point to the next 4 element array, you would add 4 to the
pointer, data = data +4, or data += 4. Adding 4 just means you have skipped over
the 4 elements in the first array to be at the beginning of the second array.
To point to the third and final array, add 4 again, data += 4. At the bottom of
page 272 you see a for loop. The first loop does just what I
described. The nested for loop points to each element of the 4
element arrays.
I took the code on page 272 to show how the memory addresses are all in
sequence, and also the size of each array element since this determines the
bytes between elements.
// ar2test.cpp
#include <iostream>
int sum(int ar2[ ][4], int size);
int data[3][4] = {{1,2,3,4}, {9,8,7,6}, {2,4,6,8}};
int total = 0;
int main()
{
total = sum(data, 3);
cout << "\nThere are " << total << " cookies in the array.\n";
cin.get();
return 0;
}
int sum(int ar2[ ][4], int size)
{
cout << "The size of each array element is " << sizeof ar2 << " bytes.\n";
cout << "The address of each element is:\n";
for (int row = 0; row < size; row++)
for (int col = 0; col < 4; col++)
{
total += ar2[row][col];
cout << &ar2[row][col] << "\n";
}
return total;
}
As we have learned, arrays cannot be a parameter in a function, but a pointer
can. We also learned that when dealing with functions and arrays, there are two
ways to specify an array pointer, int *ar2, and ar2[]. So in
the declaration for the sum() function:
int sum(int ar2[ ][4], int size);
ar2[] is a pointer, pointing to an array of four int.
Functions and C-Style Strings
Here we reinforce what we learned about char arrays. See Chapter 4, pages 140
to 145. When you pass a pointer to cout that is the address of a
string array, cout prints the string. That's the way cout is
told to work. cout sees that the pointer is type char
(arrayname is a pointer), and sends the string to the screen instead of
printing the address to the screen. If you do want the address to print, then
you have to typecast the pointer to something else. In other words, make
cout see it as a pointer to something other than a char array
(see the bottom of page 143).
The main point to remember about passing arrays to functions, you can't, but
you can pass a pointer which points to the array. String arrays are even more
different, all strings end with the null character, \0.
So passing a string array (really the pointer) means you don't need to pass the
size of the array as a function parameter (argument), as the null character can
be used to detect the end of the string.
Functions That Return Strings
A brief note about Listing 7.10, strgback.cpp. You'll notice that the
buildstr() function has a * symbol before it:
char * buildstr(char c, int c);
This declaration may appear like it's declaring a function as a pointer, but
it's not. Remember that functions have to declare a return type. For instance
if the declaration was written this way:
int buildstr(char c, int c);
you'd easily recognize that the function was returning an integer type. Well,
all the char * is saying is that the function is returning a
pointer-to-char type. It's just returning a pointer, a memory address.
That takes care of arrays. In Object Rexx, I very seldom used an array, The
reason is because there are Container Classes that are so much easier to work
with. We will get to Containers in C++ eventually.
Functions and Structures
The book explains this section very well, so there's no need to add anything
that just might add confusion. I will just point to page 279, first sentence
under the subsection Another Example. We are learning material very much
related to Classes, so pay very close attention. Even then, when we start
passing Class objects we're going to learn an even better way in the next
chapter, passing-by-reference.
Passing Structure Addresses
Here we are shown how to use pointers with functions and structures. In the
previous section you learned how to pass structures by value in functions. This
uses a lot of memory if the structure happens to be large because pass-by-value
means copies are used in the functions. Using the same code but modifying it to
pass pointers (addresses) in the functions means there's no copies being made,
you are working with the original structure and its data.
The main modification to the code is in the rect_to_polar() function.
Previously it returned a structure which was assigned to the polar
type variable pplace. With pointers there's no need to return a
structure from the function since we're already working with the original
structure, not a copy of it. So the needed modification is to change the
rect_to_polar() function to also pass the address of the polar
structure pplace.
As you can see in the new code, the rect_to_polar() function is called
but isn't used to assign any return value to pplace since it's
already available in the function block code.
Recursive Functions
I don't have any real life experience with recursive functions since I never
needed to use them. Whether you use them or not, it's good to know what they
can do when needed. I would tend to just file away in my mind that they exist.
When I ever come to a point where I might need to use recursion, I'll just look
it up again.
Pointers to Functions
This is more material that I'm going to file away. I'll probably be overwhelmed enough just using the normal methods of calling functions using pointers. However, it must also be remembered that making a function call means you are infact telling your program to jump to the memory address of the function anyway.
Questions for Chapter 7
True or False
Every function, including main(), must have at least one
return statement.
A function's single return statement must be the last
statement at the end of the function.
An array variable cannot be returned from a function by value.
The pointer notations int *arr and arr[ ]
are identical and either may be substituted for the other in any part of a
C++ program.
Either passing a variable by value or by using const
will prevent the called function from modifying the data.
One good reason to pass a pointer to a structure rather than the structure
itself is because of the processing time it takes to make a copy of the
structure.
In essence, the extraction operator is implemented as a class function in
C++.
Chapter 8 Adventures in Functions
Objectives for Chapter 8
By the end of Chapter 8, you should understand these concepts:
Inline functions and when they are appropriate
Reference variables, the other side of pointers
Giving a function the capability to modify data by using reference variables
Using default arguments to cover the "normal" situation
Function overloading/polymorphism: customizing the function to the data
Templates to partially automate function overloading to save work and reduce errors
Reference Variables
This is where things become a little bit easier compared to pointers. We've learned how to work with variables as either a simple name or a pointer to the address of the variable. Now we have a reference to a variable. The big benefit is that a reference eliminates having to use a pointer as a function argument, along with the pointer dereferencing symbol, *, to be able to work with the data.
Bottom line: passing-by-reference means the function statements are working with the original data, not a copy of it. Yet, we don't need to use a pointer and pointer dereferencing to accomplish this feat. It's just plain easier to look at the code as well.
Technically, the reference symbol, &, is behaving just like the
address-of operator. In my little modification of Listing 8.5, cubes.cpp,
I used the address-of operator to check the address of the variable x
and the variable a. They are the same, as they should be.
#include <iostream>
double cube(double &a);
int main()
{
double x = 3.0;
cout << "Address of x = " << &x << "\n";
cout << cube(x);
cout << " is not the cube of " << x << "\n";
cin.get();
return 0;
}
double cube(double &a)
{
a *= a * a;
cout << "Address of a = " << &a << "\n";
return a;
}
We know that to get the address of x we use &x. So look at the
cube() declaration:
double cube(double &a);
When we call the cube() function:
cout << cube(x);
we are in reality sending the address of x into the cube()
function. This address is now assigned (copied) to &a, which is the
address of the new variable a. So &x and &a are
both the same address, just as if a pointer had been used. As a result, the
variable a is an alias for the variable x since they both
reference the same value that's stored at the one address. So inside the
function, when we change the value of a, x is being changed
because they both reference the value stored at the same address.
Using References with a Structure
In Listing 8.6, strtref.cpp, find the statement
use(use(looper));
The use() function has a reference parameter, it also returns a
reference, so you can substitute looper with use(looper). In
other words, when the use() function is finished doing its thing, it
returns a reference-to-looper back to the code that called the use()
function. The parameter for the use() function is a reference-to-looper
- they're the same thing.
This almost looks like Recursion, but it's not. In Recursion a function calls
itself. The use() is not calling itself anywhere in this example.
Just a personal observation about Listing 8.6. I view this as a very confusing
example simply because it uses the use() function as its own argument.
It would have been easier to see this work had there been another, different,
function in the code that used the return value of the use() function
as an argument, but that's just my opinion.
Function Polymorphism (Function Overloading)
This section is pretty good at explaining this subject so I don't have anything
to add, except to make sure you notice the note on page 325, What Is Name
Decoration. In my travels into newsgroups and web sites, I've seen a lot of
"Foo", and the mention of "name mangling". I was rather confused by all this,
what were these geeks talking about. Well this particular note provides a peek
into both. Try to remember this bit of info because you will see and hear a lot
more of this, especially "name mangling" and the reasons for recompiling source
code.
Templates
NOTE:
It appears that typename cannot be used with OpenWatcom, we have
to use the word class in Template declarations and definitions.
Templates is a pretty slick C++ feature. Depending on the program, these could
save quite a bit of typing for the programmer as it lets the compiler do some
coding for us.
The next few chapter subsections really get into to details, that to me, looks
like a power programmer would be using. So I'm not going to get into it too much.
For me, once I have some programming under my belt, then I might get into
finding ways to really make my code super efficient, but for a beginner, this
amount of detail can cause learning problems. Understanding the basics of
Templates is really great, but for now, getting into such detail beyond the
basic operation of Templates just isn't worth the effort right now. Once again,
this is just my personal view. You all have the same book I do, so if you wish
to really get into more detail, by all means do so.
Explicit Specialization
NOTE:
OpenWatcom presently does not support this feature of C++. Listing 8.11, twoswap.cpp, will have to use the modifications shown on page 335. However, there is a coding error. The following:
void Swap(int & n, int & m); //regular prototype
has the wrong argument types. Instead of int, use
job as the type. Actually, this declaration isn't actually
needed. The compiler will use the template:
template
void Swap(any &a, any &b);
to accomplish the task. Just comment out the line:
void Swap(job & n, job & m); //regular prototype
recompile and the program runs fine.
That's all for this lesson. The next lesson will finally get into Objects and
Classes in Chapter 10.
Terry Norton
is originally from California, moving to Vermont in 1984. His field of expertise is electrical engineering, having worked in electronics from 1969 until 1990. Starting in the Air Force for 6 years, then becoming Plant Engineer at Econco Broadcast Service in Woodland, CA. Upon moving to Vermont, he worked at Joslyn Defense System working in the field of EMP, Electro-Magnetic Pulse. In 1990 he retired from full time electronics to enter, along with his wife Lorraine, the field of helping the handicapped for an agency of the State of Vermont.
Terry's first venture into the computer world was building his first 286 computer running DOS. He started using using OS/2 when version 2.0 was released. He's stayed with OS/2 ever since. At age 54, he now wishes to learn computer programming, a goal he has had for a number of years. Not necessarily as a career move, but for the self-satisfaction and love of OS/2 and eComstation.