Lemur zaprasza
What You'll Learn Inside Memory Pointer Variables The Marriage of Arrays and Pointers Pointers to Characters Arrays of Pointers Pointers and C++ Type Safety Homework General Knowledge What's the Output? Find the Bug Write Code That. . . Extra Credit - 16 - Pointers Simplified constant pointer dereferencing operator expanded memory extended memory pointer What You'll Learn Inside memory Pointer variables The marriage of arrays and pointers Pointers to characters Arrays of pointers definition Pointer variables point to other variables. This unit takes you into the world of advanced Visual C++ programming by teaching you about pointer variables. Be warned: At first, pointer variables seem to add more work without offering any advantages. Be patient! Before this unit ends, you'll see how pointers let you write programs that manipulate string data in arrays. Until you learn about pointers, you can't keep track of string data using an array. Pointer variables are often just called pointers. Although pointers are considered advanced, the true power of Visual C++ doesn't appear until you master pointers. Many programming languages, such as COBOL, FORTRAN, and QBasic, don't support pointers. Those languages can't provide for the advanced data management that you'll read about in Lesson 9. Enough convincing! Let's get to the point. Inside Memory To understand pointers, you must understand computer memory. definition RAM stands for random access memory. The memory inside your PC, often called RAM, holds your program, data, and operating system. There are really two types of memory if you count the disk drive. The disk, however, is long-term memory. Your PC can't process any program or data on the disk until you or another program reads the disk contents into RAM. To keep things simple, RAM will be called memory throughout the rest of this book. ROM, a special kind of read-only memory, isn't discussed here. Your computer can process only data stored in memory. Therefore, your computer's memory is vital and closely tied to your computer's processor. Until now, you didn't need to know much about memory to run Visual C++ programs. However, a thorough understanding of pointers isn't possible until you really understand the makeup of your computer's memory storage. Figure 16.1 shows an overview of memory as viewed by Visual C++. When you run Windows or OS/2, you'll have to give up even more memory for your operating environment, but usually there's still enough memory left to write virtually any program you need. Figure 16.1. Your PC's memory layout. The Address column in Figure 16.1 is very important for this unit. Each memory location in your PC has a different address, just as your house has an address different from the rest of the world. The PC's addresses are sequential numbers that begin at 0 and increment at each memory location through the highest memory location. When you define a variable, Visual C++ finds the next empty address in the data area and sticks the variable at that location. Visual C++ keeps track, through an internal table, of where each variable resides. As you already know, Visual C++'s data types consume different amounts of memory, so variables are rarely stored in back-to-back addresses. Integers take two bytes, long integers take four, and so on. After the variable definitions main() { int i = 19; char c = 'X'; float f = 3.453; Visual C++ would store the data in memory to look something like the memory in Figure 16.2. Of course, the variables probably wouldn't be placed exactly at the addresses in the figure, but the important thing to note is that each variable starts at a given address and different data types take different amounts of memory. Figure 16.2. Three defined variables in memory. Aren't variable names great? Without variable names, you would have to remember the address of every data value your program worked with. Each memory location in your computer contains a specific address. When you define variables, Visual C++ finds unused memory locations and stores the variables there. When you refer to a variable by name, Visual C++ finds that variable's address and works with the value stored there. There is no Stop and Type section here due to this section's conceptual nature. Pointer Variables As with all other kinds of variables, you must define and initialize pointer variables before you can use them. A pointer variable is just thata variable. You already know that you must define all variables before you can use them, and pointers are no exception. When you define variables, the following things happen: You tell Visual C++ to find an empty place in memory. You tell Visual C++ what to call the variable. You might put something in the variable. You don't always put a value in every variable at the time you define the variable, but you can, as you already know. Nothing's new here so far. It's important to realize that when you request a pointer variable, you do exactly the same thing as when you request a nonpointer variable. If you were defining an integer variable, you'd do this: int i; // No initial value When you define pointer variables, you must tell Visual C++ that the variable is a pointer. Visual C++ knows that this variable named i is a simple integer variable. To define a pointer variable, you must include one extra symbol. Here's how you would define a pointer variable named iPtr: int * iPtr; // No initial value The * is not the multiplication operator. It's called a dereferencing operator when used in this context. Without the *, Visual C++ wouldn't know that iPtr is supposed to be a pointer variable. Without the *, Visual C++ would think that iPtr was another integer variable; therefore, you couldn't perform pointer access with iPtr. The spacing around the * isn't important. All of the following variable definitions are equivalent: int * iPtr; int *iPtr; int* iPtr; The * must be there when you define pointers just so that Visual C++ will know that the variable is a pointer and not a regular variable. The * is not part of the pointer variable's name. definition An overloaded operator performs two different operations depending on its context of use. The purpose of pointer variables is to point to other variables. When you define a pointer variable, you must tell Visual C++ what type of data the pointer will point to. In the previous definition, iPtr is a pointer to integer data. iPtr can't point to anything other than integers. If you wanted to define a pointer to a floating-point variable, you could do so like this: float * fPtr; // A pointer to a floating-point variable You can define pointer variables for all of Visual C++'s data types. In the next lesson, you'll learn how to define your own data types. Visual C++ even lets you define pointers to data types that you define. You can give pointer variables any name you want. Often, programmers develop special naming conventions for their pointer variables so that they can more easily distinguish between pointers and regular variables without having to refer often to the program's definition section. As with all variables, defining pointer variables is only half your job. You also must initialize pointer variables before you can do anything with them. When you define a pointer variable, initializing it with a value, the value you store in the pointer variable is always the address of another variable. Pointers don't hold data in the normal sense of the word. The data that a pointer variable holds is always an address. To store the address of another variable, you must learn about one additional operator. The address of operator, &, returns the address of (good name, huh?) other variables. After you define a pointer variable named fPtr, as done earlier, you can store the address of a floating-point variable named aFloat like this: fPtr = &aFloat; // Link the pointer to the floating-point variable The following code defines an integer variable named count. The value 135 is stored in count as count is defined. Immediately after that integer is defined, a pointer to an integer variable named iPtr is defined. The third line then stores the address of count in iPtr: int count = 135; int * iPtr; iPtr = &count; // Make iPtr point to count Figure 16.3 shows what the memory would look like after these three lines execute. This figure assumes that count appears at the address of 456212, but the address probably would be something else, of course, depending on the makeup of the PC's memory. The important thing to know is that whatever address count is stored at is assigned to iPtr. Figure 16.3. iPtr points to count. Notice that you don't use the * again when assigning the address of count to iPtr. The second line contains the * just to tell Visual C++ that iPtr is a pointer variable. If you want to, you can initialize the pointer variable at the time you define it. The following variable definitions are equivalent to the previous code: int count = 135; int * iPtr = &count; // Initialize iPtr and make iPtr point to count After you've defined a pointer, what can you do with it? Well, through the pointer, you can manipulate the variable being pointed to. Given the count and iPtr definitions just shown, the following statement stores a 7 in count: count = 7; That's no big deal. However, now that the iPtr pointer points to count, the following statement also puts a 7 in count: *iPtr = 7; // Stores a 7 in the variable pointed to Perhaps you can now see why the * operator, when used with pointers, is called the dereference operator. The pointer variable iPtr is dereferenced so that the address pointed to by iPtr gets the 7. Without the *, what would have happened? Visual C++ would think that you wanted to store the address 7 in iPtr! Always keep in mind that pointers hold only addresses. When you assign to a pointer, you will assign only addresses. However, when you assign to a dereferenced pointer, you assign data that you want to go in the pointer's pointed-to location. You've now seen three uses of the * operator: A multiplication operator, such as ans = p * u; A pointer definition operator, such as char * cPtr; A dereference operator, such as *cPtr = 'X'; The * is called the dereference operator both when you use it to define pointer variables and when you use it to store data in memory pointed to by the pointer. If you want to use the value pointed to by a pointer variable, you can. Given the previous variable definition, both of the following cout statements are equivalent: cout << count; // Print the value of count cout << *iPtr; // Print the value of count The first cout prints the contents of the variable count. The second cout prints the same value by dereferencing the iPtr variable. You can now define pointer variables and work with variables pointed to by pointer variables. Right now, you might wonder why there's any need to use pointer variables. After all, it's a lot easier to work directly with a specific variable by name than to dereference a pointer to that variable. You're right that variable names are easier to use than dereferenced variables. In the next lesson, however, you'll learn how to store data in unnamed memory locations! The only way to get to those locations is through pointer variables. Therefore, you should learn all about pointers now. In the next lesson, you'll be glad you did. Listing 16.1 contains a program that defines several variables and pointers to those variables. Different values are then printed using dereferenced pointer variables. Pointers point to other data values. Pointers always hold addresses of other variables, and you store only addresses in pointers. When you define a pointer, using *, you must also tell Visual C++ what data type that pointer variable will point to. You also use the * to store and retrieve data values in the memory pointed to by pointer variables. Input Listing 16.1. Working with pointers. 1:// Filename: DEFPNTS.CPP 2:// Defines several variables and pointers to those variables 3:#include <iostream.h> 4: 5:void main() 6:{ 7: int i1 = 14; // Define and initialize an integer 8: int* ip1; // Define a pointer to an integer 9: int i2 = 20; 10: int* ip2 = &i2; // Define and initialize the pointer 11: float f = 92.345; 12: float* fp = &f; 13: double d; 14: double* dp; 15: 16: ip1 = &i1; 17: 18: cout.precision(3); 19: cout.setf(ios::showpoint); 20: cout.setf(ios::fixed); 21: 22: cout << "i1 is " << i1 << " and *ip1 is also " 23: << *ip1 << endl; 24: 25: cout << "i2 is " << i2 << " and *ip2 is also " 26: << *ip2 << endl; 27: 28: cout << "f is " << f << " and *fp is also " 29: << *fp << endl; 30: 31: *fp = 1010.10; 32: 33: cout << "After changing f through fp, " << endl; 34: cout << "f is now " << f << " and *fp is also " 35: << *fp << endl; 36: 37: dp = &d; 38: *dp = 83949443.54333; // Change dp 39: 40: cout << "d is now " << d << " and *dp is also " 41: << *dp << endl; 42: return; 43:} OUTPUT i1 is 14 and *ip1 is also 14 i2 is 20 and *ip2 is also 20 f is 92.345 and *fp is also 92.345 After changing f through fp, f is now 1010.100 and *fp is also 1010.100 d is now 83949443.543 and *dp is also 83949443.543 ANALYSIS This program simply defines and assigns variables and pointers to those variables. In lines 7 and 8, integer i1 is defined and assigned the value 14, and the pointer to an integer named iPtr1 is defined. iPtr1 isn't assigned anything until later in the program. Line 9 defines another integer named i2 and stores 20 in the variable. Line 10 defines and links the iPtr2 pointer to i2. Lines 11 and 12 define and initialize a floating-point variable named f and a pointer to that floating-point variable named fPtr. Line 13 defines a double variable without initializing it. Line 14 defines a pointer that can point to a double, but no address is placed there yet. Line 16 links iPtr1 to the address of i1. Although this could be done in line 8, the initialization occurs here for your review. The couts that follow in lines 22 through 29 print both the regular variable and the regular variable's value using the pointer. Line 37 finally connects dPtr to d, and line 38 stores a value in d, indirectly, through a dereferenced dPtr. A subsequent cout prints the result. It wouldn't make a lot of sense to print the contents of a pointer variable unless you needed access to internal memory addresses because you were writing system-level programs such as operating system utilities. The actual address stored in a pointer isn't a meaningful number. Due to a sectioned addressing scheme that the PC uses, the address appears in hexadecimal. From one run of a program to another, the address might change as the program might be put into a different bit of memory by the operating system. The Marriage of Arrays and Pointers Arrays share common storage methods with pointers. As this section explains, an array is nothing more than a special kind of pointer. Often, Visual C++ programmers use array subscript notation and pointer notation interchangeably. This is possible because Visual C++ stores both arrays and pointers in the same way. After you master the similarities between arrays and pointers, you'll be able to understand how to represent an array of string values, something not possible in Visual C++ without pointers. Until now, an array was considered a list of values. That's still what an array is conceptually, but internally an array name is a pointer to the first element in the array. Such a pointer is a fixed pointer (called a constant pointer). Unlike with a pointer variable, you can't change the contents of an array name pointer. When you define an array such as this: int ages[] = {32, 45, 42, 19, 67}; Visual C++ really stores six values in memory. Visual C++ stores the five data values in the array, as you already know. It's important to realize that Visual C++ stores every array element in back-to-back memory. In other words, ages[0] always comes right before ages[1], ages[1] always comes right before ages[2], and so on. The sixth value that Visual C++ storesa value that you didn't need to know about until nowis the array name itself. The array name is a separate variable that is always a pointer to the array's first element. A picture is worth a thousand words (although I'll give you both!). Figure 16.4 shows how Visual C++ stores the ages array in memory. Figure 16.4. When Visual C++ stores an array, it stores the data and a pointer to the array. Suppose that you want to print the array's first element. The following cout does the trick: cout << "The first element is " << ages[0] << endl; The following cout also prints the array's first element: cout << "The first element is " << *ages << endl; If you want to change the contents of the array's first value, you can do so using either array or pointer notation. For example, you can do this: ages[0] = 24; // Store a new value in the first element This also does the same thing: *ages = 24; // Store a new value in the first element The array name is nothing more than a pointer. The only limitation is that you can't change where an array name points because its value is always a fixed constant. (Perhaps now you can see why you can't put an array name on the left side of an equal sign!) Given the pointer definition int i = 9; int j = 10; int *pt = &i; you can change where pt points: pt = & j; // pt now points to j You can never switch an array name to another value, however. Given the previous ages array definition, the following is not allowed: ages = &j; // Invalid! Arrays must keep pointing to their first value You can use the pointer notation to step into the array elements, accessing the remaining elements, without using the actual array notation. For example, you can print the contents of the ages array using the familiar subscript notation: cout << "ages[0] is " << ages[0] << endl; cout << "ages[1] is " << ages[1] << endl; cout << "ages[2] is " << ages[2] << endl; cout << "ages[3] is " << ages[3] << endl; cout << "ages[4] is " << ages[4] << endl; You also can print them by adding to and dereferencing a pointer: cout << "*(ages + 0) is " << *(ages + 0) << endl; cout << "*(ages + 1) is " << *(ages + 1) << endl; cout << "*(ages + 2) is " << *(ages + 2) << endl; cout << "*(ages + 3) is " << *(ages + 3) << endl; cout << "*(ages + 4) is " << *(ages + 4) << endl; The parentheses are important because the dereferencing operator, *, has higher precedence than the addition operator, +, as shown in Appendix C. To print the second array element, you want Visual C++ to first add one integer memory location to the address stored in ages before dereferencing that location to print its value. Of course, astute Visual C++ programmers would never print the five elements one at a time when a for loop does more work in less code: for (c = 0; c < 5; c++) { cout << "ages[c] is " << ages[c] << endl; } The same for loop will print the array using pointer notation also: for (c = 0; c < 5; c++) { cout << "*(ages + c) is " << *(ages + c) << endl; } The whole key to being able to point into an array is that all array elements are stored back-to-back with no padding between them. Even though most data types require more than one byte of memory, when you add one to a subscript or a pointer's value, Visual C++ adds one data type location and not just 1. ages[2] is two bytes away from ages[3]; therefore, *(ages + 2) is also two bytes away from *(ages + 3) even though they lie back-to-back in sequential array element locations. Listing 16.2 contains a program that defines a floating-point array and then uses lots of pointer notation to print various values from the array. You'll see that you can use some strange notation to get to individual array elements using pointers. If you keep in mind that a pointer contains an address, you'll be able to figure out how the program works. Keep in mind the following equivalent notations: ara[3], *(ara + 3), and *(ara + 1)[2] all reference the same value! Just do the math and then subscript accordingly from the answer, and you'll see for yourself. You can reference arrays as if they were pointers. However, you can't change where an array name points. An array name always points to the array's first element. Input Listing 16.2. Referencing an array through pointer notation. 1:// Filename: FLOTARPT.CPP 2:// Defines a floating-point array and then 3:// accesses elements from the array using 4:// pointer notation 5:#include <iostream.h> 6: 7:void main() 8:{ 9: float ara[6] = {11.1, 22.2, 33.3, 44.4, 55.5, 66.6}; 10: int ctr; // for-loop counter 11: 12: // First, print the array using subscripts 13: cout << "Here is the array using subscripts:" << endl; 14: cout.precision(1); 15: cout.setf(ios::showpoint); 16: cout.setf(ios::fixed); 17: for (ctr = 0; ctr < 6; ctr++) 18: { cout << ara[ctr] << ' '; } 19: 20: // Print the array using simple pointer notation 21: cout << endl << endl 22: << "Here is the array using pointers:" << endl; 23: for (ctr = 0; ctr < 6; ctr++) 24: { cout << *(ara + ctr) << ' '; } 25: 26: // You can even combine pointer and array notation! 27: cout << endl << endl 28: << "Here is the array using a combination:" << endl; 29: cout << (ara + 0)[0] << ' '; // ara[0] 30: cout << (ara + 1)[0] << ' '; // ara[1] 31: cout << (ara + 0)[2] << ' '; // ara[2] 32: cout << (ara + 0)[3] << ' '; // ara[3] 33: cout << (ara + 3)[1] << ' '; // ara[4] 34: cout << (ara + 2)[3] << ' '; // ara[5] 35: return; 36:} OUTPUT Here is the array using subscripts: 11.1 22.2 33.3 44.4 55.5 66.6 Here is the array using pointers: 11.1 22.2 33.3 44.4 55.5 66.6 Here is the array using a combination: 11.1 22.2 33.3 44.4 55.5 66.6 ANALYSIS Familiar subscript notation in line 17's for loop prints the contents of the floating-point array as you're used to. Then the fun begins. Lines 23 and 24 print the same array using pointer notation. Being able to reference the array as *(ara + 1) instead of ara[1] proves beyond a shadow of a doubt that an array is nothing more in Visual C++ than a pointer to the first element in the array. The expression *(ara + 1) tells Visual C++ to add one floating-point memory location (which takes four bytes) to the address stored in ara. That references the array's second element, which the * then dereferences to print the value stored there. This program uses some strange notation to print the array in lines 29 through 34, but as you can see from the output, the array elements appear as if you used subscripts to print them. An array name is nothing more than an address, so you can add to that name, as in ara + 2, and subscript from there instead of from the start of the array (ara) like this: (ara + 2)[1]. To bring the expression back down to simple subscript notation, just do the math. In other words, the expression (ara + 2)[1] references the same element as ara[3] (2 plus 1 is 3). Likewise, an expression such as (ara - 25)[27] references nothing more than ara[2] (-25 plus 27 is 2). Although this program doesn't change any of the array elements using pointer notation, it could do so. All of the following assignments store 8.9 in ara[4]: ara[4] = 8.9; *(ara + 4) = 8.9; (ara + 4)[0] = 8.9; (ara + 2)[2] = 8.9; (ara + 3)[1] = 8.9; You'll never use the dereference operator and a subscript at the same time. *(ara + 4), ara[4], (ara + 4)[0], and (ara + 1)[3] all refer to ara[4], but notice that the * is never used when a subscript in brackets is used also. The subscript is a sort of dereference too. Master pointer notation now because, in the next lesson, you'll be defining data that has no variable or array name. Sometimes, the only way to access data is to use pointer notation. Pointers to Characters Using pointers to characters, you can assign string literals directly to variables. Never before could you assign a string literal to a variable in Visual C++. Before this unit, the only variables you knew of that held strings were character arrays, and an array name can never appear on the left side of an equal sign. In Visual C++ terminology, an array name isn't an lvalue (left value) and therefore can't be changed. You learned in this unit that an array name is a constant. Thus, you see why an array name can't appear on the left side of an equal sign. A pointer variable, however, doesn't have that limitation. Pointers are variables, and variables can appear on the left side of equal signs. Consider the following character array definition: char name[] = "I like Visual C++!"; Nothing is new here. A pointer constant, name, points to the array's first value, I. Now consider how the following variable definition differs from the preceding one: char * nameP = "I like Visual C++!"; In this case, nameP is also a pointer that points to the first letter, I. The difference is that nameP is a variable. Therefore, if you want to change the string pointed to by nameP, you can do so like this: nameP = "I love Visual C++!"; To change the name array, you have two choices: Use strcpy(): strcpy(name, "I love Visual C++!"; Change the array one character at a time: name[3] = 'o'; // Change "like" to "love" name[4] = 'v'; name[5] = 'e'; You got lucky here, because only three letters needed to be changed. Usually, it's not so easy because you have to change many more over several assignment statements just to put a new string value in an array. Changing arrays is tedious. Changing strings pointed to by pointer variables is extremely easy with a simple assignment statement. Keep in mind that you assigned the address of a string into the nameP variable; you did not overwrite the storage that was pointed at. As long as the character pointer's string ends with a terminator, all the string functions work for both arrays and character data pointed to with pointers. You can use strcpy() to make pointer variables point to new strings with some limitations, as described in next. An array is nothing more than a pointer, and you print strings in character arrays. The following cout prints the contents of nameP: cout << nameP; Visual C++ always replaces string literals in memory with the address where they're stored. In other words, the assignment nameP = "I love Visual C++!"; stores a null-terminated I love Visual C++! in memory and then assigns that string literal's address to nameP. Some warnings are in order before you go any further. Only string literals work in assignments such as the ones just shown. If you use any of the array-changing functions, such as get() or strcpy(), you must treat the character pointer as if it is an array because those functions all assume that they're working with an array. Consider what happens if you define a character pointer like this: char *myName; And then you assign it a string literal like this: myName = "Sam"; Visual C++ finds the address of Sam and stores that address in myName. Everything is fine. However, what if, instead of using that assignment, you initialized myName like this: char * myName; cin.get(myName, 20); // Oops! You must use some value for the maximum length of the string. The 20 used here was arbitrarily chosen but no value will work properly. The user might enter a long name or a short name, but whatever she enters, something bad will probably happen! get() knows that you might pass it a character array that lives in a fixed place in memory. Therefore, get() knows that it should never change the address of myName. Even though, in this case, myName happens to be a character pointer, Visual C++ doesn't bother about the difference between arrays and pointers. Therefore, Visual C++ stores the user's name wherever myName points. myName could have anything in it, even an address that's pointing to part of the operating system. Remember that Visual C++ doesn't initialize variables for you, so the definition char * myName; simply reserves a character pointer that points to an unknown location. If you want to use a character pointer inside character array functions, the easiest way is to reserve a character array and assign a character pointer to the first element of that array: char largeArray[101]; // Can hold a string as long as 100 bytes char *cPtr = largeArray; // cPtr now points to the // array's first element You can now store strings up to 100 bytes long in cPtr using get() and strcpy() because the data will go to the memory already reserved for the array. Listing 16.3 contains a program that demonstrates what you can and can't do with pointers to characters. Remember that none of the string functions changes its argument's address because the functions might be working with arrays. You can directly assign string literals to character pointers. You can also use character pointers in all string functions as long as the character pointer is already pointing to a string long enough to hold the result. Input Listing 16.3. Using character pointers and arrays. 1:// Filename: CHARFUNS.CPP 2:// Uses character arrays and character pointers 3:#include <iostream.h> 4:#include <string.h> 5: 6:void main() 7:{ 8: char c; 9: 10: char * cpt0; // A stand-alone character pointer 11: char * cpt5 = "abcd"; // Points to 5 bytes 12: char * cpt12 = "Programming"; // Points to 12 bytes 13: char ara27[27]; // Points to 27 bytes 14: char * cpt27 = ara27; // Points to 27 bytes 15: 16: cpt0 = "cpt0 is pointing to this string"; 17: cout << cpt0; // No problem 18: cpt0 = "A new string for cpt0"; // Still no problem 19: cout << endl << cpt0 << endl; 20: // You couldn't 21: // strcpy(cpt0, "This is a string for cpt0 that is too long") 22: // though! 23: 24: cout << "Please type your name (up to 12 characters): "; 25: cin.get(cpt12, 12); // Okay because of get() 12-char limit 26: cout << "You typed " << cpt12 << endl << endl; 27: 28: // cin.get(cpt5, 12) wouldn't work either because 29: // all characters after the fifth one would overwrite 30: // memory not pointed to by cpt5 31: 32: // Fill the 27-character array 33: for (c = 'A'; c <= 'Z'; c++) 34: { ara27[c - 65] = c; } // ASCII A is equivalent to decimal 65 35: ara27[26] = '\0'; 36: 37: strcpy(cpt27, ara27); // Okay because they point to 38: // the same number of bytes 39: // strcpy(cpt12, ara27) would NOT be okay 40: cout << "cpt27 contains: " << cpt27; 41: return; 42:} OUTPUT cpt0 is pointing to this string A new string for cpt0 Please type your name (up to 12 characters): Derek Prince You typed Derek Prince cpt27 contains: ABCDEFGHIJKLMNOPQRSTUVWXYZ ANALYSIS The numbers at the end of this program's character pointers indicate the maximum number of bytes each character pointer can point to. Remember that once you assign a string literal to a character pointer, or once you make a character pointer point to the starting location of a character array, you can never make the pointer point to more bytes than it already points to, except by assigning a string literal to the pointer. Lines 16 and 18 show an uninitialized character pointer being assigned two different string literals. Everything works fine. Note the comment starting on line 21. Although you can assign a string literal of any length to cpt0, you can't use strcpy() to assign a string longer than what cpt0 already holds at the time. If you do, Visual C++ stores the longer string directly over the existing one, overwriting other data or code that might appear behind the existing string. It does not produce an error or warning while doing so either. It's okay to use get() to enter strings into character pointers as done on line 25 because get() ignores any characters longer than get()'s second argument. cpt12 already points to a string 12 bytes long, so there's no problem using get() to get a string up to 12 bytes long. The for loop on lines 33 and 34 stores the letters A through Z in ara27, adding the terminator at the end of the letters to turn the data into a 26-byte string. Notice how the character variable c is used as the character data (holding A, B, and so on), as the loop counter, and as part of line 34's subscript expression. Visual C++ lets you interchange char data and int expressions to create such a loop. Line 37's strcpy() has no problem copying the array to the character pointer because both happen to be pointing to data that's the same length. You also could use strcpy() to assign an array to a pointer that's pointing to a longer string, but not to a shorter string. Arrays of Pointers When you need lots of pointers, define an array of pointers. You can define an array that holds any data type, including pointers. When you want a list of pointers, you'll want to use an array to hold that list of pointers. Figure 16.5 gives you a general idea of what such an array looks like. A pointer contains addresses to other memory locations. When you have an array of pointers, you have an array that acts like the one in Figure 16.5. Figure 16.5. You can define an array of pointers! Figure 16.5 shows an array of nine integer pointers. The array is named intPtrAra, and each element in the array is a pointer to an integer somewhere else in memory. The arrows indicate that each element holds an address of another value somewhere. Defining such an array is easy. As long as you keep in mind the difference between a nonpointer definition and a pointer definition, you can remember how to define an array of pointers. You define a nonarray integer like this: int i; You define a pointer to an integer like this: int * iPtr; You define an array of nine integers like this: int iAra[9]; Finally, you define an array of nine pointers to integers like this: int * intPtrAra[9]; // Defines an array of 9 pointers to integers Reading all variable definitions from right to left makes understanding them easier. The preceding definition defines an array of nine elements. That array is named intPtrAra. The array contains pointers (indicated by the *). The pointers can point only to integers. To write advanced programs, you'll need to be able to define several pointer variables at once. Although you could define several nonarray pointers with different names such as int *iPtr1, *iPtr2, *iPtr3, *iPtr4, *iPtr5, *iPtr6, *iPtr7, *iPtr8, *iPtr9; you already know that an array gives you much more power than separately named variables because you can use a for loop to step through an array. We'll return to numeric pointers in the next lesson. For now, it's important to learn how to store an array of pointers to characters. There's really nothing different about pointers to characters, except that you can now hold an array of strings! (Well, you can simulate holding an array of strings.) Study the following definition. See whether you can figure out what's being defined (remember to read from right to left): char * cities[5]; This definition builds a memory layout similar to that in Figure 16.5, with these two exceptions: Only five elements are defined. Each element points to character data. Here is how you could initialize such an array at the same time that you define it: char * cities[5] = {"San Diego", "Miami", "New York", "Oklahoma City", "St. Louis"}; Remember that Visual C++ treats all string literals as an address. In other words, Visual C++ stores the five city names somewhere in free memory. Visual C++ then assigns the address of each of those string literals to each element of the cities array. The end result is that each element in cities points to one of the cities. The array named cities doesn't end in a null zero because the array holds pointers, not strings. However, the strings pointed to by each element in cities do end in null zeros, as all strings do. Figure 16.6 shows what the cities array looks like in memory. Each element, cities[0] through cities[4], holds an address to a city name. You can print each of the names using the %s format code. Figure 16.6. The cities array. Listing 16.4 defines the array of pointers to five cities and prints the cities on-screen. An array of character pointers lets you work with lists of strings as if those strings were stored inside the array. Input Listing 16.4. Storing and printing data in an array of character pointers. 1:// Filename: CITYNAME.CPP 2:// Stores and prints a list of city names 3:#include <iostream.h> 4: 5:void main() 6:{ 7: int ctr; 8: 9: char * cities[5] = {"San Diego", "Miami", "New York", 10: "Oklahoma City", "St. Louis"}; 11: 12: // Print the cities 13: // Anywhere a character array can appear, so can the 14: // elements from the cities array of pointers 15: cout << "Here are the stored cities:" << endl; 16: for (ctr = 0; ctr < 5; ctr++) 17: { cout << cities[ctr] << endl; } 18: cout << endl; 19: 20: // Change the cities with literals 21: // These assignments store the address of 22: // the string literals in the elements 23: cities[0] = "Tulsa"; 24: cities[1] = "Boston"; 25: cities[2] = "Indianapolis"; 26: cities[3] = "Las Vegas"; 27: cities[4] = "Dallas"; 28: 29: // Print the cities again using pointer notation 30: cout << endl << "After changing the pointers:" << endl; 31: for (ctr = 0; ctr < 5; ctr++) 32: { cout << *(cities + ctr) << endl ; } 33: cout << endl; 34: 35: return; 36:} OUTPUT Here are the stored cities: San Diego Miami New York Oklahoma City St. Louis After changing the pointers: Tulsa Boston Indianapolis Las Vegas Dallas ANALYSIS After assigning pointers to the city names in lines 9 and 10, the for loop in line 16 prints the five cities using a subscript notation. Lines 23 through 27 then assign new strings to the array. In reality, the addresses of the new strings are assigned to each array element. A strcpy() function wouldn't work reliably in place of the assignment if the new string literal were longer than the string being pointed to at the time by each element. Notice that the for loop body in line 32 prints the names of the five new cities using pointer dereferencing instead of subscript notation. Pointers and C++ Type Safety The types of pointers have to match the data that they point to and other pointers in assignments. A pointer to a float can't be used to access an integer value. There is a very practical reason for this restriction. Different types take up different amounts of storage, and if pointers were mixed up, the data underneath would get confused. To avoid this, C++ insists that pointers to different types are not interchangeable, even though the pointers themselves all hold memory addresses. There are a couple of exceptions to the rule. First, you can have a special pointer type of void *. A void * counts in bytes of memory. This means a pointer to an undefined type. You can't do much with such a pointer, but it is a useful representation for when you need to mess around with some memory whose type you have not yet determined. You can always assign into a void*, but you cannot assign a void* into any other type. Second, there is the C++ casting mechanism. By casting a pointer, you can assign it into a different type. Given the declarations int i[5] = {0x1,0x22,0x333,0x4444,1000}; long l[5] = {0x1,0x22,0x333,0x4444,1000}; char c[5] = {'1','2','3','4','5'}; this is what a memory printout might look like. (The source is on the disk as SHOWHEX.CPP if you want to look at this further. However, it is a little confusing because it uses quite a bit of fiddling to get to the data character by character.) Array of ints [ 0] 01 [ 1] 00 [ 2] 22 [ 3] 00 [ 4] 33 [ 5] 03 [ 6] 44 [ 7] 44 [ 8] e8 [ 9] 03 Array of longs [ 0] 01 [ 1] 00 [ 2] 00 [ 3] 00 [ 4] 22 [ 5] 00 [ 6] 00 [ 7] 00 [ 8] 33 [ 9] 03 [10] 00 [11] 00 [12] 44 [13] 44 [14] 00 [15] 00 [16] e8 [17] 03 [18] 00 [19] 00 Array of chars [ 0] 31 [ 1] 32 [ 2] 33 [ 3] 34 [ 4] 35 Look carefully at the data. You can see that an int is two characters long, but the long values are four characters long. Can you see that long longValue = ((long*)i)[0]; produces the hexadecimal value 0x00220001, not 1, the value of i[0]? It has read the first element of a long array as the first two values of the int array. The correct way to read that very tricky assignment expression is "cast the pointer i to a long pointer before using it to get the first (long) element pointed at." Operator precedence means that without the extra parentheses, C++ would interpret the subscript before the cast. You can also see that character data has a very different value from the number it represents. Homework General Knowledge What is an address? What is a pointer? What is the & operator called? What does a pointer variable hold? What are the three uses of *? What is * called when it's used with pointer variables? What do arrays have in common with pointers? Assume that iPtr is a pointer to an integer and that integers take two bytes in Visual C++. How many bytes does Visual C++ really add to iPtr in the following assignment? Hint: Remember that pointer arithmetic adds enough for each data type when you increment a pointer. iPtr += 2; Which of the following are equivalent, assuming that iAry is an integer array and that iPtr is an integer pointer pointing to the start of the array? A. iAry and iPtr B. iAry[3] and iPtr + 3 C. iAry[2] and *iPtr + 2 D. *iAry and *iPtr[0] E. iAry[5] and (iPtr + 4)[1] How does Visual C++ simulate holding arrays of string data? Explain what kind of data the following definition defines: float * measures[250]; Given the following floating-point array and pointer to a float definition float fAra[] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8}; float * fPtr1, *fPtr2; which of the following assignments are allowed? A. iPtr1 = fAra; B. iPtr2 = iPtr1 = &fAra[2]; C. fAra = 19.45; D. *(iPtr2 + 2) = 19.45; True or false: Two or more memory locations can have the same address. True or false: A pointer variable can point to floating-point values only. True or false: An array name is nothing more than a pointer variable. What's the Output? What is in i after the following executes? int i = 18; int *ip = &i; *ip = 99; Describe what is in ip after the preceding code executes. Find the Bug Given the variable definitions int i; long int j; int * ip1; int * ip2; why will the following not work? ip1 = &i; ip2 = &j; Given the array definition int num[5] = {1, 2, 3, 4, 5}; why doesn't the following cout produce a 3? cout << (*num + 2); // Tries to print num[2] Write Code That. . . Write two couts that print the first value in a double array named values. Use subscript notation for the first cout and use pointer notation for the second. Write a program that stores the names of your all-time favorite movies in an array of character pointers. Print the names of the movies. Write a program that stores your favorite temperature for each of the four seasons in an array of floating-point pointers. Print the array forward, and then backward, using only pointer notation. Extra Credit Pretend that you're a teacher who just gave 15 students a hard pop quiz. Write a program that defines an array of 15 pointers to float values that you initialize when you define the array. Print the values for your grade sheet. Search the array for the highest score and the lowest score and print both. Step through the array again and compute the average test score. Use only pointer notation. |