I. Memory Allocation
In the last chapter we explored one way to implement a list -
using arrays embedded inside a class structure. The nice thing
about this technique was that one could use as little or as much
of the array as one wanted - depending on the size of the list.
But, what happens when the array fills up? In the case of the
contract list, what happens when there are more than MAX_CONTRACTS
contracts? If you recall, the code was written to output a warning
message saying that it was not possible to add another contract.
Now we could always make the array huge, as big as RAM memory
will allow, so it would be very unlikely that there would be too
many contracts. However, consider a slightly more complex example.
We have ten different kinds of contracts each requiring its own
list. And, we have no idea which of the lists will be large and
which will be small. We can't make the arrays for all the lists
huge, there is not enough memory. We are left with the ugly choice
of guessing which lists will be huge and hoping we are right or
we can make the lists all the same size and potentially wind up
in the ridiculous situation of having one list be practically
empty while another is full and unable to take more contracts.
Be sure you understand the reason for this: when the program first
begins, memory is set aside for each array - long before the program
knows how big each array needs to be. And, once the program starts,
there is no way to change the amount of memory allocated for any
given array unless one stops the program, changes the code, recompiles
it, and starts again! This is an example of what we call static
memory allocation. 'Static' because the amount of memory allocated
does not, cannot, change, while the program is running.
In this chapter we are about to learn about a new kind of memory
allocation - dynamic memory allocation. Using this, a program
is able to request memory from the operating system as it needs
it - up to the maximum amount of memory in the system. To handle
the above situation the program would start by allocating a small
to medium size amount of memory for each list and then as any
particular array filled up, it would ask for more memory JUST
for that list. If the program was sophisticated enough, it might
even look for lists that had extra room and allocate less space
for them once the system as a whole ran out of memory.
II. The Pointer
To accomplish this we need to introduce a new concept - the pointer.
Remember each memory location has a unique address and that
variables are really symbolic names for these memory addresses.
A pointer is a memory location that contains the actual address
of some other memory location instead of containing an integer,
a double, a character or the beginnings of some user-defined type
A picture might help:

Here we see two boxes representing memory locations. The box on
the left with the symbolic (variable) name 'ptrVar' contains
what looks like a large integer. This integer is actually the
address of the memory location represented by the second box.
The arrow between the two boxes represents the idea that the address
in the left hand box 'points to' the box/memory location on the
right.
If we had two arrays each pointed to by a pointer, we might have
the following picture

This time each memory location on the left points to the first
memory location of an array with each array being of a different
size.
Note that in all these pictures, there is no variable name associated
with the boxes on the right. The only way one gets to these memory
locations is by 'following' the addresses (pointers) in the memory
locations on the left. The memory locations on the left do have
variable names associated with them and would be accessible to
a program if that program correctly declared the variables. We
will see how to do so in a bit.
A. Declaring a Pointer
OK, so now you understand this at the conceptual level. It is
time to get down to the details. As usual, we will start with
a simpler goal - simply declaring pointers to some of the built-in
types. Frankly, there is little value in doing so but it is easier
if we start this way and it is always nice to begin slow and easy.
We know what
means to the computer - "set aside enough memory for an integer
and give that memory the symbolic name 'x' ." We also know
that this memory location initially contains garbage.
Above we saw that a value is interpreted differently if it is declared as a pointer. To declare a variable that is to hold a pointer to an integer we write:
The '*' is the new part. What this means to the computer is 'Set
aside enough memory for a pointer to an integer and give
that memory the symbolic name 'ptr' ." In other words, whenever
the system works with 'ptr' it knows that the contents of 'ptr'
is not simply an integer but it is an integer acting as a memory
address.
One important point in C++ is that pointers point to a memory
location holding a value of some specific type. Consider these
declarations:
int* ptrInteger; 'ptrInteger' holds the address of a
memory location containing an integer;
double* ptrDouble; 'ptrDouble' holds the address of a
memory location containing a double;
char* ptrChar; 'ptrChar' holds the address of a
memory location containing a character;
Contract* ptrCont; 'ptrCont' holds the address of a
memory location containing a contract;
This means that, for example, 'ptrInteger' can NOT point to a
memory location containing a double.
In chapter two we said that each type takes up its own
amount of memory. Thus characters might take up one byte, integers
two bytes, doubles four bytes. The amount of memory required by
a contract is a bit more complex but it takes up at least the
amount required for each of the individual data members in a contract.
And, an array of 20 contracts will take up 20 times the memory
required for one contract.
The point (a pun?) of this is that when it says above that some
variable "holds the address of a memory location containing
a ..." what it really means is that the variable holds the
address of the first byte of the memory used for whatever type
is being pointed to. That is one reason why a variable acting
as a pointer must point to a specific type. The system needs to
know how many bytes to handle as part of the data being pointed
to.
B. Assigning Values to Pointers
So we know that pointer variables hold memory addresses and they
point to memory locations containing some specific type. Now,
how do we put the addresses into pointer variables? One thing
we don't do is:
This would be extremely dangerous if it were allowed. It
would be saying you want to be able to access memory location
324 or, as we will see in a bit, be able to put a value into this
memory location. But, what if address 324 is part of the operating
system or part of your program. If you could change it, you would
be changing the code for the operating system or the program and
it is most unlikely that the change would be for the better. So,
we are, in general, not allowed to choose which memory locations
we have direct access to. Instead, we ask the system to provide
us with memory that it knows is free. We can then do what we want
with that memory without worrying about possible bad effects.
C++ provides a instruction for making such a request. If you want to ask for enough memory for a double and store the address of that memory location in 'ptrDouble', you write:
If you want to ask for enough memory for a contract and store the address of that memory location in 'ptrContract' you write:
The same is true for any other pointer type.
Although 'new' was referred to above as an instruction, it really
is an operator - the same as '+', '-', etc. At this point, however,
that is not very important. What is important is what this 'new'
operator does. When the operator is called, two things happen:
Consider again the code:
When the 'new' operator is called here, the system allocates enough
memory for a double. Assume that the allocated memory begins with
the byte address 345,112. This value is then placed in the variable
'ptrDouble' and we get the usual picture:

Often programmers do not include the type in variable names so
one might write:
or even:
This is legal just as any other declaration/initialization is legal. What is not legal is, for example:
In this case, you are trying to put the address of an integer
into a variable that has been declared to hold a pointer to a
character. As we said above, a pointer variable can only hold
the address of the type it was declared to hold.
To request enough memory for an array of some type, a slightly
different form of the 'new' operator is used. Since the address
returned will point to the first byte of the array, the pointer
declaration remains the same. Thus, if you want a pointer to an
array of integers, you can declare your pointer as:
The change is that you must include the size of the array in the
'new' statement. Thus, if you want to request enough memory for
ten integers, you write:
To declare and assign a pointer to twenty contracts, one could
write:
There are two other ways to assign a value to a pointer. First, it is also legal to write:
This means that for the moment 'ptr1' points to nothing; it holds
the null pointer. (If the 'new' operator fails in its attempt
to allocate enough memory for whatever is to be pointed to, it
returns the null pointer.) Later we will see a number of uses
for this null pointer value.
The third legal way to assignment a value to a pointer is by assigning the value of one pointer variable to another - as long as they are both of the same type. Therefore, it is legal to write:
Now both 'ptr1' and ptr2' point to the same memory location.

This also can be quite useful as we will see.
III. Using Pointers
The key to these memory address assignment rules is that we as
programmers do not have the ability to explicitly say that we
want to use memory location 'X'. However, once the system has
given us a memory location to use, we can trust that it is safe
and we can pass that memory location on to other pointer variables
and use it for our own purposes. The next step is to learn how
to access the addresses (or memory locations) being pointed to
by variables.
In the code:
we first declare 'ptrInt1' to be a pointer to an integer and we can think of memory as looking as shown in figure 9.5. (The question mark inside the memory cell indicates that this memory location holds garbage.)

Then we ask
the system to provide enough memory to hold an integer and to
assign the address of the first byte of that memory to 'ptrInt1'. Now memory can be considered to have the form:

Note how the left memory cell now contains an address. The function 'new' set aside the memory with this address and returned this address whereupon it was stored in 'ptrInt1'. This new memory (the memory cell on the right) has not had anything placed in it yet so it holds garbage.
To store the value five into the memory made available, we would write:
This brings up the distinction between
The first acts like any other variable name and refers to the
memory symbolized by the variable 'intPtr1'. It thus refers to
the box labeled "ptrInt1" in figure 9.6. In contrast
'*ptrInt1' refers to the address pointed to by the memory location
symbolized by 'ptrInt1'. In other words, '*ptrInt1' refers to
the second box in the figure.
When a system sees an asterisk followed by a variable declared to point to some type, it looks in the variable for an address and follows that address to a memory location. Therefore,
means "Go the memory location symbolized by the variable 'ptrInt1' and get the address stored there. Then go to the memory location with that address and put the value five in it. After this code is executed, the picture changes to:

Now consider this code:
The semantics of the last line here are as follows:
This process of 'following a pointer' to get to a memory location is called dereferencing the pointer and a dereferenced pointer can be used anywhere a variable can be used. For example, consider the following code:
We will see code similar to this in a bit. For the moment just
remember that:
IV. Arrays and Pointers
A. The Similarity of the Two
In chapter 7 you learned that arrays are automatically passed
by reference. Now that we are working more closely with memory
addresses, the meaning of pass by reference can become clearer.
When a variable is passed by reference to a function, the address
of the memory is passed to the function. In other words, the
variable is treated a bit like a pointer.
In most cases this is invisible and the underlying mechanism is
of little use to us as long as we remember that variables passed
by reference can have their contents changed upon returning from
the function while variables passed by value cannot have their
values so changed. However, with arrays this is not true. Not
only are array parameters automatically treated as reference parameters
but arrays themselves can be treated exactly like pointers. The
line of code:
is the same as:
Both request memory for 10 integers.
Consider this more drastic example:
typedef int IntArray[20];
` void main ()
{ int* ptrArray;
ptrArray = new IntArray;
for (int i = 0; i < 20; i++)
{ ptrArray[i] = i * 10;
}
}
We start with a user defined type for an array of 20 integers.
We then declare a pointer to an integer. Third, we ask for memory
to be set aside for 20 integers ('new IntArray') and that the
address for the beginning of this memory be stored in the pointer
to an integer, 'ptrArray'. And, finally, in the for loop we use
the pointer as if it is referred to an array!This business of converting back and forth can be very confusing so let's take our time with it. The key point is that C++ works with arrays by working with the address of the first byte of the array. A variable that represents an array therefore can be considered a pointer to the first byte of the array. Likewise, the name for an array element is the same as a pointer to the memory containing that specific array element. For example, in the code:
IntArray AnArray;
AnArray[0] = 5;
AnArray[1] = 10;
'AnArray[0]' is equivalent to a pointer to the first element of
the array and 'AnArray[1]' is equivalent to a pointer to the second
element of the array. The reverse is also true. In the code inside
'main' above, 'ptrArray' is declared to be a pointer to an integer.
But, once the 'new' statement is executed, it is also equivalent
to a pointer to the first byte of an array of integers. As such
it is equivalent in meaning to 'AnArray'. And, once 'ptrArray'
is seen to be equivalent in meaning to 'AnArray', it makes sense
that 'ptrArray[0]' be equivalent to 'AnArray[0] and 'ptrArray[1]'
be equivalent to 'AnArray[1], etc. Thus the for loop code makes
sense. (Well, maybe it does not make sense yet, but keep with
it and it will! You are urged to read this paragraph again if
you don't follow its logic the first time.)
When we say equivalent here we mean that the two statements have the same semantics, not that they refer to the same memory. In fact, as written, 'AnArray' refers to a different set of memory locations than 'ptrArray' because one requests memory at compile time and the other requests a different block of memory at run-time. But, consider the following:
IntArray AnArray1;
int* AnArray2;
AnArray2 = AnArray1;
As the picture below shows, 'AnArray1' and 'AnArray2' both refer
to the same memory. 'AnArray1' is the symbolic (variable) name
for the memory of the array itself while 'AnArray2' is the symbolic
(variable) name for a memory location that points to the memory
of the array.

To demonstrate this, experiment with the code in the file, ch9prog1.cpp part of which is show below:
typedef int IntArray[20];
void main ()
{
IntArray array1;
int* array2;
array1[0] = 111;
array2 = array1;
array2[0] = 222;
cout << endl << array1[0] ;
*array2 = 333;
cout << endl << array1[0];
cout << endl << "An Address: " << array2;
cout << endl << "The contents of that address: " << *array2;
}
When you run this, you will find that when we output 'array1[0]'
the first time, we get the value placed in 'array2[0]' and when
we output it the second time we get the value put in '*array2'
! If 'array1[0]', 'array2[0]' and '*array2' did not all refer
to the same memory, how could placing a value in '*array', for
example, affect 'array1[0]'?
The last two outputs are there to demonstrate something a bit
different. The line that begins "An Address" is actually
showing the address of the array. That makes sense because 'AnArray2'
is the symbolic name not for a memory location containing an integer
but for a memory location containing an address of a memory location
that contains an integer. The next line uses the derefencing
operator (*) to actually access the contents of the
memory pointed to by 'AnArray2'. Note that this is the same value
(333) that was just placed in this location and output a few lines
up.
B. Pointer Arithmetic
There is one more little trick to all this. Consider this addition
to the for loop code above (also found in the file "ch9prog2.cpp"
typedef int IntArray[20];
` void main ()
{ int* ptrArray;
ptrArray = new IntArray;
for (int i = 0; i < 20; i++)
{ ptrArray[i] = i * 10;
}
// Here is the new part
int* ptr;
ptr = ptrArray;
for (i = 0; i < 20; i++)
{ cout << *ptr << endl;
ptr++;
}
}
First, the code initializes the array as we have already discussed.
When this initialization is completed, the array looks as follows:

Then, in the 'new part', the code creates a second pointer to
an integer and has that integer also point to the array. This
is followed by a for loop that obviously is to output something.
The first time into the loop 'ptr' points to the first element
of the array, '*ptr' means retrieve the contents of that memory
location, and so '10' is output. The following line "ptr++"
increments something by 1. The key is what gets incremented. Since
'ptr' represents a memory location containing an address and the
'++' operator increments the contents of a memory location, it
must be the address in 'ptr' that is being incremented.
Suppose ptr contained the address 30,456. If this address was
incremented by 1, 'ptr' would be pointing to address 30,457. But,
if memory location 30,456 held an integer, as the declarations
say it must, and, if an integer takes two bytes, as we have suggested
it might, then address 30,457 is in the middle of an integer.
Then the next time through the loop, what would it mean when we
tried to output the contents of the middle of an integer?
Actually, the computer would come to a halt for reasons based
on hardware design. The situation would be even worse if 'ptr'
pointed to a double. It would seem then that either C++ should
not allow code like 'ptr++' or it should mean something else.
In fact, it is valid code and therefore has a slightly different
meaning. The '++' here means increment the pointer the equivalent
of one integer which, if an integer takes up two bytes, would
mean that the address becomes 30,456. If 'ptr' was declared to
be a pointer to a double and doubles required 4 bytes, the address
would increment by four bytes. If 'ptr' was declared to be a pointer
to a contract and contracts took 10 bytes, then the address would
be incremented by 10.
Thus, each time through the loop in the code above, 'ptr' is incremented to point to the next integer in the array and the result is that all 20 elements of the array are output. This is the equivalent of the code we have seen in earlier chapters to output an array by incrementing the array index.
You might ask why the code creates a second variable 'ptr' to
'walk through' the array elements. Here is how the memory 'looks'
just before the for loop begins.

Note that the only way to access the array is via the two pointers.
Now imagine that 'ptr' is gone and we increment 'ptrArray' to
get to the 2nd and following elements of the array. After one
increment (and without 'ptr') here is how the picture would look:

Take a moment to see if you recognize the problem
Did you notice that once 'ptrArray' has been incremented that there is no longer any way to access the first element of the array! Actually, we could execute the line:
but we would have to be very careful to decrement as many times
as we incremented or we would either go back too many memory locations
or not enough. The safest thing to handle this is to always leave
a pointer to the beginning of the array and use a second pointer
to walk through the array.
C. Memory Leaks
Pointer based code can be very dangerous. It is possible to completely
lose access to some data needed in a program or, as we will see
later, to leave a pointer pointing to memory that it should not
have access to. We just saw an example of the first problem and
here is a second example.
Consider the following code and pictures
d1 = new double;
*d1 = 12;
d2 = new double;
*d2 = 24;
A picture of memory at this point::

Now consider the line of code
and the picture that goes with it:

As you can see, the memory location with the value 24 is no longer accessible. In fact, given that we have no idea what address it had to begin with and that it no longer has any connection with 'd1', there is NO way that a program with this code can again access this value! The moral is:
D. Another Look at Arrays as Parameters
Back in chapter 7 you learned how to declare a function parameter
that could be used to receive or return an array. At that time,
we used C++'s 'typedef' and the ability to create user-defined
types
. Since C++ essentially treats array
variable names as pointers to memory there is a second, commonly
used way to declare functions to handle array parameters.
One example we studied in chapter 7 involved an array of 10,000 integer In place of the user-defined type name ' InventoryArray', the cod s representing an inventory. The code included the function 'OutputInventory' with the declaration:
Now that we know that:
it is easy to see why the following function declaration is valid:
e simply says that the function will receive a pointer
to an integer (the address of an integer). Since when an array
is passed as a parameter, what is actually passed is the address
of the beginning of the array, this makes complete sense. As a
second example, if a function is to receive an array of contracts,
the function declaration might look as follows:
As we saw above, a pointer to what really is an array
of elements can be manipulated using either pointer or array notion.
Therefore, in the case of ' OutputInventory' we could
write the function definition either as:
void OutputInventory(InventoryArray items)
{
for (int itemCode = 0; itemCode < MAX_ITEMS; itemCode++)
{
cout << "The inventory amount for item " << itemCode << " is: "
<< items[itemCode] << endl;
}
}
which is exactly how we wrote it in chapter 7. Or, we could write:
void OutputInventory(int* items)
{
for (int itemCode = 0; itemCode < MAX_ITEMS; itemCode++)
{
cout << "The inventory amount for item " << itemCode << " is: "
<< *items<< endl;
items++;
}
}
By the way, there also is a third way one could declare an array
parameter. Still using 'OutputInventory' as an example,
one could write:
This says that some array of integers is being passed. Again,
since all that is passed is the address of the array, this works
just fine. This approach does emphasize a point that should be
stressed here. When an array is passed, the function has no direct
way of knowing how big the array is. Back in chapter 7 and here
we have handled this by using the global constant 'MAX_ITEMS'.
As we noted, the other way to handle this would be to pass a second,
integer parameter to provide the number of elements in the array.
Many people prefer this second approach because it makes the size of the array clear. Whatever you do, be sure you carefully handle this situation. Suppose the size of the 'items' array was actually 1000 integers but you thought it was still 10,000 integers and you did not use the MAX_ITEMS constant. If you wrote the following code:
for (int itemCode = 0; itemCode < 10000; itemCode++)
{
items[itemCode] = 0;
// OR: *items = 0 ; items++;
}
you would be placing zeroes into 9,000 memory locations that were
not part of the array. Who knows what damage you could be creating.
In fact, a smart programmer once took advantage of the fact that
the Unix operating system was writing values into the elements
of arrays without checking for what are referred to as array
bounds and was able to break into what were thought to be
secure systems!
V. Strings
A. Declaring String Variables Using Arrays
As you may recall, a string is a collection of characters. Up
to now, we have only worked with string constants such
as "Please enter the inventory code". We have not been
able to handle strings as variables and input, output, store,
or manipulate strings such as names, addresses, etc. Clearly our
contract program would benefit from being able to handle customer's
names etc.
In C++ strings have traditionally been treated as arrays with
one special feature - all string arrays must end with a special
symbol which we write as '\0'. This is called the null character
and C++ strings are said to be null terminated . For
example, the string, "Please enter the inventory code"
is stored as:

Note how the symbol '\0' takes up just one of the character slots of the array. While we write it as two symbols, internally it is represented with one value. It's purpose is to allow any built-in or user designed function to know the end of the string. Other languages use one of the elements of the string array to hold an integer representing the size of the array. Such a representation or data structure works well but usually means that strings in such a language have a maximum size. In C++ there is no limit to the size of a string but we must make sure when we perform operations on strings to leave the '\0' symbol at the end.
A string such as "Please enter the inventory code" is
stored in an array but that array does not have a name - there
is no variable name for this array. We have already learned how
to declare arrays with names and our focus here is on named arrays
treated as strings. Here's a start:
'helloString' is an array of 14 characters. Note that at
least 14 characters are needed - 13 for the individual characters
(including the space) in the string constant and one for the '\0'.
While, it would not work to declare a smaller array, we could
have declared a larger array as in:
Some space is wasted here but any properly written string processing
function will ignore the extra space because it will discover
the null character at the end of the string and stop.
It is also legal to declare a string array without stating explicitly the size of the array if you initialize it right away as in:
C++ actually lets you declare any array without providing its
size if you immediately include the array's initial values - the
system determines the array's size by counting the values in the
initialization list. (Since unnamed strings such as "Howdy
Pardner" already have a null character in their internal
representation, there is no danger of the system providing too
small an array.)
To declare and initialize a non-char array one uses brackets to hold the initializing values as in:
This creates and initializes an array of 4 integers.
B. Declaring String Types
Since traditionally C and C++ did not include a built-in string
type, users would declare their own string types using the typedef
statement. Since strings are really character arrays, we can declare
a string type by declaring an array type as we did in chapters
7 and 8. For example, to delare a string type to represent strings
of 13 characters plus a null character, we could write:
The name used here for the string type is meant to convey the
fact that this type can be used for any string of 13 or less
characters - with space provided for the null character. Of course,
you can use any name for the type that you want but do try to
make it meaningful.
Whatever name you use, be sure to make the array big enough to
hold the largest string you expect to be handling. For example,
if you write code that allows customer names up to 50 characters,
you want a type defined as:
Also, don't forget that a type name does not set aside any memory.
To create space for customer names, you still need to declare
a variable of your new type as in:
C. The String Function Library
While C++ does not have a built-in string type, it has long been
standard to include a library of string functions. Most of these
are found in the file, string.h, which is included with
the <...> symbols in a program since it is a standard library
file as is 'iostream.h'. We will look at three of these functions
here. If you want others you should consult the manual for the
C++ compiler you are using.
First is the function strlen. It receives a string and returns the length of the string NOT including the null character. Thus, given the code:
the variable 'len' will have the value 13 after the function call
- assuming any of the above initializations of 'helloString'.
The second function is strcpy. This receives two strings and copies the contents of the second string into the first. One must be careful that the first string parameter represents enough memory to hold the second string including the null character. As an example of this function, consider the following code:
This will copy the "Howdy Pardner" stored in 'helloString'
into the string variable 'userName'. (So what if this is a very
strange name!).
The third function is strcmp. It receives two strings and
compares them using whatever character code system the computer
uses. If the two strings
are the same, the function returns 0. If the first string parameter
is 'less than' the second, the function returns an integer less
than 0. And, if the first string parameter is 'greater than' the
second, the function returns an integer greater than 0.
You might wonder how such string comparisons are made. There are two common character representation schemes used in computers - ASCII and EBCDIC. Both of these represent characters (alphabetic characters, numeric
characters, and symbols such as ',' and '$') with numeric codes
formed out of eight bits or one byte. Thus a string such as "Howdy
Pardner" is a set of such numeric codes. These codes are
carefully set up so that the code for 'A' is less than the code
for 'B' which is less than the code for 'C' etc. The algorithm
for comparing two strings (call them string1 and string2) starts
by looking at the numeric code for the first character in each
string. If the code for the first character in string1 is less
than the first character in string2 this implies that string1
comes before string2 alphabetically. (This is what is meant by
'less than' in the previous paragraph. Likewise, if the code for
the first character of string1 is greater than the code for the
first character of string2, this means string1 comes after string2
alphabetically.
Just as when humans are comparing two strings, if the codes for
the two first characters are not the same, the algorithm is finished.
But, if the two strings have the same first character and thus
have the same numeric codes for the first character, the algorithm
must proceed to compare the second characters etc. If the strings
have all the same characters, they will all have the same set
of codes and thus will be alphabetically equal.
There are some issues with this. First, ASCII and EBCDIC arrange
special characters and digits in different ways so the two systems will order strings that
have non-alphabetic characters in different ways. Second, we noted
earlier that the code for 'A' is different from the code for 'a'.
This is true for all the alphabetic characters. Thus, this algorithm
will conclude that the strings "PARDNER" and "pardner"
are different. Often programmers convert all string characters
to either upper or lower case before making comparisons if the
case of the characters is not significant.
To assist in this the standard C++ library includes the functions
tolower and toupper. Both of these receive a character
and return a character. The first one returns the lower case character
corresponding to the character received. In other words, if it
receives an 'A' or an 'a', it returns 'a'. The second function
does just the opposite. Both these functions do nothing if the
received character is a digit or a symbol.
D. Using the Null Character
Below is the code for a test program that calls a function "ConvertToUpper"
which receives a string and returns the same string with all its
characters in upper case. Notice how the test string consists
of uppercase characters, lower case characters, a digit, and a
representative set of symbols. A good test always tries to include
a set of cases that represents all possibilities.
// Test program to convert a whole string to upper case // File: Strcnvrt.cpp #includeThe for statement in "ConvertToUpper" walks through the string "s1" (really an array) converting each character and then moving the converted character to the second string, "s2". Notice the test condition in the for loop. "s1[index]" will have a value other than 0 until the end of the end of the string. Since '\0' is the character with the numeric code of 0, when the end of the string is encountered, the test goes FALSE and the loop stops. To emphasize the point: the test condition could also have been written as:#include typedef char string10[11]; void ConvertToUpper(string10 s1, string10 s2); void main() { string10 s1 = "hELlo 1,$"; string10 s2; ConvertToUpper(s1, s2); cout << "s1 = " << s1 << endl; cout << "s2 = " << s2; } void ConvertToUpper(string10 s1, string10 s2) { char ch; for (int index = 0; s1[index]; index++) { ch = toupper(s1[index]); s2[index] = ch; } s2[index] = '\0'; }
The last line of this function is very important. Since nothing
is done with the '\0' in "s1", its value is moved into
"s2" after the loop. Notice how the '\0' in "s1"
is used. Without it, this for statement might never end! Many
of the built-in string functions use a 'for' or 'while' statement
with the same test to determine when to finish. For that reason
you need to always make sure you leave room for '\0' in your strings
and add it when necessary. To demonstrate the need for this, comment
out the last line of "ConvertToUpper" and run the program.
If you are wondering about the use of string variables in the
output lines here, check the section of this chapter entitled
"String Input and Output" (section F below).
E. Strings Declared as Pointers
There is another way to declare string variables given that arrays
are so closely related to pointers. One can write:
You need to remember, however, that the code "char* myString" does not set aside memory for the string. It simply declares a pointer to the string. If you want a string of say size 50, you would write the code:
Only after you did this could you use myString, for example,
in a call to strcpy:
Note that when a string is passed to a function the memory should
already have been set aside by the calling side. Therefore, functions
such as strcpy or ConvertToUpper can be declared using character
pointers as parameters without requiring that memory be set aside
inside the function with the 'new' operator. It is the responsibility
of the code for the calling function to make sure enough memory
has been set aside for both parameters.
For example, one could rewrite the declaration for "ConvertToUpper"
as:
In this second example, one assumes any actual parameters passed
to this function will represent enough memory for whatever string
is to be converted.
F. String Input and Output
String output is quite simple and you have already been doing
it with code such as:
A string variable can be output in the same way. For example, to output "s1" and "s2" in the "Strcnvrt.cpp" program above we simply wrote:
This would have worked just as well if the string was declared as a pointer:
Both these forms work because, again, the "<<"
operator is looking for the '\0' symbol as an indicator that the
string has completed. Another example of the importance of this
symbol.
Input is a bit more complex. It is possible to use the ">>" operator as in:
We have not said much about 'cin' yet and we won't say much until the next chapter. What you do need to know for the moment is that C++ programmers like to consider 'cin' as a stream of data connecting a data source such as keyboard or file with a program. For us, then, 'cin' is the connection, the stream, connecting the keyboard with the program. If I have the code:
and I type a '3', that '3' enters the 'cin' stream' at the keyboard end and flows out at the program end to be processed by the ">>" operator and stored in the variable "num1". With "cin >> myString", the process is a bit more complex. The ">>" operator starts by skipping any leading blanks in the data coming down the stream. Once it sees the first non-blank character it starts passing characters into the string variable until another blank is encountered. Then it adds the null character to 'myString' and stops. In other words, if the code is:
and you type:
the characters 'H' 'e' 'l' 'l' 'o' will be stored in "myString"
along with a '\0'. See the picture below:

Notice that:
These characters after "Hello" (including the blank)
are actually still in the stream waiting to be processed by the
program's next request for input.
While this works, it does have two problems. First, if there are
more characters uninterrupted by a blank in the input stream than
there are elements in the string array, the ">>"
operator will continue placing characters passed the end of the
array. (We have already talked about the danger of overflowing
an array.)
Second, if we want a string variable to hold blanks, this method
won't work. For example, suppose we want a one string
variable to hold a first and last name such as "Curtis Sollohub".
If the user enters this name with a blank between the first and
last name, only the first name will be saved in the string using
the ">>" operator.
So far all our input has come through the ">>" operator but there are a number of functions we can use to input data. The stream "cin" is actually an instance of a standard C++ class called istream, and in the <iostream.h> header file there is a declaration:
as there is a declaration:
One of the member functions associated with the class "istream"
is named get. This function has many forms (like the constructors
we have seen) and one of those forms can be used for string input.
There are two required and one optional parameter with this member
function. (Optional parameters use the default value mechanism
discussed in chapter 10 section VI. C.) The first required
parameter is the string into which the data coming off the stream
should be read. The second is the maximum number of characters
to be read plus 1. (The "plus 1" is C++'s way
of reminding us that we need enough memory for the '\0'.)
Remember that a member function is called by writing a class instance, followed by a period, followed by the function name. Thus, the code below:
will read up to 50 characters and place them, along with a '\0'
in "myString". What the 'get' function does is start
reading immediately from the stream ('cin' in this case) without
skipping leading blanks. As called, this function continues to
read until it has read in 50 characters or until it sees a newline
character ('\n'). (These are placed on the stream by the computer
when we hit the "Enter" key.) Thus, if you type "Curtis
Sollohub" and then hit the "Enter" key, the computer
will store "Curtis Sollohub" in "myString".

Note that if the user types some blanks before the word "Curtis",
they will be included in the string. Notice also that if the user
types "Curtis" and then hits the "Enter" key
before typing "Sollohub, the string will only hold the letters,
'C' 'u' 'r' 't' 'i' 's' - along with the null character.
It is also important to understand that the "get" function
does not consume the newline character. It is still sitting on
the stream. In other words, if we again use the "get"
function, it will read in nothing because the first thing it will
encounter is the newline character left over from the last input
- even if we have typed more characters at the keyboard. To get
past this we can use a character-based version of the 'get' function
as in the following example:
name = new char[81]; // left over from punch card days
// when lines were often 81 characters long
char* address;
address = new char[81];
char dummy;
cout << "Please enter your name and hit the Enter
key.\n"
cin.get(name, 81);
cin.get(dummy);
cout << "Please enter your street address and hit
the Enter key.\n";
cin.get(address, 81);
The first call to 'get' will read in a name the user types. The
line "cin.get(dummy)" will read in the newline character
and store it in the variable dummy. The function needs a place
to put the character read in but the program doesn't do anything
with it so we can call the variable 'dummy'. The third call to
the function 'get' will read in an address.
This code does not have a second:
at the bottom. For this code fragment it is not necessary, but,
if the program was longer and included other input, it would be
safer to have gotten rid of the second newline right away.
As a review, remember that the compiler knows which version of
the overloaded function 'get' to use by looking at the number
and type of parameters involved in the call. In the file 'iostream.h'
'get' has been declared a number of times - once with one parameter,
a char type; and once with three parameters - a string type, an
int type, and a char type. The third parameter has a default value
of '\n' and therefore can be skipped in a call. It's purpose
is to indicate the character to look for to stop reading in characters.
See chapter 11 and the "Essentials of C++ for more information.