C++ Class Part 4

From EDM2
Jump to: navigation, search
C++ Class / Part
1 2 3 4 5 6

By Terry Norton

Welcome to lesson 4 about C++ Functions.

Chapter 7 C++'s Programming Modules

Objectives for Chapter 7

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

  1. Every function, including main(), must have at least one return statement.
  2. A function's single return statement must be the last statement at the end of the function.
  3. An array variable cannot be returned from a function by value.
  4. The pointer notations int *arr and arr[] are identical and either may be substituted for the other in any part of a C++ program.
  5. Either passing a variable by value or by using const will prevent the called function from modifying the data.
  6. 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.
  7. 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.