multi_array

C++创建多维数组

int* pOneDimArr = new int[10]; //新建一个10个元素的一维数组

        pOneDimArr[0] = 0; //访问

        int**pTwoDimArr = new int[10][20]; //错误!

        pTwoDimArr[0][0] = 0; //访问

问题

,new int[10][20]返回的并非int**类型的指针,而是int (*)[20]类型的指针(这种指针,对它“+1”相当于在数值上加上一行的大小(本例为20),也就是说,让它指向下一行),

解决:

int(*pTwoDimArr)[20] = new int[i][20]; //正确

pTwoDimArr[1][2]= 0; //访问

    注意pTwoDimArr的类型——int(*)[20]是个很特殊的类型,它不能转化为int**,虽然两者索引元素的语法形式一样,都是“p[i][j]”的形式,但是访问内存的次数却不一样,语义也不一样。

限制

    最关键的问题还是:这种方法创建多维数组,有一个最大的限制,就是:除了第一维,其它维的大小都必须是编译期确定的。例如:

    int(*pNdimArr)[N2][N3][N4] = new int[n1][N2][N3][N4];

    这里N2,N3,N4必须都是编译期常量,只有n1可以是变量,这个限制与多维数组的索引方式有关——无论多少维的数组都是线性存储在内存中的,所以:

       pTwoDimArr[i][j]= 0;

被编译器生成的代码类似于:

       *((int*)pTwoDimArr+i*20+j ) = 0;

   20就是二维数组的行宽,问题在于,如果允许二维数组的行宽也是动态的,这里编译器就无法生成代码(20所在的地方应该放什么呢?)。基于这个原因,C++只允许多维数组的第一维是动态的。

更好的解决multi_array创建一个“完全动态的”多维数组。

这里“完全动态”的意思是,所有维的大小都可以是动态的变量,而不仅是第一维。归根到底,我们需要的是一个多维数组实现:

 

    //创建一个int型的3维数组,dim_sizes表示各维的大小:n1*n2*n3

    multi_array<int,3> ma ( dim_sizes[n1][n2][n3]);

    ma[i][j][k] =value; //为第i页j行k列的元素赋值

例子

int main () {

     // 创建一个尺寸为3×4×2的三维数组

     #define DIMS 3 //数组是几维的

     typedef boost::multi_array<double,DIMS>array_type; // (1-1)

     array_typeA(boost::extents[3][4][2]);   // (1-2)

     // 为数组中元素赋值

     A[1][2][0] =120;      // (1-3)

     ... ...

     return 0;

}

递归定义:boost::extents[3][4][2]展开为操作符调用的方式就相当于:

extents.operator [](3).operator [](4).operator [](2);

extents的工作方式:

1,每调用一次operator [],都会返回一个extent_gen<NumRange+1>类型的对象,所以,对于boost::extents[3][4][2],依次返回的是:

extent_gen<1> => extent_gen<2> =>extent_gen<3>

最后一个也是最终的返回类型——extent_gen<3>。其成员ranges_中,共有[0,3)、[0,4)、[0,2)三组区间。

2,当boost::extents准备完毕后,就被传入multi_array的构造函数,用于指定各维的下标区间:

3,   multi_array接受了ranges参数中的信息,取出其中各维的下标区间,然后保存,最后调用allocate_space()来分配底层内存。

使用extent_gen的好处

使用boost::extents,可以防止用户写出错误的代码

例如:

multi_array<int,3> A(boost::extents[3][4][2][5]);//错!多了一维!

运行:上面的语句是无法通过编译

原因: multi_array<int,3>的构造函数只能接受extent_gen<3>类型的参数,而根据我们前面对extents的分析,boost::extents[3][4][2][5]返回的却是extent_gen<4>类型的对象,于是就会产生编译错误。

另一种替代方案及其缺点

boost::array<int,4shape ={{3,4,2,5}}; //一个四维数组的shape

multi_array<int,3>A(shape); // 竟然可以通过编译!!

原因:构造函数根据自己的需求用iteratorextents中取出它所需要的数值——A是三维数组,于是构造函数从shape中取出前三个数值作为A三个维度的下标区间,而不管shape究竟是包含了几个数值。

multi_array的架构

多层的继承结构:

 multi_array-> multi_array_ref -> const_multi_array_ref -> multi_array_impl_base-> value_accessor_n/value_accessor_one

其中每一层都担任各自的角色:

¨        multi_array : 为数组元素分配空间,将各种操作转发至基类。

¨        multi_array_ref : 提供与STL容器一致的数据访问界面。也可以独立出来作为一个adapter使用。

¨        const_multi_array_ref : 提供constSTL数据访问界面。也可以作为一个const adapter使用。

¨        multi_array_impl_base及其基类 :最底层实现,提供一组对原始数据的基本操作。

其中的(const_)multi_array_ref都可以独立出来作为一个adapter使用——例如:

 

    inta[24]; //一维的10个元素数组

    //把一维数组a看成一个3*4*2的三维数组:

    multi_array_ref<int,3>arr_ref(a,boost::extents[3][4][2]);

    arr_ref[i][j][k]= value; //multi_array一样的使用界面

multi_array的存储策略

例如:C风格的多维数组存储方式是按行存储,而fortran恰恰相反,是按列存储,甚至,用户可能有自己的存储策略要求。那么,如何支持多种风格的存储策略呢?

 class general_storage_order {

     general_storage_order(const c_storage_order&){//4-1

         for (size_typei=0; i != NumDims; ++i)

         {ordering_[i] = NumDims - 1 - i; }

         ascending_.assign(true);

         }

     ... ...

     boost::array<size_type,NumDims> ordering_;

     boost::array<bool,NumDims> ascending_;

     };

ordering_ascending_是两个数组,

当函数(4-1)执行完毕后,ordering_中的元素应当是{NumDims-1, NumDims-2,...1,0},如果将这些元素作为各维度存储顺序的标识——具有较小ordering_值的维度先存储——那么这和C语言中的存储方式就完全一致了

ascending_勿庸置疑就是用来表明各维度是否升序存储。

底层,存储仍然是退化为一维数组的存储,allocate_space分配一块连续空间用以存储元素。

operator[]

multi_array以内建数组访问方式访问数组元素,multi_array支持以连续的operator[]来访问数组元素(类似extend_gen的递归定义)--每调用一层就返回一个“proxy”,然后在其上继续调用operator[],如此往复...

A[x1][x2][x3]方式访问A中的元素,就相当于

operator[x1].operator[x2].operator[x3]//连续调用“[]”

最后一次调用“[]”返回的恰好是对元素的引用T&

你可能感兴趣的:(multi_array)