《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源码剖析》