《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();
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();
set.insert();
set.insert();
set.insert();
set.insert();
set.insert();
set.insert();
} 输出:
coll1:
coll2:
coll1:
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() > )
{
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();         // integral sequence starting with value 1
// insert sequence beginning with 1
generate_n(back_inserter(coll), , seq); // insert sequence beginning with 1 again
generate_n(back_inserter(coll), , 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(); // 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
, // number of elements
seq); // generates values
print_elements(coll); // insert values from 42 to 45
// 传值,不会改变原来仿函数的状态,改变的是仿函数的副本的状态
generate_n(back_inserter(coll), , IntSequence());
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), , seq);
print_elements(coll); // continue with first sequence again
// 传值,不会改变原来仿函数的状态,改变的是仿函数的副本的状态
generate_n(back_inserter(coll), , seq);
print_elements(coll); } 输出:

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(), sum() { } // "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() { }
bool operator() (int)
{
return ++count == nth;
}
}; int main()
{
list<int> coll;
// insert elements from 1 to 9
for(int i = ; i <= ; ++i)
{
coll.push_back(i);
} print_elements(coll, "coll:"); // remove third element
list<int>::iterator pos;
pos = remove_if(coll.begin(), coll.end(), Nth());
coll.erase(pos, coll.end());
print_elements(coll, "nth removed:");
} 输出:
coll:
nth removed:

有两个(而不是一个)元素——也就是第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 = ; 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>() << endl;
cout << "267 as binary long:" << bitset<numeric_limits<unsigned long>::digits>() << endl;
cout << "10, 000, 000 with 24 bits:" << bitset<>(1e7) << endl; /* transform binary representation into integral number
*/
cout << "\"1000101011\" as number:" << bitset<>(string("")).to_ulong() << endl;
} // 程序可能输出如下(具体内容视平台上的short和long的位数量而定)
as binary short:
as binary long:
, , with bits:
"" as number:

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

《C++标准程序库》笔记之二的更多相关文章

  1. 《Java编程思想》学习笔记(二)——类加载及执行顺序

    <Java编程思想>学习笔记(二)--类加载及执行顺序 (这是很久之前写的,保存在印象笔记上,今天写在博客上.) 今天看Java编程思想,看到这样一道代码 //: OrderOfIniti ...

  2. Hibernate学习笔记(二)

    2016/4/22 23:19:44 Hibernate学习笔记(二) 1.1 Hibernate的持久化类状态 1.1.1 Hibernate的持久化类状态 持久化:就是一个实体类与数据库表建立了映 ...

  3. X-Cart 学习笔记(二)X-Cart框架1

    目录 X-Cart 学习笔记(一)了解和安装X-Cart X-Cart 学习笔记(二)X-Cart框架1 X-Cart 学习笔记(三)X-Cart框架2 X-Cart 学习笔记(四)常见操作 四.X- ...

  4. C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)

    上一篇学习完了MEF的基础知识,编写了一个简单的DEMO,接下来接着上篇的内容继续学习,如果没有看过上一篇的内容, 请阅读:http://www.cnblogs.com/yunfeifei/p/392 ...

  5. Hadoop阅读笔记(二)——利用MapReduce求平均数和去重

    前言:圣诞节来了,我怎么能虚度光阴呢?!依稀记得,那一年,大家互赠贺卡,短短几行字,字字融化在心里:那一年,大家在水果市场,寻找那些最能代表自己心意的苹果香蕉梨,摸着冰冷的水果外皮,内心早已滚烫.这一 ...

  6. DuiLib学习笔记(二) 扩展CScrollbar属性

    DuiLib学习笔记(二) 扩展CScrollbar属性 Duilib的滚动条滑块默认最小值为滚动条的高度(HScrollbar)或者宽度(VScrollbar).并且这个值默认为16.当采用系统样式 ...

  7. guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用

    guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用 1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection ...

  8. SQL*Loader实验笔记【二】

      所有SQL*Loader实验笔记 实验案例总结(1-7):     SQL*Loader实验笔记[一] 实验案例总结(8-13):   SQL*Loader实验笔记[二] 实验案例总结(14-19 ...

  9. Dynamic CRM 2013学习笔记(二十八)用JS动态设置字段的change事件、必填、禁用以及可见

    我们知道通过界面设置字段的change事件,是否是必填,是否可见非常容易.但有时我们需要动态地根据某些条件来设置,这时有需要通过js来动态地控制了. 下面分别介绍如何用js来动态设置.   一.动态设 ...

随机推荐

  1. ERROR:tornado上传文件过大超出范围报错

    该怎么解决呢? HTTPServer里面指定max_buffer_size就可以了 EXAMPLE # server = HTTPServer(application, max_buffer_size ...

  2. (笔记)Mysql命令select from:查询表中的数据(记录)

    select from命令用来查询表中的数据. 1) 查询所有行命令格式: select <字段1, 字段2, ...> from < 表名 > where < 表达式 ...

  3. android O 蓝牙设备默认名称更改

    安卓系统会首先读取BTM_DEF_LOCAL_NAME的值,如果为空,就使用"ro.product.model"作为蓝牙设备名. system/bt/btif/src/btif_d ...

  4. 如何解决#1045 - Access denied for user 'root'@'localhost' (using password: NO)问题

    1. #1045 - Access denied for user 'root'@'localhost'  (using password: NO) 解决方案 在phpMyAdmin中librarie ...

  5. 利用smba实现windows上写程序,linux上运行

    1.在linux下载程序代码(确保获取正确的文件属性) 2.在windows编写代码,对于已有代码,不改变文件权限,如原先为755的,更改文件内容后依然是755的文件,如果要新建文件,默认为644,其 ...

  6. C# IEnumerator的详解

    迭代器模式是设计模式中行为模式(behavioral pattern)的一个例子,他是一种简化对象间通讯的模式,也是一种非常容易理解和使用的模式.简单来说,迭代器模式使得你能够获取到序列中的所有元素而 ...

  7. 【翻译自mos文章】Linux的/var/log/messages是空的(0k),messages.0, messages.1也是空的

    Linux的/var/log/messages是空的(0k),messages.0, messages.1也是空的 来源于: The /var/log/messages is empty, and s ...

  8. css box-shadow添加阴影

    基础说明:     外阴影:box-shadow: X轴  Y轴  Rpx  color;     属性说明(顺序依次对应): 阴影的X轴(可以使用负值)    阴影的Y轴(可以使用负值)    阴影 ...

  9. php public,static,private,protected,final,const,abstract

    public:权限是最大的,可以内部调用,实例调用等. protected: 受保护类型,用于本类和继承类调用. private: 私有类型,只有在本类中使用. final:PHP 5:不被改,不被继 ...

  10. Android学习之——实现圆角Button

    在drawable文件夹下新建btn_shape.xml文件: <?xml version="1.0" encoding="utf-8"?> < ...