Boost的动态多维数组-multi_array

本文介绍了Boost库中的动态多维数组类`multi_array`的使用,包括如何创建和初始化3D数组,如何将连续内存适配为多维数组,以及如何生成和操作子视图。此外,还讲解了改变数组形状和大小的操作,以及`multi_array`在图像格式转换中的应用。

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

Boost.MultiArray是一个动态多维数组库。它实现了一个通用、与标准库的容器一致的接口,并且具有与C++中内建的多维数组一样的接口和行为。

简短的例子

以下是使用 multi_array 的一个简短例子:

  1. #include "boost/multi_array.hpp"
  2. #include <cassert>
  3.  
  4. int main () {
  5.   // 创建一个 3 x 4 x 2 的 3D 数组
  6.   typedef boost::multi_array<double, 3> array_type;
  7.   typedef array_type::index index;
  8.   array_type myarray(boost::extents[3][4][2]);
  9.  
  10.   // 赋值到数组的元素
  11.   int values = 0;
  12.   for(index i = 0; i != 3; ++i)
  13.     for(index j = 0; j != 4; ++j)
  14.       for(index k = 0; k != 2; ++k)
  15.         myarray[i][j][k] = values++;
  16.  
  17.   // 校验元素值
  18.   int verify = 0;
  19.   for(index i = 0; i != 3; ++i)
  20.     for(index j = 0; j != 4; ++j)
  21.       for(index k = 0; k != 2; ++k)
  22.         assert(myarray[i][j][k] == verify++);
  23.  
  24.   return 0;
  25. }

上面的代码可以告诉我们这样一些信息:

  • boost::multi_array是一个模板类,第一个模板参数指定元素数据类型;第二个模板参数是一个数值,指出数组维度。
  • multi_array::index类型定义用于描述数据索引,一般来说它就是int
  • boost:: extents的连续多个[]操作用于指明每个维度的大小(另外,也可以使用boost::array来指明维度大小,如boost:: array<int,3> dims={3,4,2}; array_type myarray(dims);)
  • boost::multi_array的读写接口和原生多维数组相同

把连续内存适配成多维数组

有时我们需要把已有的一段连续内存(比如一维数组)当作多维数组使用,Boost.MultiArray提供了multi_array_ref和const_multi_array_ref类用于把原始内存块适配成多维数组。

如,可以如下修改前例的代码,在这个代码里使用multi_array_ref代替multi_array,它把double buf[24]适配成了“double A[3][4][2]”:

  1. /* 原来的代码
  2.   typedef boost::multi_array<double, 3> array_type;
  3.   typedef array_type::index index;
  4.   array_type myarray(boost::extents[3][4][2]);
  5. */
  6.   typedef boost::multi_array_ref<double, 3> array_type;
  7.   typedef array_type::index index;
  8.   double buf[24]; //连续内存块
  9.   array_type myarray(buf, boost::extents[3][4][2]); //把buf适配成3 x 4 x 2 的3D数组
  10. ...
  11. Boost.MultiArray是一个动态多维数组库。它实现了一个通用、与标准库的容器一致的接口,并且具有与C++中内建的多维数组一样的接口和行为。
    子视图

    子 视图,在图像处理中定义为从一个大的图片(如100x100的图片)中取一小块(如左上角25,25到右下角75,75的50x50区域),把它看作一个 新的图片,这个新的图片即为子视图(可以参考本站GIL库教程)。多维数组的概念与此相似,作为聪明的你肯定已经知道是怎么一回事了。下面是生成子视图的 例子,还是以前例代码为基础,从3x4x2的myarray数组中产生一个2x2x2的子视图,其中第二维以2步进:

    1. ...
    2.   typedef array_type::index_range range;
    3.   array_type::array_view<3>::type myview =
    4.     myarray[ boost::indices[range(1,3)][range(0,4,2)][range(0,2)] ];
    5.  
    6.   for (array_type::index i = 0; i != 2; ++i)
    7.     for (array_type::index j = 0; j != 2; ++j)
    8.       for (array_type::index k = 0; k != 2; ++k)
    9.     assert(myview[i][j][k] == myarray[i+1][j*2][k]);

    上面的代码我们总结出的信息是:

    • multi_array的子视图类型由它的array_view::type类型定义确定,这个array_view是个模板类,其参数指定了视图的维度。
    • multi_array的[]操作除了接受整数来模拟原生数组以外,还接受boost::indices对象的[]操作所返回的东东(这个东东是index_gen类型,我们只管用,不予深究),向multi_array的[]操作传入这个东东可以得到一个子视图。
    • multi_array有个index_range类型,用于指定索引的起始、终止和步进值(默认是1)。boost::indices对象的[]操作接受这个类型的数据才能生成指定区域的子视图。
    • 子视图的用法和多维数组一样

    boost::indices对象的[]操作除了接受index_range,还可以接受整型数值,这样我们可以生成一个比原有数组的维度更少的子视图(也称为切片)。

    下面的代码取3维数组myarray的X轴为1的一个2维切片(假设把myarray看成一个3维空间,3个维度3x4x2分别对应于ZxYxX三个轴)

    1. ...
    2.   typedef array_type::index_range range;
    3.   array_type::array_view<2>::type myview =
    4.     myarray[ boost::indices[range(1,3)][range(0,4,2)][1] ];
    5.  
    6.   for (array_type::index i = 0; i != 2; ++i)
    7.     for (array_type::index j = 0; j != 2; ++j)
    8.         assert(myview[i][j] == myarray[i+1][j*2][1]);

  12. 存储的顺序

    每 个数组类都提供了接受一个存储顺序参数的构造函数。这在与某些跟标准C的数组存储顺序不同的遗留代码进行接口时非常有用,如 FORTRAN. 可选的值有 c_storage_order, fortran_storage_order, 和 general_storage_order.

    c_storage_order 是缺省值,它将以C数组相同的顺序在内存中存储各元素,即从后往前保存各个维度。

    fortran_storage_order 则以 FORTRAN 的顺序在内存中存储各个元素:从第一个维度到最后一个。注意,在使用这个参数时,数组的索引仍然保持为从零起计。

    例子

    1. typedef boost::multi_array<double,3> array_type;
    2. array_type A(boost::extents[3][4][2],boost::fortran_storage_order);
    3. call_fortran_function(A.data());

    general_storage_order 允许你定制在内存中保存各个维度的顺序,以及各个维度是按升序还是降序来保存。

    例子

    1. typedef boost::general_storage_order<3> storage;
    2. typedef boost::multi_array<int,3> array_type;
    3.  
    4. // 先保存最后一个维度,然后是第一个维度,最后是中间
    5. array_type::size_type ordering[] = {2,0,1};
    6.  
    7. // 以降序保存第一个维度(维度0)
    8. bool ascending[] = {false,true,true};
    9.  
    10. array_type A(extents[3][4][2],storage(ordering,ascending)); 

    改变数组的形状

    Boost.MultiArray 数组提供了整形操作。只要维数和元素数量保持不变,数组的形状即维度的大小改变。

    例子

    1. ...
    2.   boost::array<int,3> dims = {2, 3, 4};
    3.   myarray.reshape( dims );
    4.   //现在myarray是2x3x4的数组了

    改变数组的大小

    和 标准库的容器一样,boost::multi_array类也提供了保留元素的调整大小操作。维数必须保持一致,但每个维度的长度可以按需要增加或减少。 当一个数组被扩大时,原有元素将被复制到新的内存中,然后旧内存中的元素将被析构。而数组中的新元素将是缺省构造的。但是,如果新数组的某些维度的大小是 缩短的,则有些元素将不再可用。

    例子

    1. myarray.resize(boost::extents[1][2][3]);

  13. 使用multi_array做图像格式转换

    本站有AGGCImg的图像处理教程,AGG库倾向于矢量绘图,CImg倾向于图像处理。我们可以考虑双剑合壁,共同来生成我们要的图像。可是它们的内部数据格式却不完全相同:

    AGG的内部格式是  color buf[y][x][v]
    CImg的内部格式是  color buf[v][y][x]
    其中color为单通道颜色值、v代表颜色通道(如RGB三色)、x,y是坐标。我们这里让CImg的z轴为1,即二维图像。

    我们得找个方法可以方便地互相转换,这里我们选用multi_array来做这件事(另,GIL也是一个不错的候选方案,见本站GIL教程)。

    1. #include "boost/multi_array.hpp" // multi_array
    2. #include "cimg.h" // CImg
    3. #include <agg_pixfmt_rgb.h> //后面全是AGG的
    4. #include <agg_scanline_u.h>
    5. #include <agg_renderer_scanline.h>
    6. #include <../font_win32_tt/agg_font_win32_tt.h>
    7. #include <agg_font_cache_manager.h>
    8. #include <agg_conv_bspline.h>
    9. #include <agg_path_storage.h>
    10. #include <agg_conv_curve.h>
    11. #include <agg_conv_transform.h>
    12. #include <agg_ellipse.h>
    13. #include <agg_trans_single_path.h>
    14.  
    15. using cimg_library::CImg;
    16. int main () {
    17.     // AGG画图,写了一行字
    18.     char buf[200][300][3];
    19.     agg::rendering_buffer rbuf(
    20.             (unsigned char*)buf,
    21.             300, 200,
    22.             300*3);
    23.     agg::pixfmt_rgb24 pixf(rbuf);
    24.     agg::renderer_base<agg::pixfmt_rgb24> renb(pixf);
    25.  
    26.     typedef agg::font_engine_win32_tt_int16 fe_type;
    27.     typedef agg::font_cache_manager<fe_type> fcman_type;
    28.  
    29.     renb.clear(agg::rgba(0.5,0.5,1));
    30.     fe_type font(::GetDC(0));
    31.     fcman_type font_manager(font);
    32.     font.height(32.0);
    33.     font.flip_y(true);
    34.     font.hinting(true);
    35.     if(!font.create_font("Comic Sans MS",agg::glyph_ren_outline)) return -1;
    36.     //坐标转换管道
    37.     typedef agg::conv_curve<
    38.         fcman_type::path_adaptor_type
    39.     > cc_pa_type;
    40.     cc_pa_type ccpath(font_manager.path_adaptor());
    41.  
    42.     typedef agg::conv_transform<cc_pa_type,
    43.         agg::trans_single_path> ct_cc_pa_type;
    44.     agg::trans_single_path trans_path;
    45.     ct_cc_pa_type ctpath(ccpath, trans_path);
    46.     
    47.     agg::path_storage ps;
    48.     ps.move_to(20,100);
    49.     ps.line_rel(80,50);
    50.     ps.line_rel(100,-100);
    51.     ps.line_rel(100,100);
    52.     
    53.     agg::conv_bspline<agg::path_storage> cb_ps(ps);
    54.     trans_path.add_path(cb_ps);
    55.  
    56.     agg::rasterizer_scanline_aa<> ras;
    57.     agg::scanline_u8 sl;
    58.     
    59.     double x=0, y=0;
    60.     for(const wchar_t *p = L"http://www.cpp-prog.com"; *p; p++)
    61.     {
    62.         const agg::glyph_cache* gc = font_manager.glyph(*p);
    63.         if(gc)
    64.         {
    65.             font_manager.init_embedded_adaptors(gc, x, y);
    66.             ras.add_path(ctpath);
    67.  
    68.             x += gc->advance_x;
    69.             y += gc->advance_y;
    70.         }
    71.     }
    72.     agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(1,0,0));
    73.  
    74.     // 定义CImg
    75.     CImg<unsigned char> img(300,200,1,3);
    76.  
    77.     // 用multi_array把AGG图像数据转成CImg
    78.     typedef boost::multi_array_ref<unsigned char,3> array_type;
    79.     // AGG->多维数组
    80.     /**
    81.     * AGG排列是[y][x][v], CImg是[v][y][x]
    82.     * 设y=2, x=1, v=0(C语言默认顺序)
    83.     * 则AGG的[2][1][0]对应CImg就是[0][2][1]
    84.     */
    85.     typedef boost::general_storage_order<3> storage;
    86.     array_type::size_type ordering[] = {0,2,1};
    87.     bool ascending[] = {true,true,true};
    88.     array_type array_agg((unsigned char*)buf,boost::extents[3][200][300],storage(ordering,ascending));
    89.     // 把AGG图像数据赋值给CImg,multi_array_ref内部做转换工作
    90.     array_type(img.data, boost::extents[3][200][300]) = array_agg;
    91.  
    92.     // CImg图像处理
    93.     img.blur(6).noise(10).erode(4);
    94.  
    95.     // 把CImg处理过的图像又传给AGG
    96.     array_agg = array_type(img.data, boost::extents[3][200][300]);
    97.  
    98.     // AGG在此基础上画图
    99.     agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(1,1,0));
    100.  
    101.     // 给CImg显示
    102.     array_type(img.data, boost::extents[3][200][300]) = array_agg;
    103.     img.erode(2);
    104.     img.display("view");  
    105.     return 0;
    106. }

    显示效果:


一组新的多维数组模板类 by chen3feng(RoachCock@smth) email: chen3feng@163.com, chen3fengx@163.com, chen3fengx@hotmail.com [引言] 在C/C++开发中,多维数组是一个让很多人感到棘手的问题.原因是C/C++中,多维数组被看 作是数组数组. 在向函数传递参数的时候,多维数组经常让人感到是麻烦的制造者,通常都是传递首地址 和每一维的大小: void foo(int *,int ,int);; int a[10][10];; foo(&a[0][0],10,10);; //... 十分的麻烦,在函数中访问时也得自己用乘法来计算元素的位置,更是十分麻烦. C99标准推出了可变大小的多维数组,当然,实现该标准的编译器还不多,但是也从一个方 面说明了变量大小的多维数组是很有用的. C++标准直到现在还不支持,明年(2003年)的新标准也不知道会不会加进去.但是C++程序 员自己有办法,利用C++的模板,运算符重载等技巧,完全可以构建出方便实用的多维数组类 我抢在明年之前做出这组模板类,也希望即使新标准增加了变量大小的多维数组,我的工 作也仍有一些意义, :) 另外,多维数组的空间是连续的,这跟用vector of vector实现的不一样,可以用迭代器 从头到脚挨个摸一遍. boost库也提供了多维数组类,当然还有别的几个数组.我感觉boost多维数组类的缺点 就是只支持动态数组,对静态和动态数组没有一个统一的非GP的接口,因此我着重于这方 面的改进, [简介] 该组类有以下几个类模板组成 1. template <;typename T, size_t DimNum>; class array_base;; 该类是其他几个数组类的基类 // 由于编译器对C++标准实现参差不齐的原因,该类实际 上不是根类,不过应用中不需要知道这一点. 提供了基本的功能,比如[]运算符,迭代器的类型声明,迭代器的获取,value_type等的定 义等 等 2. template <;typename T, size_t d1, size_t d2 = -1, size_t d3 = -1>; class static_array;; 静态的数组类,从array_base派生而来,因此除了兼容也是由array_base派生出来的其他 类外,还有自己的特点,就是提供了一个elements的public成员,直接暴露给用户,访问 速度可以很快. 3. template<;typename T,size_t DimNum, typename A=std::allocator<;T>; >; class dynamic_array;; //:public array_base<;T, DimNum>; 看得出也是从array_base派生的,另外,他是可以resize的.还支持reserve等STL容器的操 作. 4. template <;typename T, size_t DimNum, typename A=std::allocator<;T>; >; class shared_array;; //: public array_base<;T, DimNum>; 就是支持引用计数的动态数组.不过刚写了个外皮,内容还没开工,因为我最近要回家. sorry! [用法] 先要包含各自的头文件: #include ";static_array.hpp"; #include ";dynamic_array.hpp"; #include ";shared_array.hpp"; 1.然后就可以定义对象 cfc::static_array<;int,10>; sa1;; cfc::static_array<;int,10, 10>; sa2;; cfc::static_array<;int,10, 10, 10>; sa3;; cfc::dynamic_array<;int, 1>; da1(cfc::extents[10],10);; cfc::dynamic_array<;int, 2>; da2(cfc::extents[10][10], 10);; cfc::dynamic_array<;int, 3>; da3(cfc::extents[10][10][10], 10);; cfc::shared_array<;int,1>; sha1(cfc::extents[10]]);; cfc::shared_array<;int,2>; sha2(cfc::extents[10][10]);; cfc::shared_array<;int,3>; sha3(cfc::extents[10][10][10]);; extents是一个数组的维度生成器,用起来的很方便,跟boost学的,不过没仔细看它的实现 ,我觉得我的也不错,哈哈 2.访问元素: sa1[0] = 0;; da1[0] = 0;; sa2[0][0] = 0;; da2[0][0] = 0;; sa3[0][0][0] = 0;; da3[0][0][0] = 0;; 3.比较相等与否: bool f;; f = sa1==sb1;; f = da1==da1;; f = sa1==da1;; // 说明:只提供了==和!=,别的没提供,我觉得别的意义大 4.交换: cfc::swap(da1,db1);; cfc::swap(sa1,sb1);; cfc::swap(sa1,db1);; //说明:动态数组的交换很高效,换个指针而已, :) 5.resize: da3.resize(cfc::extents[10][100][1]);; da3.resize(cfc::extents[10][50][1]);; da3.resize(cfc::extents[10][10][20]);; da3.resize(cfc::extents[10][10][10]);; //说明:只有动态数组才能resize, 还有将来的shared_array, zz 6.赋值: da3 = db3;; sa1 = sb1;; da1 = db1;; 静态数组维度不一样不能赋值,否则会引起编译错误 动态数组和丢失了静态大小成为了array_base的数组维度不一样时,赋值引发 std::length_error异常,可以捕捉到, 比较也是这样 7.作为函数的参数 还举开头的那个例子 void foo(array_base<;int,2>; &a) { a[0][0]=10;; } 8.重要概念 <;子数组>; 高维数组的子类型,也就是低一维的数组.数组的类型为array_base,支持array_base的所有操作,但是不再支持原来数组的特定 操作子数组由[]运算符得到, sa3[0] da3[0] //类型均为array_base<;int,2>; 子数组还可以在取子数组 da3[0][1];;//类型为array_base<;int,1>; [性能] 三维大小均为100的静态,动态,原生数组以及boost::multi_array.以三重循环每次隔一个 填充, 我测试的结果,速度大概是原生数组的60%,boost数组的速度是原生数组的1/5,因此速度 大概是boost的3倍. 如果用迭代器顺序访问的话,跟原生数组相比就区别不大了.但是代码要好写一点,而且直 接支持STL算法. [实现与移植] 由于要兼顾各种编译器,而且是在VC6上做的,因此像模板偏特化等特性都不能用,需要变 通,因此相当繁琐,由此可见一个好的编译器多么重要啊. 不过话说回来,这样的代码移植性才好呢.想想连VC6都能编译的代码,移植性应该不错, :) [后记] 这是对以前的那个多维数组类的扩充与改进,增加了不少功能,去掉了不少限制, 现在静态数组的最大维数做到了3,动态数组的维数不限//你需要多高维数的?维数越高越 慢, :) 由于时间不多,精力和水平有限,其中的缺点和错误欢迎指正,也十分欢迎哪位能帮我进一 步提高访问速度. 谢谢! 附带测试程序,其中包括与boost::multi_array<;>;的速度比较代码. //the end. ^=^
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值