C++: getting the row size of a multidimensional array passed to a function
I'm trying to write a function that will print out the contents of a multidimensional array. I know the size of the columns, but not the size of the rows.
EDIT: Since I didn't make this clear, the arrays passed to this function are NOT dynamically allocated. The sizes are known at compile time.
I am testing it using a 3x2 array. Here is the function as it stands:
void printArrays(int array1[][2], int array2[][2]) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
cout << "\narray1[" << i << "][" << j << "] = "
<< setfill('0') << setw(2) << array1[i][j]
<< "\tarray2[" << i << "][" << j << "] = "
<< setfill('0') << setw(2) << array2[i][j];
}
}
}
Obviously, this only works if I know the size of "i" is 3 (it is in this case). Ideally, however, I would like the function to work no matter what the size of the first dimension.
I thought I would be able to do this using the sizeof() function, e.g.
int size = sizeof(array1);
... and do some math from there.
Here's the odd part. If I use the sizeof() function inside the array, it returns a value of 4. I can use pointer notation to dereference the array:
int size = sizeof(*array1);
... but this actually returns a value of 8. This is odd, because the total size should be rows(which = 3) * columns(= 2) * sizeof(int)(= 4), or 24. And, indeed, this is the result, when I use sizeof(*array1) outside of the function.
Does anyone know what is going on here?开发者_运维问答 More importantly, does anyone have a solution?
The answer is that you can not do this. You must pass the number of rows as an argument to the function, or use an STL container such as std::vector
or std::array
.
sizeof
is computed compile time; sizeof
is never useful in determining dynamic size of objects in C/C++. You (yourself, the programmer) can always calculate sizeof(x)
just from looking at code and header files since sizeof
counts the number of bytes used to represent the object. sizeof(*array1)
will always be 8 since array1[i]
is an array of two ints
and 4==sizeof(int)
. When you declare int array1[][2]
this is equivalent to int *array1[2]
. That is, array1
is a pointer to arrays of two integers. sizeof(array1)
is therefore 4 bytes, since it takes 4 bytes on your machine to represent a pointer.
You can accomplish this, to some degree, by using templated functions. The caveats are:
- You will need to include the function definition (not just declaration) anywhere it is used
- It will only work when array size is fixed at compile time
- You will generate a separate function for every call to the function, resulting in some code bloat
I am working form the code on this blog post by Kevin Heifner.
template <typename T>
struct array_info
{
};
template <typename T, size_t N, size_t M>
struct array_info<T[N][M]>
{
typedef T type;
enum { size1 = N, size2 = M };
};
template <typename A1>
void printArrays(A1 array1, A1 array2) {
size_t A1_i = array_info<A1>::size1;
size_t A1_j = array_info<A1>::size2;
for (size_t i = 0; i < A1_i; i++) {
for (size_t j = 0; j < A1_j; j++) {
cout << "\narray1[" << i << "][" << j << "] = "
<< setfill('0') << setw(2) << array1[i][j]
<< "\tarray2[" << i << "][" << j << "] = "
<< setfill('0') << setw(2) << array2[i][j];
}
}
}
You can get the size of both arrays with some template magic:
template< typename T, std::size_t n, std::size_t m >
void foo( T(&)[n][m] ) {
std::cout << n << " " << m << std::endl;
}
int main() {
int a[3][3];
int b[2][5];
foo(a); foo(b);
}
This only works for arrays whose bounds are known at compile time and not for dynamically allocated arrays.
In any case: You should use std::vector
or boost::multiarray
.
The size is 8 outside the function because you're dereferencing the first array, which gives you the column size (2) times the size of an int
(4). If you wanted 24, you'd do sizeof(array)
(outside the function). The answer is 4 inside the function because it treats array
like a pointer, the size of which is 4 bytes.
However, to reliably get the size of arrays that have been passed to functions, you either have to pass the size or use something like vector
.
A very simple way to do it, without needing vectors, templates, classes, or passing the size of the array, is to have a last row of data that contains something unique such as the following example, where a -1 is used in the last row, first column:
#define DATA_WIDTH 7
const float block10g[][DATA_WIDTH] = {
{0, 15, 25, 50, 75, 100, 125},
{2.12, 0, 1.000269, 3.000807, 4.24114056, 5.28142032, 6.001614},
{6.36, 0, 1.2003228, 3.84103296, 6.24167856, 8.16219504, 10.08271152},
{10.6, 0, 1.2003228, 4.4011836, 7.2019368, 9.2024748, 11.8031742},
{21.2, 0, 2.000538, 6.001614, 8.002152, 10.4027976, 14.4038736},
{ -1}
};
const float block10g[][DATA_WIDTH] = {
{0, 20, 50, 100, 150, 200, 250},
{2.12, 0, 2.88077472, 5.04135576, 5.84157096, 6.08163552, 5.84157096},
{6.36, 0, 3.84103296, 7.92213048, 11.52309888, 13.56364764, 14.4038736},
{10.6, 0, 3.8010222, 8.8023672, 13.003497, 16.4044116, 18.4049496},
{21.2, 0, 4.4011836, 9.2024748, 14.003766, 18.4049496, 22.4060256},
{ -1}
};
printArrays(block10g,block20g);
Then just break out of the loop(s) when you reach that unique value:
void printArrays(float array1[][DATA_WIDTH], float array2[][DATA_WIDTH]) {
for (int i = 0; array1[i][0]!=-1 && array2[i][0]!=-1 ; i++) {
for (int j = 0; j < DATA_WIDTH; j++) {
cout << "\narray1[" << i << "][" << j << "] = "
<< array1[i][j]
<< "\tarray2[" << i << "][" << j << "] = "
<< array2[i][j];
}
}
}
Simply use better arrays!
What I mean by that is you can make your own array class which wraps an array, or use some common libraries with such classes (e.g. boost). This is much more reliable, and likely is easier to reason about that straight-up C++ arrays.
One reason for this is if your write the function
void foo( int a[][2] )
{
// etc.
}
you don't actually have as many guarantees on the array as you might think. For example, it is not guaranteed that the second dimension of the array is two elements wide (I could be wrong about this point, as I don't have references on hand, but I'm pretty confident about it). This is because that actual signature for the function is
void foo( int ** );
This is because arrays degenerate to pointers when used in function declarations (which is why you're sizeof(array)
returns 4, since 4 bytes is the size of a pointer type on your machine). Also, you clearly have no guarantee on the size of the first dimension, so assuming it is going to be 3 is dangerous, and potentially the result of confusing bugs in the future.
This is where a custom array
class would be great, especially if it were templated. For example, a two dimensional array class could be declared like
template<typename T, int length1, int length2>
class Array2D
{
// Array2D's gutsy stuff to make it act kind of like T[length1][length2]
};
Using such an approach allows you to maintain all the information about the array in any situation, e.g.
void foo( Array2D<int, 3, 2> &array ) {}
Now you can decide the sizes every dimension in the array.
An added benefit, is that you can add bounds checking to your Array2D
class, which C++ array do not have. E.g. in a 3x2 array, you are able to access the fourth element in the first row, even though it's not conceptually valid. Such a common source of bugs can easily be eradicated by using an array class like Array2D.
There are some drawbacks, which is normal when using templates. The big one is that, because of the way template are instantiated, you have to define any templated classes in your header files, not in separate source files (technically, you can use the "export" keyword to do split the class up as normal, but this has limited support on major compilers).
As a last note (if you're interested, as I am), the situation becomes even better in C++0x (comming soon!) with the advent of variadic templates:
template<typenameT, int... lengths>
class Array
{
// etc...
};
Now all array types can be defined by a single class template. Never been easier.
精彩评论