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

本文介绍了一种在C++中自定义二维数组的方法,该方法支持动态分配内存且内存连续,提供了方便的下标访问,并能表现得像原生指针。同时,文章详细解释了其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

2019-07-02 修改:
添加了相关文档的链接:https://blog.youkuaiyun.com/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;
	}
};

欢迎拍砖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值