STL(五):连续空间的二维数组实现

原创 2016年05月31日 10:56:30

这个不属于STL 中的内容,仅是个人补充使用

嘛~一开始是这样的。
假如我们需要新建一个二维数组,一般像下面这样用:

int row = 2, col = 3;
int array[2][3]; //1
//or
vector< vector<int> > v(row, vector<int>(col) );//2

//or 
int* array = new int[row*col];//3

//or 
int** array = new int*[row];  //4
//循环new

但是,如果在java 里面,一句搞定:

int[][] a = new int[row][col];

嗯,不得不说,实在是方便。事后也不需要delete 。
but,java 能够这么傲娇的使用,也是基于引用计数的。这有成本。

那讨论一下C++ 几种方式。
就1而言,只能是静态指定,不能在程序中动态分配,差评。

2 算是比较好的选择,但是,她会浪费掉空间。首先是初始化的时候,就会产生一个 vector<int>(3) 的副本。其次,每个维度的vector 都会有额外的三个迭代器空间浪费掉。空间的利用率不高。

3是用一维的数组进行动态分配,然后用指针的偏移量来访问。它的优点很多,空间连续,没有空间浪费,适用于任何的高维数组。但是缺点也很明显,需要动态回收,需要手动计算偏移量,不够直观。

4和3基本是一样的,但是要做的工作更多,循环new 和循环delete 必不可少,在我看来实在是糟糕。

我一般避免在程序的逻辑中直接new 和delete。暴露过多的new 和delete 实在不是一个好的习惯,一不小心就会忘记回收内存了。而且,万一在delete 语句之前就return 的话,内存就泄露了。

我看到java 的使用方式简洁到让我嫉妒,于是就想着自己写个二维的数组吧,基于连续的空间分配。

为什么我一直强调连续的空间呢?嗯~大概是个人喜好吧。
首先空间连续,就没有空间浪费。
其次,分配和回收内存的动作一步到位,不懂拆分成多步。
最后,不管任何情况下,都能拿出原生指针,自己做最基本的C++ 操作。

扯那么多,下面来看看怎么实现。

array2

我将类命名为array2。
在开始写之前,要明确一些需求:

  • 我希望能够在程序中动态生成二维数组
  • 希望分配的空间内存连续
  • 希望能够自动申请和回收内存
  • 提供方便的下标访问

更高级一点的:

  • 能够指定迭代器,会数组赋值
  • 表现得越像原生指针越好

明确需求之后,就可以一步一步地编码了。

如何访问元素

这个问题我想了好久,最后敲定了现在的解决方案。

最理想的情况是,能够使用[][]进行访问。

但是,[] 只能接受一个参数,如果这么用的话,必须要加工。

一个方法是,让array2 operator [] return 一个结构的,那个结构再重载operator[] 从而实现元素的访问。

大概类似于这样

template<typename T>
struct _array1 
{
    T* data;
    array1(T* _data):data(_data) {}
    T& operator [] (int i) { return data[i]; }
};

//array2
template<typename T>
class array2
{
    _array1 operator [] (int i)
    {
        //...
        return _array1(data); //把计算的偏移量放进来
    }
};

这样子的好处是显而易见的。能够像原生指针一样访问元素:

array2<int> x(3,4); //3 行4列
x[1][2]; 

但是也有明显不好的地方,就是每次的下标访问,都会有_array1 的构造和析构。在循环中,感觉不妙。

而且,当实现三维的时候怎么办,需要两次的类过渡。

其实这不失为一个好的方法,但是为了一致性,我们只能寻找另外一种方法了
operator ()

重载括号,能够接受自己设定的参数数量。nice 就它了。

构造函数

受vector 的影响,array2 中也用了各种typedef ,构造函数的形式和很像:

/** 二维数组
*/
template<typename T, typename Alloc=alloc>
class array2
{
public:
    typedef T value_type;
    typedef T* pointer; 
    typedef T& reference;
    typedef const T& const_reference;
    typedef size_t size_type;
    typedef T* iterator;
    typedef const T* const_iterator;

private:
    typedef simple_alloc<T,Alloc> data_alloc;
    enum{ dimension_size = 2 };

    pointer _data;
    size_type _dimension[dimension_size]; //记录维数信息
    //....
public:
    array2() : _data(0) {}
    array2(size_type row, size_type col):_data(0)
    {
        init(row, col, value_type() );
    }
    array2(size_type row, size_type col, const value_type& val):_data(0)
    {
        init(row, col, val);
    }

    template<typename Input_iter>
    array2(size_type row, size_type col, Input_iter first):_data(0)
    {
        init(row, col, first);
    }
    array2(const array2& x)
    {
        init(x.rows(), x.cols(), x.data() );
    }
    //....
 }

其中init 如下:

    /** init
    */
    void init(size_type row, size_type col)
    {
        init(row, col,value_type() );
    }
    void init(size_type row, size_type col, const value_type& val)
    {
        _clear();
        set_dimension(row, col);
        allocate_and_fill(_dimension[0], val);
    }
    template<typename Input_iter>
    void init(size_type row, size_type col, Input_iter first)
    {
        _clear();
        set_dimension(row, col);
        allocate_and_copy(_dimension[0], first);
    }

用到的几个辅助函数如下:

//--------------------function------------
    void allocate_and_fill(size_type _s, const value_type& val)
    {
        _data = data_alloc::allocate(_s);
        uninitialized_fill_n(_data, _s, val);
    }
    template<typename Input_iter>
    void allocate_and_copy(size_type _s, Input_iter first)
    {
        _data = data_alloc::allocate(_s);
        uninitialized_copy_n(first, _s, _data);
    }

    void _clear()
    {
        if(_data!=0)
        {
            destroy(_data, _data + _dimension[0]);
            data_alloc::deallocate(_data, _dimension[0]);
            _data = 0;
        }
    }

    void set_dimension(size_type row, size_type col)
    {
        _dimension[1] = col;
        _dimension[0] = row*col;
    }

init 的可以重新设定数组的维度和内容,就相当于重来一遍。
dimension 中存储维度信息,大小为2:

row*col , col

为什么这么设计呢?
首先,size 的信息直接可以拿到(row*col), 然后另一个考虑是为了一致性。

考虑正在设计三维的数组:

dimension[3];
//x,y,z 表示维度, int[x][y][z]

dimension[2] = z;
dimension[1] = y*dimension[2];
dimension[0] = x*dimension[1];

这样子有什么好处呢?考虑到这时候我要访问[i][j][k] 的元素:

return data[i*dimension[1] + j*dimension[2] +k ];
//原来的如下:
return data[i*y*z + j*z + k];

明白没有?这样子可以省去(y*z) 的计算。
当维度越來越高时,能够省去的计算也就越多。

访问元素

访问元素就用operator () 进行:

    /** operator ()
    */
    reference operator () (size_type x, size_type y)
    {
        return _data[x * _dimension[1] + y];
    }
    const_reference operator () (size_type x, size_type y) const
    {
        return _data[x* _dimension[1] + y];
    }

其他的设计倒是稀松平常,源代码在文末贴上。

如何表现得像一个原生指针

我们希望,下面的代码能够被支持:

array2<int> x(3,4);//尽量让x 相当于一个int* 
int i = *(x+2);
int e = x[23];

在使用的时候,要知道,其实这是一维的数组,抽象上的哦二维数组。所以x 的表现就像一个int*

那么实现和类型转换函数就可以了:

    /** operator pointer
    */
    operator pointer ()
    {
        data();
    }
    operator const pointer () const
    {
        data();
    }

源代码

/** 二维数组
*/
template<typename T, typename Alloc=alloc>
class array2
{
public:
    typedef T value_type;
    typedef T* pointer; 
    typedef T& reference;
    typedef const T& const_reference;
    typedef size_t size_type;
    typedef T* iterator;
    typedef const T* const_iterator;

private:
    typedef simple_alloc<T,Alloc> data_alloc;
    enum{ dimension_size = 2 };

    pointer _data;
    size_type _dimension[dimension_size]; //记录维数信息


//--------------------function------------
    void allocate_and_fill(size_type _s, const value_type& val)
    {
        _data = data_alloc::allocate(_s);
        uninitialized_fill_n(_data, _s, val);
    }
    template<typename Input_iter>
    void allocate_and_copy(size_type _s, Input_iter first)
    {
        _data = data_alloc::allocate(_s);
        uninitialized_copy_n(first, _s, _data);
    }

    void _clear()
    {
        if(_data!=0)
        {
            destroy(_data, _data + _dimension[0]);
            data_alloc::deallocate(_data, _dimension[0]);
            _data = 0;
        }
    }

    void set_dimension(size_type row, size_type col)
    {
        _dimension[1] = col;
        _dimension[0] = row*col;
    }

    void _check_range(size_type row, size_type col)
    {
        if(row * _dimension[1] + col >= _dimension[0] )
        {
            printf(" In array2, index out of range!\n");
            abort();
        }
    }

public:
    array2() : _data(0) {}
    array2(size_type row, size_type col):_data(0)
    {
        init(row, col, value_type() );
    }
    array2(size_type row, size_type col, const value_type& val):_data(0)
    {
        init(row, col, val);
    }

    template<typename Input_iter>
    array2(size_type row, size_type col, Input_iter first):_data(0)
    {
        init(row, col, first);
    }
    array2(const array2& x)
    {
        init(x.rows(), x.cols(), x.data() );
    }


    ~array2()
    {
        _clear();
    }

    /** init
    */
    void init(size_type row, size_type col)
    {
        _clear();
        init(row, col,value_type() );
    }
    void init(size_type row, size_type col, const value_type& val)
    {
        _clear();
        set_dimension(row, col);
        allocate_and_fill(_dimension[0], val);
    }
    template<typename Input_iter>
    void init(size_type row, size_type col, Input_iter first)
    {
        _clear();
        set_dimension(row, col);
        allocate_and_copy(_dimension[0], first);
    }

    /** size    
    */ size_type size() const { return _dimension[0]; }

    /** at
    */
    reference at(size_type x, size_type y)
    {
        _check_range(x, y);
        return operator () (x, y);
    }
    const_reference at(size_type x, size_type y) const
    {
        _check_range(x, y);
        return operator () (x, y);
    }


    /** begin and end 
    */
    iterator begin() { return _data; }
    const_iterator begin() const { return _data; }
    iterator end() { return _data+_dimension[0]; }
    const_iterator end() const { return _data+_dimension[0]; }

    /** operator ()
    */
    reference operator () (size_type x, size_type y)
    {
        return _data[x * _dimension[1] + y];
    }
    const_reference operator () (size_type x, size_type y) const
    {
        return _data[x* _dimension[1] + y];
    }

    /** data
    */
    pointer data() { return _data; }
    const pointer data() const { return _data; }

    /** row and col dimension
    */
    size_type rows() const { return _dimension[0]/_dimension[1]; }
    size_type cols() const { return _dimension[1]; }    

    size_type dimension(int i) const
    {
        if(i==0) return rows();
        else if(i==1) return cols();
        else return 0;
    }

    /** assign_elem
    */
    template<typename Input_iter>
    void assign_elem(const_iterator pos, Input_iter first, Input_iter last)
    {
        copy(first, last, const_cast<iterator>(pos) );
    }

    /** operator ostream
    */
    friend std::ostream& operator << (std::ostream& out, const array2& x)
    {
        out<<"[\n";
        int row = x._dimension[0] / x._dimension[1];
        int index = 0;
        for(int i=0; i<row; ++i)
        {
            for(int j=0; j<x._dimension[1]; ++j, ++index)
                out<<x._data[index]<<"  ";
            out<<endl;
        }
        out<<"] "<<endl;
        return out;
    }

    /** operator pointer
    */
    operator pointer ()
    {
        data();
    }
    operator const pointer () const
    {
        data();
    }

    /** operator = 
    */
    array2& operator = (const array2& x)
    {
        if(&x != this)
        {
            if(size() == x.size() )
            {
                assign_elem(begin(), x.begin(), x.end() );//TO DO
            }
            else
            {
                init(x.rows(), x.cols(), x.data());
            }
        }
        return *this;
    }
};

欢迎拍砖

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

new一个动态二维数组并实现内存连续

#include using namespace std; int main() { int **p;//新建一个二级指针p,即指向指针的指针 p = new int*[3];//开辟3个【用...

二维数组分配,释放空间

http://blog.csdn.net/xcyangfan/article/details/7055028 二维数组申请与释放 [cpp] view pl...

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

二维数组分配,释放空间

二维数组申请与释放 int nGLen=10000;//Graph length int **test=new int* [nGLen]; for (int g=0;g <nGL...

数组 指针杂记

数组和指针有莫大的练习,介绍并实现一维数组和二维数组中每次+1的步长和效果区别,如果使用二级指针管理二维空间等

基于环形缓冲区的deque实现方法

众所周知,C++ STL中有一个叫做deque的容器,实现的是双端队列数据结构,这种队列允许同时从队列的首部和尾部插入和删除数据。 然而在STL中这种数据结构是用”分段连续”的物理结构实现的(可以参...

求数组的连续子数组之和的最大值(一维二维)

求数组的连续子数组之和的最大值 输入一个N个元素的整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。 例如输入的数组为-9 ...

多维数组VS多维空间

数组VS物理多维空间 多维数组是如何构成的: 在数组的定义里,只有一维数组的概念,通常用[ ]表示,并没有定义多维数组。但是,幸运的是,因为并没有规定数组里面的元素的数据类型。所以,可以设...

首尾连接的二维数组中连续子数组的最大和

#include using namespace std; #define M 4 #define N 5 void initColSumArray(int src[][N], int dest...

如何创建动态的多维数组且内存空间连续

大部分同学都会创建一位数组,并且一位数组以创建好内存空间就是连续的。但常常也会要用到创建多维连续空间的数组,下面以二三维为例子,大家看了就明白。这就是数组指针与指针数组的灵活应用。(这里说的创建都是指...

连续数字的旋转二维数组

生成一个连续数字的旋转二维数组,起始位置位于左上角,然后从起始位置开始递增,按照顺时针或者逆时针从外向里将连续的数组存入二维数组中,并输出生成的旋转二维数组。  Java代码   ...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)