简体   繁体   中英

Interpret pointer as a two-dimensional array

I have an array transpose function that mirrors arrays like so:

[1][2][3]            [1][4][7]
[4][5][6]    ===>    [2][5][8]
[7][8][9]            [3][6][9]

Here is the concept of the algorithm I came up:

size_t numvars = variables.size(), numsegs = segments.size();

for (int v = 0; v < numvars; ++v) {
  for (int s = 0; s < numsegs; ++s) {
    float * row = in + (s * numvars);
    out[v][s] = *(row + v);
  }
}

When proceeding through the algorithm by hand, all works as expected. I would like to implement the function such that it takes two pointers to two-dimensional arrays, one with the source array and the second to a memory buffer that will hold the transposed array. When I try to implement the algorithm in C++ in a function I get the following error:

void transposeArray(float * in, float * out) throw()
{
  size_t numvars = variables.size(), numsegs = segments.size();

  for (int v = 0; v < numvars; ++v) {
    for (int s = 0; s < numsegs; ++s) {
      float * row = in + (s * numvars);
      out[v][s] = *(row + v);
    }
  }
}

out[v][s] = *(row + v); invalid types 'float[int]' for array subscript

Is this because the compiler does not know that it should treat the second float * out as a 2-dimensional array? If so, what's the fix?

Well, your out variable is a pointer to a float , so derefencing it in out[v] yields a float value. And you can't subscript a float value.

What you need to do, is to calculate the array index for the out 2D array the same way you calculated it for the in 2D array:

void transposeArray(float * in, float * out) throw() {
  size_t numvars = variables.size(), numsegs = segments.size();

  for (int v = 0; v < numvars; ++v) {
    for (int s = 0; s < numsegs; ++s) {
      out[v*numsegs + s] = in[s*numvars + v];
    }
  }
}

Note:
It's a pity that you are apparently writing this code in C++, and not in C. Because in C, you can do this in a quite nice way:

void transposeArray(int numvars, int numsegs, float (*in)[numvars], float (*out)[numsegs]) {
  for (int v = 0; v < numvars; ++v) {
    for (int s = 0; s < numsegs; ++s) {
      out[v][s] = in[s][v];
    }
  }
}

The trick here is that the in and out arguments are declared to be pointers to line arrays, which allows the language to invoke the same pointer arithmetic magic that it uses when you declare an array with float myArray[numvars][numsegs]; . This pointer arithmetic boils down to do the same thing implicitly which v*numsegs + s does explicitly.

The advantage of C is, that it allows for array types with run time sizes , something C++ does not do. Of course, if numvars and numsegs are compile time constants, you can do the equivalent in C++.

Here is how you can write your function if you want to use the same function signature as the one you gave in the question:

void transposeArray(float * in, float * out) throw() {
  size_t numvars = variables.size(), numsegs = segments.size();
  float (*in2D)[numvars] = (void*)in;
  float (*out2D)[numsegs] = (void*)out;

  for (int v = 0; v < numvars; ++v) {
    for (int s = 0; s < numsegs; ++s) {
      out2D[v][s] = in2D[s][v];
    }
  }
}

The problem is already solved but I wanted to post a C++-ish solution to your 2D array handling problem. If you want to treat a pointer (basically a 1 dimensional array) as a 2D array whose size is known only at runtime then you could employ one of the following helper templates. They not only make your code look much nicer but they also help you to catch wrong out-of-range indexes in debug mode and in release they compile basically to the same code as your hand-written harder-to-read code. Today's C++ compilers are extremely good at optimizing away such simple methods/functions:

#include <assert.h>
#include <stdio.h>


// An implementation that performs range checking on both dimension and
// has nice array subscript syntax. This has some performance overhead
// in debug mode but in release the compiler does the optimization magic.
template <typename T>
class PtrArray2D
{
public:
    class SubDim
    {
    public:
        SubDim(T* p, int d1) : m_Ptr(p), m_D1(d1) {}
        T& operator[](int d1)
        {
            assert(d1>=0 && d1<m_D1);
            return m_Ptr[d1];
        }
        const T& operator[](int d1) const
        {
            assert(d1>=0 && d1<m_D1);
            return m_Ptr[d1];
        }
    private:
        T* m_Ptr;
        int m_D1;
    };

    PtrArray2D(T* p, int d0, int d1) : m_Ptr(p), m_D0(d0), m_D1(d1) {}
    SubDim operator[](int d0)
    {
        assert(d0>=0 && d0<m_D0);
        return SubDim(m_Ptr + m_D1*d0, m_D1);
    }
    const SubDim operator[](int d0) const
    {
        assert(d0>=0 && d0<m_D0);
        return SubDim(m_Ptr + m_D1*d0, m_D1);
    }
    int GetD0() const { return m_D0; }
    int GetD1() const { return m_D1; }
private:
    T* m_Ptr;
    int m_D0;
    int m_D1;
};

template <typename T>
inline PtrArray2D<T> MakePtrArray2D(T* p, int d0, int d1)
{
    return PtrArray2D<T>(p, d0, d1);
}


template <typename T>
void Transpose(const PtrArray2D<T>& src, PtrArray2D<T>& dest)
{
    assert(src.GetD0() == dest.GetD1() && src.GetD1() == dest.GetD0());
    for (int i=0,i_e=src.GetD0(); i<i_e; ++i)
    {
        for (int j=0,j_e=src.GetD1(); j<j_e; ++j)
        {
            dest[j][i] = src[i][j];
        }
    }
}


int test()
{
    const int DIMENSION0 = 5;
    const int DIMENSION1 = 2;
    const int ARRAY_SIZE = DIMENSION0*DIMENSION1;
    float* p = new float[ARRAY_SIZE];
    for (int i=0; i<ARRAY_SIZE; ++i)
        p[i] = (float)i;

    PtrArray2D<float> arr0(p, DIMENSION0, DIMENSION1);
    printf("%f, %f, %f\n", arr0[0][0], arr0[0][1], arr0[1][0]);
    arr0[1][0] = 8;
    // The statement below will cause an assert as the second dimension is out of range.
    //arr0[0][2];

    float* q = new float[ARRAY_SIZE];
    PtrArray2D<float> arr1(q, DIMENSION1, DIMENSION0);

    Transpose(arr0, arr1);

    // OR if you want to create helper array object on-the fly only for the time of execution of Transpose():
    //Transpose(MakePtrArray2D(p, DIMENSION0, DIMENSION1), MakePtrArray2D(q, DIMENSION1, DIMENSION0));

    printf("%f, %f, %f\n", arr1[0][0], arr1[0][1], arr1[1][0]);

    return 0;
}

The compiler doesn't know the dimensions of the array.

So if you keep your simple pointers then you need to do the same addressing arithmetic for out as you currently do for in .

Namely, calculate a row pointer, use an offset into that row, instead of out[v][s] .


Technically,

  • the expression out[v] produces a reference to a float , and

  • if the float is denoted f , the expression f [s] is then just invalid: you can't index a float value.


As general advice, unless you're using some framework that uses float everywhere, such as SFML, then just use double . That's the default floating point type in C and C++. Eg, the literal 3.14 is of type double , not float .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM