Working with Arrays, Pointers, and References - Advanced Programming - C++ All-in-One For Dummies (2009)

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

Book IV

Advanced Programming

image

Contents at a Glance

Chapter 1: Working with Arrays, Pointers, and References

Building Up Arrays

Pointing with Pointers

Referring to References

Chapter 2: Creating Data Structures

Working with Data

Structuring Your Data

Naming Your Space

Chapter 3: Constructors, Destructors, and Exceptions

Constructing and Destructing Objects

Programming the Exceptions to the Rule

Chapter 4: Advanced Class Usage

Inherently Inheriting Correctly

Using Classes and Types within Classes

Chapter 5: Creating Classes with Templates

Templatizing a Class

Parameterizing a Template

Typedefing a Template

Deriving Templates

Templatizing a Function

Chapter 6: Programming with the Standard Library

Architecting the Standard Library

Containing Your Classes

The Great Container Showdown

Copying Containers

Chapter 1: Working with Arrays, Pointers, and References

In This Chapter

Working with arrays and multidimensional arrays

Understanding the connection between arrays and pointers

Dealing with pointers in all their forms

Using reference variables

When the C programming language, predecessor to C++, came out in the early 1970s, it was a breakthrough because it was small. C had only a few keywords. Tasks like printing to the console were handled not by built-in keywords but by functions.

Technically, C++ is still small. So what makes C++ big?

♦ The language itself is small, but its libraries are huge.

♦ The language is small, but it’s extremely sophisticated, resulting in millions of things you can do with the language.

In this chapter, we give you the full rundown of topics that lay the foundation for C++: arrays, pointers, and references. In C++, these items come up again and again.

We assume that you have a basic understanding of C++ — that is, that you understand the material in Minibook I and Minibook II, Chapter 1. You know the basics of pointers and arrays (and maybe just a teeny bit about references) and you’re ready to grasp them thoroughly.

Building Up Arrays

As you work with arrays, it seems like you can do a million things with them. This section provides the complete details on arrays. The more you know about arrays, the less likely you are to use them incorrectly, resulting in a bug.

image Know how to get the most out of arrays when necessary — not just because they’re there. Avoid using arrays in the most complex way imaginable.

Declaring arrays

The usual way of declaring an array is to simply put the type name, followed by a variable name, followed by a size in brackets, as in this line of code:

int Numbers[10];

image This declares an array of 10 integers. The first element gets index 0, and the final element gets index 9. Always remember that in C++ arrays start at 0, and the highest index is one less than the size. (Remember, index refers to the position within the array, and size refers to the number of elements in the array.)

A common question that the usual programming student asks is, Can I just declare an array without specifying a size, like this:

int Numbers[]

In certain situations, you can declare an array without putting a number in the brackets. For example, you can initialize an array without specifying the number of elements:

int MyNumbers[] = {1,2,3,4,5,6,7,8,9,10};

The compiler is smart enough to count how many elements you put inside the braces, and then the compiler makes that count the array size.

Another time you can skip the number in brackets is when you use the extern word. (Remember, the extern statement refers to variables in other source files. See Minibook I, Chapter 5 for more information.) Suppose you have one source file, perhaps numbers.cpp, that contains this line:

int MyNumbers[] = {1,2,3,4,5,6,7,8,9,10};

Then, in another source file, say main.cpp, you can declare an external reference to the array in numbers.cpp:

#include <iostream>

using namespace std;

extern int MyNumbers[];

int main(int argc, char *argv[])

{

cout << MyNumbers[5] << endl;

return 0;

}

When you compile these two source files (numbers.cpp and main.cpp), you get the correct result for MyNumbers[5]:

6

(Remember that MyNumbers[5] refers to the sixth element because the first element has index 0. The sixth element has a 6 in it.)

imageAlthough you can get away with leaving out the size in an external array declaration, we do not recommend doing so because you would be asking for errors. Instead, include it. In fact, we would rewrite numbers.cpp to have an explicit array size as well, as in

int MyNumbers[10] = {1,2,3,4,5,6,7,8,9,10};

Then main.cpp would look like this:

extern int MyNumbers[10];

Specifying the array size helps decrease your chances of having bugs, bugs, everywhere bugs. Plus, it has the added benefit that, in the actual declaration, if the number in brackets does not match the number of elements inside braces, the compiler issues an error, at least if the number is smaller anyway. The following

int MyNumbers[5] = {1,2,3,4,5,6,7,8,9,10};

yields this compiler error

excess elements in aggregate initializer

But if the number in brackets is greater than the number of elements, as in the following code, you will not get an error. So be careful!

int MyNumbers[15] = {1,2,3,4,5,6,7,8,9,10};

You also can skip specifying the array size when you pass an array into a function, like this:

int AddUp(int Numbers[], int Count) {

int loop;

int sum = 0;

for (loop = 0; loop < Count; loop++) {

sum += Numbers[loop];

}

return sum;

}

This technique is particularly powerful because the AddUp function can work for any size array. You can call the function like this:

cout << AddUp(MyNumbers, 10) << endl;

But this is kind of annoying because you have to specify the size each time you call into the function. However, you can get around this. Look at this line of code:

cout << AddUp(MyNumbers, sizeof(MyNumbers) / 4) << endl;

With the array, the sizeof operator tells you how many bytes it uses. But the size of the array is usually the number of elements, not the number of bytes. So you divide the result of sizeof by 4 (the size of each element).

But now you have that magic number, 4, sitting there. (By magic number, we mean a seemingly arbitrary number that is stuffed somewhere into your code.) So a slightly better approach would be to enter this:

cout << AddUp(MyNumbers, sizeof(MyNumbers) / sizeof(int)) << endl;

Now this line of code works, and here’s why: The sizeof the array divided by the sizeof each element in the array gives the number of elements in the array.

Arrays and pointers

The name of the array is a pointer to the array itself. The array is a sequence of variables stored in memory. The array name points to the first item.

This is an interesting question about pointers: Can we have a function header, such as the following line, and just use sizeof to determine how many elements are in the array? If so, this function wouldn’t need to have the caller specify the size of the array.

int AddUp(int Numbers[]) {

Consider this function and a main that calls it:

void ProcessArray(int Numbers[]) {

cout << “Inside function: Size in bytes is “

<< sizeof(Numbers) << endl;

}

int main(int argc, char *argv[])

{

int MyNumbers[] = {1,2,3,4,5,6,7,8,9,10};

cout << “Outside function: Size in bytes is “;

cout << sizeof(MyNumbers) << endl;

ProcessArray(MyNumbers);

return 0;

}

When you run this program, here’s what you see:

Outside function: Size in bytes is 40

Inside function: Size in bytes is 4

Outside the function, the code knows that the size of the array is 40 bytes. But why does the code think that the size is 4 after it is inside the array? The reason is that even though it appears that you’re passing an array, you’re really passing a pointer to an array. The size of the pointer is just 4, and so that’s what the final cout line prints.

imageDeclaring arrays has a slight idiosyncrasy. When you declare an array by giving a definite number of elements, such as this:

int MyNumbers[5];

the compiler knows that you have an array, and the sizeof operator gives you the size of the entire array. The array name, then, is both a pointer and an array! But if you declare a function header without an array size like this

void ProcessArray(int Numbers[]) {

the compiler treats this as simply a pointer and nothing more. This last line is, in fact, equivalent to this:

void ProcessArray(int *Numbers) {

Thus, inside the functions that either line declares, the following two lines of code are equivalent:

Numbers[3] = 10;

*(Numbers + 3) = 10;

This equivalence means that if you use an extern declaration on an array, such as this:

extern int MyNumbers[];

and then take the size of this array, the compiler will get confused. Here’s an example: If you have two files, numbers.cpp and main.cpp, where numbers.cpp declares an array and main.cpp externally declares it, you will get a compiler error if you call sizeof:

#include <iostream>

using namespace std;

extern int MyNumbers[];

int main(int argc, char *argv[])

{

cout << sizeof(MyNumbers) << endl;

return 0;

}

In CodeBlocks (which is the compiler we’re using for most of this book; see Appendix B for more information), the gcc compiler gives us this error:

error: invalid application of `sizeof’ to incomplete type `int[]’

The solution is to put the size of the array inside brackets. Just make sure that the size is the same as in the other source-code file! You can fake out the compiler by changing the number, and you won’t get an error. But that’s bad programming style and just asking for errors.

image Although an array is simply a sequence of variables all adjacent to each other in memory, the name of an array is really just a pointer to the first element in the array. You can use the name as a pointer. However, do that only when you really need to work with a pointer. After all, you really have no reason to write code that is cryptic, such as *(Numbers + 3) = 10;.

The converse is also true. Look at this function:

void ProcessArray(int *Numbers) {

cout << Numbers[1] << endl;

}

This function takes a pointer as a parameter, yet we access it as an array. Again, we do not recommend writing code like this; instead, we recommend that you understand why code like this works. That way, you gain a deeper knowledge of arrays and how they live inside the computer, and this knowledge, in turn, can help you write code that works properly.

image Even though, throughout this chapter, we’re telling you that the array name is just a pointer, the name of an array of integers isn’t the exact same thing as a pointer to an integer. Check out these lines of code:

int LotsONumbers[50];

int x;

LotsONumbers = &x;

We’re trying to point the LotsONumbers pointer to something different: something declared as an integer. The compiler doesn’t let you do this; you get an error. That wouldn’t be the case if LotsONumbers were declared as int *LotsONumbers; then this code would work. But as written, you get a compiler error. And believe it or not, here’s the compiler error we get in CodeBlocks:

error: incompatible types in assignment of `int*’ to `int[50]’

This error implies the compiler does see a definite distinction between the two types, int * and int[]. Nevertheless, the array name is indeed a pointer, and you can use it as one; you just can’t do everything with it that you can with a normal pointer, such as reassign it. (If we were philosophers, we might argue that an array name’s type is not equivalent to its equivalent. But we’re not philosophers, so when we suggest something like that, we’re only being equivalent to philosophers.)

imageWhen using arrays, then, we suggest the following tips. These will help you keep your arrays bug-free:

♦ Keep your code consistent. If you declare, for example, a pointer to an integer, do not treat it as an array.

♦ Keep your code clear and understandable. If you pass pointers, it’s okay to take the address of the first element, as in &(MyNumbers[0]) if this makes the code clearer — though it’s equivalent to just MyNumbers.

♦ When you declare an array, always try to put a number inside the brackets, unless you are writing a function that takes an array.

♦ When you use the extern keyword to declare an array, go ahead and also put the array size inside brackets. But be consistent! Don’t use one number one time and a different number another time. The easiest way to be consistent is to use a constant, such as const int ArraySize = 10; in a common header file and then use that in your array declaration: int MyArray[ArraySize];.

Using multidimensional arrays

Arrays do not have to be just one-dimensional. You can declare a multidimensional array, as shown in Listing 1-1.

Listing 1-1: Using a Multidimensional Array

#include <iostream>

using namespace std;

int MemorizeThis[10][20];

int main(int argc, char *argv[])

{

int x,y;

for (x = 0; x < 10; x++) {

for (y = 0; y < 20; y++ ) {

MemorizeThis[x][y] = x * y;

}

}

cout << MemorizeThis[9][13] << endl;

cout << sizeof(MemorizeThis) / sizeof(int) << endl;

system(“PAUSE”);

return 0;

}

When you run this, MemorizeThis gets filled with the multiplication tables (thus the clever name!). Here’s the output for the program, which is the contents of MemorizeThis[9][13], and then the size of the entire two-dimensional array:

117

200

And indeed, 9 times 13 is 117. The size of the array is 200 elements. Because each element, being an integer, is 4 bytes, that means that the size in bytes is 800.

imageYou can have many, many dimensions, but be careful. Every time you add a dimension, the size multiplies by the size of that dimension. Thus an array declared like the following line has 48,600 elements, for a total of 194,400 bytes:

int BigStuff[4][3][5][3][5][6][9];

And the following array has 4,838,400 elements, for a total of 19,353,600 bytes. That’s about 19 megabytes!

int ReallyBigStuff[8][6][10][6][5][7][12][4];

image If you really have this kind of a data structure, consider redesigning it. Any data stored like this would be downright confusing. And fortunately, the compiler will stop you from going totally overboard. Just for fun we tried this giant monster:

int GiantMonster[18][16][10][16][15][17][12][14];

This is the error we got:

error: size of array `GiantMonster’ is too large

(That would be 1,974,067,200 bytes: more than a gigabyte!)

Initializing multidimensional arrays

Just as you can initialize a single-dimensional array by using braces and separating the elements by commas, you can initialize a multidimensional array with braces and commas and all that jazz, too. But to do this, you combine arrays inside arrays, as in this code:

int Numbers[5][6] = {

{1,2,3,4,5,6},

{7,8,9,10,12},

{13,14,15,16,17,18},

{19,20,21,22,23,24},

{25,26,27,28,29,30}

};

The hard part is remembering whether you put five batches of six or six batches of five. Think of it like this: Each time you add another dimension, it goes inside the previous. That is, you can write a single-dimensional array like this:

int MoreNumbers[5] = {

100,

200,

300,

400,

500,

};

Then, if you add a dimension to this, each number in the initialization is replaced by an array initializer of the form {1,2,3,4,5,6}. Then you end up with a properly formatted multidimensional array.

Passing multidimensional arrays

If you have to pass a multidimensional array to a function, things can get just a bit hairy. That’s because you don’t have as much freedom in leaving off the array sizes as you do with single-dimensional arrays. Suppose you have this function:

int AddAll(int MyGrid[5][6]) {

int x,y;

int sum = 0;

for (x = 0; x < 5; x++) {

for (y = 0; y < 6; y++) {

sum += MyGrid[x][y];

}

}

return sum;

}

So far, the function header is fine because we’re explicitly stating the size of each dimension. But you may want to do this:

int AddAll(int MyGrid[][]) {

or maybe pass the sizes as well:

int AddAll(int MyGrid[][], int rows, int columns) {

But unfortunately, when we compile either of these two lines, we get this error:

declaration of `MyGrid’ as multidimensional array

must have bounds for all dimensions except the first

That’s strange: The compiler is telling us that we must explicitly list all the dimensions, but it’s okay if we leave the first one blank as with one-dimensional arrays.

So that means this crazy thing will compile:

int AddAll(int MyGrid[][6]) {

How about that? The reason is that the compiler treats multidimensional arrays in a special way. A multidimensional array is not really a two-dimensionalarray, for example; rather, it’s an array of an array. Thus, deep down inside C++, the compiler treats the statement MyGrid[5][6] as if it were MyGrid[5] where each item in the array is itself an array of size 6. And you’re free to not specify the size of a one-dimensional array. Well, the first brackets represent the one-dimensional portion of the array. So you can leave that space blank, as you can with other one-dimensional arrays. But then, after that, you have to give the array bounds. Sounds strange, we know. And perhaps just a bit contrived. But it’s C++, and it’s the rule: You can leave the first dimension blank in a function header, but you must specify the remaining dimension sizes.

imageWhen using multidimensional arrays, it’s often easier on our brains if we think of them as an array of arrays. Then we use a typedef so that, instead of it being an array of arrays, it’s an array of some user-defined type, such as GridRow. Either of the following function headers are confusing:

int AddAll(int MyGrid[][6]) {

int AddAll(int MyGrid[][6], int count) {

Here’s our recommendation: Use a typedef! So here’s a cleaner way:

typedef int GridRow[6];

int AddAll(GridRow MyGrid[], int Size) {

int x,y;

int sum = 0;

for (x = 0; x < Size; x++) {

for (y = 0; y < 6; y++) {

sum += MyGrid[x][y];

}

}

return sum;

}

The typedef line defines a new type called GridRow. This type is an array of six integers. Then, in the function, you are passing an array of GridRows.

Using this typedef is the same as simply using two brackets, except it emphasizes that you are passing an array of an array — that is, an array in which each member is itself an array of type GridRow.

Arrays and command-line parameters

In a typical C++ program, the main function receives an array and a count as parameters. However, to beginning programmers, the parameters can look intimidating. But they’re not: Think of the parameters as an array of strings and a size of the array. However, each string in this array of strings is actually a character array. In the old days of C, and earlier breeds of C++, no string class was available. Thus, strings were always character arrays, usually denoted as char *MyString. (Remember, an array and a pointer can be used interchangeably for the most part). Thus, you could take this thing and turn it into an array either by throwing brackets at the end char *MyString[ ] or by making use of the fact that an array is a pointer and adding a second pointer symbol, as in char **MyString. The following code shows how you can get the command-line parameters:

#include <iostream>

using namespace std;

int main(int argc, char *argv[])

{

int loop;

for (loop = 0; loop < argc; loop++) {

cout << argv[loop] << endl;

}

return 0;

}

When you compile this program, name the executable CommandLineParams, and then run it from the command prompt using the following command

CommandLineParams abc def “abc 123”

You see the following output. (Note that the program name comes in as the first parameter and the quoted items come in as a single parameter.)

CommandLineParams

abc

def

abc 123

imageYou can also specify command-line arguments using the IDE for debugging purposes when working with the CodeBlocks compiler. Choose Project⇒Set Program’s Arguments. CodeBlocks displays the Select Target dialog box, where you choose a target in the first field and type the arguments in the Program Arguments field. Click OK and then click Run. CommandLineParams displays the command-line arguments in the command window as it did when you typed the command at the command prompt.

Allocating an array on the heap

Arrays are useful, but it would be a bummer if the only way you could use them were as stack variables. If you could allocate an array on the heap by using the new keyword, that would be nice. Well, good news! You can! But you need to know about a couple little tricks to make it work.

First, you can easily declare an array on the heap by using new int[50], for example. But think about what this is doing: It declares 50 integers on the heap, and the new word returns a pointer to the allocated array. But, unfortunately, the makers of C++ didn’t see it that way. For some reason, they made the array pointer type based on the first element of the array (which is, of course, the same as all the elements in the array).

Thus, the call

new int[50]

returns a pointer of type int *, not something that explicitly points to an array, just like this call does:

new int;

Nice, huh? But that’s okay. We can deal with it. So if you want to save the results of new int [50] in a variable, you have to have a variable of type int *, as in the following:

int *MyArray = new int[50];

But here’s the bizarre part: An array name is a pointer and vice versa. So now that you have a pointer to an integer, you can treat it like an array:

MyArray[0] = 25;

And now for the really bizarre part. When you’re all finished with the array, you can call delete. But you can’t just call delete MyArray;. The reason is that the compiler knows only that MyArray is a pointer to an integer; it doesn’t know that it’s an array! Thus, delete MyArray will only delete the first item in the array, leaving the rest of the elements sitting around on the heap, wondering when their time will come. So the makers of C++ gave us a special form of delete to handle this situation. It looks like this:

delete[] MyArray;

imageWhenever you allocate an array by using the new keyword, remember to delete the array by using delete[] rather than just plain old delete.

If you’re really curious about the need for delete[] and delete, recognize the distinction between allocating an array and allocating a single element on the stack. Look closely at these two lines:

int *MyArray = new int[50];

int *somenumber = new int;

The first allocates an array of 50 integers, while the second allocates a single array. But look at the types of the pointer variables: They are both the same! How about that? They are both a pointer to an integer. And so the statement

delete something;

is ambiguous if something is a pointer to an integer: Is it an array, or is it a single number? The designers of C++ knew this was a problem, so they unambiguated it. They declared and proclaimed that delete shall only delete a single member. Then they invented a little extra that must have given the compiler writers a headache; they said that if you want to delete an array instead, just throw on an opening and closing bracket after the word delete. And all will be good.

image All this stuff about pointers and arrays raises an interesting question: How do you specify a pointer to an array? Well, remember that if you have a line like this

int LotsONumbers[50];

LotsONumbers is really a pointer to an integer — it points to the first position in the array. So, by that regard, you already have a pointer to an array. In fact, if you were to write a function declared with a header like this

int AddUp(int Numbers[], int Count) {

and look at the generated assembly code, you would see that the Numbers array really does get passed in as a pointer. To view the disassembly in CodeBlocks, create a breakpoint, start the debugger by pressing F8, and choose Debug⇒Debugging Windows⇒Disassembly. CodeBlocks displays the Disassembly window, which contains an assembler view of the code.

So the real question is this: When you have an array, how do you not use a pointer with it? The answer? You don’t! C++ simply does not have a fundamental array type. Other languages do (Pascal, for example), but C and C++ don’t. Yet, even though that’s the case, the compiler does have a basic feel for the brackets and does seem to understand arrays. Strange, but true.

Storing arrays of pointers and arrays of arrays

Because of the similarities between arrays and pointers, you are likely to encounter some strange notation. For example, in main itself, we have seen both of these at different times:

char **argc

char *argc[]

If you work with arrays of arrays and arrays of pointers, the best bet is to make sure that you completely understand what these kinds of statements mean. Remember that, although you can treat an array name as a pointer, you’re in for some technical differences. The following lines of code show these differences. First, think about what happens if you initialize a two-dimensional array of characters like this:

char NameArray[][6] = {

{‘T’, ‘o’, ‘m’, ‘\0’, ‘\0’, ‘\0’},

{‘S’, ‘u’, ‘z’, ‘y’ , ‘\0’, ‘\0’},

{‘H’, ‘a’, ‘r’, ‘r’ , ‘y’, ‘\0’}

};

This is an array of an array. Each inner array is an array of 6 characters. The outer array stores the 3 inner arrays. (The individual content of an array is sometimes called a member — the inner array has 6 members and the outer array has 3 members.) Inside memory, the 18 characters are stored in one consecutive row, starting with T, then o, and ending with y and finally \0, which is the null character.

But now take a look at this:

char* NamePointers[] = {

“Tom”,

“Suzy”,

“Harry”

};

This is an array of character arrays as well, except that it’s not the same as the code that came just before it. This is actually an array holding three pointers: The first points to a character string in memory containing Tom (which is followed by a null-terminator, \0); the second points to a string in memory containing Suzy ending with a null-terminator; and so on. Thus, if you look at the memory in the array, you won’t see a bunch of characters; instead, you see three numbers, each being a pointer.

imageIt’s often helpful to see the content of memory as you work with arrays. To see memory in CodeBlocks, choose Debug⇒Debugging Windows⇒Examine Memory. You see the Memory window. Type the name of the variable you want to view in the Address field and click Go.

image So where on earth (or in the memory, anyway) are the three strings, Tom, Suzy, and Harry when you have an array of three pointers to these strings? When the compiler sees string constants such as these, it puts them in a special area where it stores all the constants. These then get added to the executable file at link time, along with the compiled code for the source module. (For information on linking, see Appendix A.) And that’s where they reside in memory. The array, therefore, contains pointers to these three constant strings in memory.

Now if you try to do the following (notice the type of PointerToPointer)

char **PointerToPointer = {

“Tom”,

“Suzy”,

“Harry”

};

you will get an error:

error: initializer for scalar variable requires one element

A scalar is just another name for a regular variable that is not an array. In other words, the PointerToPointer variable is a regular variable (that is, a scalar), not an array!

Yet, inside the function header for main, you can use char **, and you can access this as an array. What’s going on? As usual, there’s a slight but definite difference between an array and a pointer. You cannot always just treat a pointer as an array; for example, you can’t initialize a pointer as an array. But you can go the other way: You can take an array and treat it as a pointer most of the time. Thus, you can do this:

char* NamePointers[] = {

“Tom”,

“Harry”,

“Suzy”

};

char **AnotherArray = NamePointers;

This compiles, and you can access the strings through AnotherArray[0], for example. Yet, you’re not allowed to skip a step and just start out initializing the AnotherArray variable like so because this is the same as the code just before this example, and it yields a compiler error!

char** AnotherArray = {

“Tom”,

“Harry”,

“Suzy”

};

Thus, this is an example of where slight differences between arrays and pointers occur. But it does explain why you can see something like this

int main(int argc, char **argv)

and you are free to use the argv variable to access an array of pointers — specifically, in this case, an array of character pointers, also called strings.

Building constant arrays

If you have an array and you don’t want its contents to change, you can make it a constant array. The following lines of code demonstrate this:

const int Permanent[5] = { 1, 2, 3, 4, 5 };

cout << Permanent[1] << endl;

This array works like any other array, except you cannot change the numbers inside it. If you add a line like the following line, you get a compiler error, because the compiler is aware of constants:

Permanent[2] = 5;

Here’s the error we got when we tried this in CodeBlocks:

error: assignment of read-only location

Being the inquisitive sorts, we asked ourselves this question: What about a constant array of nonconstants? Can we do that? Well, sometimes, depending on the compiler. As horrible as the following looks (and it’s not ANSI-standard!), you are allowed to do this with the gcc compilers. (Microsoft Visual C++ and Borland C++ Builder don’t allow it, and the CodeBlocks compiler presents you with an error: ISO C++ forbids assignment of arrays error message.)

int NonConstant[5] = { 1, 2, 3, 4, 5 };

int OtherList[5] = { 10, 11, 12, 13, 14 };

OtherList = NonConstant;

In other words, that third line is saying, “Forget what OtherList points to; instead, make it point to the first array, {1,2,3,4,5}!” Now, we really don’t recommend writing code like this (remember, keep things simple and understandable!), so if you want to prevent this kind of thing, you can make the array constant:

const int NonConstant[5] = { 1, 2, 3, 4, 5 };

const int OtherList[5] = { 10, 11, 12, 13, 14 };

OtherList = NonConstant;

Now when the compiler gets to the third line, it gives us an error:

error: assignment of read-only variable `OtherList’

But you may notice that the way we made the array constant was the same way that we made its elements constant in the code that came just before this example! Oops! What’s that all about? Turns out there are some rules.

image The following list describes the rules, in detail, for making arrays constant:

♦ If you want to make an array constant, you can precede its type with the word const. When you do so, the array name is constant, and the elements inside the array are also constant. Thus, you cannot have a constant array with nonconstant elements, nor can you have a nonconstant array with constant elements.

♦ The notion of a nonconstant array exists only in gcc and is not ANSI-standard.

image If you really want to get technical, the C++ ANSI standard says that when you put the word const in front of an array declaration, you’re not making the array constant; you’re saying that the array holds only constants. Yet, when you do this, most compilers also make the array itself constant. But that’s fine; people shouldn’t be taking an array name and copying it to something else. It’s not good programming style, and it’s just asking for bugs — or, at the very least, confusion — later.

Pointing with Pointers

To fully understand C++ and all its strangeness and wonders, you need to become an expert in pointers. One of the biggest sources of bugs is when programmers who have a so-so understanding of C++ work with pointers and mess them up. But what’s bad in such cases is that the program may run properly for a while, and then suddenly not work. Those bugs are the hardest bugs to catch, because the user may see the problem occur and then report it; but when the programmer tries to reproduce the problem, he or she can’t make the bug happen! (It’s just like when you take your car in to be fixed and suddenly it doesn’t misbehave.) Both the car repair person and the programmer together say, “Worked fine when I tried it!” How frustrating is that?

In this section, we show you how you can get the most out of pointers and use them correctly in your programs, so you won’t have these strange problems.

Becoming horribly complex

We’re not making this up, we have seen a function header like this:

void MyFunction(char ***a) {

Yikes! What are all those asterisks for? Looks like a pointer to a pointer to a pointer to . . . something! How confusing. Now we suppose that some humans have brains that are more like computers, and they can look at that code and understand it just fine. Not us. So don’t worry if you don’t either.

So to understand the code, think about this: Suppose that you have a pointer variable, and you want a function to change what the pointer variable points to. Now be careful: We’re not saying that the function wants to change the contents of the thing it points to. Rather, we’re saying the function wants to make the pointer point to something else. There’s a difference. So how do you do that? Well, any time you want a function to change a variable, you have to either pass it by reference or pass its address. And this can get confusing with a pointer. So what we like to do is take a detour. First, we’re going to define a new type using our friend, the typedef word. It goes like this:

typedef char *PChar;

This is a new type called PChar that is equivalent to char *. That is, PChar is a pointer to a character.

Now look at this function:

void MyFunction(PChar &x)

{

x = new char(‘B’);

}

This function takes a pointer variable and points it to the result of new char(‘B’). That is, it points it to a newly allocated character variable containing the letter B. Now think this through carefully: A PChar simply contains a memory address, really. We pass it by reference into the function, and the function modifies the PChar so that the PChar contains a different address. That is, the PChar now points to something different from what it previously did.

To try out this function, here’s some code you can put in main that tests MyFunction:

char *ptr = new char(‘A’);

char *copy = ptr;

MyFunction(ptr);

cout << “ptr points to “ << *ptr << endl;

cout << “copy points to “ << *copy << endl;

Think it through carefully: The first line declares a variable called ptr that is a pointer to a character. (Notice that we’re just using char * this time, but that’s okay — char * is the same as PChar because of our typedef.) The first line also allocates a new character A on the heap and stores its address in the ptr variable.

The second line allocates a second variable that’s also a pointer to a character. The variable is called copy, and it gets the same value stored in ptr; thus it also points to that character A that’s floating around out in the heap.

Next we call MyFunction. That function is supposed to change what the pointer points to. Then we come back from the function and print the character that ptr points to and the character that copy points to. Here’s what we get when we run it:

ptr points to B

copy points to A

This means that it worked! The ptr variable now points to the character allocated in MyFunction (a B), while the copy variable still points to the original A. In other words, they no longer point to the same thing: MyFunction managed to change what the variable points to.

Now consider the same function, but instead of using references, try it with pointers. Here’s a modified form:

void AnotherFunction(PChar *x)

{

*x = new char(‘C’);

}

Now because the parameter is a pointer, we have to dereference it to modify its value. Thus, we have an asterisk, *, at the beginning of the middle line.

And here’s a modified main that calls this function:

char *ptr = new char(‘A’);

char *copy = ptr;

AnotherFunction(&ptr);

cout << “ptr points to “ << *ptr << endl;

cout << “copy points to “ << *copy << endl;

Because our function uses a pointer rather than a reference, we have to pass the address of the ptr variable, not the ptr variable directly. So notice the call to AnotherFunction has an ampersand, &, in front of the ptr. And this code works as expected. When we run it, we see this output:

ptr points to C

copy points to A

This version of the function, called AnotherFunction, made a new character called C. And indeed it’s working correctly: ptr now points to a C character, while copy hasn’t changed. Again, the function pointed ptr to something else.

Now we can unravel things. We created a typedef, and honestly, we would prefer to keep it in our code because we think that using typedefs makes it much easier to understand what the functions are doing. However, not everybody does it that way; therefore, we have to understand what other people are doing when we have to go in and fix their code. You may have to, too. So here are the same two functions, MyFunction and AnotherFunction, but without typedef. Instead of using the new PChar type, they directly use the equivalent char * type:

void MyFunction(char *&x)

{

x = new char(‘B’);

}

void AnotherFunction(char **x)

{

*x = new char(‘C’);

}

To remove the use of the typedefs, all we did was replace the PChar in the two function headers with its equivalent char *. You can see that the headers now look goofier. But they mean exactly the same as before: The first is a reference to a pointer, and the second is a pointer to a pointer.

But think about char ** x for a moment. Because char * is also the same as a character array in many regards, char **x is a pointer to a character array. In fact, sometimes you may see the header for main written like this

int main(int argc, char **argv)

instead of

int main(int argc, char *argv[])

Notice the argv parameter in the first of these two is the same type as we’ve been talking about: a pointer to a pointer (or, in a more easily understood manner, the address of a Pchar). But you know that the argument for main is an array of strings.

So now follow this somewhat convoluted thought. Go slowly if you have to: What if you have a pointer that points to an array of strings, and you have a function that is going to make it point to a different array of strings?

Better typedef this one; it’s going to get ugly. And just as a reminder, we’re still using the previous typedef, PChar, too:

typedef char **StringArray;

typedef char *PChar;

Make sure that you believe us when we tell you that StringArray is a type equivalent to an array of strings. In fact, if you put these two lines of code before your main, you can actually change your main header into the following and it will compile!

int main(int argc, StringArray argv)

Now here’s a function that will take as a parameter an array of strings, create a new array of strings, and set the original array of strings to point to this new array of strings. (Whew!)

void ChangeAsReference(StringArray &array)

{

StringArray NameArray = new PChar[3];

NameArray[0] = “Tom”;

NameArray[1] = “Suzy”;

NameArray[2] = “Harry”;

array = NameArray;

}

Just to make sure that it works, here’s something you can put in main:

StringArray OrigList = new PChar[3];

OrigList[0] = “John”;

OrigList[1] = “Paul”;

OrigList[2] = “George”;

StringArray CopyList = OrigList;

ChangeAsReference(OrigList);

cout << OrigList[0] << endl;

cout << OrigList[1] << endl;

cout << OrigList[2] << endl << endl;

cout << CopyList[0] << endl;

cout << CopyList[1] << endl;

cout << CopyList[2] << endl;

This time in main, we’re using the typedef types, because, frankly, the code is getting a bit confusing, and that helps keep what we’re doing clear. Note that we first create a pointer to an array of three strings. Then we store three strings in the array. Next, we save a copy of the pointer in the variable called CopyList, and we print all the values.

Now when you run this main, you see the following:

Tom

Suzy

Harry

John

Paul

George

The first three are the elements in OrigList, which we passed into the function: But they no longer have the values John, Paul, and George. The three original Beatles names have been replaced by three new names: Tom, Harry, and Suzy. However, the Copy variable still points to the original string list. Thus, once again, it worked.

Now we did this change by reference. Next, we do it with pointers. Here’s the modified version of the function, this time using pointers:

void ChangeAsPointer(StringArray *array)

{

StringArray NameArray = new PChar[3];

NameArray[0] = “Tom”;

NameArray[1] = “Harry”;

NameArray[2] = “Suzy”;

*array = NameArray;

}

As before, here’s the slightly modified sample code that tests the function:

StringArray OrigList = new PChar[3];

OrigList[0] = “John”;

OrigList[1] = “Paul”;

OrigList[2] = “George”;

StringArray CopyList = OrigList;

ChangeAsPointer(&OrigList);

cout << OrigList[0] << endl;

cout << OrigList[1] << endl;

cout << OrigList[2] << endl << endl;

cout << CopyList[0] << endl;

cout << CopyList[1] << endl;

cout << CopyList[2] << endl;

You can see that when we call ChangeAsPointer, we’re passing the address of OrigList. The output of this version is the same as the previous version.

And now, as before, we unravel all this. Here are the two function headers without using the typedefs:

int ChangeAsReference(char **&array) {

and

int ChangeAsPointer(char ***array) {

We have seen code like these two lines from time to time. They’re not the easiest to understand, but after you know what they mean, you can interpret them.

imageOur preference is to go ahead and use a typedef, even if it’s just before the function in question. That way, it’s much more clear to other people what the function does. You are welcome to follow suit. But if you do, make sure that you’re familiar with the non-typedef version so you understand that version when somebody else writes it without using typedef. (Or if the person says to you, “This function takes a pointer to a pointer to a pointer.” Yes, we’ve heard people say that!)

Pointers to functions

When a program is running, the functions in the program exist in the memory; so just like anything else in memory, they have an address. And having an address is good, because that way, people can find you.

You can take the address of a function by taking the name of it and putting the address-of operator (&) in front of the function name, like this:

address = &MyFunction;

But to make this work, you need to know what type to declare address. The address variable is a pointer to a function, and the cleanest way to assign a type is to use a typedef. (Fortunately, this is one time when most people are willing to use a typedef.)

Here’s the typedef, believe it or not:

typedef int(*FunctionPtr)(int);

It’s hard to follow, but the name of the new type is FunctionPtr. This defines a type called FunctionPtr that returns an integer (the leftmost int) and takes an integer as a parameter (the rightmost int, which must be in parentheses). The middle part of this statement is the name of the new type, and you must precede it by an asterisk, which means that it’s a pointer to all the rest of the expression. Also, you must put the type name and its preceding asterisk inside parentheses.

And then you’re ready to declare some variables! Here goes:

FunctionPtr address = &MyFunction;

This line declares address as a pointer to a function and initializes it to MyFunction. Now for this to work, the code for MyFunction must have the same prototype declared in the typedef: In this case, it must take an integer as a parameter and return an integer.

So, for example, you may have a function like this:

int TheSecretNumber(int x) {

return x + 1;

}

Then, you could have a main that stores the address of this function in a variable and then calls the function by using the variable:

int main(int argc, char *argv[])

{

typedef int (*FunctionPtr)(int);

int MyPasscode = 20;

FunctionPtr address = &TheSecretNumber;

cout << address(MyPasscode) << endl;

}

Now just so you can say that you’ve seen it, here’s what the address declaration would look like without using a typedef:

int (*address)(int) = &TheSecretNumber;

The giveaway should be that you have two things in parentheses side by side, and the set on the right has only types inside it. The one on the left has a variable name. So this is not declaring a type; rather, it’s declaring a variable.

Pointing a variable to a member function

It’s surprising to find out that most C++ programmers have no idea that this exists. So this is a big secret! Revel in it! What is the secret? The secret is that you can take the address of an object’s member function. Ooh-wee!

Now remember that each instance of a class gets its own copy of the member variables, unless the variables are static. But functions are shared throughout the class. Yes, you can distinguish static functions from nonstatic functions. But that just refers to what types of variables they access: Static functions can access only static member variables, and you don’t need to refer to them with an instance. Nonstatic (that is, normal, regular) member functions work with a particular instance. However, inside the memory, really only one copy of the function exists.

So how does the member function know which instance to work with? A secret parameter gets passed into the member function: the this pointer. Suppose you have a class called Gobstopper that has a member function called Chew. Next, you have an instance called MyGum, and you call the Chew function like so:

MyGum.Chew();

When the compiler generates assembly code for this, it actually passes a parameter into the function — the address of the MyGum instance, also known as the this pointer. Therefore, only one Chew function is in the code, but to call it you must use a particular instance of the class.

Because only one copy of the Chew function is in memory, you can take its address. But to do it requires some sort of cryptic-looking code. Here it is, quick and to the point. Suppose your class looks like this:

class Gobstopper {

public:

int WhichGobstopper;

int Chew(string name) {

cout << WhichGobstopper << endl;

cout << name << endl;

return WhichGobstopper;

}

};

The Chew function takes a string and returns an integer. Here’s a typedef for a pointer to the Chew function:

typedef int (Gobstopper::*GobMember)(string);

And here’s a variable of the type GobMember:

GobMember func = &Gobstopper::Chew;

If you look closely at the typedef, it looks similar to a regular function pointer. The only difference is that the classname and two colons precede the asterisk. Other than that, it’s a regular old function pointer.

But whereas a regular function pointer is limited to pointing to functions of a particular set of parameter types and a return type, this function pointer shares those restrictions but is further limited in that it can point to only member functions within the class Gobstopper.

To call the function stored in the pointer, you need to have a particular instance. Notice that in the assignment of func in the earlier code there was no instance, just the classname and function, &Gobstopper::Chew. So to call the function, grab an instance, add func, and go! Listing 1-2 shows a complete example with the class, the member function address, and two separate instances.

Listing 1-2: Taking the Address of a Member Function

#include <iostream>

#include <string>

using namespace std;

class Gobstopper

{

public:

int WhichGobstopper;

int Chew(string name) {

cout << WhichGobstopper << endl;

cout << name << endl;

return WhichGobstopper;

}

};

int main()

{

typedef int (Gobstopper::*GobMember)(string);

GobMember func = &Gobstopper::Chew;

Gobstopper inst;

inst.WhichGobstopper = 10;

Gobstopper another;

another.WhichGobstopper = 20;

(inst.*func)(“Greg W.”);

(another.*func)(“Jennifer W.”);

return 0;

}

You can see in main that first we create the type for the function, which we call GobMember, and then we create a variable, func, of that type. Then we create two instances of the Gobstopper class, and we give them each a different WhichGobstopper value.

Finally, we call the member function, first for the first instance and then for the second instance. Just to show that you can take the addresses of functions with parameters, we pass in a string with some names.

When you run the code, you can see from the output that it is indeed calling the correct member function for each instance:

10

Greg W.

20

Jennifer W.

Now when we say “the correct member function for each instance,” really what that means is the code is calling the same member function each time but using a different instance. However, when thinking in object-oriented terms, consider each instance as having its own copy of the member function. Therefore, it’s okay to say “the correct member function for each instance.”

Pointing to static member functions

A static member function is, in many senses, just a plain old function. The difference is that you have to use a class name to call a static function. But remember that a static member function does not go with any particular instance of a class; therefore, you don’t need to specify an instance when you call the static function.

Here’s an example class with a static function:

public:

static string MyClassName() {

return “Gobstopper!”;

}

int WhichGobstopper;

int Chew(string name) {

cout << WhichGobstopper << endl;

cout << name << endl;

return WhichGobstopper;

}

};

And here’s some code that takes the address of the static function and calls it by using the address:

typedef string (*StaticMember)();

StaticMember staticfunc = &Gobstopper::MyClassName;

cout << staticfunc() << endl;

Note in the final line that, to call staticfunc, we didn’t have to refer to a specific instance, and we didn’t need to refer to the class, either. We just call it. Because the truth is that deep down inside, the static function is just a plain old function.

Referring to References

In this section, we reveal all the ins, outs, upsides, and downsides of using references. And we tell you a few things about them, too.

We’re assuming in this section that you already know how to pass a parameter by reference when you’re writing a function. (For more information about passing parameters by reference, see Minibook I, Chapter 6.) But you can use references for more than just parameter lists. You can declare a variable as a reference type. And just like job references, they can be both good and devastating. So be careful when you use them.

Reference variables

Declaring a variable that is a reference is easy. Whereas the pointer uses an asterisk, *, the reference uses an ampersand, &. But there’s a twist to it. You cannot just declare it like this:

int &BestReference; // Nope! This won’t work!

If you try this, you see an error that says BestReference declared as reference but not initialized. That sounds like a hint: Looks like you need to initialize it.

Yes, references need to be initialized. As the name implies, reference refers to another variable. Therefore, you need to initialize the reference so it refers to some other variable, like so:

int ImSomebody;

int &BestReference = ImSomebody;

Now from this point on, forever until the end of eternity (or at least as long as the function containing these two lines runs), the variable BestReference will refer to — that is, be an alias for — ImSomebody.

And so if you do this:

BestReference = 10;

Then you will really be setting ImSomebody to 10. So take a look at this code that could go inside a main:

int ImSomebody;

int &BestReference = ImSomebody;

BestReference = 10;

cout << ImSomebody << endl;

When you run this, you see the output

10

That is, setting BestReference to 10 caused ImSomebody to change to 10, which you can see when you print out the value of ImSomebody.

That’s what a reference is: A reference refers to another variable.

image Because a reference refers to another variable, that implies that you cannot have a reference to just a number, as in int &x = 10. And, in fact, the offending line has been implicated: You are not allowed to do that. You can only have a reference that refers to another variable.

Returning a reference from a function

It’s possible to return a reference from a function. But be careful if you try to do this: You do not want to return a reference to a local variable within a function, because when the function ends, the storage space for the local variables goes away. Not good!

But you can return a reference to a global variable. Or, if the function is a member function, you can return a reference to a member variable.

For example, here’s a class that has a function that returns a reference to one of its variables:

class DigInto

{

private:

int secret;

public:

DigInto() { secret = 150; }

int &GetSecretVariable() { return secret; }

void Write() { cout << secret << endl; }

};

Notice the constructor stores 150 in the secret variable, which is private. The GetSecretVariable function returns a reference to the private variable called secret. And the Write function writes out the value of the secret variable. Lots of secrets here! And some surprises too, which we tell you about shortly. You can use this class like so:

int main(int argc, char *argv[])

{

DigInto inst;

inst.Write();

int &pry = inst.GetSecretVariable();

pry = 30;

inst.Write();

return 0;

}

When you run this, you see the following output:

150

30

The first line is the value in the secret variable right after the program creates the instance. But look at the code carefully: The variable called pry is a reference to an integer, and it gets the results of GetSecretVariable. And what is that result? It’s a reference to the private variable called secret. That means that pry itself is now a reference to that variable. Yes, a variable outside the class now refers directly to a private member of the instance! After that, we set pry to 30. When we call Write again, the private variable will indeed change.

Referring to someone else

And now for the $1,000,000 question: After you have a reference referring to a variable, how can you change that reference so it refers to something else? Brace yourself for a wild answer! Here goes: You can’t. Yes, it’s true, and yes, you may know some people who, nevertheless, have managed to do it. Here’s the whole story.

Back when C++ first came out, companies that made compilers gave their compilers some sophisticated capabilities in terms of references. Many of them let you unseat a reference — that is, make the reference refer to something else. But, lo and behold, when the ANSI standard came out in the late 1990s, the standard outlawed this practice! So now the rule is that you cannot unseat a reference. Further, you cannot have a pointer to a reference, nor can you have a reference that refers to another reference. This somewhat restrictive rule actually resolves some ambiguity: Suppose you wrote a line of code asking for the address of a reference with the hope of storing it in a pointer to reference variable: Because a reference refers to another variable, does that mean you want the address of the other variable, or do you somehow want the address of the reference itself?

The standard clears this up: You don’t have a pointer to a reference. But interestingly, the gcc does let you write code that seems to take the address of a reference; however, in fact, you are taking the address of the variable the reference refers to. So, again, no pointers to references, and that means that you can’t even take the address of a reference!

Is it just us, or does that seem like a bad idea? We made the variable private. And now the GetSecretVariable function pretty much wiped out any sense of the variable actually remaining private. The main function was able to grab a reference to it and poke around and change it however it wanted, as if it were not private! Trouble in C++ land!

That’s a problem with references: They can potentially leave your code wide open. Therefore, think twice before returning a reference to a variable. One of the biggest risks is this: Somebody else might be using this code and may not understand references, and may not realize that the variable called pry has a direct link to the private secret variable. Such an inexperienced programmer might then write code that uses and changes pry without realizing that the member variable is changing along with it. Later on, then, a bug results — a pretty nasty one at that!

imageBecause functions returning references can leave unsuspecting and less-experienced C++ programmers with just a wee bit too much power on their hands, we recommend using caution with references. No, we suggest just plain being careful. Use them only if you really feel that you must. But remember also that a better approach in classes is to have member access functions that can guard the private variables.

However, having issued the usual warnings, references can be a very powerful thing, provided you understand what they do. When you have a reference, you can easily modify another variable without having to go through pointers. Using references makes life much easier sometimes. So please: use your newfound powers carefully.