boost I 容器与数据结构(四)

目录

八、Variant

1.union

2.variant

(1)初始化

(2)访问元素

(3)元素比较

(4)访问器

3.variant与any比较

4.boost::variant与std::variant比较

九、multi_array

1.初始化

2.遍历元素

3.访问某个元素

4.改变形状和大小

5.创建子视图

6.适配普通数组

7.设置、修改索引基数

8.禁用全局对象


八、Variant

        variant与 any 有些类似,它是一种可变类型,是对 C / C ++ 中 union 概念的增强和扩展。普通的union只能持有 POD ( 普通数据类型 ),而不能持有如 string、vector 等复杂类型,variant 则没有这个限制,variant 已经被收入 C ++17 标准。

1.union

        union 只能持有struct和普通数据类型,它的作用主要是节省内存空间,所有的成员内存起始地址都是相同的,在嵌入式编程中有用。在任意时刻,union 中只能有一个数据成员可以有值。当给union 中某个成员赋值之后,其它成员就变成未定义状态了。

union MyUnion
{
    int num1;
    float num2;
    struct _struct {
        int a;
    } __struct;
};
//如果赋值的类型不一样,则前面赋过的值丢失
MyUnion _myUnion;
_myUnion.num1 = 10;
_myUnion.num2 = 11.1;
std::cout << _myUnion.num1 << " , " << _myUnion.num2 << " , " << _myUnion.__struct.a << std::endl;//1093769626 , 11.1 , 1093769626
//如果赋值的类型一样
_myUnion.num1 = 10;
_myUnion.__struct.a = 11;
std::cout << _myUnion.num1 << " , " << _myUnion.num2 << " , " << _myUnion.__struct.a << std::endl;//11 , 1.54143e-44 , 11
 

2.variant

        variant的接口与 any 类似,但它是一个模板类。variant 允许保存的数据类型必须在模板参数列表中声明 , 其形式很像 tuple,它对类型参数 TN 的最低要求是可拷贝构造且析构不抛出异常。

        variant支持拷贝构造和赋值,其赋值操作要求模板类型参数都支持赋值操作。

(1)初始化

        使用 variant 时 , 我们必须要在模板参数中指定它所能容纳的类型。类型的数量默认最多是 20 个。
        无参的构造函数将把 variant 的值用第一个模板参数 ( T1 ) 的默认构造函数初始化,如果 T1 没有默认构造函数,那么 variant 也不能默认构造。参数的构造函数的参数可以是模板类型参数中任意类型的值,varaint 将初始化为这种类型。

(2)访问元素

        variant的操作要比 any 方便,它能够直接访问元素的值。一旦 variant 对象被创建,它就总是可用的,不需要使用 union 的点号操作符来访问其内部成员,variant对象就像个普通的数据类型,非常方便。

        另外,也可以使用boost库中的泛型函数get( )来访问内部元素。但 get( )函数通常不是最方便、最有效的访问方法,它与 any_cast 一样也存在着类型不安全的隐患,操作 get( )函数时必须查询 variant 当前值的类型。

        variant的成员函数 empty( )和 type( )的用法与 any 相同,用来检测当前 variant 持有的对象。但 empty ( ) 永远返回 false , 因为 variant 不能是空的 , 它总有值。which ( ) 函数可以返回variant当前值的类型在模板参数列表的索引号(从 0 开始计数)。

#include <boost/variant.hpp>
#include <boost/exception/diagnostic_information.hpp>
void TestVaiant()
{
    //创建variant对象
    boost::variant<int, std::string> _variant1; 
    std::cout << "当前有值的元素:" << _variant1.which()<<",值是"<< _variant1 << std::endl;//当前有值的元素:0,值是0
    _variant1 = "one"; 
    std::cout << "当前有值的元素:" << _variant1.which() << ",值是" << _variant1 << std::endl;//当前有值的元素:1,值是one

    //获取元素的值
    std::string str= boost::get<std::string>(_variant1);
    std::cout << "_variant1的值:" << str << std::endl;
    try {
        //异常:_variant1的值不是int类型
        int num = boost::get<int>(_variant1);
    }
    catch (std::exception e)
    {
        //Throw location unknown (consider using BOOST_THROW_EXCEPTION)
        //Dynamic exception type : struct boost::wrapexcept<class boost::bad_get>
        //std::exception::what : boost::bad_get : failed value get using boost::get
       std::cout<< boost::current_exception_diagnostic_information() << std::endl;
    }
}

(3)元素比较

        variant提供了比较操作符,variant对象自身支持比较,这使得它可以被用作关联容器的元素或用于排序,但前提是 variant 的所有模板参数也是可以比较的。

(4)访问器

        variant基于访问者模式提供了模板类static_visitor ,它解耦了 variant 的数据存储和访问操作,把访问操作集中在访问器类,易于增加新的访问操作,使这两者可以彼此独立地变化。static_visitor 是一个静态访问 variant 的基类,它应该被实际访问器函数对象继承使用。

        使用 static_visitor 时首先要从 static_visitor 继承,然后重载operator( ),用来访问 variant 的内部值。访问器必须能够处理 variant 可能拥有的所有元素类型,不能仅处理其中的一部分,否则会引发编译错误。

        将访问器对象应用于 variant 需要使用函数apply_visitor( )。 apply_visitor( )有多种重载形式:

  • 最常用的形式是接收一个访问器对象和一个 variant 对象,对 variant 内的值调用访问器对象的 operator ()。
  • 只接收访问器对象,这时它会返回一个新的函数对象apply_visitor_delayed_t,用于延后对 variant 的访问。这个新的函数对象包装了原访问器,同样提供 operator (),可以直接操作 variant 对象而无须再使用apply_visitor( )。
struct MyStruct :public boost::static_visitor<>
{
    template <typename T>
    void operator()(T& i)const {
        std::cout << i << std::endl;
    }
    //对vector<int>类型的重载
    void operator()(std::vector<int>& i)const {
        for (auto a : i)
        {
            std::cout << a << ",";
       }
    }
};

void TestApplyVisitor()
{
    boost::variant<int, std::string, std::vector<int>> _variant(10);
    MyStruct myStruct;
    //apply_visitor接收两个对象:访问器对象、variant对象
    boost::apply_visitor(myStruct, _variant);//10

    _variant = "hello world";
    boost::apply_visitor(myStruct, _variant);//hello world

    _variant = std::vector<int>({ 1,2 });//operator()需要重载,适配vector
    boost::apply_visitor(myStruct, _variant);//1,2,

    //apply_visitor接收一个对象
    auto visitor = boost::apply_visitor(myStruct);
    visitor(_variant);//1,2,
}

3.variant与any比较

        variant很像 any ,它们都能容纳一个可变类型的元素。

        variant 是有界类型,其元素类型的范围由用户指定。 variant 的有界类型将取值类型限定在一个较小的范围内,所以它更容易处理和理解。因为所有允许的类型都在模板参数列表中指定,variant 还可以在编译期进行类型检查,充分利用 C ++ 作为静态强类型语言的优点。variant还提供泛型的ⅵsitor 方式访问内部元素,这是一个强大的设计模式,也是导致 variant 与any的使用方法不同的根本。

        any 是无界类型,它可以容纳任意类型的元素。any任意类型的自由往往也意味着程序员有更多的责任,就像 void * 指针,虽然灵活但要付出更多的安全检查代价。

4.boost::variant与std::variant比较

        variant在收入 C ++17 标准后做了一些修改 ,std::variant 与 boost::variant 的主要区别如下:

  • emplace ( ) 直接使用参数构造对象。
  • index ( ) 取代了 which ( )。
  • 使用 visit ( ) 访问 variant 对象 , 访问器不必从某个特定类派生。
  • 元函数 variant_size 可以获取 variant 的类型(模板参数)数量 。

九、multi_array

        multi_array 是一个多维容器,高效地实现了 STL 风格的多维数组,比使用原始多维数组或 vector<vector<T>>更好用。

        multi_array 是递归定义的,它的每个维度都是一个 multi_array ,最底层的则是个一维的multi_array ,因此 multi_array 是组合模式的具体应用。

1.初始化

        multi_array 的模板参数很像标准容器,除容纳的元素类型外,它多了一个维数的模板参数,用来指定多维数组的维数。使用extent_gen类和预定义的实例boost::extents可以指定每个维度的具体值。

#include <boost/multi_array.hpp>
void TestMultiArray(){
    boost::multi_array<int, 3> _array0;
    boost::multi_array<int, 3> _array1(boost::extents[2][3][4]);
    int num = 0;
    for (size_t i = 0; i < 2; i++)
    {
        for (size_t j = 0; j < 3; j++)
        {
            for (size_t z = 0; z < 4; z++)
            {
                _array1[i][j][z] = num++;
            }
        }
    }
}

         除了使用 extents 对象,multi_array 还支持另一种创建对象的方法 , 直接向构造函数传入一个维度序列(使用std::vector或者array)。使用序列的构造方式通常要比 extents 对象要快一些,也可以节省一些内存,而且有利于编写与维度无关的代码。

    boost::array<int, 3> _arr_5 = { 2,3,4 };
    boost::multi_array<int, 3> _array5(_arr_5);

    boost::multi_array<int, 3> _array6(std::vector<int>{2, 3, 4});

2.遍历元素

        multi_array 的总维数可以用成员函数num_dimensions ( ) 获得,它的返回值就是模板参数中的 NumDims 。

        函数 shape( )返回一个常量指针(数组),里面有 NumDims 个元素,表明了各个维度的具体值。

        通过函数num_elements( )可以获得元素的总个数。

    auto shapes = _array1.shape();
    for (size_t i = 0; i < _array1.num_dimensions(); i++)//3
    {
        std::cout << shapes[i] << ",";//2,3,4,
    }
    std::cout << std::endl << _array1.num_elements() << std::endl;//24
    for (size_t i = 0; i < shapes[0]; i++)
    {
        for (size_t j = 0; j < shapes[1]; j++)
        {
            for (size_t z = 0; z < shapes[2]; z++)
            {
                std::cout << _array1[i][j][z] << " ";
            }
        }
    }

3.访问某个元素

        使用位置索引序列可以访问多维数组的某个元素,某些时候这种访问方式比operator[]效率更高。

        成员函数data( )以指针的形式返回内部的数组,它包含了所有的元素。

    boost::array<int, 3> index = { 0,0,1 };
    std::cout << "[0,0,1]位置的元素值为:" << _array1(index) << std::endl;//[0,0,1]位置的元素值为:1

    int* p = _array1.data();
    std::cout << *(p + 1) << std::endl;//1

4.改变形状和大小

        multi_array 可以在运行时使用成员函数reshape()改变多维数组的形状,即变动各个维度的大小,但总维数和元素数量保持不变,变动前的维度乘积与变动后的维度乘积必须相同。reshape()接收一个维度序列作为参数,std::vector 、 std::array 或 boost::array均可,通常 array 会更快。

        multi_array 也可以在运行时使用成员函数resize ( ) 改变多维数组的大小,动态增长或缩减元素的数量,但总维数不能变(它已经在模板参数中固定了)。 resize( )函数既可以接收维度序列,也可以接收 extents 表达式。在变动大小时, multi_array 将重新分配内存,原有的元素将被复制到新内存中,然后被析构,新增的元素使用默认构造函数构造。如果multi_array 的元素总数变少,那么原有的多余元素将被删除。

    //改变形状:由2*3*4->3*2*4
    boost::array<int, 3> newArray1 = { 3,2,4 };
    _array1.reshape(newArray1);
    //改变大小:由2*3*4->10*10*10
    boost::array<int, 3> newArray2 = { 10,10,10 };
    _array1.resize(newArray2);
    std::cout << "由2*3*4->10*10*10后元素个数:" << _array1.num_elements() << std::endl;
    //由10*10*10->8*8*8
    _array1.resize(boost::extents[8][8][8]);
    std::cout << "由10*10*10->8*8*8后元素个数:" << _array1.num_elements() << std::endl;

5.创建子视图

        多维数组的操作是比较复杂的, multi_array 允许用户为多维数组创建一个只查看其中一部分数据的子视图( view ) ,子视图既可以与原数组拥有相同的维数,也可以少于原数组的维数。后一种情况被称为多维数组的“切片” ( slice ),它可以降低数组的维数,使之更易处理。

        index_range可以指定从多维数组中抽取的维度范围。不带参数的 index_range 构造函数表示该维度的所有元素,它可以在不知道具体维度的情况下获取该维度的元素。index_range 还重载了比较操作符,可以使用比较操作符指定索引的范围,配合不带参数的index_range 构造函数可以灵活地指定子视图的范围。

        index_gen类的预定义实例boost::indices可以定义子视图的索引范围。multi_array的operator[]接收boost::indices对象返回的子视图。子视图也是一个multi_array,但是不能改变子视图的形状和大小。

        使用boost::indices的时候也可以改变子视图的步长,只需要在index_range里增加第三个参数即可。

        view的成员函数 strides( )可以获得每个维度的步长,类似 shape( ),它返回的是一个数组指针。[不理解~]

        创建多维数组的切片与子视图类似,只需要向indices的 operator [ ] 传入要被切片维度的整数值即可。

    boost::multi_array<int, 2> _array2(boost::extents[3][4]);//二维数组,三行四列
    //[0 1 2 3]
    //[1 2 3 4]
    //[2 3 4 5]
    for (size_t i = 0; i < 3; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            _array2[i][j] = i + j;
        }
    }
    typedef boost::multi_array<int, 2>::index_range range;
    //index_range重载了比较操作符 ;参数为空表示所有元素
    boost::multi_array<int, 2>::array_view<2>::type view0 = _array2[boost::indices[1 <= range() <= 2][range()]];//第一维索引1~2,第二维所有元素,即两行四列
    //[1 2 3 4]
    //[2 3 4 5]
    for (size_t i = 0; i < 2; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            std::cout << view0[i][j] << " ";
        }
        std::cout << std::endl;
    }
    //获取2*2的子视图,可设置步长
    boost::multi_array<int, 2>::array_view<2>::type view = _array2[boost::indices[range(0, 2, 1)][range(0, 4, 2)]];
    //[0 2]
    //[1 3]
    std::cout << " view.num_elements():" << view.num_elements() << std::endl;
    for (size_t i = 0; i < view.shape()[0]; i++)
    {
        for (size_t j = 0; j < view.shape()[1]; j++)
        {
            std::cout << view[i][j] << ",";
        }
        std::cout << std::endl;
    }
    //获得每个维度的步长,我不理解
    //for (size_t i = 0; i < view.num_dimensions(); i++)
    //{
    //    std::cout << view.strides()[i] <<  "  ";//4  2
    //}

    //获取切片,第一行的四列
    boost::multi_array<int, 1>::array_view<1>::type view1 = _array2[boost::indices[0][range(0, 4)]];
    for (size_t i = 0; i < 4; i++)
    {
        std::cout << view1[i] << " ";//0 1 2 3
    }

6.适配普通数组

        multi_array 是多维数组最常用的类,它自己管理内存,可以动态增长,但有的时候我们需要将一个一维数组适配成多维数组进行处理,适配后的多维数组不能动态增长。

        multi_array 库提供了另外两个类multi_array_ref和 const_multi_array_ref来满足这一需求。它们可以把一段连续的内存(原始数组)适配成多维数组。const_multi_array_ref是只读的,功能少一些,适配后的多维数组不能修改数组元素的值。

    int arr[12];                            //创建一维数组
    for (size_t i = 0; i < 12; i++)
    {
        arr[i] = i;
    }
    std::cout << std::endl << "使用multi_array_ref:" << std::endl;
    boost::multi_array_ref<int, 2> _array3(arr, boost::extents[3][4]);//使用multi_array_ref转成二维数组
    for (size_t i = 0; i < 3; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            _array3[i][j] += 2;                                           //可以修改值
            std::cout << _array3[i][j] << " ";//2 3 4 5 6 7 8 9 10 11 12 13
        }
    }
    std::cout << std::endl << "使用const_multi_array_ref:" << std::endl;
    boost::const_multi_array_ref<int, 3> _array4(arr, boost::extents[2][2][3]);
    for (size_t i = 0; i < 2; i++)
    {
        for (size_t j = 0; j < 2; j++)
        {
            for (size_t z = 0; z < 3; z++)
            {
                std::cout << _array4[i][j][z] << ",";//2,3,4,5,6,7,8,9,10,11,12,13,
            }
        }
    }

7.设置、修改索引基数

        multi_array 的数组索引遵循 C 的惯例,从0开始计数,但有些时候多维数组都从0开始计数会不太方便,有的维度使用其他的计数方式可能会更好。multi_array 提供了多种方式来变更索引基数来满足这个特殊的要求。

        我们可以在创建多维数组时使用 extent_range 对象的构造函数指定索引范围,用来代替简单的整数维度的定义,extent_range 的用法与index_gen 类似。

        成员函数 reindex ( )可以在多维数组创建后随时更改索引基数,它有两种重载形式:如果传入一个整数 N ,那么它把所有维度的基数都改为这个整数 N ,如果传入一个整数序列,那么就按序列中的值逐个变更维度的基数。

        成员函数 index_bases( )返回各维度索引的起始值数组指针,它的行为类似 shape( )。

    typedef  boost::multi_array<int, 3>::extent_range _range;
    boost::multi_array<int, 3> _array7(boost::extents[_range(1, 5)][4][_range(-2, 2)]);//4*4*4
    _array7[1][0][-2] = 10;//第一个元素

    //修改索引基数
    _array7.reindex(2);//三个维度的索引都是从2开始
    std::cout << "_array7[2][2][2]为第一个元素,其值为:" << _array7[2][2][2] << std::endl;
    _array7.reindex(boost::array<int, 3>{0, 1, 1});//第一维的索引从0开始,第二维、第三维的索引从1开始
    std::cout << "_array7[0][1][1]为第一个元素,其值为:" << _array7[0][1][1] << std::endl;

    //获取各维度的索引
    std::cout << "第一维索引:" << *_array7.index_bases()
        << ",第二维索引:" << *(_array7.index_bases() + 1)
        << ",第三维索引:" << *(_array7.index_bases() + 2) << std::endl;

8.禁用全局对象

        在创建多维数组时我们会用到 extents 和 indices这两个“全局”对象,它们并不是真正的全局对象,而是被定义在一个匿名的名字空间里,相当于两个静态全局变量,它们只能被源文件使用。

        如果工程中有多处使用了 multi_array 组件,那么可能会有很多个 extents 和 indices 对象,它们的构造将会造成不小的“开销”,可以在包含头文件#include <boost/multi_array.hpp> 前定义宏
#define BOOST_MULTI_ARRAY_NO_GENERATORS,以禁止它们的预定义。这时多维数组的创建可以改用维度序列的方式。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烫青菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值