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

Posted 小键233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL:连续空间的二维数组实现相关的知识,希望对你有一定的参考价值。

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

2019-07-02 修改:
添加了相关文档的链接:https://blog.csdn.net/u014613043/article/details/94492607


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

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

欢迎拍砖

以上是关于STL:连续空间的二维数组实现的主要内容,如果未能解决你的问题,请参考以下文章

连续空间的多维数组实现

C++二维数组离散动态和连续动态空间申请

STL——vector模拟实现

STL源码剖析(deque)

常见STL 容器的实现原理

memcpy()对二维数组和二维指针操作的区别