CHAPTER 7
| Section IV: Arrays as Parameters | Section I : Introducing Arrays | Section II: Accessing Array Elements | Section III: Arrays and For Loops |
|
Table of Contents
Learning C++:
An Index of Entry Points
2. The A reference document on the basic elements of C++.
3. The Patterns
|
A. Typedef's and Arrays 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 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 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. We need to indicate to the function that 10,000 integers are to be
passed. Given what you know at this point, there are two ways to
accomplish this. First, we could write This is legal and 'kind of' does what we want but only kind of. What
this actually means is that the function will accept arrays of ANY number
of integers. Interestingly enough, if one adds 'MAX_ITEMS' and
writes the compiler ignores the 'MAX_ITEMS' and still accepts any array of integers. (What is actually going on here will become clearer when we study pointers and their relationship to arrays in Section III of Chapter 9.) To make our code safer, it would be nice if we had a 'type' that
represented 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 #include <iostream.h> typedef int InventoryArray[10000]; void main() } 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 declares the existence of the variable 'itemInventory' of the type
'InventoryArray'. Since 'InventoryArray' is the name for an array of
10,000 integers, 'itemInventory' is the symbolic name for enough memory
for 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: 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;
}
}
Here is how the whole program looks so far: // Program to handle inventory for a small company // File: ch7inv1.cpp #include B. Globals 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 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 The call to this function would be a bit different in that it would be
passing an element of the array instead of the whole array. Here are the
relevant lines of code for 'main': Note that itemInventory[itemCode], the first actual parameter in
the call to 'OutputOneInventoryAmount', is a single element of the array.
In other words, one integer, an element of the array, is passed as the
first parameter to 'OutputOneInventoryAmount'. Since array elements of
type integer can be used wherever integer variables can be used, they can
be used as integer parameters. (The same is also true for all other types
including class 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:
{
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. D. Arrays as Function Parameters to Return
Data InputInventory Receives: NONE 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 review and 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 main() {
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,
Thus, when the value '3' is assigned to 'abc' in the function
'DemoFunction', this operation has no effect on the contents of 'def'.
When 'DemoFunction' finishes 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 demonstrates this.
![]() 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 second 'abc' as long as 'DemoFunction' was executing.
Again, when 'DemoFunction' completed, its 'abc' variable would disappear
and so would any changes made to its contents.
![]() 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 main() {
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'.
![]() When we talked about this back in chapter four, we used the idea of one-way and two-way streets. The term "pass by value" is a more precise way of talking about a one-way street. The value in the actual parameter is copied to a new memory location symbolized by the formal parameter name and the system does not provide a mechanism to directly copy the value back to the memory symbolized by the actual parameter. (Remember, the term 'actual parameter' refers to the parameter on the calling side, while 'formal parameter' refers to the parameter name in the function declaration/definition.) The term "pass by reference" is the more precise way of talking about a two-way street. Since both the actual and the formal parameters are symbolic names for the same memory location, whatever is in the memory symbolized by one of the parameter names, is in the other. There really isn't a 'street' at all!
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': The code to call this function would look the same as the code to call
'OutputInventory'. And, the code in the definition would use the same
'for' statement we wrote above for inputting all the product inventory
amounts at once. It would simply change the code inside the 'for'
loop: 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;
}
}
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 11, we will look at file handling and fix
this detail. STAY TUNED! Topics Covered in the "Essentials of C++" Examples
| |