Running Your First Program

Chapter 10 - Using Pointers

Visual C++ 6: The Complete Reference
Chris H. Pappas and William H. Murray, III
  Copyright 1998 The McGraw-Hill Companies

Pointers and Arrays—A Closer Look
The following sections include many example programs that deal with the topic of arrays and how they relate to pointers.
Strings (Arrays of Type char)
Many string operations in C/C++ are generally performed by using pointers and pointer arithmetic to reference character array elements. This is because character arrays or strings tend to be accessed in a strictly sequential manner. Remember, all strings in C/C++ are terminated by a null (\0). The following C++ program is a modification of a program used earlier in this chapter to print palindromes and illustrates the use of pointers with character arrays:
//
//  chrary.cpp
//  A C++ program that prints a character array backwards
//  using a character pointer and the decrement operator
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>
#include <string.h>

void main( )
{
 char pszpalindrome[]="POOR DAN IN A DROOP";
 char
*pc;
 
 pc=pszpalindrome+(strlen(pszpalindrome)-1);
 do {
   cout <<
*pc ;
   pc—;
 } while (pc >= pszpalindrome);
}
After the program declares and initializes the pszpalindrome palindrome, it creates a pc of type char *. Remember that the name of an array is in itself an address variable. The body of the program begins by setting the pc to the address of the last character in the array. This requires a call to the function strlen( ), which calculates the length of the character array.
  Note The strlen( ) function counts just the number of characters, excluding the null terminator \0.
You were probably thinking that was the reason for subtracting the 1 from the function’s returned value. This is not exactly true; the program has to take into consideration the fact that the first array character’s address is at offset zero. Therefore, you want to increment the pointer variable’s offset address to one less than the number of valid characters.
Once the pointer for the last valid array character has been calculated, the do-while loop is entered. The loop simply uses the pointer variable to point to the memory location of the character to be printed and prints it. It then calculates the next character’s memory location and compares this value with the starting address of pszpalindrome. As long as the calculated value is >=, the loop iterates.
Arrays of Pointers
In C and C++, you are not restricted to making simple arrays and simple pointers.You can combine the two into a very useful construct—arrays of pointers. An array of pointers is an array whose elements are pointers to other objects. Those objects can themselves be pointers. This means you can have an array of pointers that point to other pointers.
The concept of an array of pointers to pointers is used extensively in the argc and argv command-line arguments for main( ) you were introduced to in Chapter 8. The following program finds the largest or smallest value entered on the command line. Command-line arguments can include numbers only, or they may be prefaced by a command selecting a choice for the smallest value entered (-s,-S) or the largest (-l,-L).
//
//  argcgv.cpp
//  A C++ program using an array of pointers to process
//  the command-line arguments argc, argv
//  Copyright (c) Chris H. Pappas and William H. Murray, 1998
//

#include <iostream.h>
#include <process.h>     // exit( )
#include <stdlib.h>      // atoi( )

#define IFIND_LARGEST 1
#define IFIND_SMALLEST 0

int main(int argc,char
*argv[])
{
 char
*psz;
 int ihow_many;
 int iwhich_extreme=0;
 int irange_boundary=32767;

 if(argc < 2) {
   cout << “\nYou need to enter an -S,-s,-L,-l”
        << “ and at least one integer value”;
   exit(1);
 }
 while(—argc > 0 && (*++argv)[0] == ‘-’) {
   for(psz=argv[0]+1;
*psz != ‘\0’; psz++) {
     switch(
*psz) {
       case ‘s’:
       case ‘S’:
         iwhich_extreme=IFIND_SMALLEST;
         irange_boundary=32767;
         break;
       case ‘l’:
       case ‘L’:
         iwhich_extreme=IFIND_LARGEST;
         irange_boundary=0;
         break;
       default:
         cout << “unknown argument ”<<
*psz << endl;
         exit(1);
     }
   }
 }

 if(argc==0) {
   cout << “Please enter at least one number\n”;
   exit(1);
 }
 ihow_many=argc;

 while(argc—) {
   int present_value;
   present_value=atoi(
*(argv++));
   if(iwhich_extreme==IFIND_LARGEST && present_value >
      irange_boundary)
     irange_boundary=present_value;
   if(iwhich_extreme==IFIND_SMALLEST && present_value <
      irange_boundary)
     irange_boundary=present_value;
 }

 cout << “The ”;
 cout << ((iwhich_extreme) ? “largest” : “smallest”);
 cout << “ of the ” << ihow_many << “ value(s) input is ” <<
         irange_boundary << endl;

 return(0);
}
Before looking at the source code, take a moment to familiarize yourself with the possible command combinations that can be used to invoke the program. The following list illustrates the possible command combinations:
argcgv
argcgv 98
argcgv 98 21
argcgv -s 98
argcgv -S 98 21
argcgv -l 14
argcgv -L 14 67
Looking at the main( ) program, you will see the formal parameters argc and argv that you were introduced to in Chapter 8. To review, argc is an integer value containing the number of separate items, or arguments, that appeared on the command line. The variable argv refers to an array of pointers to character strings.
  Note argv is not a constant. It is a variable whose value can be altered, a key point to remember when viewing how argv is used below. The first element of the array, argv[0], is a pointer to a string of characters that contains the program name.
Moving down the code to the first if statement, you find a test to determine if the value of argc is less than 2. If this test evaluates to TRUE, it means that the user has typed just the name of the program argcgv without any switches. Since this action would indicate that the user does not know the switch and value options, the program will prompt the user at this point with the valid options and then exit( ).
The while loop test condition evaluates from left to right, beginning with the decrement of argc. If argc is still greater than zero, the right side of the logical expression will be examined.
The right side of the logical expression first increments the array pointer argv past the first pointer entry (++argv), skipping the program’s name, so that it now points to the second array entry. Once the pointer has been incremented, it is then used to point (*++argv) to the zeroth offset ((*++argv)[0]) of the first character of the string pointed to. Obtaining this character, if it is a - symbol, the program diagnoses that the second program command was a possible switch—for example, -s or -L.
The for loop initialization begins by taking the current pointer address of argv, which was just incremented in the line above to point to the second pointer in the array. Since argv‘s second element is a pointer to a character string, the pointer can be subscripted (argv[0]). The complete expression, argv[0]+1, points to the second character of the second string pointed to by the current address stored in argv. This second character is the one past the command switch symbol -. Once the program calculates this character’s address, it stores it in the variable psz. The for loop repeats while the character pointed to by *psz is not the null terminator \0.
The program continues by analyzing the switch to see if the user wants to obtain the smallest or largest of the values entered. Based on the switch, the appropriate constant is assigned to the iwhich_extreme. Each case statement also takes care of initializing the variable irange_boundary to an appropriate value for the comparisons that follow. Should the user enter an unrecognized switch—for example, -d—the default case will take care of printing an appropriate message.
The second if statement now checks to see if argc has been decremented to zero. An appropriate message is printed if the switches have been examined on the command line and there are no values left to process. If so, the program terminates with an exit code of decimal 1.
A successful skipping of this if test means there are now values from the command line that need to be examined. Since the program will now decrement argc, the variable ihow_many is assigned argc‘s current value.
The while loop continues while there are at least two values to compare. The while loop needs to be entered only if there is more than one value to be compared, since the cout statement following the while loop is capable of handling a command line with a single value.
The function atoi( ) converts each of the remaining arguments into an integer and stores the result in the variable present_value. Remember, argv++ needed to be incremented first so that it points to the first value to be compared. Also, the while loop test condition had already decremented the pointer to make certain the loop wasn’t entered with only a single command value.
The last two if statements take care of updating the variable irange_boundary based on the user’s desire to find either the smallest or largest of all values entered. Finally, the results of the program are printed by using an interesting combination of string literals and the conditional operator.
More on Pointers to Pointers
The next program demonstrates the use of pointer variables that point to other pointers. It is included at this point in the chapter instead of in the section describing pointers to pointers because the program uses dynamic memory allocation. You may want to refer back to the general discussion of pointers to pointers before looking at the program.
/*
*   dblptr.c
*   A C program using pointer variables with double
*   indirection
*   Copyright (c) Chris H. Pappas and William H. Murray, 1998
*/

#include <stdio.h>
#include <stdlib.h>

#define IMAXELEMENTS 3

void voutput(int
**ppiresult_a, int **ppiresult_b,
            int
**ppiresult_c);
void vassign(int
*pivirtual_array[],int *pinewblock);

void main( )
{
 int
**ppiresult_a, **ppiresult_b, **ppiresult_c;
 int
*pivirtual_array[IMAXELEMENTS];
 int
*pinewblock, *pioldblock;
 ppiresult_a=&pivirtual_array[0];
 ppiresult_b=&pivirtual_array[1];
 ppiresult_c=&pivirtual_array[2];

 pinewblock=(int
*)malloc(IMAXELEMENTS * sizeof(int));
 pioldblock=pinewblock;

 vassign(pivirtual_array,pinewblock);

 
**ppiresult_a=7;
 
**ppiresult_b=10;
 
**ppiresult_c=15;

 voutput(ppiresult_a,ppiresult_b,ppiresult_c);

 pinewblock=(int
*)malloc(IMAXELEMENTS * sizeof(int));

 
*pinewblock=**ppiresult_a;
 
*(pinewblock+1)=**ppiresult_b;
 
*(pinewblock+2)=**ppiresult_c;

 free(pioldblock);

 vassign(pivirtual_array,pinewblock);

 voutput(ppiresult_a,ppiresult_b,ppiresult_c);
}
void vassign(int *pivirtual_array[],int *pinewblock)
{
 pivirtual_array[0]=pinewblock;
 pivirtual_array[1]=pinewblock+1;
 pivirtual_array[2]=pinewblock+2;
}

void voutput(int
**ppiresult_a, int **ppiresult_b, int
 
           **ppiresult_c)
{
 printf(“%d\n”,
**ppiresult_a);
 printf(“%d\n”,
**ppiresult_b);
 printf(“%d\n”,
**ppiresult_c);
}
The program is designed so that it highlights the concept of a pointer variable (ppiresult_a, ppiresult_b, and ppiresult_c), pointing to a constant address (&pivirtual_array[0], &pivirtual_array[1], and &pivirtual_array[2]), whose pointer address contents can dynamically change.
Look at the data declarations in main( ). ppiresult_a, ppiresult_b, and ppiresult_c have been defined as pointers to pointers that point to integers. Let’s take this slowly, looking at the various syntax combinations:
ppiresult_a
*ppiresult_a
**ppiresult_a
The first syntax references the address stored in the pointer variable ppiresult_a. The second syntax references the pointer address pointed to by the address in ppiresult_a. The last syntax references the integer that is pointed to by the pointer address pointed to by ppiresult_a. Make certain you do not proceed any further until you understand these three different references.
The three variables ppiresult_a, ppiresult_b, and ppiresult_c have all been defined as pointers to pointers that point to integers int **. The variable pivirtual_array has been defined to be an array of integer pointers int *, of size IMAXELEMENTS. The last two variables, pinewblock and pioldblock, are similar to the variable pivirtual_array, except they are single variables that point to integers int *. Figure 10-17 shows what these seven variables look like after their storage has been allocated and, in particular, after ppiresult_a, ppiresult_b, and ppiresult_c have been assigned the address of their respective elements in the pivirtual_array.
Figure 10-17: The variables after ppiresult_a, ppiresult_b, and ppiresult_c get their initial addresses
It is this array that is going to hold the addresses of the dynamically changing memory cell addresses. Something similar actually happens in a true multitasking environment. Your program thinks it has the actual physical address of a variable stored in memory, when what it really has is a fixed address to an array of pointers that in turn point to the current physical address of the data item in memory. When the multitasking environment needs to conserve memory by moving your data objects, it simply moves their storage locations and updates the array of pointers. The variables in your program, however, are still pointing to the same physical address, albeit not the physical address of the data but of the array of pointers.
To understand how this operates, pay particular attention to the fact that the physical addresses stored in the pointer variables ppiresult_a, ppiresult_b, and ppiresult_c never change once they are assigned.
Figure 10-18 illustrates what has happened to the variables after the dynamic array pinewblock has been allocated and pioldblock has been initialized to the same address of the new array. Most important, notice how the physical addresses of pinewblock‘s individual elements have been assigned to their respective counterparts in pivirtual_array.
Figure 10-18: Dynamically creating the block of memory
The pointer assignments were all accomplished by the vassign( ) function. vassign( ) was passed the pivirtual_array (call-by-value) and the address of the recently allocated dynamic memory block in the variable pinewblock. The function takes care of assigning the addresses of the dynamically allocated memory cells to each element of the pivirtual_array. Since the array was passed call-by-value, the changes are effective in the main( ).
At this point, if you were to use the debugger to print out ppiresult_a, you would see ACC8 (the address of pivirtual_array‘s first element), and *ppiresult_a would print 1630 (or the contents of the address pointed to). You would encounter a similar dump for the other two pointer variables, ppiresult_b and ppiresult_c.
Figure 10-19 shows the assignment of three integer values to the physical memory locations. Notice the syntax to accomplish this:
Figure 10-19: Filling the memory block with data
**ppiresult_a=7;
**ppiresult_b=10;
**ppiresult_c=15;
At this point, the program prints out the values 7, 10, and 15 by calling the function voutput( ). Notice that the function has been defined as receiving three int ** variables. Notice that the actual parameter list does not need to precede the variables with the double indirection operator ** since that is their type by declaration.
As shown in Figure 10-20, the situation has become very interesting. A new block of dynamic memory has been allocated with the malloc( ) function, with its new physical memory address stored in the pointer variable pinewblock. pioldblock still points to the previously allocated block of dynamic memory. Using the incomplete analogy to a multitasking environment, the figure would illustrate the operating system’s desire to physically move the data objects’ memory locations.
Figure 10-20: Dynamically allocating and filling the second block of memory
Figure 10-20 also shows that the data objects themselves were copied into the new memory locations. The program accomplished this with the following three lines of code:
*pinewblock=**ppiresult_a;
*(pinewblock+1)=**ppiresult_b;
*(pinewblock+2)=**ppiresult_c;
Since the pointer variable pinewblock holds the address to the first element of the dynamic block, its address is dereferenced (*), pointing to the memory cell itself, and the 7 is stored there. Using a little pointer arithmetic, the other two memory cells are accessed by incrementing the pointer. The parentheses were necessary so that the pointer address was incremented before the dereference operator * was applied.
Figure 10-21 shows what happens when the function free( ) is called and the function vassign( ) is called to link the new physical address of the dynamically allocated memory block to the pivirtual_array pointer address elements.
Figure 10-21: What happens after updating pivirtual_array with the new physical addresses
The most important fact to notice in this last figure is that the actual physical address of the three pointer variables ppiresult_a, ppiresult_b, and ppiresult_c has not changed. Therefore, when the program prints the values pointed to **ppiresult_a and so on, you still see the values 7, 10, and 15, even though their physical location in memory has changed.
Arrays of String Pointers
One of the easiest ways to keep track of an array of strings is to define an array of pointers to strings. This is much simpler than defining a two-dimensional array of characters. The following program uses an array of string pointers to keep track of three function error messages.
/*
*   aofptr.c
*   A C program that demonstrates how to define and use
*   arrays of pointers.
*   Copyright Chris H. Pappas and William H. Murray, 1998
*/

#include <ctype.h>
#include <stdio.h>

#define INUMBER_OF_ERRORS 3

char
*pszarray[INUMBER_OF_ERRORS] =
         {
           “\nFile not available.\n”,
           “\nNot an alpha character.\n”,
           “\nValue not between 1 and 10.\n”
         };
FILE *fopen_a_file(char *psz);
char cget_a_char(void);
int iget_an_integer(void);

FILE
*pfa_file;

void main( )
{
 char cvalue;
 int ivalue;

 fopen_a_file(“input.dat”);
 cvalue = cget_a_char( );
 ivalue = iget_an_integer( );

}

FILE
*fopen_a_file(char *psz)
{
 const ifopen_a_file_error = 0;

 pfa_file = fopen(psz,"r");
 if(!pfa_file)
   printf(“%s”,pszarray[ifopen_a_file_error]);
 return(pfa_file);
}

char cget_a_char(void)
{
 char cvalue;
 const icget_a_char_error = 1;

 printf(“\nEnter a character: ”);
 scanf(“%c”,&cvalue);
 if(!isalpha(cvalue))
   printf(“%s”,pszarray[icget_a_char_error]);
 return(cvalue);
}
int iget_an_integer(void)
{
 int ivalue;
 const iiget_an_integer = 2;
 printf(“\nEnter an integer between 1 and 10: ”);
 scanf(“%d”,&ivalue);
 if( (ivalue < 1) || (ivalue > 10) )
   printf(“%s”,pszarray[iiget_an_integer]);
 return(ivalue);
}
The pszarray is initialized outside all function declarations. This gives it a global lifetime. For large programs, an array of this nature could be saved in a separate source file dedicated to maintaining all error message control. Notice that each function, fopen_a_file( ), cget_a_char( ), and iget_an_integer( ), takes care of defining its own constant index into the array. This combination of an error message array and unique function index makes for a very modular solution to error exception handling. If a project requires the creation of a new function, the new piece of code selects a vacant index value and adds one error condition to pszarray. The efficiency of this approach allows each code segment to quickly update the entire application to its peculiar I/O requirements without having to worry about an elaborate error detection/alert mechanism.

Books24x7.com, Inc 2000 –  


Visual C++ 6(c) The Complete Reference
Visual Studio 6: The Complete Reference
ISBN: B00007FYGA
EAN: N/A
Year: 1998
Pages: 207

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net