Arrary Parameters

Started by
6 comments, last by Zahlman 17 years ago
In C++: I have two questions. If I want to pass a multidimensional array to a function, I know I need to do something like:
void doSomething(int arrayHere[][DEPTH]){ /* ... */ };

However, this seems messy to me. Is it? Is there some method of using pointers and would that method be considered normal/better practice? Second, how would you return (multidimensional)arrays bar referencing a parameter? Pointers again? I'm pretty sure, from reading around on the net, it should be something like:
int *doSomething(){ /* ... */ };

But I'm unsure if that would make sense as I am unable to get my head around it. Thanks in advance.
James
Advertisement
Unfortunately with 2D arrays in C++, to properly access them you always need to know the "width" dimension. It is possible to pass them as pointers, but you still need to pass the width dimension as a parameter to get at the data. This is why C++ makes you include the dimension in the function parameter. If you don't know how 2D arrays work, I'll explain why this is the case. If you know already or don't care, then stop reading now.

As you probably already know, on their most basic level an array in C++ is just a pointer to the start of a string of sequential memory locations (these locations are the elements in the array). This is why for 1D arrays, you can pass them around as if they were pointers with no problems. When you access an element in the array, you're actually doing something like this: go to the memory location where the array starts, move up in memory an amount equal to the number of the element requested (times the number of bytes in the data-type), and grab the data at that memory location.

So, as for 2D arrays...when you make a static array in C++, you're really just making a 1D array whose length is equal to the length * width of the specified 2D array. So something like "int twoDeeArray[HEIGHT][WIDTH]" is just converted to "int twoDeeArray[HEIGHT * WIDTH]". To access them, the two elements also have to be converted. The formula for this is as follows: "twoDeeArray[y][x] -> twoDeeArray[(y * WIDTH) + x]".

So knowing this, you might notice that it should be possible to pass a 2D static array as a parameter to a function by simply passing it as a pointer and then accessing the elements as you would in a 1D array. However this brings us back to the original problem, which is that you can't actually access the element without knowing value of WIDTH. This means we still have to pass WIDTH as a parameter to function. The upside however, is that it can allow your function to be more flexible (IE, it can work for 2D arrays of different sizes). Here's a little code sample that demonstrates what I was talking about:

#include <iostream>using namespace std;void print2DArray (int* p, int width, int height){	for (int y = 0; y < height; y++)		for (int x = 0; x < width; x++)			cout << p[(y * width) + x] << endl;}int main (){	int twoDeeArray [3][2];	twoDeeArray [0][0] = 0;	twoDeeArray [0][1] = 1;	twoDeeArray [1][0] = 2;	twoDeeArray [1][1] = 3;	twoDeeArray [2][0] = 4;	twoDeeArray [2][1] = 5;	print2DArray ((int*)twoDeeArray, 2, 3);	return(0);}
As MJP said to pass multidimensional array to function you need to know the width of the array.

About returning array from function, array is actually a pointer, 2d array is pointer to pointer and etc.
So you dont need to return it is like if you want to change the value of
int ok;
inside
changeOkFunc(int);

you can do:
1. int changeOkFunc(int ok){ ok = 4; return ok;}
than you call it like ok = changeOkFunc(ok);
or
2. void changeOkFunc(int *ok){ *ok = 2;}
and you call it like changeOkFunc(&ok);

So arrays are pointer so there is no need to return them from function.

I would love to change the world, but they won’t give me the source code.

I don't know where and for what reason you need to do it, but in almost any case, there's other, cleaner, ways to achieve the same.

Let's say we're talking about a 2D grid, a level of some sort, and the function that operates on it will fill it with values read from a file. In this case, I would wrap up the 2-dimensional array inside a Level class, and write a member function named LoadLevel(std::string fileName). This function has direct access to the grid data, so now there's no need to pass it around anymore.

However, that may not be a solution in your case. What you could do, is wrap up the array in a class (it's not some random data after all, right?), and pass a reference to it to the function. A pointer would do, too, but basically, references are a safer version of pointers: they always reference an existing object, and can't be changed to point to another object. Pointers can be a source of subtle problems, so I avoid them whenever I can.


However, some say arrays are evil. That may sound gross, but that article is a good, insightfull read. Following the given advice there will make you more productive. :)
Create-ivity - a game development blog Mouseover for more information.
Thanks everyone!

Captain: That is pretty much what I am doing. I am creating a Tetris clone and I figured that I can update the 2D array to keep track of where my blocks are. Is this a legitimate idea? I have a render class that will read through the array and create a block, while it runs through a for loop, depending on the particular depth values. This seems to be logical in my head (as I have not started to code it), but I am unsure how it will look "on paper".

Anyway, I made this post more for my general knowledge too. Thanks again.
James
Sure, for a simple game like Tetris, a 2D array is fine. I would still put it inside a Field class as a private member variabele, and provide functions like GetTile(int x, int y) and SetTile(int x, int y). It's more work, but also more flexible: you can exchange the 2D-array for a 1D-array, or a vector, or list, or something else, and the only thing that you have to change is those GetTile and SetTile functions. As long as they still do their job correctly, it's as if nothing has changed, as viewed from the rest of the code.

Having a renderer draw blocks based on a passed array isn't really flexible, but it'll work. However, with the above approach, the Renderer could call the GetTile function various times and draw the appropriate blocks.

Or, you could give the Renderer a DrawBlock() function, and the Field class could get a Draw() function, in which it calls Renderer's DrawBlock() function for every tile that should be drawn.


Personally, I strive to keep classes as self-contained as possible. Ideally, Renderer shouldn't even know about Fields or tile arrays or blocks. It should only know how to load images and how to display them. So, for example, no DrawBlock() function anymore, but a LoadImage() and a DrawImage() function. This may take some time to get right, but then it'll be instantly useable for other purposes as well, like drawing the high-score table, drawing the menu's, etc. Otherwise you would've had to write additional Renderer functions, but now, your game code just tells it to load some button images and to draw them on the screen.
Create-ivity - a game development blog Mouseover for more information.
Thanks for the tips.

At the moment, because I am beginning, I may chose the easier and less flexible method. However, if I find out that moving around this 2D is too difficult. I will switch to your method.

But yeah, the way I was planing the render class was to have a draw function that would get passed coordinates to draw. I would have another method that would then load the textures centrally and then apply them.

Currently, I have a game class, a block class (for seperate creation and movement), a render class, and now possibly a grid class to deal with the map.

As I get farther I will make a new thread. I appreciate it!
James
I can't let this thread pass without mentioning:

- std::vector (for 1d storage where the size is variable)
- boost::array (for 1d storage where the size is fixed, like an ordinary array; but because it's a struct, you can return one from a function, and passing them is much cleaner. Also, a boost::array of boost::arrays will lay things out in memory basically the same way as a normal 2D array, but be much nicer to work with in general.)
- boost::multi_array (for multiple-dimension storage where each dimension is potentially variable, BUT the available storage space is always rectangular, and packed neatly in memory just like 'normal' multiple-D arrays. This is different from what you get by making a std::vector of std::vectors, which lets you set each "row length" separately.)

This topic is closed to new replies.

Advertisement