C++是一种独特的语言,有些奇技淫巧,可以解决很棘手的问题,并且实现很优雅。以前很懒,什么学习笔记都没留下,就留些代码。
多维数值(multi-array)的接触是在学习写游戏时,编写地图时想到的。一般C++的数组定义是静态的,动态的需要在堆上申请。静态的可以定义多维数组,例如:
二维数组:int map[6][7];
三维数组:int cube[6][7][8];
但是数组的维度是固定,不可改变。动态的多维数组申请,可以这样实现:
int** map = new int*[6];
for(int i=0;i<6;++i){
map[i] = new int[7];
}
显然上面的代码还好,要是需要三维的,四维,N维数组,头开始有些疼了吧。可能有些同学会想可以用STL的vector来实现,不错的想法,这样其实已经离最终的实现方法,只差捅破那层窗户纸了。我们分析一下vector方式的做法,先申请第一个维度的vector,再申请第二个维度的vector,这不是和普通的数组申请没有什么区别,只是内存管理不用操心了。其实vector的运算符“[]”重载才是后来实现multi-array的关键。
google确实是个好工具,boost确实是C++的好库。multi-array在boost中已经实现了。先总结用到的一些技术要点:
1)模板(Template)
2)运算符(Operator)重载
3)模板偏特化
4)模板“递归”(我乱写的,大家好理解一些)
以上知识点,1和2,比较普通,C++的教程都有讲解。下面一边贴代码,一边讲解3和4的具体细节。
最优雅的实现,追求的目标
int map[x][y]…[z];
x,y,…,z是随便制定数值,那就现用上模板,
multi_array<int, 3> map;
取数组的某下标的值,
map[x][y];
那么怎么定义map的维数呢?我们需要引入entent_gen这个类。
template<size_t Number>
class entent_gen
{
public:
typedef size_t index;
typedef array<index, Number> rangs;
rangs _rangs;
entent_gen(){}
entent_gen(const entent_gen<Number-1>& other, index index)
{
other._rangs.copyTo(this->_rangs, 0, Number-1);
this->_rangs[Number-1] = index;
}
entent_gen<Number+1>
operator[](index index)
{
return entent_gen<Number+1>(*this, index);
}
};
entent_gen会保存维数的信息。构造函数会进行“递归”产生number-1的entent_gen,存储维数。operator[]函数,number+1会进行递归调用下一个entent_gen,取得维数。比如需要6×7的map,那么描述信息是:
entent_gen<0>[6][7];
下面就可以构建multi-array了,
template<typename T, size_t Number>
class multi_array_s
{
public:
typedef entent_gen<Number> type;
type _entent;
T* _data;
multi_array_s(const type& entent):
_entent(entent),
_data(new T[getLength()]){}
multi_array_s(const type& entent, T* data):
_entent(entent),
_data(data){}
multi_array_s(const multi_array_s<T, Number+1>& other, size_t index)
{
other._entent._rangs.copyTo(this->_entent._rangs, 1, Number);
this->_data = other._data + index * this->getLength();
}
multi_array_s<T, Number-1>
operator[](size_t index)
{
return multi_array_s<T, Number-1>(*this, index);
}
const multi_array_s<T, Number-1>
operator[](size_t index)const
{
return multi_array_s<T, Number-1>(*this, index);
}
const size_t getLength()
{
size_t length =1;
for(size_t i=0; i<Number; i++)
{
length *= _entent._rangs[i];
}
return length;
}
};
实验里的类名是multi_array_s,为什么加s,最后再讲。
大家看到这个类比较长,但主要是构造函数和取值函数。第一个构造函数,接受entent_gen保存的数组维数信息。第二个构造函数,多了一个内存块的参数,可以从已有的数据,构建多维访问。第三个构造函数,就是“递归”构造number-1的多维数组,技巧的核心。最后,我们发现当number=1的时候应该特殊处理,终止“递归”。
模板的偏特化,上场了,其实很简单的。
template<typename T>
class multi_array_s<T, 1>
{
public:
T* _data;
multi_array_s(const multi_array_s<T, 2>& other, size_t index)
{
this->_data = other._data + index * other._entent._rangs[1];
}
T&
operator[](size_t index)
{
return _data[index];
}
const T&
operator[](size_t index)const
{
return _data[index];
}
};
最后,大家发现好像有内存泄露。对,
_data(new T[getLength()]){}
这句代码,分配了内存,没有在析构函数中释放(而且根本没写析构函数,吭爹呀)。请看下面真正的multi_array:
template<typename T, size_t Number>
class multi_array
{
protected:
typedef multi_array_s<T, Number> type;
type _multi_array_s;
T* _data;
public:
multi_array(const entent_gen<Number>& entent):
_multi_array_s(entent),
_data(_multi_array_s._data){}
multi_array(const entent_gen<Number>& entent, T* data):
_multi_array_s(entent, data),
_data(_multi_array_s._data){}
~multi_array()
{
delete[] _data;
}
multi_array_s<T, Number-1>
operator[](size_t index)
{
return _multi_array_s[index];
}
const multi_array_s<T, Number-1>&
operator[](size_t index) const
{
return _multi_array_s[index];
}
const T* getData() const
{
return _data;
}
T* getData()
{
return _data;
}
const size_t getDataSize()
{
return _multi_array_s.getLength()*sizeof(T);
}
const size_t getLength()
{
return _multi_array_s.getLength();
}
};
这里在析构函数,释放了内存。因为 multi_array_s是递归产生的,不能在某个中间产物中释放内存,会出现错误。
entent_gen也不好直接使用,所以在命名空间中定义
static entent_gen<0> extents;
可以方便的使用。
讲了一堆代码,看看怎么用吧。
int a=6,b=7,c=8, x=0;
multi_array<int, 3> map(extents[a][b][c]);
for(int i=0; i<a; ++i){
for(int j=0; j<b; ++j){
for(int k=0; k<c; ++k){
map[i][j][k] = x++;
}
}
}
cout << map[4][5][6] << endl;
源代码: multi_array
我这个实现有局限性,大家有兴趣可以直接阅读boost库的multi-array代码。
http://www.boost.org/doc/libs/1_41_0/libs/multi_array/doc/index.html