C++的多维数组(MultiArray)实现,附带实验性实作

http://blog.chukong-inc.com/index.php/2011/12/22/c的多维数组multiarray实现,附带实验性实作/


C++的多维数组(MultiArray)实现,附带实验性实作

Published on  2011 年 12 月 22 日, by  yingtao in  C/C++,  iOS技术.

C++是一种独特的语言,有些奇技淫巧,可以解决很棘手的问题,并且实现很优雅。以前很懒,什么学习笔记都没留下,就留些代码。

多维数值(multi-array)的接触是在学习写游戏时,编写地图时想到的。一般C++的数组定义是静态的,动态的需要在堆上申请。静态的可以定义多维数组,例如:

二维数组:int map[6][7];

三维数组:int cube[6][7][8];

但是数组的维度是固定,不可改变。动态的多维数组申请,可以这样实现:

 

Source code    
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 map;

取数组的某下标的值,

map[x][y];

那么怎么定义map的维数呢?我们需要引入entent_gen这个类。

 

Source code    
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了,

 

Source code    
template&lt;typename T, size_t Number&gt;
 
class multi_array_s
 
{
 
public:
 
typedef entent_gen&lt;Number&gt; type;
 
type _entent;
 
T* _data;
 
multi_array_s(const type&amp; entent):
 
_entent(entent),
 
_data(new T[getLength()]){}
 
multi_array_s(const type&amp; entent, T* data):
 
_entent(entent),
 
_data(data){}
 
multi_array_s(const multi_array_s&lt;T, Number+1&gt;&amp; other, size_t index)
 
{
 
other._entent._rangs.copyTo(this-&gt;_entent._rangs, 1, Number);
 
this-&gt;_data = other._data + index * this-&gt;getLength();
 
}
 
multi_array_s&lt;T, Number-1&gt;
 
operator[](size_t index)
 
{
 
return multi_array_s&lt;T, Number-1&gt;(*this, index);
 
}
 
const multi_array_s&lt;T, Number-1&gt;
 
operator[](size_t index)const
 
{
 
return multi_array_s&lt;T, Number-1&gt;(*this, index);
 
}
 
const size_t getLength()
 
{
 
size_t length =1;
 
for(size_t i=0; i&lt;Number; i++)
 
{
 
length *= _entent._rangs[i];
 
}
 
return length;
 
}
 
};

实验里的类名是multi_array_s,为什么加s,最后再讲。

大家看到这个类比较长,但主要是构造函数和取值函数。第一个构造函数,接受entent_gen保存的数组维数信息。第二个构造函数,多了一个内存块的参数,可以从已有的数据,构建多维访问。第三个构造函数,就是“递归”构造number-1的多维数组,技巧的核心。最后,我们发现当number=1的时候应该特殊处理,终止“递归”。

模板的偏特化,上场了,其实很简单的。

 

Source code    
template&lt;typename T&gt;
 
class multi_array_s&lt;T, 1&gt;
 
{
 
public:
 
T* _data;
 
multi_array_s(const multi_array_s&lt;T, 2&gt;&amp; other, size_t index)
 
{
 
this-&gt;_data = other._data + index * other._entent._rangs[1];
 
}
 
T&amp;
 
operator[](size_t index)
 
{
 
return _data[index];
 
}
 
const T&amp;
 
operator[](size_t index)const
 
{
 
return _data[index];
 
}
 
};

 

 

最后,大家发现好像有内存泄露。对,

_data(new T[getLength()]){}

这句代码,分配了内存,没有在析构函数中释放(而且根本没写析构函数,吭爹呀)。请看下面真正的multi_array:

 

Source code    
template&lt;typename T, size_t Number&gt;
 
class multi_array
 
{
 
protected:
 
  typedef multi_array_s&lt;T, Number&gt; type;
 
  type _multi_array_s;
 
  T* _data;
 
public:
 
multi_array(const entent_gen&lt;Number&gt;&amp; entent):
 
_multi_array_s(entent),
 
_data(_multi_array_s._data){}
 
multi_array(const entent_gen&lt;Number&gt;&amp; entent, T* data):
 
_multi_array_s(entent, data),
 
_data(_multi_array_s._data){}
 
~multi_array()
 
{
 
  delete[] _data;
 
}
 
multi_array_s&lt;T, Number-1&gt;
 
operator[](size_t index)
 
{
 
  return _multi_array_s[index];
 
}
 
const multi_array_s&lt;T, Number-1&gt;&amp;
 
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;

可以方便的使用。

讲了一堆代码,看看怎么用吧。

 

Source code    
int a=6,b=7,c=8, x=0;
 
multi_array&lt;int, 3&gt; map(extents[a][b][c]);
 
for(int i=0; i&lt;a; ++i){
 
  for(int j=0; j&lt;b; ++j){
 
    for(int k=0; k&lt;c; ++k){
 
      map[i][j][k] = x++;
 
    }
 
  }
 
}
 
cout &lt;&lt; map[4][5][6] &lt;&lt; endl;

源代码: multi_array

我这个实现有局限性,大家有兴趣可以直接阅读boost库的multi-array代码。

http://www.boost.org/doc/libs/1_41_0/libs/multi_array/doc/index.html


你可能感兴趣的:(iOS开发)