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++只允许多维数组的第一维是动态的。
这里“完全动态”的意思是,所有维的大小都可以是动态的变量,而不仅是第一维。归根到底,我们需要的是一个多维数组实现:
//创建一个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);
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()来分配底层内存。
使用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,4> shape ={{3,4,2,5}}; //一个四维数组的shape
multi_array<int,3>A(shape); // 竟然可以通过编译!!
原因:构造函数根据自己的需求用iterator从extents中取出它所需要的数值——A是三维数组,于是构造函数从shape中取出前三个数值作为A三个维度的下标区间,而不管shape究竟是包含了几个数值。
多层的继承结构:
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 : 提供const的STL数据访问界面。也可以作为一个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一样的使用界面
例如: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分配一块连续空间用以存储元素。
multi_array以内建数组访问方式访问数组元素,multi_array支持以连续的operator[]来访问数组元素(类似extend_gen的递归定义)--每调用一层就返回一个“proxy”,然后在其上继续调用operator[],如此往复...
A[x1][x2][x3]方式访问A中的元素,就相当于
operator[x1].operator[x2].operator[x3]//连续调用“[]”
最后一次调用“[]”返回的恰好是对元素的引用T&