CHAPTER 7 cs1ch7.htm
 
 
CHAPTER 7
 
ARRAYS: A BETTER WAY OF HANDLING MULTIPLE INSTANCES
 

  


Table of Contents

1. The Story
of C++!

(The basics for any beginner!)


2. The

of C++

A reference document on the basic elements of C++.



3. The Patterns





A. Typedef's and Arrays
We now have most of the elements for a simple program to handle inventory. Such a program would allow the user to initialize the inventory to empty, enter the inventory amounts for all products, update the amounts as desired, and output the amounts as desired. However, each of the pieces we have looked at so far - initializing the array, reading values into the array, and outputting those values - would be best written as a separate function.

The names for these three functions might be 'InitializeInventory', 'ReadInInventory', and OutputInventory'. We will explore 'OutputInventory' first. As usual when working with a new function we start with the design:

OutputInventory
Purpose: To output the inventory amounts for all the items in the inventory
Goal: The inventory amounts are all output

What does this function need if anything from the calling function and what does it return? As with all output functions, this one returns nothing but it needs the data that it is to output. We could explicitly pass each individual element and have 10,000 parameters! Or, we could pass the array. Here we see another advantage to giving a group name for all 10,000 items. We can pass the group name and the system knows to pass all 10,000 elements. Our 'receives' and 'returns' analysis then looks as follows:

Receives: The inventory of items
Returns: NONE

As we move on to declare this function we need to ask what is the type of this array? Careful! The type of the elements of the array is integer but the array itself is NOT an integer. To make this clear consider this analogy. Each of the students on a class roster is a person (we presume!) but the roster itself is not a person.

There is no 'array' type in C++ and even if there were it would not help since the type we want here refers not just to any array but to an array of 10,000 integers. It is time to use the 'typedef' statement we learned in the previous chapter. At that time we declared a new type 'Boolean' with the declaration going into a separate file because we anticipated using that type in many programs and wanted to be able to repeatedly take advantage of the type declaration without re-writing it. In this case, the specific array type we are working with is unlikely to be found in too many programs so it should not be coded in a separate file. Instead we put it at the top of the file containing 'main':

// Program to handle inventory for a small company
// File: ch7inv1.cpp

#include <iostream.h>

typedef int InventoryArray[10000];

void main()
{

InventoryArray itemInventory;
.
.
}

What the 'typedef' line does here is declare the existence of a new type called 'InventoryArray' and say that any variable of this type is an array of 10,000 integers. Remember that a type is not a variable so what the typedef is doing is describing a new type of 'thing' and giving that type a name. It is not setting aside any specific memory. Inside 'main' the line

InventoryArray itemInventory;

declares the existence of the variable 'itemInventory' of the type 'InventoryArray' and since 'InventoryArray' is the name for an array of 10,000 integers, 'itemInventory' is an array of 10,000 integers.

With this, we now also have a type name for the type of the data to be received by 'OutputInventory'. Here is the finished declaration:

void OutputInventory(InventoryArray items);

We again use the type name 'InventoryArray'. This indicates that the array will receive an array of 10,000 integers - which is just what we want. The local name for this array was arbitrarily chosen to be 'items'. We learned earlier that the name does not matter. We could have used the same name, 'itemInventory', that we used in 'main' but that sometimes is confusing. When we write the function definition, however, we need to use whatever name we placed in the parameter list. Therefore, if the function definition parameter is 'items', the rest of the code better use the variable 'items':

    void OutputInventory(InventoryArray items)
    {
    	for (int itemCode =
    
     0; itemCode
    <
    	MAX_ITEMS; itemCode++) {  cout <<  "The  inventory
    	amount	
    		for  item " << itemCode << "  is:  " << items[itemCode] 
    	        	          <<  endl;
    	}
    }
    
The code inside this function is exactly the same as the code we saw
above for outputting the elements of an array.

Here is how the whole program looks so far:

    // Program to handle inventory for a small company
    //  File: ch7inv1.cpp
    
    #include 
    
    typedef int InventoryArray[10000];
    
    void OutputInventory(InventoryArray items);
    
    void main()
    {
    	InventoryArray itemInventory;
    	OutputInventory(itemInventory);
    }
    
    void OutputInventory(InventoryArray items)
    {
    	for (int itemCode =
    
      
    
      
    
     
    
    	 
    	
    
    
     0; itemCode
    <
    	MAX_ITEMS; itemCode++) {  cout <<  "The  inventory
    	amount	
    		for  item " << itemCode << "  is:  " << items[itemCode] 
    	        	          <<  endl;
    	}
    }
    

The typedef comes before the function declaration so that the compiler knows about the new type, 'InventoryArray', before seeing it used. Later in 'main' the function 'OutputInventory' is given the array of inventory items which it needs. Of course, in a complete program there had better be some way of getting information into the array before printing out the information. Otherwise garbage will be printed. But, we will get there.

B. Globals
You may not have noticed, but there is a problem here with MAX_ITEMS - it has not been declared. OOPS! Now, where should the declaration go? We haven't really explained why yet, but if you look at all earlier programs, you will discover that constants have been declared outside of any specific function such as 'main'. In fact, they have been declared at the top of the file, before any variable or function declarations.

In contrast, all the variables we have used in our programs have been declared either as parameters or inside a function and we have called them local variables. By local we mean that they are known only inside the function(s) they are declared in. Thus, we could declare MAX_ITEMS as a constant inside 'main' and add it as a parameter to 'OutputInventory'. After all, it is true that 'OutputInventory' does need it to accomplish its work.

This approach is sometimes used but, more often, a different approach is taken. It is possible to declare a constant (or a variable) as global. This means that it is known in ALL functions that occur in the same file after the declaration. More precisely, a global constant (or variable) is declared outside all functions and classes and is known from the point of declaration to the end of the file. So, if we declare MAX_ITEMS at the top of the file containing 'main', it will be known to all functions in the file including 'OutputInventory'. In our case, we will put it just before the typedef statement and change the typedef to take advantage of it.:

    // Program to handle inventory for a small company
    //  File: ch7inv1.cpp
    
    #include 
    
    const
    	int MAX_ITEMS = 10000;
    
    typedef int InventoryArray[MAX_ITEMS];
    
    void OutputInventory(InventoryArray items);
    
    void main()
    {
    	InventoryArray itemInventory;
    	OutputInventory(itemInventory);
    }
    
    
    void OutputInventory(InventoryArray items)
    {
    	
    
    for
    	(int itemCode = 
    
      
    
      
    
     
    
    	 
    	
    
    
    
     0; itemCode
    <
    	MAX_ITEMS; itemCode++) {  cout <<  "The  inventory
    	amount	
    		for  item " << itemCode << "  is:  " << items[itemCode] 
    	        	          <<  endl;
    	}
    }
    
    

If you were reading these last paragraphs carefully, you may be asking yourself, "If variables can be declared as globals and therefore be known throughout the whole program, why have parameters - after all, parameters can be a real hassle!?!" It is true that you can do this but remember all we said about encapsulation. Using global variables is an invitation to buggy code. Don't do it! In fact, your instructor is likely to deduct points from your assignments if he or she finds you using global variables. There are certain controlled circumstances when they can and should be used but you have not seen any yet.

By the way, the typedef statement also creates a global type - 'InventoryArray'. We could have declared this new type inside 'main' but then it would have been unavailable outside of 'main'. In fact, it is very rare to use local typedef's.

C. Array Elements as Parameters
Our inventory program could also have a function that output the amount of inventory for one item. Such a function would be useful if one option available to users was to get the inventory information on one product. The program would ask the user for the item code of the product he or she wanted the inventory information on and pass the array element corresponding to that item to the function. Since the inventory amounts are integers the output function would clearly receive an integer representing that amount. If we wanted to also output the item code as part of the display, this information would also need to be passed. Still, the function declaration would be similar to many we have already seen:

void OutputOneInventoryAmount(int amount, int itemCode);

The call to this function would be a bit different in that it would be passing one element of the array. Here are the relevant lines of code for 'main':

int itemCode;
itemCode = GetItemCode(); // declared and defined elsewhere
OutputOneInventoryAmount(itemInventory[itemCode], itemCode);

Note that the first actual parameter in the call to 'OutputOneInventoryAmount' - itemInventory[itemCode] - is a single element of the array. In other words, since array elements of type integer can be used wherever integer variables are used, they can be used as parameters. (The same is true for arrays of all other types.)

The code for 'OutputInventory' in the previous section received the whole array and included a 'for' statement to go through the array elements one at a time. This is the most efficient way to handle the option of outputting the whole array but we could also have accomplished the same result by using the function we just wrote, 'OutputOneInventoryAmount', and rewriting the calling side as:

    for (int itemCode = 0; itemCode < MAX_ITEMS; itemCode++)
    {
      OutputOneInventoryAmount(itemInventory[itemCode], itemCode);
    }

This would cause the individual amounts to be passed to the output function. It is not the best code because every function call takes a bit of time - just as it takes slightly longer to get someone else to start a task than to it takes to start it yourself. This last bit of code makes 10,000 simple requests (function calls) as opposed to one more complex request.

VI. Arrays as Function Parameters to Return Data
Now, let's consider the function to allow the user to enter the initial inventory amounts. Instead of outputting values it inputs values and instead of receiving the inventory amounts it returns them:

InputInventory
Purpose: To allow a user to input the inventory amounts for all the items in the inventory
Goal: The inventory amounts are all input

Receives: NONE
Returns: The Inventory

Ordinarily a function with this kind of a design analysis would have no parameters and would return one item. However, functions in C++ cannot return arrays. This means we have to use our other mechanism for returning values - the parameter list.

Earlier we made a distinction between function parameters declared with the '&' symbol and those without. We said that formal parameters with the '&' symbol can return information because a 'two way path' has been created while formal parameters without the '&' symbol could only be used to transmit information into the function but not back. It is time to say more about what the '&' operator does to cause this difference.

When a formal parameter does not have the '&' symbol, the system makes a copy of the variable being passed and the function uses that copy. Consider the following code segment:

    void DemoFunction(int abc);
    .
    .
    .
    void main()
    {
      int def = 5;
      DemoFunction(def);
      ....
    }

When the system calls 'DemoFunction' from inside 'main', it already has set aside memory for a variable called 'def'. And, since the function declaration for 'DemoFunction' declares 'abc' as a formal parameter without a '&' symbol, the system, when the call actually takes place, sets aside enough memory for a second integer, calls that memory location 'abc', and copies the contents of 'def' to 'abc'. Thus any changes made to 'abc' in the function 'DemoFunction' have no effect on the contents of 'def'. When 'DemoFunction' finishes operation and control is returned to 'main', the memory for 'abc' is returned to the system and the variable itself no longer exists.

Figure 7.2


Even if both variables had been called 'abc' the result would be the same. The system would start by setting aside the memory location for the 'abc' in 'main'. When 'DemoFunction' was called, the system would then set aside a second memory location for the 'abc' found in 'DemoFunction' and copy the value found in the first 'abc' to the second. The system would know to use the 'abc' for 'DemoFunction' as long as 'DemoFunction' was executing. Again, when 'DemoFunction' completed, its 'abc' variable would disappear and so would any changes made to its contents.

Figure 7.3


Technically, this parameter passing process is called pass by value. The value found in the memory location for the actual parameter is passed to the copy. When the formal parameter is associated with the '&' symbol, a process called pass by reference is used. In this process, the address of the memory symbolized by the actual parameter is passed. (Memory addresses are sometimes referred to as references thus the name 'pass by reference'.) Since the function receives and uses the address of the actual parameter, it works with the actual parameter's memory and not a copy. Any changes made in the function are made in the original.

Again, this process works whether or not the actual and formal parameters have the same name. The variable name used in the formal parameter becomes a 'nickname' for the memory location named in the actual parameter. Both the actual and formal parameter names are symbols referring to the same memory location.

Consider our same example but with the '&' symbol added to the function declaration:

    void DemoFunction(int& abc);
    .
    .
    .
    void main()
    {
      int def = 5;
      DemoFunction(def);
      ....
    }

Now, when 'def' is passed as a parameter, the system does not make a copy of the contents of 'def'. Instead, 'abc' becomes a second name for the same memory location and any changes made to 'abc' are made to 'def'. When 'DemoFunction' completes, 'abc' still disappears as a variable name but the memory it refers to is still available as 'def'. And, any changes made to 'abc' still exist because they were made to the same memory referred to as 'def'.

Figure 7.4


What all this has to do with arrays as parameters is that arrays are AUTOMATICALLY passed by reference. In other words, any time an array is used as a parameter, the 'two way path' is created. There is no need to (and it is poor style to) use the '&' symbol. Thus, the function declaration for 'InputInventory' has the same form as 'OutputInventory':

void InputInventory(InventoryArray items);

The code to call this function would look the same as the code to call 'OutputInventory'. And, the code in the definition would be the same 'for' statement we wrote above for inputting all the product inventory amounts at once:

    void InputInventory(InventoryArray items)
    { 	int itemAmount;
    	for (int itemCode =
      
     	 0;
    	itemCode < MAX_ITEMS;  itemCode++) {  cout  <<
    	"Please	
    		enter  the inventory count for item: " << itemCode;  cin
    		>>  itemAmount;
    		items[itemCode] = itemAmount;
    	}
    }
    

One of the reasons for having parameters that only passed information into functions was to control the change possible. In other words, if it was impossible to change the value of the actual parameter, the code could do whatever it wanted to the formal parameter without worrying about the effect on the rest of the program. With arrays, however, since they are automatically passed by reference, this safeguard is not available. But, if you wish to protect an array from being changed, you can add the word 'const' in front of the parameter declaration in the function declaration. For example, it would be better then to write the declaration for 'OutputInventory' as:

void OutputInventory(const InventoryArray items);

Note that we would not want to do this for 'InputInventory' because we ARE changing the values in the array elements.

Click here for the complete code for this problem - with a menu added to allow the user to do as he or she wishes. To make the program easier to test, the number of inventory items has been reduced to 5. This allows the user to enter the inventory information without a lot of effort. As we have already said when talking about program testing - it is important to design one's tests so that they focus on the real issues and don't take unnecessary time or energy. And, by the way, this demonstrates the value in using constants. No change needs to be made between the test and 'full version' of this program except for the value of MAX_ITEMS.

There is one element that makes this program highly artificial. In any real program of this kind, the user would be allowed to save the inventory data to a file at the end of the day and, at the start of next day's business, read that information back into the array. In chapter 10, we will look at file handling and fix this detail. STAY TUNED!

Next Page