《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 criteriontypedef 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 normalRuntimeCmp(cmp_mode m = normal) : mode(m) { }// comparision of elementsbool operator() (const T& t1, const T& t2) const{return mode == normal ? t1 < t2 : t2 < t1;}// comparision of sorting criteriabool 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 orderRuntimeCmp<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 criterioncoll1 = 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 orderset.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:// constructorIntSequence(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),     // start4,         // number of elementsseq);     // 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 elementslong sum;      // sum of all element valuespublic:// constructorMeanValue() : num(0), sum(0) { }// "function call"// - process one more element of the sequencevoid operator() (int elem){num++;          // increment countsum += elem;      // add value
    }// return mean valuedouble 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 trueint count;        // call counterpublic:Nth(int n) : nth(n), count(0) { }bool operator() (int){return ++count == nth;}
};int main()
{list<int> coll;// insert elements from 1 to 9for(int i = 1; i <= 9; ++i){coll.push_back(i);}print_elements(coll, "coll:");// remove third elementlist<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/colorsbitset<numColors> usedColors;// set bits for two colorsusedColors.set(red);        // 设置red(0号位置)上的bit位usedColors.set(blue);        // 设置blue(3号位置)上的bit位// print some bitset datacout << "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 usedif (usedColors.any()){// loop over all colorsfor (int c = 0; c < numColors; ++c){//if the actual color is usedif (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源码剖析》

转载于:https://www.cnblogs.com/yyxt/p/5017942.html

《C++标准程序库》笔记之二相关推荐

  1. Programming C# 学习笔记(二) 出发:“Hello World”

    小序:      准备写这章的学习笔记了,啊,Hello World!多么亲切的语句,呵呵,当初学C语言的第一个程序就是输出它, 还记得费了好大劲终于把它输出来时候的那种兴奋感觉,真是让我怀念哦!(然 ...

  2. SVO学习笔记(二)

    SVO学习笔记(二) 这篇文章 稀疏图像对齐 地图点投影(地图与当前帧间的关系) reprojectMap reprojectPoint reprojectCell 特征点对齐中的非线性优化 结尾 这 ...

  3. Udacity机器人软件工程师课程笔记(二)-样本搜索和找回-基于漫游者号模拟器

    Robotics Software engineer编程笔记(二) 5.确定漫游者号的行进方向 (1)漫游者号如何确定自己的行进方向? 我们已经有了一个由前置摄像头得到的图像,然后可以通过对图像进行处 ...

  4. 响应式编程笔记(二):代码编写

    2019独角兽企业重金招聘Python工程师标准>>> 响应式编程笔记(二):代码编写 博客分类: 架构 原文:Notes on Reactive Programming Part ...

  5. Python学习笔记(二):标准流与重定向

    Python学习笔记(二):标准流与重定向 - SamWei - 博客园 Python学习笔记(二):标准流与重定向 Posted on 2012-02-19 22:36 SamWei 阅读(176) ...

  6. pytorch学习笔记(二):gradien

    pytorch学习笔记(二):gradient 2017年01月21日 11:15:45 阅读数:17030

  7. opencv学习笔记(二):基于肤色的人手检测

    opencv学习笔记(二):基于肤色的人手检测 原文:http://blog.csdn.net/wzmsltw/article/details/50849810 先写了人手的检测程序,下一步基于检测程 ...

  8. python做直方图-python OpenCV学习笔记实现二维直方图

    本文介绍了python OpenCV学习笔记实现二维直方图,分享给大家,具体如下: 官方文档 – https://docs.opencv.org/3.4.0/dd/d0d/tutorial_py_2d ...

  9. GEEK学习笔记— —程序猿面试宝典笔记(二)

    所谓笔记,就是比較个人的东西,把个人认为有点意思的东西记录下来~~ 程序猿面试宝典笔记(一)基本概念 程序猿面试宝典笔记(二)预处理.const和sizeof 程序猿面试宝典笔记(三)auto_ptr ...

最新文章

  1. 【干货】值得收藏的 14 个 Linux 下 CPU 监控工具
  2. 检测日期格式是否为yyyy-MM-dd
  3. C 语言运算符优先级(记忆口诀)
  4. 通过gparted 调整 ubuntu 磁盘
  5. .htaccess详解
  6. 高清壁纸:60款可爱的圣诞节电脑桌面壁纸《中篇》
  7. bigdecimal 减_市市场监管局多措并举推进高频事项“减时效”
  8. FCKeditor 2.6.4在ASP.NET中的配置方法
  9. 一阶广义差分模型_微波射频差分探针去嵌入理论研究
  10. 屏幕录像专家 EXE视频转MP4
  11. Apache CarbonData学习资料汇总
  12. 初中数学知识点总结_初中数学知识点总结大全 这下全了!
  13. Glide加载常用类型图片(带白色边框的圆形图、圆角矩形图片、圆形图片)
  14. JS前端数据多条件筛选
  15. 【软考】【知识产权与法律法规】
  16. python 老师和父亲_强烈推荐给老师和父母的21部电影
  17. 劉克振:下一波萬物互聯網的創新大機會
  18. 路由器wan口和lan口短接_路由器WAN口和LAN口功能介绍
  19. hardmard积 用什么符号表示_复数域符号相乘的差分网络编码
  20. Echarts关于中国地图的china.js文件下载

热门文章

  1. Xposed源码剖析——概述
  2. Android平台类加载流程源码分析
  3. centos6.x 安装elasticsearch6.x步骤
  4. 优秀的API接口设计原则及方法
  5. jquery queue ajax,如何通过jQuery和Queue发送ajax请求?
  6. JZOJ 5407. 【NOIP2017提高A组集训10.21】Deep
  7. matlab作图标记
  8. 洛谷 [SDOI2009]晨跑
  9. linux脚本执行进度条,shell脚本实现进度条
  10. php 获取坐标api,怎么获取百度坐标api返回的json格式字符串或对象呢