《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();
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++标准程序库》笔记之二的更多相关文章
- 《Java编程思想》学习笔记(二)——类加载及执行顺序
<Java编程思想>学习笔记(二)--类加载及执行顺序 (这是很久之前写的,保存在印象笔记上,今天写在博客上.) 今天看Java编程思想,看到这样一道代码 //: OrderOfIniti ...
- Hibernate学习笔记(二)
2016/4/22 23:19:44 Hibernate学习笔记(二) 1.1 Hibernate的持久化类状态 1.1.1 Hibernate的持久化类状态 持久化:就是一个实体类与数据库表建立了映 ...
- X-Cart 学习笔记(二)X-Cart框架1
目录 X-Cart 学习笔记(一)了解和安装X-Cart X-Cart 学习笔记(二)X-Cart框架1 X-Cart 学习笔记(三)X-Cart框架2 X-Cart 学习笔记(四)常见操作 四.X- ...
- C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)
上一篇学习完了MEF的基础知识,编写了一个简单的DEMO,接下来接着上篇的内容继续学习,如果没有看过上一篇的内容, 请阅读:http://www.cnblogs.com/yunfeifei/p/392 ...
- Hadoop阅读笔记(二)——利用MapReduce求平均数和去重
前言:圣诞节来了,我怎么能虚度光阴呢?!依稀记得,那一年,大家互赠贺卡,短短几行字,字字融化在心里:那一年,大家在水果市场,寻找那些最能代表自己心意的苹果香蕉梨,摸着冰冷的水果外皮,内心早已滚烫.这一 ...
- DuiLib学习笔记(二) 扩展CScrollbar属性
DuiLib学习笔记(二) 扩展CScrollbar属性 Duilib的滚动条滑块默认最小值为滚动条的高度(HScrollbar)或者宽度(VScrollbar).并且这个值默认为16.当采用系统样式 ...
- guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用
guava 学习笔记(二) 瓜娃(guava)的API快速熟悉使用 1,大纲 让我们来熟悉瓜娃,并体验下它的一些API,分成如下几个部分: Introduction Guava Collection ...
- SQL*Loader实验笔记【二】
所有SQL*Loader实验笔记 实验案例总结(1-7): SQL*Loader实验笔记[一] 实验案例总结(8-13): SQL*Loader实验笔记[二] 实验案例总结(14-19 ...
- Dynamic CRM 2013学习笔记(二十八)用JS动态设置字段的change事件、必填、禁用以及可见
我们知道通过界面设置字段的change事件,是否是必填,是否可见非常容易.但有时我们需要动态地根据某些条件来设置,这时有需要通过js来动态地控制了. 下面分别介绍如何用js来动态设置. 一.动态设 ...
随机推荐
- Linux 错误记录
1.libmysqlclient.so.18: cannot open shared object file: No such file or directory 解决办法: [root@linux- ...
- Android hide Navigation bar
最近一个app需要隐藏Navigation bar导航栏. 参考文档 http://blog.csdn.net/zwlove5280/article/details/52823128 http://j ...
- JDBC结果集
SQL语句执行后从数据库查询读取数据,返回的数据放在结果集中. SELECT语句用于从数据库中选择行并在结果集中查看它们的标准方法. java.sql.ResultSet接口表示数据库查询的结果集. ...
- e795. 获得和设置JSlider的值
// To create a slider, see e794 创建JSlider组件 // Get the current value int value = slider.getValue(); ...
- 指定webapi 返回 json 格式 ; GlobalConfiguration.Configuration.Formatters.Clear()
因为 Internet Explorer 和 Firefox 发送了不同的 Accept 头,所以 web API 在响应里就发送了不同的内容类型. 解决方法,在 Global.asax的 App ...
- Tomcat的Admin和Manager工具初探
版本说明 apache-tomcat-5.5.28.zip(解压缩版) Manager工具是自带的,Admin工具apache-tomcat-5.5.28-admin.zip需要单独安装 下载地址:h ...
- C#页面前台<%%><%#%><%=%>
ASP.net前台绑定用的最多,今天小小总结一下. 1:<%#Eval("")%> 2:<%#Bind("")%> 3:<%=变量 ...
- YII2 搭建redis拓展(教程)
安装redis扩展: 1.通过composer进行安装,到项目根目录cmd运行(推荐) php composer.phar require --prefer-dist yiisoft/yii2-red ...
- nodejs npm 使用淘宝 NPM 镜像
使用淘宝 NPM 镜像 大家都知道国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像. 淘宝 NPM 镜像是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读), ...
- LoadRunner 使用介绍
功能介绍 安装流程 LoadRunner是一款测试系统行为和性能的负债测试工具,通过模拟上千万用户实施并发复杂以实时性能监控的方式来确认和查找问题.它是一款付费商业软件,开发商为HP,个人开发者可以使 ...