如何在C++中创建一个连续的2D数组

gdrx4gfi  于 11个月前  发布在  其他
关注(0)|答案(7)|浏览(93)

我想在C++中创建一个返回连续2D数组的函数。
使用以下命令创建阵列没有问题:

int (*v)[cols] = new (int[rows][cols]);

字符串
但是,我不确定如何将此数组作为函数的一般类型返回。函数是:

NOT_SURE_WHAT_TYPE create_array(int rows, int cols)
  {
        int (*v)[cols] = new (int[rows][cols]);
        return v;
  }


我尝试了double*[]和double**,但都不起作用。我不想使用double*,因为我想从外部访问这个2D数组。
相关问题:How do I declare a 2d array in C++ using new?

ijnw1ujt

ijnw1ujt1#

如果你想创建一个数据连续的数组,而不想创建一个一维数组(即你想使用[][]语法),那么下面应该可以工作。它创建一个指针数组,每个指针指向内存池中的一个位置。

#include <iostream>
#include <exception>

template <typename T>
T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T())
{
   if (nrows == 0)
        throw std::invalid_argument("number of rows is 0");
   if (ncols == 0)
        throw std::invalid_argument("number of columns is 0");
   T** ptr = nullptr;
   T* pool = nullptr;
   try
   {
       ptr = new T*[nrows];  // allocate pointers (can throw here)
       pool = new T[nrows*ncols];  // allocate pool (can throw here)
       T* startpool = pool;  // Remember the start of the pool
 
       // now point the row pointers to the appropriate positions in
       // the memory pool
       for (unsigned i = 0; i < nrows; ++i, pool += ncols )
           ptr[i] = pool;

       // Initialize pool
       std::fill(startpool, startpool + nrows*ncols, val);
       return ptr;
   }
   catch (std::bad_alloc& ex)
   {
       delete [] ptr; // either this is nullptr or it was allocated
       throw ex;  // memory allocation error
   }
}

template <typename T>
void delete2DArray(T** arr)
{
   delete [] arr[0];  // remove the pool
   delete [] arr;     // remove the pointers
}

int main()
{
   try 
   { 
      double **dPtr = create2DArray<double>(10,10);
      dPtr[0][0] = 10;  // for example
      delete2DArray(dPtr);  // free the memory
   }
   catch(std::bad_alloc& ex)
   {
      std::cout << "Could not allocate array";
   }
}

字符串
请注意,只进行了2次分配。这不仅因为分配的数量更少而更有效,而且如果内存分配失败,我们现在有更好的机会回滚已分配的内存,这与在非连续内存中分配2D数组的“传统”方式不同:

// The "traditional" non-contiguous allocation of a 2D array (assume N x M)
T** ptr;
ptr = new T*[N];
for (int i = 0; i < N; ++i)
   ptr[i] = new T [M]; // <<-- What happens if new[] throws at some iteration?


如果new[]for循环的操作过程中在某处抛出异常,则必须回滚之前发生的对new[]的所有成功调用--这需要更多的代码并增加了复杂性。
请注意如何在连续版本中释放内存--在连续分配时,只对delete[]进行两次调用,而不是对每行调用delete[]
此外,由于数据位于连续内存中,因此假设数据位于连续内存中的算法,函数等,就像一维数组一样,现在可以通过指定M*N矩阵的开始和结束范围来使用:
[&array[0][0], &array[M-1][N])
举例来说,您可以:
std::sort(&myArray[0][0], &myArray[M-1][N]);
将按升序对整个矩阵进行排序,从索引[0][0]开始,直到最后一个索引[M-1][N-1]
你可以通过使它成为一个真正的类来改进设计,而不是将分配/释放作为两个单独的函数。
编辑:正如注解中所说,这个类并不像RAII。我把它留给读者作为练习。上面的代码中缺少的一件事是在创建这样一个数组时检查ncol和nCols是否> 0。
编辑二:添加了一个try-catch,以确保在尝试分配内存时抛出std::bad_alloc异常时,正确回滚内存分配。
编辑:对于一个类似于上面的三维数组代码示例,请参阅此答案。其中包括在分配失败时回滚分配的代码。
编辑:添加了基本的RAII类:

#include <exception>
#include <iostream>

template <typename T>
class Array2D
{
    T** data_ptr;
    unsigned m_rows;
    unsigned m_cols;

    T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T())
    {
        T** ptr = nullptr;
        T* pool = nullptr;
        try
        {
            ptr = new T*[nrows];  // allocate pointers (can throw here)
            pool = new T[nrows*ncols];  // allocate pool (can throw here)
            T* startpool = pool;  // Remember the start of the pool

            // now point the row pointers to the appropriate positions in
            // the memory pool
            for (unsigned i = 0; i < nrows; ++i, pool += ncols )
                ptr[i] = pool;

            // Initialize pool
            std::fill(startpool, startpool + nrows*ncols, val);       
            return ptr;     
        }
        catch (std::bad_alloc& ex)
        {
            delete[] ptr; // either this is nullptr or it was allocated
            throw ex;  // memory allocation error
        }
    }

    void deleteAllData() noexcept
    {
        if (data_ptr)
        {
            delete[] data_ptr[0];  // remove the pool
            delete[] data_ptr;     // remove the pointers
            data_ptr = nullptr;  
        }
    }

    public:
        typedef T value_type;
        T** data() {
            return data_ptr;
        }

        unsigned get_rows() const {
            return m_rows;
        }

        unsigned get_cols() const {
            return m_cols;
        }

        Array2D() : data_ptr(nullptr), m_rows(0), m_cols(0) {}
        Array2D(unsigned rows, unsigned cols, const T& val = T())
        {
            if (rows == 0)
                throw std::invalid_argument("number of rows is 0");
            if (cols == 0)
                throw std::invalid_argument("number of columns is 0");
            data_ptr = create2DArray(rows, cols, val);
            m_rows = rows;
            m_cols = cols;
        }

        ~Array2D()
        {
            deleteAllData();
        }

        Array2D(const Array2D& rhs) : m_rows(rhs.m_rows), m_cols(rhs.m_cols)
        {
            data_ptr = create2DArray(m_rows, m_cols);
            std::copy(&rhs.data_ptr[0][0], &rhs.data_ptr[m_rows-1][m_cols], &data_ptr[0][0]);
        }

        Array2D(Array2D&& rhs) noexcept
        {
            data_ptr = rhs.data_ptr;
            m_rows = rhs.m_rows;
            m_cols = rhs.m_cols;
            rhs.data_ptr = nullptr;
        }

        Array2D& operator=(Array2D&& rhs) noexcept
        {
            if (&rhs != this)
            {
                deleteAllData();
                swap(rhs, *this);
            }
            return *this;
        }

        void swap(Array2D& left, Array2D& right) noexcept
        {
            std::swap(left.data_ptr, right.data_ptr);
            std::swap(left.m_cols, right.m_cols);
            std::swap(left.m_rows, right.m_rows);
        }

        Array2D& operator = (Array2D rhs)
        {
            swap(*this, rhs);
            return *this;
        }

        T* operator[](unsigned row)
        {
            return data_ptr[row];
        }

        const T* operator[](unsigned row) const
        {
            return data_ptr[row];
        }

        void create(unsigned rows, unsigned cols, const T& val = T())
        {
            *this = Array2D(rows, cols, val);
        }
};

int main()
{
    try
    {
        Array2D<double> dPtr(10, 10);
        std::cout << dPtr[0][0] << " " << dPtr[1][1] << "\n";
    }
    catch (std::exception& ex)
    {
        std::cout << ex.what();
    }
}

jq6vz3qz

jq6vz3qz2#

由于你使用的是C++而不是C,我建议你使用一个vector,而不是乱用new/delete。
你可以像这样定义一个连续的内存块:

std::vector<int> my_matrix(rows*cols);

字符串
现在你可以用类似2d数组的方式访问这个向量,公式是i*n + j,i是行索引,j是列索引,n是行的长度:

my_matrix[i*n + j];


这和用array[i][j]访问一个二维数组是一样的。但是现在你有了一个连续的内存块的优势,你不需要担心new/delete,你可以很容易地用函数共享和返回这个向量对象。

hpxqektj

hpxqektj3#

除非在编译时知道两个维度的大小,否则您没有太多的选择:分配一个rows*cols的数组,并使用整数乘法和加法来滚动您自己的2D索引。将其 Package 在一个类中可以生成一个漂亮的语法,用于使用方括号操作符访问数组元素。由于您的数组是2D的,您将需要使用代理(也称为“surrogate”)对象进行第一级数据访问。
下面是一个使用std::vector<T>在动态内存中维护连续内存区域的小示例代码:

template<class T>
class Array2D {
    vector<T> data;
    size_t cols;
public:
    // This is the surrogate object for the second-level indexing
    template <class U>
    class Array2DIndexer {
        size_t offset;
        vector<U> &data;
    public:
        Array2DIndexer(size_t o, vector<U> &dt) : offset(o), data(dt) {}
        // Second-level indexing is done in this function
        T& operator[](size_t index) {
            return data[offset+index];
        }
    };
    Array2D(size_t r, size_t c) : data (r*c), cols(c) {}
    // First-level indexing is done in this function.
    Array2DIndexer<T> operator[](size_t index) {
        return Array2DIndexer<T>(index*cols, data);
    }
};

字符串
您现在可以将Array2D<int>当作内建C++数组来使用:

Array2D<int> a2d(10, 20);
for (int r = 0 ; r != 10 ; r++) {
    for (int c = 0 ; c != 20 ; c++) {
        a2d[r][c] = r+2*c+1;
    }
}


Running demo on ideone的一个。

wdebmtf2

wdebmtf24#

处理原始内存资源通常是令人讨厌的。最好的办法是一个简单的 Package 器:

struct array2D : private std::vector<int>
{
  typedef  std::vector<int> base_type;

  array2D() : base_type(), height_(0), width_(0) {}
  array2D(std::size_t h, std::size_t w) : base_type(h*w), height_(h), width_(w);

  int operator()(std::size_t i, std::size_t j) const 
  { 
     return base_type::operator[](i+j*height_); 
  }

  int& operator()(std::size_t i, std::size_t j) 
  { 
     return base_type::operator[](i+j*height_); 
  }

  std::size_t rows() const { return height_; }
  std::size_t cols() const { return width_; }

  private:
  std::size_t height_, width_;
}

字符串
私有继承让你可以从vector中获取所有的好处,只需要添加你的2D构造函数。Resslogy管理是免费的,因为vector ctor/dtor会发挥它们的魔力。显然,i+h*j可以改变为你想要的任何存储顺序。
vector< vector< int >>是2D的,但在内存中不会连续。
你的功能变成:

array2D create_array(int rows, int cols)
{
  return array2D(cols,rows);
}


编辑:
您还可以使用usign子句检索其他向量接口部分,如开始/结束或大小,以使私有继承成员函数再次公开。

yv5phkfx

yv5phkfx5#

在我看来,没有一种在标准C++中定义2D动态数组的方法是完全令人满意的。
你最终不得不滚动你自己的解决方案。幸运的是,在Boost中已经有了一个解决方案。boost::multi_array

#include "boost/multi_array.hpp"

template<typename T>
boost::multi_array<T, 2> create_array(int rows, int cols) {
  auto dims = boost::extents[rows][cols];
  return boost::multi_array<T, 2>(dims);
}

int main() {
  auto array = create_array<int>(4, 3);
  array[3][2] = 0;
}

字符串
Live demo的一个。

jogvjijk

jogvjijk6#

PaulMcKenzie提供的“基本RAll”类是一个很好的解决方案。在我使用它的过程中,我确实发现了一个内存泄漏,在下面显示的版本中得到了修复。
内存泄漏是由于Array2D& operator=(Array2D&& rhs) noexcept的问题。
需要删除rhs.m_dataPtr = nullPtr语句,以允许rhs析构函数删除从lhs交换的原始数据(池和指针)。
下面是PaulMcKenzie提供的“基本RAll”类的更正代码

template <typename T>
class Array2D
{
    T** data_ptr;
    unsigned m_rows;
    unsigned m_cols;

    T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T())
    {
        T** ptr = nullptr;
        T* pool = nullptr;
        try
        {
            ptr = new T*[nrows];  // allocate pointers (can throw here)
            pool = new T[nrows*ncols]{ val };  // allocate pool (can throw here)

            // now point the row pointers to the appropriate positions in
            // the memory pool
            for (unsigned i = 0; i < nrows; ++i, pool += ncols)
                ptr[i] = pool;

            // Done.
            return ptr;
        }
        catch (std::bad_alloc& ex)
        {
            delete[] ptr; // either this is nullptr or it was allocated
            throw ex;  // memory allocation error
        }
    }

public:
    typedef T value_type;
    T** data() {
        return data_ptr;
    }

    unsigned get_rows() const {
        return m_rows;
    }

    unsigned get_cols() const {
        return m_cols;
    }

    Array2D() : data_ptr(nullptr), m_rows(0), m_cols(0) {}
    Array2D(unsigned rows, unsigned cols, const T& val = T())
    {
        if (rows == 0)
            throw std::invalid_argument("number of rows is 0");
        if (cols == 0)
            throw std::invalid_argument("number of columns is 0");
        data_ptr = create2DArray(rows, cols, val);
        m_rows = rows;
        m_cols = cols;
    }

    ~Array2D()
    {
        if (data_ptr)
        {
            delete[] data_ptr[0];  // remove the pool
            delete[] data_ptr;     // remove the pointers
        }
    }

    Array2D(const Array2D& rhs) : m_rows(rhs.m_rows), m_cols(rhs.m_cols)
    {
        data_ptr = create2DArray(m_rows, m_cols);
        std::copy(&rhs.data_ptr[0][0], &rhs.data_ptr[m_rows-1][m_cols], &data_ptr[0][0]);
    }

    Array2D(Array2D&& rhs) noexcept
    {
        data_ptr = rhs.data_ptr;
        m_rows = rhs.m_rows;
        m_cols = rhs.m_cols;
        rhs.data_ptr = nullptr;
    }

    Array2D& operator=(Array2D&& rhs) noexcept
    {
        if (&rhs != this)
        {
            swap(rhs, *this);
        }
        return *this;
    }

    void swap(Array2D& left, Array2D& right)
    {
        std::swap(left.data_ptr, right.data_ptr);
        std::swap(left.m_cols, right.m_cols);
        std::swap(left.m_rows, right.m_rows);
    }

    Array2D& operator = (const Array2D& rhs)
    {
        if (&rhs != this)
        {
            Array2D temp(rhs);
            swap(*this, temp);
        }
        return *this;
    }

    T* operator[](unsigned row)
    {
        return data_ptr[row];
    }

    const T* operator[](unsigned row) const
    {
        return data_ptr[row];
    }

    void create(unsigned rows, unsigned cols, const T& val = T())
    {
        *this = Array2D(rows, cols, val);
    }
};

int main()
{
    try
    {
        Array2D<double> dPtr(10, 10);
        std::cout << dPtr[0][0] << " " << a2[0][0] << "\n";
    }
    catch (std::exception& ex)
    {
        std::cout << ex.what();
    }
}

字符串

nzk0hqpo

nzk0hqpo7#

我认为你应该写一个简单的类来 Package 一个一维数组。然后你可以用operator()重载来实现一个二维数组来获取值,并解构func来释放内存。代码如下:

#include <assert.h>

template <typename T>
class Array_2D
{
private:
    T *data_inside;
public:
    int size[2];
    Array_2D(int row, int column);
    ~Array_2D();

    // 
    T operator()(int index1, int index2){
        return data_inside[get_index(index1, index2)];
    }

    int get_index(int index1, int index2){
        if(index1>=0 and index1<size[0] and index2>=0 and index2<=size[1]){
            return index1*size[0] + index2;
        }else{
            assert("wrong index for array!" == "True");
        }
    }

};

template <typename T>
Array_2D<T>::Array_2D(int row, int column)
{
    size[0] = row;
    size[1] = column;
    data_inside = new T[row*column];
}

template <typename T>
Array_2D<T>::~Array_2D()
{
    // 使用析构函数,自动释放资源
    delete[] data_inside;
}

字符串

相关问题