Referring to Your Data through Pointers - Introducing C++ - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book I

Introducing C++

Chapter 6: Referring to Your Data through Pointers

In This Chapter

Using two types of memory: the stack and heap

Accessing variable addresses through pointers

Creating variables on the heap by using the new keyword

Taking pointers as parameters and returning pointers

Modifying variables the easy way

Where do you live? Don’t say it out loud because thousands of people are reading this book, and you don’t want them all to know. So just think about your address. Most places have some sort of address so the mail service will know where to deliver your packages and the cable guy can show up sometime between now and 5:00 next Thursday. (So make sure that you’re there.)

Other things have addresses too. For example, a big corporation in an office building likely has all its cubes numbered. And offices in buildings usually have numbers; and apartments normally have numbers, too.

Now suppose someone named Sam works in office number 180. Last week, however, Sam got booted out the door for spending too much time surfing the Web. Now Sally gets first dibs on office number 180, even though she’s not taking over Sam’s position. Sam moved out; Sally moved in. Same office — different person staying there.

The computer’s memory works similarly. Every little part of the computer’s memory is associated with a number that represents its location, or address. In this chapter, we show you that after you determine the address of a variable stored in memory, you can do powerful things with it, which gives you the tools to create powerful programs.

imageIf any single topic in C++ programming is most important, it is the notion of pointers. Therefore, if you want to become a millionaire, read this chapter. Okay, so it may not make you a millionaire, but suggesting it could give you the incentive to master this chapter. Then you can become an ace programmer and make lots of money.

Heaping and Stacking the Variables

C++ programs use two kinds of memory: heap and stack. The heap is a common area of memory that your program allocates — that is, sets aside — for the different functions in your program to use. Global variables go in this heap.

Whenever your program calls a function, however, the function gets its own little private area of memory storage in an area of memory known as a stack. The reason that this is called a stack is because it’s treated like a stack of papers: You can put something on the stack, and you can take something off, but you can’t put anything in the middle or take anything from the middle. The computer uses this stack to keep track of all your function calls.

For example, suppose you have a function called GoFishing. The function GoFishing calls StopAndBuyBait, which then calls PayForBait, which calls GetOutCreditCard, which calls UseFakeCreditCard. How can the computer keep track of all this mess? It uses the stack metaphor. First it saves the original function, GoFishing. Then when that function calls StopAndBuyBait, the computer remembers that function by putting it on top of GoFishing — not in the same storage bin, but in one on top of the preceding item so that the preceding item is still there. Then, when that function calls PayForBait, the computer once again remembers that function by putting it on top of StopAndBuyBait, and so on, until it has all the items piled one on top of the other, with UseFakeCreditCard on the top andGoFishing on the bottom. This process pushes items onto the top of the stack.

Next, when the computer is finished with UseFakeCreditCard, it pops off the top of the stack. What it picks up is the place it left off before calling UseFakeCreditCard, which happens to be GetOutCreditCard. And when that function is finished, once again the computer pops the top off the stack to find PayForBait. And, as before, that’s where it left off last. It continues this until it gets all the way back to the beginning, which was GoFishing.

Every position in memory has a number associated with it. When your program starts, the computer sets aside a large chunk of memory and then works closely with the microprocessor itself to assign a bunch of numbers to the memory. Your program’s variables and your program’s code goes in this memory. And consider this: If your program sits in memory, each function sits in a particular place in memory, a place with a number or address associated with it. In other words, each function has an address.

image Each function and each variable in your program has a place where it resides in memory. That place has a number associated with it. Therefore, each function and each variable has an address.

Placing a hex on C++

Sooner or later in your computer programming, you will encounter a strange way of notating numbers on the computer. This strange way is called hexadecimal, or sometimes just hex. In C++, you can recognize a hex number because it starts with the characters 0x. These characters aren’t actually part of the number; they just notate it in the same way double quotes denote a string. Whereas our usual decimal numbers consist of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9, a hex number consists of these digits plus six more: A, B, C, D, E, and F. That makes a total of 16 digits. (Yes, we know, the letters A through F are not digits. But in hex, they are considered digits.) A good way to picture counting with regular decimal numbers is by using the odometer in a car, which (if you’re honest) only goes forward, not backward. It starts out with 00000000 (assuming eight digits, which is a lot). The rightmost digit runs from 0 through 9, over and over. When any digit reaches 9 and all digits to the right of that are nine, the next digit to the left goes up by 1. For example, when you reach 00000999, the next digit to the left goes up by 1 as each 9 goes back to 0, to get 00001000.

With hex numbers, you count this same way, except instead of stopping at 9 to loop back, you then go to A, then B, and then up to F. And then you loop back. So the first 17 hex numbers are, using eight digits, 00000000, 00000001, 00000002, 00000003, 00000004, 00000005, 00000006, 00000007, 00000008, 00000009, 0000000A, 0000000B, 0000000C, 0000000D, 0000000E, 0000000F, 00000010. Notice when we hit F toward the end there, we wrapped around again, adding 1 to the next digit to the left. When working with hex numbers, you may see such numbers as 0xAAAA0000 and 0X0000A3FF. (We included the 0x for C++ notation.) And incidentally, 1 more than each of these is 0xAAAA0001 and 0x0000A400.

The stack where the computer keeps track of the function calls is just a bunch of memory, too. What the computer considers the top of the stack is really the next position in memory. And the way the computer puts a function on the stack is by putting on the stack the address of where the computer left off in the preceding function.

When the computer calls one of your functions, not only does it save the address of the return location on the stack, it also reserves some space on the stack for your local variables.

This means that your variables can live in two places:

Heap: The heap is a common area in memory where you can store global variables.

Stack: The stack is the area where the computer stores both the information about the functions being called and the local variables for those functions.

image A stack is an example of all sorts of wonderful things called data structures. Computer programmers have a tendency to try to model things from real life on the computer. A stack of papers apparently wasn’t good enough for the computer folk; they wanted to be able to do the same type of thing with their data in the computer, and they called this thing a stack. They have come up with many types of data structures, including a similar one called a queue: With a queue, you put data in one end and take it out the other. It’s like putting sheets of paper on top but taking them only from the bottom. You experience a queue when you wait in line at a store. The people are forming a queue, and some even call a line of people a queue.

imageEvery hex number has a decimal equivalent. When you make a list showing decimal numbers side by side with hex numbers, you see, for example, that 0x0000001F is next to the decimal number 31. Thus, these two numbers represent the same quantity of items, such as apples. Remember that when you want to buy some apples: “I would like to buy one-ef apples.”

image Looks can be deceiving. The hex number 10 represents the same number of apples as the decimal number 16. That’s why it’s a good idea to use the 0x notation. Thus instead of hex 10, we would write 0x10, making it clear that we’re not talking about a decimal number.

Converting between hexadecimal and decimal

If you want to convert between hex and decimal, you can use the calculator program that comes with Windows. However, you need to make sure it’s running in scientific mode. To turn on this mode, choose View⇒Scientific (if it’s not already chosen). When you do, you will see the calculator magically transform into a much bigger, more powerful calculator.

To convert a hex number to decimal, click the Hex option in the upper left. Then type the hex number by using the number keys and the letters A through F, such as FB1263. (You don’t need to type the zeros at the beginning, such as 00FB1263 — they won’t show up — nor do you type the 0x used in C++.) After you finish typing it all in, click the Dec option, which is next to the Hex option. The calculator instantly transforms this beautiful hex thing into an equally beautiful thing — a decimal number! In this case, you see 16454243. You can go the other way, too: If you have a decimal number, such as 16454243, you can click the Hex option to convert it to hex. If you convert 16454243 to hex, you get back FB1263, which is what you started with.

You can convert words, too (if you’re bored). The hex number and disco group ABBA is 43962 in decimal. And the hex number FADE is 64222. And Jeff’s house, which he calls hex number FACADE, is 16435934. Have fun!

imageYou can represent hex numbers by using either uppercase or lowercase letters. However, do not mix cases within a single number. Don’t use 0xABab0000. Instead use either 0xabab0000 or 0xABAB0000.

Getting a variable’s address

Because every variable lives somewhere in memory, every variable has an address. If you have a function that declares an integer variable called NumberOfPotholes, then when your program calls this function, the computer will allocate space for NumberOfPotholes somewhere in memory.

If you want to take the address of (which is computerspeak for find the address of) the variable NumberOfPotholes, you simply throw an ampersand, &, in front of it.

Listing 6-1 shows an example of taking the address of a variable and printing it.

Listing 6-1: Using the & Character to Take the Address of a Variable

#include <iostream>

using namespace std;

int main()

{

int NumberOfPotholes = 532587;

cout << &NumberOfPotholes << endl;

return 0;

}

When you run this program, a hexadecimal number appears on the console. This may or may not match ours, and it may or may not be the same each time you run the program. The result depends on exactly how the computer allocated your variable for you and the order in which it did things. This could be very different between versions of compilers. When we run Listing 6-1, we see

0x22ff74

image The output you see from this program is the address of the variable called NumberOfPotholes. In other words, that number is the hex version of the place where the NumberOfPotholes variable is stored in memory. The output is not the contents of the variable or the contents of the variable converted to hex; rather, it’s the address of the variable converted to hex.

A pointer example

Suppose NumberOfPotholes contains the number 5000. That means the computer stores the number 5000 somewhere in memory. When you take the address of NumberOf Potholes, you are taking the address of the memory where you can find the number 5000.

And so, when you set

ptr = &NumberOfPotholes;

ptr points to a memory location that contains the number 5000.

That output is not very useful, unless you want to sound like a computer techie. You could walk around announcing that the variable lives at 0x22ff74, but that’s not going to get you very far in life. (It may get you some interesting looks, though, which may be worth it.) But when you take that address, you can use it for other purposes. For example, you can use it to modify the variable itself by using what are called pointer variables. A pointer variable is just like any other variable except that it stores the address of another variable.

To declare a pointer variable, you need to specify the type of variable it will point to. Then you precede the variable’s name with an asterisk, as in the following:

int *ptr;

This declares a variable that points to an integer. In other words, it can contain the address of an integer variable. And how do you grab the address of an integer variable? Easy! By using the & notation! Thus, you can do something like this:

ptr = &NumberOfPotholes;

This puts the address of the variable NumberOfPotholes in the ptr variable. Remember that ptr doesn’t hold the number of potholes; rather, it holds the address of the variable called NumberofPotholes.

imageYou specify the type of a pointer by the type of item it points to. If a pointer variable points to an integer, its type is pointer to integer. In C++ notation, its type is int * (with a space between them) or int* (no space); you are allowed to enter it with or without a space. If a pointer variable points to a string, then its type is pointer to string, and notation for this type is string *.

image The ptr variable holds an address, but what’s at that address? That address is the location in memory of the storage bin known as NumberOfPotholes. Right at that spot in memory is the data stored in NumberOfPotholes.

imageThink this pointer concept through carefully. If you have to, read this section over a few times until it’s in your head. Then meditate on it. Wake up in the night thinking about it. Call strangers on the telephone and chitchat about it. But the more you understand pointers, the better off your programming career will be — and the more likely you will make a million dollars.

Changing a variable by using a pointer

After you have a pointer variable holding another variable’s address, you can use the pointer to access the information in the other variable. That means that we have two ways to get to the information in a variable: We can use the variable name itself (such as NumberOfPotholes) or we can use the pointer variable that points to it.

If we want to store the number 6087 in NumberOfPotholes, we could do this:

NumberOfPotholes = 6087;

Or we could use the pointer. To use the pointer, we first declare it as follows.

ptr = &NumberOfPotholes;

Then, to change NumberOfPotholes, we don’t just assign a value to it. Instead, we throw an asterisk in front of it, like so:

*ptr = 6087;

If ptr points to NumberOfPotholes, these two lines of code will have the same effect: Both will change the value to 6087. This process of sticking the asterisk before a pointer variable is called dereferencing the pointer. By the time you’re finished with this book, you will know gobs of words that nobody else does. (And your newly enriched vocabulary makes talking on the telephone difficult at first.)

Take a look at Listing 6-2, which demonstrates all this.

Listing 6-2: Modifying the Original Variable with a Pointer Variable

#include <iostream>

using namespace std;

int main()

{

int NumberOfPotholes;

int *ptr;

ptr = &NumberOfPotholes;

*ptr = 6087;

cout << NumberOfPotholes << endl;

return 0;

}

In Listing 6-2, the first line declares an integer variable, while the second line declares a pointer to an integer. The next line takes the address of the integer variable and stores it in the pointer. Then the fourth line modifies the original integer by dereferencing the pointer. And just to make sure that the process really worked, the next line prints the value of NumberOfPotholes. When you run the program, you will see the following output:

6087

This is correct; it is the value that the program stored in the original variable by using the pointer variable.

You can also read value of the original variable through the pointer. Take a look at Listing 6-3. This code accesses the value of NumberOfPotholes through the pointer variable, ptr. When the code gets the value, it saves it in another variable called SaveForLater.

Listing 6-3: Accessing a Value through a Pointer

#include <iostream>

using namespace std;

int main()

{

int NumberOfPotholes;

int *ptr = &NumberOfPotholes;

int SaveForLater;

*ptr = 6087;

SaveForLater = *ptr;

cout << SaveForLater << endl;

*ptr = 7000;

cout << *ptr << endl;

cout << SaveForLater << endl;

return 0;

}

When you run this program, you see the following output.

6087

7000

6087

Notice also in this listing that we changed the value through ptr again, this time to 7000. When we run the program, you can see that the value did indeed change, but the value in SaveForLater remained the same. That’s because SaveForLater is a separate variable and is not connected to the other two. The other two, however, are connected to each other.

Pointing at a string

Pointer variables enjoy pointing. Pointer variables can point to any type, including strings. However, after you say that a variable points to a certain type, it can only point to that type. That is, like any variable, you cannot change its type out from underneath it. The compiler won’t let you do it.

To create a pointer to a string, you simply make the type of the variable string *. You can then set it equal to the address of a string variable. Listing 6-4 demonstrates this.

Listing 6-4: Pointing to a String with Pointers

#include <iostream>

using namespace std;

int main()

{

string GoodMovie;

string *ptrToString;

GoodMovie = “Best in Show”;

ptrToString = &GoodMovie;

cout << *ptrToString << endl;

return 0;

}

In Listing 6-4, you can see that the pointer variable called ptrToString points to the variable called GoodMovie. But when you want to use the pointer to access the string itself, you need to dereference the pointer by putting an asterisk, *, in front of it.

When you run this code, you see the results of the dereferenced pointer, which is the value of the GoodMovie variable:

Best in Show

You can change the value of the string through the pointer, again by dereferencing it, as in the following code:

*ptrToString = “Galaxy Quest”;

cout << GoodMovie << endl;

Here, we dereferenced the pointer to set it equal to the string “Galaxy Quest” (a fine movie, we might add). Then to show that it really changed, we printed the variable itself, GoodMovie. The result of this code, when added at the end of Listing 6-4 (but prior to the return 0) is

Galaxy Quest

You can also use the pointer to access the individual parts of the string, as we did in Listing 6-5.

Listing 6-5: Using Pointers to Point to a String

#include <iostream>

using namespace std;

int main()

{

string HorribleMovie;

string *ptrToString;

HorribleMovie = “L.A. Confidential”;

ptrToString = &HorribleMovie;

for (unsigned i = 0; i < HorribleMovie.length(); i++)

{

cout << (*ptrToString)[i] << “ “;

}

cout << endl;

return 0;

}

When you run this program, you see the letters of the terrible movie appear one with spaces between them, as in the following.

L . A . C o n f i d e n t i a l

Okay, so we didn’t like L.A. Confidential. But it won two Oscars and was nominated for seven more, and it won a boatload of other awards, so we don’t feel so bad saying so.

image When you access the characters of the string through a pointer, you need to put parentheses around the asterisk and the pointer variable. Otherwise, the compiler gets confused and first tries to do the index in brackets with the variable name and afterwards applies the asterisk. That’s backwards, and it won’t make sense to the computer, so the compiler gives you an error message. But you can make it all better by using parentheses, as we did in Listing 6-5.

This program loops through the entire string, character by character. We used the length function for the string to find out how many characters are in the string. And inside the loop we grabbed the individual characters of the string, printing them with a space after each.

Notice that i is of type unsigned, rather than int. The length function returns an unsigned value, rather than an int value. If you try to use an int for i, the compiler will display the following warning:

warning: comparison between signed and unsigned integer

It’s important to use the correct data types for loop variables. Otherwise, when the loop value increases over the amount that the loop variable can support, the application will fail. Trying to find such an error can prove frustrating even for the best developers.

imageYou can also change the individual characters in a string through a pointer. You can do this by using a line like (*ptrToString)[5] = ‘X’;. Notice, as before, that we had to put parentheses around the variable name along with the dereferencing (that is, the asterisk) character.

imageThe length of a string is also available through the pointer. You can call the length function by dereferencing the pointer, again with the carefully placed parentheses, such as in the following:

for (unsigned i = 0; i < (*ptrToString).length(); i++)

{

cout << (*ptrToString)[i] << “ “;

}

Pointing to something else

When you create a pointer variable, you must specify what type of data it points to. After that, you cannot change the type of data it points to, but you can change what it points to. For example, if you have a pointer to an integer, you make it point to the integer variable calledExpensiveComputer. Then, later, in the same program, you can make it point to the integer variable called CheapComputer. We demonstrate this in Listing 6-6.

Listing 6-6: Using Pointers to Point to Something Else and Back Again

#include <iostream>

using namespace std;

int main()

{

int ExpensiveComputer;

int CheapComputer;

int *ptrToComp;

ptrToComp = &ExpensiveComputer;

*ptrToComp = 2000;

cout << *ptrToComp << endl;

ptrToComp = &CheapComputer;

*ptrToComp = 500;

This code starts out by initializing all the goodies involved — two integers and a pointer to an integer.

Next, the code points the pointer to ExpensiveComputer and uses the pointer to put something inside ExpensiveComputer. It then writes the contents of ExpensiveComputer, again by using the pointer.

Then the code changes what the pointer points to. To do this, we set the pointer to the address of a different variable, &CheapComputers. Pretty simple. And the next line stores 500 in whatever the pointer points to. But that’s CheapComputers. And again we print it.

Now just to drive the point home in case the computer isn’t listening, we then point the pointer back to the original variable, ExpensiveComputer. But we don’t store anything in it. This time we just print what’s already inside this high-powered supermachine. We do this again by dereferencing the pointer. And when we run the program, we see that ExpensiveComputer still has 2000 in it, which is what we originally put in it. That means that after we pointed the pointer to something else and did some finagling, the original variable remained unchanged. That’s a good thing, considering that nobody was pointing at it and it was just being left alone, totally ignored in a world all by itself, feeling neglected.

imageBe careful if you use one pointer to bounce around several different variables. It’s easy to lose track of which variable the pointer is pointing to.

Tips on pointer variables

Here are some pretty good tips on using pointer variables.

imageYou can declare two pointer variables of the same type by putting them together in a single statement, as you can with regular variables. However, you must precede each one with an asterisk, as in the following line.

int *ptrOne, *ptrTwo;

image If you try to declare multiple pointers on a single line but put an asterisk only before the first pointer, only that one will be a pointer. The rest will not be. This can cause serious headaches and muscle spasms later because this line will compile fine. The following line is just such an example:

int *ptrOne, Confused;

Here, Confused is not a pointer to an integer; rather, it’s just an integer. So beware!

imageSome people like to put the asterisk right after the type, as in the following, to emphasize the fact that the type is pointer to integer.

int* ptrOne;

However, we prefer not to do that simply because it makes it easy for a forgetful persons like ourselves to not remember that any variables that follow, separated by a comma, need their own asterisks if they are to be pointers.

imageWhen we declare a pointer variable, we usually start its name with the letters ptr, which is an abbreviation for pointer. That way, we immediately know (when we’re looking at our code) that it’s a pointer variable. That makes life a little easier sometimes, at least in the sanity areas of life.

Dynamically Allocating with new

The heap is a special place where you can declare storage. However, to use this storage, you take a different approach from just declaring a variable.

image When you create a variable, you go through the process of actually typing a variable, giving it a type, a name, and (sooner or later) a value. When you write the code, that’s when you decide that you want a variable. However, you can also write code that can cause the computer to allocate space only after it’s running. The computer allocates this space on the heap. This process is called dynamic allocation.

Using new

To declare a storage bin on the heap, first you need to set up a variable that will help you keep track of the storage bin. This variable must be a pointer variable.

For example, suppose you already have an integer declared out on the heap somewhere. (We show you how to do that in the next paragraph.) We won’t give it a name, because such variables don’t have names. Just think of it as an integer on the heap. Then, with the integer variable, you could have a second variable. This second variable is not on the heap, and it’s a pointer holding the address of the integer variable. So if you want to access the integer variable, you do so by dereferencing the pointer variable.

To allocate memory on the heap, you need to do two things: First, declare a pointer variable. Second, call a function called new. The new function is a little different from other functions in that you don’t put parentheses around its parameter. For this reason, it’s actually considered to be an operator. Other operators are + and - for adding and subtracting integers. These other operators behave similar to functions, but you don’t use parentheses.

To use the new function, you specify the type of variable you want to create. For example, the following line creates a new integer variable:

int *somewhere = new int;

After the computer creates the new integer variable on the heap, it stores the address of the integer variable in the somewhere variable. And that makes sense: The somewhere variable is a pointer to an integer. Thus, it holds the address of an integer variable. Listing 6-7 demonstrates this.

Listing 6-7: Allocating Memory by Using new

#include <iostream>

using namespace std;

int main()

{

int *ptr = new int;

*ptr = 10;

cout << *ptr << endl;

return 0;

}

When you run this program, you see the sweet and simple output:

10

In this program, we first allocated a pointer variable, which we called ptr. Then we called new with an int type, which returns a pointer to an integer. We saved that return value in the ptr variable.

Then we started doing our magic on it. Okay, so it’s not all that magical, but we saved a 10 in the thing that ptr points to. And then we printed the value stored in the thing that ptr points to.

But what exactly is the thing that ptr points to, and why does it fancy itself so important as to justify italics? It’s the memory that was allocated by the new operator. Think of it as a variable out there somewhere. But unlike regular variables, this variable doesn’t have a name. And because it doesn’t have a name, the only way you can access it is through the pointer. It’s kind of like an anonymous author with a publicist. If you want to send fan mail to the author, you have to go through the publicist. Here, the only way to reach this unnamed but famous variable is through the pointer.

But this doesn’t mean that the variable has a secret name such as BlueCheese and that, if you dig deep enough, you might discover it; it just means that the variable has no name. Sorry.

image When you call new, you get back a pointer. This pointer is of the type that you specify in your call to new. You can then store the pointer only in a pointer variable of the same type.

imageWhen you use the new operator, the usual terminology is that you are allocating memory on the heap.

Now at this point, you may be asking the all-important question: Why? Why would we go through the trouble of creating an integer variable somewhere out on the heap, a variable that has no name, if we just have to create a second variable to point to it? Doesn’t that seem counterproductive?

The answer is this: You can take advantage of many features if you allocate your variables on the heap. You can use pointers along with something called an array. An array is simply a large storage bin that has multiple slots, each of which holds one item. And if you set up an array that holds pointers, you can store away all these pointers without having to name them individually. And these pointers can point to complex things, called objects. (We cover objects in Minibook I, Chapter 7, and arrays in Minibook I, Chapter 8.) And then if you want to, for example, pass all these variables (which could be quite large, if they’re strings) to a function, you need to pass only the array, not the strings themselves. That step saves memory on the stack.

In addition to objects and arrays, you can also have a function create and return a variable. Then, when you get the variable back from the function, you can use it, and when you are finished with the variable, delete it. Finally, you can pass a pointer into a function. When you do so, the function can actually modify the pointer for you. See “Passing Pointer Variables to Functions” and “Returning Pointer Variables from Functions,” later in this chapter.

Using an initializer

When you call new, you can provide an initial value for the memory you are allocating. For example, if you are allocating a new integer, you can, in one swoop, also store the number 10 in the integer.

Listing 6-8 demonstrates this.

Listing 6-8: Putting a Value in Parentheses to Initialize Memory That You Allocate

#include <iostream>

using namespace std;

int main()

{

int *ptr = new int(10);

cout << *ptr << endl;

return 0;

}

In this code, we called new, but we also put a number in parentheses. That number will get put in the memory initially. This line of code is equivalent to the following two lines of code:

int *ptr = new int;

*ptr = 10;

image When you initialize a value in the new operator, the technical phrase for what you are doing is invoking a constructor. The reason is that the compiler adds a bunch of code to your program, code that operates behind the scenes. This code is called the runtime library. The library includes a function that initializes an integer variable if you pass an initial value. The function that does this is known as a constructor. When you run it, you are invoking it. Thus, you are invoking the constructor. For more information on constructors, see Minibook I, Chapter 7.

Making new strings

You can use new to allocate almost any type, including strings. You simply type new followed by string.

image You cannot allocate one special type with new. If a function has no return, you specify the return type as void. You cannot use new to allocate a void type. For that matter, you also cannot create a variable of type void. The compiler won’t let you do it.

Listing 6-9 is an example of calling new for a string. As usual, remember the include line for <string>.

Listing 6-9: Using the new Operator with Strings

#include <iostream>

using namespace std;

int main()

{

string *Password = new string;

*Password = “The egg salad is not fresh.”;

cout << *Password << endl;

return 0;

}

This code allocates a new string by using the new keyword and saves the results in the Password variable. Next, it stores an interesting commentary in the newly allocated string by dereferencing the pointer. Finally, it prints the commentary, again by dereferencing the pointer. Remember, the string variable itself is off in the heap somewhere and has no name. And if it’s going to make comments like those heard at a fine restaurant, it’s probably best that it remain nameless.

image When you store a string of characters in a string variable that you allocated by using new, you are storing the string in the allocated memory, not in the pointer variable. The pointer variable still holds the address of the allocated memory. The pointer is just the publicist for the memory, handling all its deals and transactions for it, whether ethical or not.

imageWhen you are working with strings, you can use a shortcut to the somewhat cumbersome method of putting parentheses around the name preceded by an asterisk in order to call the various string functions. (That was even hard to type!) Instead of typing(*Password).length(), for example, you can use a shortcut notation that looks like the following line of code. (The characters after Password are a minus sign and then a greater than sign, which together resemble an arrow.)

cout << Password->length() << endl;

imageYou can initialize a string by using parentheses when you call new for a string type. To do this, simply put the string in quotes and then in parentheses after the word string, as in the following line of code:

string *Password = new string(“The egg salad is still not fresh.”);

This line of code is equivalent to the first two lines of code inside main in Listing 6-9, shown previously.

image Even though the pointer points to a string, the pointer itself still holds a number (in particular, the address of the string it’s pointing to). This is a number, but do not confuse it with an integer. However, you can do some basic arithmetic with pointers, as detailed in Minibook I, Chapter 8.

Freeing Pointers

When you allocate memory on the heap by calling the new function and you’re finished using the memory, you need to let the computer know, whether it’s just a little bit of memory or a lot. The computer doesn’t look ahead into your code to find out if you’re still going to use the memory. So in your code, when you are finished with the memory, you free the memory.

The way you free the memory is by calling the delete function and passing the name of the pointer:

delete MyPointer;

This line would appear after you’re finished using a pointer that you allocated by using new. (Like the new operator, delete is also an operator and does not require parentheses around the parameter.)

Listing 6-10 shows a complete example that allocates a pointer, uses it, and then frees it.

Listing 6-10: Using delete to Clean Up Your Pointers

#include <iostream>

using namespace std;

int main()

{

string *phrase = new string(“All presidents are cool!!!”);

cout << *phrase << endl;

(*phrase)[20] = ‘r’;

phrase->replace(22, 4, “oked”);

cout << *phrase << endl;

delete phrase;

return 0;

}

When you run this program, you see the following output:

All presidents are cool!!!

All presidents are crooked

In this code, we first allocated a new string and initialized it, saving its address in the pointer variable called phrase. Then we wrote the phrase, manipulated it (providing some editorial content), and then wrote it again. Finally, we freed the memory used by the phrase.

imageAlthough people usually say that you’re deleting the pointer or freeing the pointer, really you’re freeing the memory that the pointer points to. The pointer can still be used for subsequent new operations. Nevertheless, we will abide by tradition and use these phrases.

imageYou can actually get away with not freeing your pointers because the computer frees all the memory used by your program when it ends. That way, your memory is available to all the other cool programs you want to run. However, getting into the habit of freeing your pointers when you are finished using them is a good practice; otherwise, you may use all the memory allotted for the heap while your program is running. And some big software systems at big companies run on and on, shutting down maybe once a week or every two weeks. If one part of the program continues to refuse to free its data, eventually the heap probably fills and the whole program shuts down.

image If you free a pointer, the memory it points to is now free. However, immediately after the call to delete, the pointer still points to that particular memory location, even though it’s no longer being used. Therefore, do not try to use the pointer after that until you set it to point to something else through a call to new or by setting it to another variable.

Whenever you free a pointer, a good habit is to set the pointer to the value 0. (Some people set it to the value null, but that’s the same thing, and 0 is guaranteed to work on all compilers.) Then, whenever you use a pointer, first check whether it’s equal to 0 and only use it if it’s not 0. This always works because the computer will never allocate memory for you at address 0. So the number 0 can be reserved to mean I point to nothing at all.

The following code sample shows this. First, this code frees the pointer and then clears it by setting it to 0:

delete ptrToSomething;

ptrToSomething = 0;

This code checks if the pointer is not 0 before using it:

ptrToComp = new int;

*ptrToComp = 10;

if (ptrToComp != 0)

{

cout << *ptrToComp << endl;

}

image Only call delete on memory that you allocated by using new. Although the free compiler that ships with this book doesn’t seem to complain when you delete a pointer that points to a regular variable, it serves no purpose to do so. You can free only memory on the heap, not local variables on the stack.

Passing Pointer Variables to Functions

One of the most important uses for pointers is this: If they point to a variable, you can pass the pointer to a function, and the function can modify the original variable. This lets you write functions that can actually modify the variables passed to them.

Changing variable values with pointers

Normally, when you call a function and you pass a few variables to the function, the computer just grabs the values out of the variables and passes those values. Take a close look at Listing 6-11.

Listing 6-11: A Function Cannot Change the Original Variables Passed into It

#include <iostream>

using namespace std;

void ChangesAreGood(int myparam)

{

myparam += 10;

cout << “Inside the function:” << endl;

cout << myparam << endl;

}

int main()

{

int mynumber = 30;

cout << “Before the function:” << endl;

cout << mynumber << endl;

ChangesAreGood(mynumber);

cout << “After the function:” << endl;

cout << mynumber << endl;

return 0;

}

Listing 6-11 includes a function called ChangesAreGood that modifies the parameter it receives. (It adds 10 to its parameter called myparam.) It then prints the new value of the parameter.

The main function initializes an integer variable, mynumber, to 30 and prints its value. It then calls the ChangesAreGood function, which changes its parameter. After coming back from the ChangesAreGood function, main prints the value again.

When you run this program, you see the following output:

Before the function:

30

Inside the function:

40

After the function:

30

Before the function call, mynumber is 30. And after the function call, it’s still 30. But the function added 10 to its parameter. This means that when the function modified its parameter, the original variable remained untouched. The two are separate entities. Only the value 30 went into the function. The actual variable did not. It stayed in main.

That keeps mean and nasty functions from messing things up in the outside world. But what if you write a function that you want to modify the original variable?

A pointer contains a number, which represents the address of a variable. If you pass this address into a function and the function stores that address into one of its own variables, its own variable also points to the same variable that the original pointer did. Make sense? The pointer variable in main and the pointer variable in the function both point to the same variable because both pointers hold the same address.

That’s how you let a function modify data in a variable: You pass a pointer. But when you call a function, the process is easy, because you don’t need to make a pointer variable. Instead, you can just call the function, putting an & in front of the variable. Then, you are not passing the variable or its value — instead, you are passing the address of the variable.

Listing 6-12 is a modified form of Listing 6-11; this time the function actually manages to modify the original variable.

Listing 6-12: Using Pointers to Modify a Variable Passed into a Function

#include <iostream>

using namespace std;

void ChangesAreGood(int *myparam)

{

(*myparam) += 10;

cout << “Inside the function:” << endl;

cout << (*myparam) << endl;

}

int main()

{

int mynumber = 30;

cout << “Before the function:” << endl;

cout << mynumber << endl;

ChangesAreGood(&mynumber);

cout << “After the function:” << endl;

cout << mynumber << endl;

return 0;

}

When you run this program, you see the following output:

Before the function:

30

Inside the function:

40

After the function:

40

Notice the important difference between this and the output from Listing 6-11: The final line of output is 40, not 30. The variable was modified by the function!

To understand how this happened, first look at main. The only difference we had to make to main was a little one: We threw an ampersand, &, in front of the mynumber argument in the call to ChangesAreGood. That’s it: Instead of passing the value stored in mynumber, we passed the address of mynumber.

Now the function has some major changes. We rewrote the function header so it takes a pointer rather than a number. We did this by adding an asterisk, *,so the parameter is a pointer variable. This pointer receives the address being passed into it. Thus, it points to the variablemynumber. Therefore, any modifications we make by dereferencing the pointer will attack the original variable. And attack it, it does: It changes it! The following line changes the original variable. Excellent!

(*myparam) += 10;

image When you pass a pointer to a function, you are still passing a number. In Listing 6-11, you are passing to the function the value stored in mynumber. In Listing 6-12, you aren’t somehow passing the variable itself. Instead, you are passing the value of mynumber’s address. The value is still a number either way. However, in Listing 6-12, because the number is an address now, we had to modify the function header so it expects an address, not just a number. To do that, we used a pointer variable because it is a storage bin that holds an address. Then we had to modify the remainder of the function to make use of the pointer, instead of a number.

image The ChangesAreGood function in Listing 6-12 no longer modifies its own parameter. The parameter starts holding the address of the original mynumber variable, and that never changes. Throughout the function, the pointer variable myparam holds the mynumber address. And any changes the function performs are on the dereferenced variable, which is mynumber. The pointer variable does not change.

Modifying string parameters

Modifying a string parameter is just as easy as modifying an integer variable. But with string variables, you have the added benefit that if you’re working with pointers, you can use the shortcut -> notation.

Listing 6-13 is an example of a function that modifies the original string variable that is passed into it. The function expects a pointer to a string. Inside, the function uses the -> notation to access the string functions. Then the function returns. main creates a string, initializes it, prints the string’s value, calls the function, and prints the value again. As you see when you run the program, the value of the string has changed.

Listing 6-13: Using a Function to Modify a String Passed into It by Using Pointers

#include <iostream>

using namespace std;

void Paranoid(string *realmessage)

{

(*realmessage)[6] = ‘i’;

realmessage->replace(9, 1, “”);

realmessage->insert(18, “ad”);

realmessage->replace(15, 2, “in”);

realmessage->replace(23, 7, “!”);

realmessage->replace(4, 3, “ali”);

}

int main()

{

string message = “The friends are having dinner”;

cout << message << endl;

Paranoid(&message);

cout << message << endl;

return 0;

}

In Listing 6-13, we chose to not make the message variable a pointer. It’s just a string variable. We then put a string into it and called the Paranoid function. But instead of passing the value stored in message, we passed the address of message. The function then receives a pointer as a parameter. Because it’s a string pointer, we made extensive use of the shortcut notation, ->. Remember, (*realmessage). equals the pointer.

When you run this program, you see the original value stored in message and then the revised value after the function has its way with it:

The friends are having dinner

The aliens are invading!

Returning Pointer Variables from Functions

Functions can return values, including pointers. To set up a function to return a pointer, specify the type followed by an asterisk at the beginning of the function header. Listing 6-14 shows this. The function returns a pointer that is the result of a new operation.

Listing 6-14: Returning a Pointer from a String Involves Using an Asterisk in the Return Type

#include <iostream>

#include <sstream>

using namespace std;

string *GetSecretCode()

{

string *code = new string;

code->append(“CR”);

int randomnumber = rand();

ostringstream converter;

converter << randomnumber;

code->append(converter.str());

code->append(“NQ”);

return code;

}

int main()

{

string *newcode;

int index;

for (index = 0; index < 10; index++)

{

newcode = GetSecretCode();

cout << *newcode << endl;

}

return 0;

}

In this code, we wedged the asterisk against the function name in the function header. This is a common way of doing it. If you prefer, you can do any of the following lines:

string *GetSecretCode() {

string* GetSecretCode() {

string * GetSecretCode() {

In the main function, we created a pointer to a string, not just a string. My function is returning a pointer to a string, and we needed the pointer and the string to match. When we used the string, we had to dereference it.

When you run this program, you see something like the following output.

CR41NQ

CR18467NQ

CR6334NQ

CR26500NQ

CR19169NQ

CR15724NQ

CR11478NQ

CR29358NQ

CR26962NQ

CR24464NQ

image Never return from a function the address of a local variable in the function. The local variables live in the stack space allocated for the function, not in the heap. When the function is finished, the computer frees the stack space used for the function, making room for the nextfunction call. If you try this, the variables will be okay for a while, but after enough function calls that follow, the variable’s data will get overwritten. Wiped out. Gone to the great variable home in the sky.

image Just as the parameters to a function are normally values, a function normally returns a value. In the case of returning a pointer, the function is still returning just a value — it is returning the value of the pointer, which is a number representing an address.

Random numbers and strings

Some special code is right smack in the middle of the function in Listing 6-14, and we need to explain that. It’s a little trick we used for generating a random number and putting it into the middle of the string. First, we had to add another include line. This one is

#include <sstream>

This line provides some of the special features we’re about to talk about, specifically the ostringstream type. Now here are the three lines that perform the magic:

int randomnumber = rand();

ostringstream converter;

converter << randomnumber;

The first of these creates a random number by calling a function called rand. You get back from this function an integer, which is random. The next one creates a variable of a type called ostringstream, which is a type that’s handy for converting numbers to strings. A variable of this type has features similar to that of a console. You can use the insertion operator, <<, except instead of going to the console, anything you write goes into the string itself. But this isn’t just any old string; it’s a special string of type ostringstream (which comes from the wordsoutput, string, and stream; usually things that allow the insertion operator << or the extraction operator >> to perform input and output are called streams). After we do this, we can add the resulting string onto our string variable called code. To do that, we use the line

code->append(converter.str());

The part inside parentheses, converter. str(), returns an actual string version of the converter variable. And that we can easily append to our code variable by using the append function. It’s kind of tricky, but it works quite nicely.

Returning a Pointer as a Nonpointer

You may find it annoying to dereference a pointer returned from a function every time you want to use it. Listing 6-14, in the preceding section, is an example of how you need to dereference a pointer each time. But you can avoid this issue by dereferencing the pointer as soon as it comes cranking out of the machine. Listing 6-15 shows this: We preceded the call to the function with an asterisk, which dereferences the result immediately. We then place the result in a local nonpointer variable. After that, we have the value in the variable, and we don’t need to dereference the pointer when we want to use the value. Thus, when we call cout, we just use the variable directly without the use of asterisks and other pointer paraphernalia.

Listing 6-15: Dereferencing Your Return Value Immediately So You Don’t Need to Use It as a Pointer

#include <iostream>

using namespace std;

string *GetNotSoSecretCode()

{

string *code = new string(“ABCDEF”);

return code;

}

int main()

{

string newcode;

int index;

for (index = 0; index < 10; index++)

{

newcode = *GetNotSoSecretCode();

cout << newcode << endl;

}

return 0;

}

When you run this program, you see the following secret but highly enticing output:

ABCDEF

ABCDEF

ABCDEF

ABCDEF

ABCDEF

ABCDEF

ABCDEF

ABCDEF

ABCDEF

ABCDEF

Passing by Reference

C++ is based on the old C language, which was a simple language. C++ has some features to make it cushier. One feature is references. A reference is another way of specifying a parameter in a function whereby the function can modify the original variable. Instead of following the parameter type with an asterisk, *, to denote a pointer, you follow it with an ampersand, &. Then, throughout your function, you can use the parameter just as you normally would, not as a pointer. But every change you make to the parameter affects the original variable! A concept ahead of its time. Or behind its time, considering that other languages have had this feature for years.

Take a look at Listing 6-16 and notice how we didn’t use any pointers.

Listing 6-16: With References, You Don’t Need Pointers!

#include <iostream>

using namespace std;

void MessMeUp(int &myparam)

{

myparam = myparam * 2 + 10;

}

int main()

{

int mynumber = 30;

MessMeUp(mynumber);

cout << mynumber << endl;

return 0;

}

Look at that code! No more pointers! In main, we don’t need to take the address of anything, and we don’t need to use that dereference word, which the spelling checker insists is wrong. And the function itself has no pointers either. We just throw the old ampersand thing in front of the parameter name in the function header.

imageIf you have string parameters, and you use the & to pass them by reference, skip the shortcut -> notation to call the string functions. And don’t dereference anything. There are no pointers. Just type the dot (or period) and the function. No asterisks needed.

image If you write a function that uses a reference and somebody else uses your function in code (see Minibook I, Chapter 5, for information on how to do this), you could end up making that other person angry. The other person may not realize that Hey man, this thing just messed up my variable! WHAM! Their variable gets changed. How do you avoid this? Warn them. Make it clear to anybody using your function that it uses references and will modify variables, even the unsuspecting little ones.

Remembering the Rules

When you use pointers and references, make your life easier:

Understand pointers and references: Your C++ programming ventures will be much happier.

Free your pointers: Whenever you call new, you should (sooner or later) call delete. Don’t leave memory in the heap when you’re finished with it.

Know your references: If you write a function that has references, make sure that everybody knows it. And if you use a function that somebody else wrote, make sure that you check both the person’s references and the function’s references.