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;
    }
};

欢迎拍砖

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

相关文章推荐

C++11系列学习之三----array/valarray

创建数组,是程序设计中必不可少的一环。我们一般可以有以下几种方法来创建数组。 一、C++内置数组 数组大小固定,速度较快 通用格式是:数据类型   数组名[ 数组大小 ]; 如 int a...

用STL 中的vector创建二维数组

用 STL vector 来创建二维数组 以前我要建立一个二维数组,总是使用  int N=5, M=6;  vector > Matrix(N);  for(int i =0; i    ...

使用vector创建一个二维数组(一)

最近在刷题的时候遇到过好几次二维数组的问题,因为我自己想在C++方向发展,所以尽可能地用C++提供的STL来完成编程,但是在使用二维数组的时候遇到了麻烦,就是如果用int[][]这种直接表示方式很简单...

C语言的二维和三维动态连续数组分配

在某些特殊的场合需要动态的数组分配,比如图像处理领域,这就需要用到malloc 和free这对好基友了本测试纯粹为了学习研究,没有意思去比较C和C++的优劣,或者和其他语言的优劣,因为比起C++,我更...
  • Kena_M
  • Kena_M
  • 2015年08月22日 10:50
  • 1466

如何在C/C++中动态分配地址连续访问快速的二维数组

如何在C/C++中动态分配二维数组 在C/C++中动态分配二维数组可以先申请一维的指针数组,然后该数组中的每个指针再申请数组,这样就相当于二维数组了,但是这种方法会导致每行可能不相邻,从而访问效...

【拾遗】C++申请动态连续内存的二维数组

前几天去科大讯飞面试,其他问题回答的都还不错,但是被问到这个“如何动态申请一个连续内存空间的二维数组”时,稍微顿了一下。倒不是回答不上来,而是之前从没有尝试敲过这样的代码,光说思路实在是太干瘪。 回...

ADT与类的设计

ADT与类的设计 个人对ADT的理解是,我们编程时可以从系统任意一个层面去思考,我们可以把精力集中在某一层面的逻辑而不需要过多的考虑底层(相对来说)的实现细节。例如做一个贪吃蛇,我们不需要去关注链表的...

相关ADT定义

开干 成绩表     Da

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

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

程序员面试题目总结--数组(五)【数组的后面m个数移动为前面m个数、列的前n项数据、判断整数x是否可以表示成n个连续正整数的和、数组中出现奇数次的元素、二维数组中的查找】

21、将数组的后面m个数移动为前面m个数 题目:
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:STL(五):连续空间的二维数组实现
举报原因:
原因补充:

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