7. METODOLOGÍA EN EL AULA DE TRES AÑOS
7.6 CÓMO SE DESARROLLAN LAS CLASES DE LENGUA EXTRANJERA
Dimensional
Arrays
P
reviously, I’ve only talked about linear array structures—those with only one dimension. This chapter will introduce you to the more complex class of arrays named multi-dimensional arrays. You will find that multi-dimensional arrays are more specific in their nature and cannot be applied to as many situations as regular arrays can be. In this chapter, you will learn:■ What a multi-dimensional array is
■ How to declare native multi-dimensional arrays ■ How to initialize multi-dimensional arrays
■ How to pass multi-dimensional arrays into functions ■ How to access multi-dimensional array cells
■ How a multi-dimensional array is structured internally ■ How to create a dynamic 2D array class
■ How to create a dynamic 3D array class ■ How to make a tilemap using 2D arrays
■ How to make a layered tilemap using 3D arrays
What Is a Multi-Dimensional
Array?
By now, you should know quite a bit about arrays. If not, you can read all about them in Chapter 3, “Arrays.” The arrays I describe in Chapter 3 are more formally known as single-dimension arrays, but no one actually calls them that. They are called that because they can be thought of in a single dimension.
If you think about graphs for a moment, the single-dimension universe has only one axis (traditionally called the x axis), often referred to as length. Any item in a single-dimension universe can only have one coordinate. What you end up with is a long one-dimensional line for the entire universe. See Figure 5.1 for a pictorial rep- resentation of the different dimensions.
109
What Is a Multi-Dimensional Array?
Now, imagine expanding that universe into two dimensions by adding another axis:
height (traditionally called the y axis). Instead of just a line, this time you have a plane, and any point on the plane can have two coordinates instead of just one. And finally, there is the three-dimensional universe, in which the third axis is depth
(traditionally called the z axis). All points in the three-dimensional universe have three coordinates.
Figure 5.1
The three common universes.The z axis for the third dimension can be thought of as coming out of the paper, toward you.
Now, there are other dimensions past the third dimension, but they are pretty much impossible to draw in a way you could understand. Therefore, this chapter will mainly be concerned with two- and three-dimensional arrays.
If a one-dimensional array looks like a plain line, then a two-dimensional array looks like a grid. Figure 5.2 shows how a two-dimensional array is usually repre- sented.
Figure 5.2
This is a two- dimensional array of size (8,8).
A two-dimensional array has two dimensions, a length and a height. In Figure 5.2, both of these dimensions are 8 cells, giving us a total of 64 cells.
A three-dimensional array uses all three dimensions, as demonstrated by Figure 5.3. As you can see, it’s difficult to represent a 3D array because half of the information is hidden. After all, the paper is only 2D. The 3D array shown in Figure 5.3 has a size of (4,4,4), giving us 64 cells.
Figure 5.3
This is a three- dimensional array of size (4,4,4).
111
Graphical Demonstration
Graphical Demonstration
The graphical demonstration for this chapter can be found in the directory \demonstrations\ch05\Demo01 - 2D Array\ . Because it is very difficult to represent arrays with more than two dimensions graphically, this demonstration only shows 2D arrays and the algorithm to resize them.
Compiling the Demo
This demonstration uses the SDLGUI library that I have developed for the book. For more information about this library, see Appendix B, “The Memory Layout of a Computer Program.”
To compile this demo, either open up the workspace file in the direc- tory or create your own project using the settings described in Appendix B. If you create your own project, all of the files you need to include are in the directory.
When you start the program, you will be presented with a screen like the one shown in Figure 5.4.
Figure 5.4
This is a screenshot for the Array2D Graphical Demonstration.
There are two buttons shown on the screen, one that will let you resize the array and one that will let you randomize the number in every cell.
The demonstration will show the exact algorithm used to resize a 2D array and will show red Xs in cells whose value is “invalid.” I used this same approach in the Array Graphical Demonstration from Chapter 3. When resizing an array, four arrows appear on the upper-right side of the screen—use these to change the size of the new array, which will be shown in gray boxes. After you have attained the size that you want, press the Continue button, and the program will illustrate how it copies cells over into the new array.
Native Multi-Dimensional
Arrays
C++ has native support for static multi-dimensional arrays. This is usually the most common way of using a multi-dimensional array because resizing one of these arrays is a rarely needed occurrence.
Declaring a Multi-Dimensional
Array
Creating a multi-dimensional array in C++ is very similar to creating a regular array. Instead of just one dimension being specified, you add the additional dimensions in square brackets after the other dimensions:
int array2d[5][5]; int array3d[4][4][4]; int array4d[3][3][3][3];
These declarations declare a 2D array, a 3D array, and a 4D array, respectively. The number of cells each array contains is determined by multiplying all of the dimen- sions together, so the 2D array has 25 cells, the 3D array has 64 cells, and the 4D array has 81 cells.
Even though I only talk about 2D and 3D arrays in the beginning of this chapter, in reality you can have an array with as many dimensions as you want, depending on the limitations of your compiler. The problems with arrays with more than three dimensions are numerous though. First of all, they are impossible to visualize, unless you come from an alternate dimension where you can see more than three dimensions. Second, arrays with large dimensions tend to get much larger quickly
113
Native Multi-Dimensional Arrays
due to the fact that their dimensions are multiplied to get the size. For example, even though each dimension in array4d in the previous code segment is only three cells large, the entire array takes up 81 cells. Compare this to array3d, however, in which each dimension is four cells large, yet the entire array only takes up 64 cells.
Initializing a 2D Array
Initializing a multi-dimensional array is just like initializing a normal array, except that it involves a lot more curly brackets. For example, if you want to initialize a 3x3 array to contain the numbers 1 through 9, you would declare it like so:
int array[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Because a 2D array can be thought of as an array of arrays, each row in the array is defined like a normal array. Outside, each row is combined together again, sepa- rated by commas and enclosed in brackets.
Initializing Arrays with More Than Two
Dimensions
Using the logic from the previous section, you can extend the idea into three dimensions:
int array[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 },
{ 7, 8 } } };
That looks bad and difficult to understand, but you can see the structure if you stare at it long enough. Lines 1 and 2 form a 2 2 2D array, and so do lines 3 and 4, so you’re looking at two 2D arrays put together, forming a 3D array.
For the particularly devious people out there, here is a 2 2 2 2 4D array ini- tialization: int array[2][2][2][2] = { { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }, { { { 9, 10 }, { 11, 12 } },
{ { 13, 14 }, { 15, 16 } } } };
I do not recommend initializing arrays like this often. As you can see, the definition gets quite messy, and it becomes almost impossible to keep track of all the little brackets. It is not intuitive to initialize arrays with more than two dimensions in code because code is represented on a 2D plane (your screen or paper).
Initializing Non-Symmetrical Multi-
Dimensional Arrays
Now you need to figure out which dimensions are initialized first. Due to C++’s notational conventions (see the “Inside a Multi-Dimensional Array” section for a more in-depth examination), the first dimension defined represents the number of rows in a 2D array. So, to initialize a 3x2 array, you would write this:
int array[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
In other words, the array is in row major form. Each row consists of two columns. The layout in memory is linear starting with row 0, followed by row 1, and lastly by row 2. Due to this arrangement, defining the array with three items in each inner- most bracket will cause a compiler error. Arrays with more than two dimensions fol- low the same pattern. For example, a 3D array with dimensions 3 2 1:
int array[3][2][1] = { { { 1 }, { 2 } }, { { 3 }, { 4 } }, { { 5 }, { 6 } } };
All multi-dimensional arrays follow the same pattern: The last dimension defined determines the number of items that are placed in the innermost brackets.
Initializing Variable Length Multi-
Dimensional Arrays
Last, like regular arrays, it is possible to define a multi-dimensional array in which you let the compiler determine the size of the array automatically, depending on how many items are in the initializer list.
115
Native Multi-Dimensional Arrays
There is one catch, however: Only the first dimension can be left out. Every other dimension must be explicitly defined. I explain the reasons for this in the section entitled “Inside a Multi-Dimensional Array” later on.
For example, this is invalid:
int array[][] = { { 1, 2 }, { 3, 4 } };
Even though it is obvious to us that this is a 2 2 array, the compiler will not accept this. The proper declaration is this:
int array[][2] = { { 1, 2 }, { 3, 4 } };
The same applies to every array of any dimension—only the first dimension can be left blank.
Accessing a Multi-Dimensional
Array
Accessing the items in a multi-dimensional array is just as easy as accessing items in a regular array.
array2d[4][3] = 10; array3d[3][1][0] = 15; array4d[2][2][1][0] = 20;
These operations put numbers into the arrays at different indexes. array2d puts the value 10 into the array at (4,3), which on a 2D grid would look like Figure 5.5. 15 is put into array3d at index (3,1,0), which would look like Figure 5.6. Of course, it is impossible to visualize where the 20 is put within array4d, so I cannot show a figure of that here.
Figure 5.5 This figure shows where the 10 is put within
Figure 5.6
This figure shows where the 15 is put within array3d.
Inside a Multi-Dimensional Array
So how does C++ represent a multi-dimensional array internally? Remember how a normal array works, first of all. You hand it an index, and it figures out the correct place in memory by multiplying the size of an item and adding that to the array off- set. Hold on to this thought for a moment.
Inside 2D Arrays
When you think of a two-dimensional array, isn’t it really just an array of arrays? Look at Figure 5.7 for a moment.
Figure 5.7
This is how you convert a 2D array into a 1D array.
117
Native Multi-Dimensional Arrays
You see, if you treat each row in the 2D array as a single item, you can view the 2D array as a 1D array of arrays. Figure 5.7 shows how you slide each row out to the right and combine all four rows into a single array.
The general formula for converting a 2D coordinate into a 1D coordinate is then:
y * width + x
Therefore, if you wanted the cell at row 2, column 3 in a 4 4 array, the result would be 2 * 4 + 3, which turns out to be 11.
This is how C++ stores and accesses 2D arrays. It stores the array data as a single array and uses the formula for getting indexes.
Expanding to Higher Dimensions
If a 2D array can be thought of as a 1D array of arrays, then a 3D array can be thought of as an array of 2D arrays. (See Figure 5.8.) Expanding upon this, a 3D array is really just an array of arrays of arrays (say that ten times fast!). How about a 4D array? Isn’t that just an array of 3D arrays? Of course, after your dimensions get larger than 3, it becomes very difficult to imagine how an array is stored visually.
Figure 5.8
A 3D array is just an array of 2D arrays.
So now you want to figure out how to access a cell within a 3D array. Because a 3D array is essentially an array of 2D arrays, you need to figure out which 2D array you want to access first. To do that, you need to know the size of each 2D array, which is simply the width times the height. After that, the algorithm is exactly like accessing a 2D array:
The first term, z * width * height, finds which 2D array you want to access first. If z
was 2 in a 3 3 3 array, then this would give you 2 * 3 * 3, which is 18. The sec- ond term determines which row within the 2D array you want to access. If y was 1, then you would have 1 * 3, which is 3. Then the third term is simply the index of the 1D array that you now have the address of. If x is 1, then you simply add 18, 3, and 1, and the final index is 22.
A Note on Conventions
Up until this point, I’ve used the standard mathematical convention for represent- ing the axes of 2D and 3D arrays. The x dimension (width) is always represented as the horizontal axis, the y dimension (height) is always represented as the vertical axis, and the z dimension (depth) is always represented as going into or out of the paper.
Unfortunately, C++ uses a different convention and reverses the ordering of the axis. When you define a 1D array, there is no confusion, because there is only one dimension. Here is how C++ arrays are defined:
int array1d[width];
int array2d[height][width]; int array3d[depth][height][width];
This doesn’t look like such a large deal on the surface. This is because when deal- ing with multi-dimensional arrays, the axes are largely arbitrary to the users’ needs. For example, you could create a simple 2D array that keeps track of the different types of monsters in a game. One axis of the array represents the size of the mon- sters (small, medium, large), and the other axis represents the types of monsters (goblin, orc, troll). Does it matter which axis is defined first? In this case, it doesn’t seem to matter. Declaring the monster array either way seems to be acceptable:
int monsters[SIZES][TYPES]; int monsters[TYPES][SIZES];
So as long as your axes are always the same for the arrays, it should cause no prob- lems. The only time the ordering of your axes matters is when you use static multi- dimensional arrays and you pass them into functions, which I discuss in the next section.
119
Native Multi-Dimensional Arrays
Passing Multi-Dimensional Arrays to
Functions
Multi-dimensional arrays can be passed into functions just like normal arrays can. There are several ways to do this.
The most popular way is to have the function assume that it will be receiving an array of a specific size, like this:
void Function( int p_array2d[4][5], int p_array3d[2][4][2] );
This function can accept a 2D array with dimensions 4 5 and a 3D array with dimensions 2 4 2 (technically, in both cases, the compiler ignores the first dimension, so passing a 6 5 and a 3 4 2 array would work perfectly fine as well. You’ll see why this is later on). If you try passing in a 2D array or 3D array with different dimensions, it won’t work and will give you a compiler error.
That works fine, but what happens when you want to pass arrays that don’t have specific sizes? You could easily do this with a 1D array by neglecting the number in the function call, but you have no such luck with multi-dimensional arrays. The fol- lowing line of code is invalid in C++:
void Function( int p_array2d[][] );
In MSVC6, this will generate an error message: error C2087: ‘<Unknown>’ : missing subscript. Of course, if you had no idea that this code was invalid, that error would make no sense.
So why can’t you do this in C++? Remember how the compiler accesses an element within the 2D array: it multiplies the row number times the width of each row and then adds the column number. Trying to pass in a 2D array without a specified width causes a problem, because then the compiler will not have any idea how to access a particular row. So to pass a 2D array into a function, you are required to at least give the width of the array as a parameter, like this:
void Function( int p_array[][4] );
This function accepts any 2D array with a width of 4. You can pass in a 1 4 array or a 2 4 array or a 100 4 array. However, you cannot pass in an array with a dif- ferent width. C++ will not let you.
Note that reversing the order of the subscript will cause a compiler error. This is invalid:
The same applies to 3D arrays, too, except with 3D arrays, you need to know the width and the height to access any given cell, so you can only pass in 3D arrays with a fixed width and height.
void Function1( int p_array[][][] ); void Function2( int p_array[][][5] ); void Function3( int p_array[][4][5] );
Functions 1 and 2 are both invalid; they will not compile. Function 2 will not com- pile for the same reason a 2D array with no sizes won’t compile: you need both a height and a width to access a 3D array. Function 3 is the only function that will compile, and it only accepts arrays with a height of 4 and a width of 5, such as 1 4 5 or 3 4 5 or 100 4 5.
As it turns out, any multi-dimension array with N dimensions requires N-1 dimen- sion sizes to access any element within the array. Therefore, a 4D array will require three static dimension sizes, and a 5D array will require four static dimension sizes, and so on:
void Function4( int p_array[][3][2][3] ); void Function5( int p_array[][4][5][4][3] ); void Function6( int p_array[][3][6][7][4][5] );
This basically means that for any array, no matter how many dimensions it has, you can only have one dimension that varies in size when you pass it into a function.