• 《C++标准程序库》笔记之二


    《C++标准程序库》笔记之二

    本篇博客笔记顺序大体按照《C++标准程序库(第1版)》各章节顺序编排。

    --------------------------------------------------------------------------------------------

    6. STL 容器

    6.1-1

    本节讲述STL容器的共通能力。其中大部分都是必要条件,所有STL容器都必须满足那些条件。三个最最核心的能力是:

    (1)所有容器提供的都是“value语意”而非“reference语意”。容器进行元素的安插操作时,内部实施的是拷贝操作,置于容器内。因此STL容器的每一个元素都必须能够被拷贝。如果你打算存放的对象不具有public copy构造函数,或者你要的不是副本(例如你要的是被多个容器共同容纳的元素),那么容器元素就只能是指针(指向对象)。参见上一篇博客C++标准程序库笔记之一,5.10节。

    (2)总体而言,所有元素形成一个次序(oder)。也就是说,你可以依相同次序一次或多次遍历每个元素。每个容器都提供“可返回迭代器”的函数,运用那些迭代器你就可以遍历元素。这是STL算法赖以生存的关键接口。

    (3)一般而言,各项操作并非绝对安全。调用者必须确保传给操作函数的参数符合需求。违反这些需求(例如使用非法索引)会导致未定义的行为。通常STL自己不会抛出异常。如果STL容器所调用的使用者自定操作抛出异常,会导致各不相同的行为。

    容器的共通操作如表6.1

    6.2 Vector

    6.2-1 vector的容量之所以重要,有以下两个原因:

    (1)一旦内存重新配置,和vector元素相关的所有references、pointers、iterators都会失效;

    (2)内存重新配置很耗时间(安插就可能导致重新配置)。

    vectors的容量不会缩减,我们便可确定,即使删除元素,其references、pointers、iterators也会继续有效,继续指向动作发生前的位置。然而安插操作却可能是references、pointers、iterators失效(内存重新配置)。

    6.3 Deques

    6.3-1 deque不支持对容量和内存重分配时机的控制。不过,deque的内存重分配优于vector,因为其内部结构显示,deque不必在内存重分配时复制所有元素。参见《STL源码剖析》

    6.5 Sets 和 Multisets(红黑树)

    有两种方式可以定义排序准则:

    (1)例如: std::set<int, std::greater<int> > coll;

    这种情况下,排序准则就是型别的一部分。因此型别系统确保“只有排序准则相同的容器才能被合并”。这是排序准则的通常指定法。更精确地说,第二参数是排序准则的型别,实际的排序准则是容器所产生的函数对象(function object,或称functor)。为了产生它,容器构造函数会调用“排序准则型别”的default构造函数。

    1. 元素比较动作只能用于型别相同的容器。换言之,元素和排序准则必须有相同的型别,否则编译时期会产生型别方面的错误。如下:

    std::set<float> c1;     // sorting criterion -> std::less<>
    std::set<float, std::greater<float> > c2;     // sorting criterion -> std::less<>
    ...
    if (c1 == c2)          // ERROR:different types
    {
        ...
    }

    2. 我们也可以使用自定之排序准则,这里同时也是把仿函数当做排序准则

    #include <iostream>
    #include <string>
    #include <set>
    #include <algorithm>
    using namespace std;
    
    class Person
    {
        public:
            string firstname() const;
            string lastname() const;
            ....
    };
    /* class for function predicate
    * - operator() returns whether a person is less than another person
    */
    class PersonSortCriterion
    {
        public:
            bool operator() (const Person& p1, const Person& P2) const
            {    
                /* a person is less than another person
                * - if the last name is less
                * - if the last name is equal and the first name is less
                */
                return p1.lastname() < p1.lastname() ||
                    (!(p2.lastname() < p1.lastname()) &&
                    p1.firstname() < p2.firstname());
            }
    };
    
    int main()
    {
        // declare set type with special sorting criterion
        typedef set<Person, PersonSortCriterion> PersonSet;
    
        // create such a collection
        PersonSet coll;
        ...
    
        // do something with the elements
        PersonSet::iterator pos;
        for(pos = coll.begin(); pos != coll.end(); ++pos)
        {
            ...
        }
        ...
    }

    (2)以构造函数参数定义之
    这种情况下,同一个型别可以运用不同的排序准则(如下面程序例子),而排序准则的初始值或状态也可以不同。如果执行期才获得排序准则,而且需要用到不同的排序准则(但数据型别必须相同),此一方式可派上用场。

    // 执行期指定排序准则,排序准则不同,但型别相同:
    #include <iostream>
    #include <set>
    using namespace std;
    
    // type for sorting criterion
    template <class T>
    class RuntimeCmp
    {
        public:
            enum cmp_mode {normal, reverse};
        private:
            cmp_mode mode;
        public:
            // constructor for sorting criterion
            // - default criterion uses value normal
            RuntimeCmp(cmp_mode m = normal) : mode(m) { }
    
            // comparision of elements
            bool operator() (const T& t1, const T& t2) const
            {
                return mode == normal ? t1 < t2 : t2 < t1;
            }
    
            // comparision of sorting criteria
            bool operator== (const RuntimeCmp& rc)
            {
                return mode == rc.mode;
            }
    };
    
    // type of a set that uses this sorting criterion
    typedef set<int, RuntimeCmp<int> > IntSet;
    
    // forward declaration
    void fill(IntSet& set);
    
    int main()
    {
        // create, fill, and print set with normal element order
        // - uses default sorting criterion
        IntSet coll1;
        fill(coll1);
        print_elements(coll1, "coll1 :");
    
        // create sorting criterion with reverse element order
        RuntimeCmp<int> reverse_order(RuntimeCmp<int>::reverse);
    
        // create, fill, and print set with reverse element order
        IntSet coll2(reverse_order);
        fill(coll2);
        print_elements(coll2, "coll2 :");
        
        // assign elements AND sorting criterion
        coll1 = coll2;
        coll1.insert(3);
        print_elements(coll1, "coll1 :");
        
        // just to make sure...
        if(coll1.value_comp() == coll2.value_comp())
        {
            cout << "coll1 and coll2 have same sorting criterion" << endl;
        }
        else
        {
            cout << "coll1 and coll2 have different sorting criterion" << endl;
        }
    }
    
    void fill(IntSet& set)
    {
        // fill insert elements in random order
        set.insert(4);
        set.insert(7);
        set.insert(5);
        set.insert(1);
        set.insert(6);
        set.insert(2);
        set.insert(5);
    }
    
    输出:
    coll1:1 2 4 5 6 7
    coll2:7 6 5 4 2 1
    coll1:7 6 5 4 3 2 1 
    coll1 and coll2 have same sorting criterion

    1. 在这个程序中,RuntimeCmp<> 是一个简单的template,提供“执行期间面对任意型别定义一个排序准则”的泛化能力。其default构造函数采用默认值normal,按升序排序;你也可以将RuntimeCmp<>::reverse传递给构造函数,便能按降序排序。

    2. 注意,coll1 和 coll2 拥有相同的型别(包括元素的型别int,和排序准则的型别RuntimeCmp),该型别即fill() 函数的参数型别。再请注意,assignment操作符不仅赋值了元素,也赋值了排序准则(否则任何一个赋值操作岂不会轻易危及排序准则)。

    3. 请注意,排序准则也被用于元素相等性检验工作,当采用缺省排序准则时,两元素的相等性检验语句如下:

    if (!(elem1 < elem2 || elem2 < elem1))

    这样做的好处有三:

    a. 只需传递一个参数作为排序准则;

    b. 不必针对元素型别提供operator==;

    c. 你可以对“相等性”有截然相反的定义,不过当心造成混淆。

    6.6 Maps  和 Multimaps

    Maps和Multimaps 的元素型别key 和 T, 必须满足一下两个条件:

    (1)key/value 必须具备assignable(可赋值的)和copyable(可复制的)性质;

    (2)对排序准则而言,key必须是comparable(可比较的)。

    Maps,Multimaps的排序准则定义和Sets,Multisets类似。同样,元素的比较动作也只能用于型别相同的容器。换言之,容器的key,value,排序准则都必须有相同的型别,否则编译期会产生型别方面的错误。

    再次强调,更易型算法不得用于关联式容器,针对关联式容器,提供的迭代器类型为const iterator。

    6.7 其他STL容器

    6.7-1

    STL 是个框架,除了提供标准容器,它也允许你使用其他数据结构作为容器。你可以使用字符串或数组作为STL容器,也可以自行撰写特殊容器以满足特性需求。如果你自行撰写容器,仍可从诸如排序、合并等算法中受益。这样的框架正是“开放性封闭(Open-Closed)”原则的极佳范例:允许扩展,谢绝修改。

    下面是是你的容器“STL化”的三种不同方法:

    (1)The invasive approach(侵入性作法) 直接提供STL容器所需接口。特别是诸如begin() 和 end() 之类的常用函数。这种作法需以某种特定方式编写容器,所以是侵入性的。

    以string为例:

    Strings可被视为以字符为元素的一种容器;字符构成序列,你可以在序列上来回移动遍历。因此,标准的string类别提供了STL容器接口。Strings也提供成员函数begin()和end(),返回随机存取迭代器,可用来遍历整个string。

    (2)The noninvasive approach(非侵入性作法)

    由你撰写或提供特殊迭代器,作为算法和特殊容器间的界面。此一作法是非侵入性的,它所需要的只是“遍历容器所有元素”的能力——这是任何容器都能以某种形式展现的能力。

    以Array为例:

    Arrays可被视为一种STL容器,但array并不是类别,所以不提供begin() 和 end() 等成员函数,也不允许存在任何成员函数。在这里,我们只能采用非侵入性作法或包装法。采取非侵入性作法很简单,你只需要一个对象,它能够透过STL迭代器接口,遍历数值的所有元素。事实上这样的对象早已存在,它就是普通指针。STL设计之初就决定让迭代器拥有和普通指针相同的接口,于是你可以将普通指针当成迭代器来使用。

    (3)The wrapper approach(包装法) 将上述两种方法加以组合,我们可以写一个外套类别来包装任何数据结构,并显示出与STL容器相似的接口。

    6.7-2 Hash Tables

    Hash Tables数据结构可用于群集身上,非常重要,却因为提议太晚,未能包含于C++标准程序库——“我们必须在某个时间点中止引入新功能,开始关注细节,否则工作永无止境”。 Hash tables相关内容可以参见《STL源码剖析》。

    6.9-1

    表6.33提供了一份STL容器能力一览表。

    ------------------------------------------------------------------------------------------------------------------------------------
    7 STL迭代器
    STL迭代器相关详细内容参见《STL源码剖析》,这里只提几点注意事项:
    (1)迭代器是一种“能够遍历某个序列内的所有元素”的对象(注意,迭代器是一种对象)。
    (2)注意,Input迭代器只能读取元素一次。如果你复制Input迭代器,并使原Input迭代器和新产生的副本都先前读取,可能会遍历到不同的值(故而,如果两个Input迭代器占用同一个位置,则两者相等,但这并不意味它们存取元素时能够传回相同的值)。
    (3)Output迭代器和Input迭代器相反,其作用是将元素值一个个写入。也就是说,你只能一个元素一个元素地赋新值,而且不能使用Output迭代器对同一序列进行两次遍历。这就好像将元素值写到“黑洞”里去,如果在相同位置上对着同一个“黑洞”进行第二次写入,不能确保这次写入的值会覆盖前一个值。
    (4) 面对Output迭代器,我们无需检查是否抵达序列尾端,便可直接写入数据。事实上由于Output迭代器不提供比较操作,所以你不能将Output迭代器和尾端迭代器相比较。
    (5)和Input迭代器及Output迭代器不同,Forward迭代器能多次指向同一群集中的同一元素,并能多次处理同一元素。同时,你必须在提领数据之前确保它有效。
    (6)只有在面对Random Access Iterator时,你才能以 operator< 作为循环结束与否的判断准则(只有Random Access Iterator提供了 operator< 操作符),如“pos < coll.end() -1;”;否则,一般情况下我们使用不等号“pos != coll.end();”。
    (7)尽可能优先选用前置式递增操作符(++iter)而不是后置式递增操作符(iter++),因为前者性能更好,参见C++小语法。
    (8)迭代器的递增和递减操作有个问题。一般而言你可以递增或递减临时迭代器,但对于vectors和strings就不行,如下例子:

    std::vector<int> coll;
    ...
    // sort, starting with the second element
    if (coll.size() > 1)
    {
     sort(++coll.begin(), coll.end() );
    }

    通常编译sort时会失败。但如果你换用deque取代vector,就可以通过编译。有时vector也可以通过编译——取决于vector的具体实作。
    这种问题的产生原因是,vector的迭代器通常被实作为一般指针。要知道,C++不允许你修改任何基本型别(包括指针)的临时值(典型的右值),但对于struct和class则允许。因为如果迭代器被实作为一般指针,编译会失败;如果被实作为class,则编译可以成功。deques、lists、sets和maps总是能够通过编译,因为它们的迭代器不可能被实作为一般指针。至于vector,就要取决于实作手法了。
    (9)Reverse(逆向)迭代器转换前后迭代器的逻辑位置发生了变化,具体参见《STL源码剖析》。容器的成员函数rbegin()和rend()各传回一个Reverse迭代器,它们就想begin()和end()一样,共同定义一个半开区间。
    (10)有三种插入迭代器:back inserters、front inserters和general inserters。general inserters(或称general insert iterator)根据两个参数而初始化:1. 容器 2. 待安插位置。迭代器内部以“待安插位置”为参数,调用成员函数insert()。general inserters对于所有标准容器均适用,因为所有容器都有insert()成员函数。然而对关联式容器(sets和maps)而言,安插位置只是个提示,因为在这两种容器中,元素的真正位置视其实值(或键值)而定,安插位置如果使用不当反而可能导致比较糟糕的性能。

     ------------------------------------------------------------------------------------------------------------------------------------
    8 仿函数(functors)——又名函数对象,function objects
    仿函数的详细信息参见《STL源码剖析》以及上一篇博客C++标准程序库笔记之一 5.9-1 ,这里只提几点需要特别注意的地方:
    (1)仿函数是passed by value(传值),不是passed by reference(传址),因此,算法并不会改变随参数而来的仿函数的状态。例如下面产生的两个序列都以1开始:

    IntSequence seq(1);         // integral sequence starting with value 1
    // insert sequence beginning with 1
    generate_n(back_inserter(coll), 9, seq);
    
    // insert sequence beginning with 1 again
    generate_n(back_inserter(coll), 9, seq);

    将仿函数以by value方式传递(而非by reference方式传递)的好处是,你可以传递常量或暂时表达式。如果不这么设计,你就不可能传递IntSequence(1)这样的表达式。至于缺点就是,你无法改变仿函数的状态。算法当然可以改变仿函数的状态,但你无法存取并改变其最终状态,因为你所改变的只不过是仿函数的副本而已。然而有时候我们的确需要存取最终状态,因此,问题在于如何从一个算法中获得结果。 有两个办法可以从“运用了仿函数”的算法中获取“结果”或“反馈”:

    1. 以by reference的方式传递仿函数;

    2. 运用for_each()算法的返回值。

    1. 为了能够以by reference 方式传递仿函数,你只需要在调用算法时,明白标示仿函数型别是个reference型别,如下:

    #include <iosteam>
    #include <list>
    #include <algorithm>
    using namespace std;
    
    class IntSequence
    {
        private:
            int value;
        public:
            // constructor
            IntSequence(int initialValue)
                : value(initialValue) { }
            // function call, 无参
            int operator() ()
            {
                return value++;
            }
    };
    
    int main()
    {
        list<int> coll;
        IntSequence seq(1);     // integral sequence starting with 1
    
        // insert values from 1 to 4
        // - pass function object by reference(传址)
        // so that it will continue with 5
        // 传址,改变了仿函数的状态
        generate_n<back_insert_iterator<list<int> >,
                        int, IntSequence&>(back_inserter(coll),     // start
                                                        4,         // number of elements
                                                        seq);     // generates values
        print_elements(coll);
    
        // insert values from 42 to 45
        // 传值,不会改变原来仿函数的状态,改变的是仿函数的副本的状态
        generate_n(back_inserter(coll), 4, IntSequence(42));
        print_elements(coll);
        
        // continue with first sequence
        // - pass function object by value(传值)
        // so that it will continue with 5 again
        // 传值,不会改变原来仿函数的状态,改变的是仿函数的副本的状态
        generate_n(back_inserter(coll), 4, seq);
        print_elements(coll);
    
        // continue with first sequence again
        // 传值,不会改变原来仿函数的状态,改变的是仿函数的副本的状态
        generate_n(back_inserter(coll), 4, seq);
        print_elements(coll);
    
    }
    
    输出:
    1 2 3 4
    1 2 3 4 42 43 44 45
    1 2 3 4 42 43 44 45 5 6 7 8
    1 2 3 4 42 43 44 45 5 6 7 8 5 6 7 8

    2. for_each() 有一个其他算法概莫有之的绝技,那就是它可以返回仿函数。这样,你就可以通过for_each() 的返回值来获取仿函数的状态了。

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    // function object to process the mean value
    class MeanValue
    {
        private:
            long num;      // number of elements
            long sum;      // sum of all element values
        public:
            // constructor
        MeanValue() : num(0), sum(0) { }
    
        // "function call"
        // - process one more element of the sequence
        void operator() (int elem)
        {
            num++;          // increment count
            sum += elem;      // add value
        }
    
        // return mean value
        double value()
        {
            return static_cast<double>(sum) / static_cast<double>(num);
        }
    };
    
    int main()
    {
        vector<int> coll;
    
        // insert elements from 1 to 8
        // for_each返回一个仿函数,保存在mv里面,记录了仿函数(的副本)改变之后的状态
        MeanValue mv = for_each(coll.begin(), coll.end(), MeanValue());
        cout << "mean value:" << mv.value() << endl;
    }

    (2)判断式(Predicates),就是返回布尔值(可转换为bool)的一个函数或仿函数。对STL而言,并非所有返回布尔值的函数都是合法的判断式,看下面的例子:

    #include <iostream>
    #include <list>
    #include <algorithm>
    using namespace std;
    
    class Nth         // function object that returns true for the nth call
    {
        private:
            int nth;            // call for which to return true
            int count;        // call counter
        public:
            Nth(int n) : nth(n), count(0) { }
            bool operator() (int)
            {
                return ++count == nth;
            }
    };
    
    int main()
    {
        list<int> coll;
        // insert elements from 1 to 9
        for(int i = 1; i <= 9; ++i)
        {
            coll.push_back(i);
        }
    
        print_elements(coll, "coll:");
    
        // remove third element
        list<int>::iterator pos;
        pos = remove_if(coll.begin(), coll.end(), Nth(3));
        coll.erase(pos, coll.end());
        print_elements(coll, "nth removed:");
    }
    
    输出:
    coll: 1 2 3 4 5 6 7 8 9
    nth removed: 1 2 4 5 7 8 9

    有两个(而不是一个)元素——也就是第3个和第6个元素——被移除了!为什么会这样?因为该算法的一般实作版本,会于算法内部保留判断式的一份副本:

    template <class ForwIter, class Predicate>
    ForwIter std::remove_if(ForwIter beg, ForwIter end, Predicate op)
    {
        beg = find_if(beg, end, op); 
        if (beg == end)
        {
            return beg;
        }
        else{
            ForwIter next = beg;
            return remove_copy_if(++next, end, beg, op);
        }
    }

    1. remove_if 传进op,此时会复制一份op的副本供内部使用(副本1);

    2. find_if 传进副本1, 同样会根据副本1 复制一份副本2,供find_if内部使用;

    3. 调用remove_copy_if时,可见的是副本1,此时的副本1并未被修改过(因为find_if使用的是根据副本1复制而来的副本2)。

    也即,这个算法使用find_if来搜寻应被移除的第一个元素。然而,接下来它使用传进来的判断式op的副本去处理剩余元素。这时原始状态下的Nth再一次被使用,因而会移除剩余元素中的第3个元素,也就是整体的第6个元素。

    这种行为不能说是一种错误,因为C++ Standard 并未明定判断式是否可被算法复制一份副本。因此,为了获得C++标准程序库的保证行为,你不应该传递一个“行为取决于被拷贝次数或被调用次数”的仿函数。也就是说,如果你以两个相同参数来调用一个单参数判断式,该判断式应该总是返回相同结果。换句话说,判断式不应该因为被调用而改变自身状态;判断式的副本应该和其正本有着相同状态。要做到这一点,你一定得保证不能因为函数调用而改变判断式的状态,你应当将 operotor() 声明为const成员函数。

    上面的尴尬结果可以避免,只要使用另一种办法来代替find_if():

    template <class ForwIter, class Predicate>
    ForwIter std::remove_if(ForwIter beg, ForwIter end, Predicate op)
    {
        // 不适用STL算法find_if,不会再额外复制多一份op的副本
        while (beg != end && !op(*beg) )
        {
            ++beg;
        }
        if (beg == end)
        {
            return beg;
        }
        else{
            ForwIter next = beg;
            return remove_copy_if(++next, end, beg, op);
        }
    }

    就我所知,目前的STL实作品中,只有remove_if()算法有这个问题。如果你换用remove_copy_if() ,就一切正常。然而如果考虑到可移植性,你就永远不该依赖任何实作细节,而应该总是将判断式的 operator() 声明为const成员函数。

    (3)C++标准程序库提供了许多预定义仿函数,表8.1, 要使用这些仿函数,必须包含头文件<functional>. 同时也提供了各种类型的函数配接器,使用组合使用函数配接器,可以构造出非常复杂的表达式。参见《STL源码剖析》

    ------------------------------------------------------------------------------------------------------------------------------------

    9 STL 算法 —— STL Algorithms

    STL算法的详细信息参见《STL源码剖析》,这里只提几点需要特别注意的地方:

    (1)STL算法采用覆盖模式而非安插模式。所以调用者必须保证目标区间拥有足够的元素空间。当然,你也可以运用特殊的安插型迭代器将覆盖模式改变为安插模式;

    (2)再次强调,判断式不应该在函数调用过程中改变其自身状态;

    (3)再次强调,关联式容器的元素被视为常数,惟其如此,你才不会在变动元素的时候有任何可能违反整个容器的排序准则。因此,你不可以将关联式容器当做变动性算法的目标区间;

    (4)注意,移除算法只是在逻辑上移除元素,手段是:将不需被移除的元素往前覆盖应被移除的元素。因此它并不改变操作区间内的元素个数,而是返回逻辑上的新终点位置。至于是否使用这个位置进行诸如“实际移除元素”之类的操作,那是调用者的事情。任何算法,都不能(也无法)通过迭代器移除元素。

    (5)Lists没有提供随机存取迭代器,所以不可以对它使用排序算法。然而lists本身提供了一个成员函数sort(),可用来对其元素排序。

    (6)重排元素(Shuffling, 搅乱次序)

    void random_suffle (RandomAccessIterator beg, RandomAccessIterator end)
    void random_suffle (RandomAccessIterator beg, RandomAccessIterator end, RandomFunc& op)

    注意,op是一个non-const reference。所以你不可以将暂时数值或一般函数传进去(临时变量、函数返回值都是典型的右值,只有const reference可以指向它们)。
    random_shuffle()的op参数需要使用non-const reference的原因:必须如此,因为典型的随机数产生器拥有一个局部状态(locate state)。旧式C函数如rand()是将其局部状态存储在某个静态变量中。但是这有一些缺点,例如这种随机数发生器本质上对于多线程(multi-threads)而言就不安全,而且你也不可能拥有两个各自独立的随机数流(streams)。如果使用仿函数,其区域状态被封装为一个或多个成员变量,那么就有了比较好的解决方案。这样一来,随机数产生器就不可能具备常数性,否则何以改变内部状态,何以产生新的随机数呢?不过你还是可以用by value方式传递随机数产生器,为什么非要以by non-const reference方式传递呢?是这样的,如果这么做(采用by value),每次调用都会在内部复制一个随机数产生器及其状态,结果,每次你传入随机数产生器,所得的随机数序列都一样,那又有何随机可言?所以,必须以by non-const reference方式传递op参数。

    ------------------------------------------------------------------------------------------------------------------------------------
    10 特殊容器
    (1)两大类:
    1. 容器配接器(container adapters):Stacks(栈),Queues(队列),Priority queues(优先队列),参见《STL源码剖析》
    2. bitset特殊容器:Bitsets造出一个内含(bits)或布尔(boolean)值且大小固定的array。当你需要管理各式标志(flags),并以标志的任意组合在表现变量时,就可运用bitsets。C程序和传统C++程序通常使用型别long来作为bits array,再通过&, |, ~等位操作符(bit operators)操作各个位。Class bitset的优点在于可容纳任意个数的位(但不能动态改变),并提供各项操作。例如你可以对某个特定位置赋值一个位,也可以将bitsets作为由0和1组成的序列,进行读写。
    注意,你不可以改变bitset内位的数量。这个数量的具体值是由template参数决定的。如果你需要一个可变长度的位容器,可考虑使用vector<bool>。
    Class bitset定义于头文件<bitset>之中, 其中的class bitset是个template class,有一个template参数,用来指定位的数量:

    #include <bitset>
    namespace std
    {
        template <size_t Bits>
            class bitset;
    }

     在这里,template参数并不是一个型别,而是一个不带正负号的整数。注意,如果template参数不同,具现化所得的template型别就不同。换句话说,你只能针对位个数相同的bitsets进行比较和组合。
    (2)Bitsets运用实例
    1. 将Bitsets当做一组标志
    这个例子展示如何运用bitsets来管理一组标志。每个标志都有一个由枚举型(enum)定义出来的值。该枚举值就表示位在bitset中的位置。举个例子,这些bits可以代表颜色,那么每一个枚举值都代表一种颜色。通过运用bitset,你可以管理一个变量,其中包含颜色的任意组合:

    #include <bitset>
    #include <iostream>
    using namespace std;
    
    int main()
    {
        /* enumeration type for the bits
         * - each bit represents a color
         */
         enum Color {red, yellow, green, blue, white, black,……numColors };
    
         // create bitset for all bits/colors
         bitset<numColors> usedColors;
    
         // set bits for two colors
         usedColors.set(red);        // 设置red(0号位置)上的bit位
         usedColors.set(blue);        // 设置blue(3号位置)上的bit位
    
         // print some bitset data
         cout << "bitfield of used colors:" << usedColors << endl;
         cout << "number of used colors:" << usedColors.count() << endl;
         cout << "bitfield of unused colors:" << ~usedColors << endl;
    
         // if any color is used
         if (usedColors.any())
         {
             // loop over all colors
             for (int c = 0; c < numColors; ++c)
             {
                 //if the actual color is used
                 if (usedColors[(Color)c])
                 {
                     ....
                 }
             }
         }
    }

    2. 在I/O中利用Bitsets表示二进制
    Bitsets一个强有力的特性就是可以在整数值和位序列之间相互转化,只要很简单地产生一个临时的bitset就可以办到:

    #include <bitset>
    #include <iostream>
    #include <string>
    #include <limits>
    using namespace std;
    
    int main()
    {
        /* print some numbers in binary representation
        */
        cout << "267 as binary short:" << bitset<numeric_limits<unsigned short>::digits>(267) << endl;
        cout << "267 as binary long:" << bitset<numeric_limits<unsigned long>::digits>(267) << endl;
        cout << "10, 000, 000 with 24 bits:" << bitset<24>(1e7) << endl;
    
        /* transform binary representation into integral number
        */
        cout << ""1000101011" as number:" << bitset<100>(string("1000101011")).to_ulong() << endl;
    }
    
    // 程序可能输出如下(具体内容视平台上的short和long的位数量而定)
    267 as binary short:                 0000000100001011
    267 as binary long:                     00000000000000000000000100001011
    10, 000, 000 with 24 bits:         100110001001011010000000
    "1000101011" as number:        555

    Class Bitsets更详细的信息参见相关源码和《STL源码剖析》

  • 相关阅读:
    Kibana 基本操作
    kibana安装
    es7.2版本安装ik分词
    Elastic Search闪退问题
    Elastic Search安装-windows
    1-ES简单介绍
    vue项目1-pizza点餐系统12-设计购物车
    vue项目1-pizza点餐系统11-设计menu页面
    前端:table、thead、th、tr、td
    某二分图匹配题的题解
  • 原文地址:https://www.cnblogs.com/yyxt/p/5017942.html
Copyright © 2020-2023  润新知