C++ chapter3 标准库类型


除了这些在语言中定义的类型外,C++标准库还定义了许多更高级的抽象数据类型(abstract data type)。之所以说这些标准库类型是更高级的,是因为其中反映了更复杂的概念;之所以说它们是抽象的,是因为我们在使用时不需要关心它们是如何表示的,只需指定这些抽象数据类型支持哪些操作就可以了。

两种最重要的标准库类型是stringvectorstring类型支持长度可变的字符串,vector可用于保存一组指定类型的对象。说它们重要,是因为它们在C++定义的级别类型基础上作了一些改进。

另一种标准库类型提供了更方便和合理有效的语言级的抽象设施,它就是bitset类。通过这个类可以把某个值当作位的集合来处理。与位操作符相比,bitset类提供操作位更直接的方法。

命名空间的using声明

在前面看到的程序都是通过直接说明名字来自std命名空间,来引用标准库中的名字。这些名字都使用了::操作符,该操作符是作用域操作符。它的含义是右操作数的名字可以在左操作数的作用域中找到。因此,std::cin的意思是说所需名字cin是在命名空间std中定义的。显然,这样这样非常麻烦。

C++提供了更简洁的方式来使用命名空间成员。using声明。

使用using声明可以在不需要加前缀namespace_name::的情况下访问命名空间中的名字。

1
using namespace::name;

一旦使用了using声明,我们就可以直接引用名字,而不需要再引用该名字的命名空间:

1
2
using std::cin;
using std::string;

1.每个名字都需要一个using声明

一个using声明一次只能作用于一个命名空间成员。using声明可用来明确指定在程序中用到的命名空间中的名字,如果希望使用std中的几个名字,则必须为要用到的每个名字都提供一个using声明。

1
2
3
using std::cin;
using std::cout;
using std::endl;

2.使用标准库类型的类定义

有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。理由是头文件的内容会被预处理器复制到程序中。用#include包含文件时,相当于头文件中的文本将称为我们编写的文件的一部分。如果在头文件中放置using声明,就相当于在包含该头文件的每个程序中都放置了同一using声明,不论该程序是否需要using声明

注意:在编译我们提供的实例程序前,读者一定要注意在程序中添加适当的#includeusing声明。

标准库string类型

string类型支持长度可变的字符串,C++标准库将负责管理与存储字符相关的内存,以及提供各种有用的操作符。标准库string类型的目的就是满足对字符串的一般应用。

与其他的标准库类型一样,用户程序要使用string类型对象,必须包含相关头文件。如果提供了合适的using声明,那么编写处理的程序将会变得简短些:

1
2
#include <string>
using std::string;

string对象的定义和初始化

string标准库支持几个构造函数。构造函数是一个特殊成员函数,定义如何初始化该类型的对象下表列出了几个string类型常用的构造函数。当没有明确指定对象初始化式时,系统将使用默认构造函数。

几种格式化string对象的方式
string s1; 默认构造函数,s1为空串
string s2(21); 将s2初始化为s1的一个副本
string s3("value"); 将s3初始化为一个字符串字面值
string s4(n,'c'); 将s4初始化为字符’c’的n个副本

警告:标准库string类型和字符串字面值
因为历史原因以及未来与C语言兼容,字符串字面值与标准库string类型不是同一种类型。

string对象的读写

1
cin>>s;

从标准输入读取string,并将读入的串存储在s中。string类型的输入操作符:

  • 读取并忽略开头所有的空白字符(入空格,换行符,制表符)。
  • 读取字符直至再次遇到空白字符,读取终止。

输入和输出操作的行为与内置类型操作符级别类似。

1.读入未知数目的string对象

和内置类型的输入操作符一样,string的输入操作符也会返回所读的数据流。因此,可以把输入操作作为判断条件。

下面的程序将从标准输入读取一组string对象,然后在标准输出上逐行输出:

1
2
3
4
5
6
7
int main(){
string word;
while(cin>>word){
cout<<word<<endl;
}
return 0;
}

2.用getline读取整行文本

这个函数接受两个参数:一个输入流对象和一个string对象。getline函数从输入流的下一行读取,并保存读取的内容到string中,但不包括换行符。和输入操作符不一样的是,getline并不忽略开头的换行符。只要getline遇到换行符,即便它是输入的第一个字符,getline也将停止读入并返回。如果第一个字符就是换行符,则string参数将被置为空string

getline函数将istream参数作为返回值,和输入操作符一样也把它用作判断条件。

1
2
3
4
5
6
int main(){
string line;
while(getline(cin,line))
cout<<line<<endl;
return 0;
}

由于getline函数返回时丢弃换行符,换行符将不会存储在string对象中。

string对象的操作

string操作
s.empty() 如果s为空字符串,则返回true,否则返回false
s.size() 返回s中字符的个数
s[n] 返回s位置为n的字符,位置从0开始计数
s1+s2 把s1和s2连接成一个新字符串,返回新生成的字符串
s1=s2 把s1内容替换为s2的副本
v1==v2 比较v1与v2的内容,相等则返回true,否则返回false
!=,<,<=, 保持这些操作符惯有的含义
>和>=
1.stringsizeempty操作

string对象的长度指的是string对象中字符的个数,可以通过size操作获取:

1
2
3
4
5
int main(){
string st("The expense of spirit\n");
cout<<st.size()<<endl;
return 0;
}

了解string对象是否为空是有用的。一种方法是将size0进行比较:

1
if(st.size()==0)

本例中,程序员并不需要知道string对象中有多少个字符,只想知道size是否为0。用string的成员函数empty()可以直接回答这个问题:

1
if(st.empty())

empty()成员函数将返回bool值,如果string对象为空则返回true,否则返回false

2.string::size_type类型

从逻辑上来讲,size()成员函数似乎应该返回整型数值,或为无符号整数。

但实际上,size操作返回的是string::size_type类型的值。我们需要对这种类型做一些解释。

string类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。size_type就是这些配套类型中的一种。它定义为与unsigned型(unsigned int 或 unsigned long)具有相同的含义,而且可以保证足够大能够存储任意string对象的长度。未来使用由string类型定义的size_type类型,程序员必须加上作用域操作符来说明使用的size_type类型是由string类定义的。

注意:任何存储stringsize操作结果的变量必须为string::size_type类型。

3.string关系操作符

string类定义了几种关系操作符用来比较两个string值的大小。这些操作符实际上是比较每个string对象的字符。

string对象比较操作是区分大小写的,即同一个字符的大小写形式被认为是两个不同的字符。在多数计算机上,大写的字符位于小写字母之前:任何一个大写字母都小于任意的小写字母。

==操作符比较两个string对象,如果它们相等,则返回true。两个string对象相等是指它们的长度相同,且含有相同的字符。标准库还定义了!=操作符来测试两个string对象是否不等。

关系操作符<,<=,>,>分别用于测试一个string对象是否小于、小于或等于、大于、大于或等于另一个string对象:

1
2
3
4
string big="big",small="small";
string s1=big;
if(big==small)
if(big<=s1)

关系操作符比较两个string对象时采用了和(大小写敏感的)字典排序相同的策略;

  • 如果两个string对象长度不同,且短的string对象与长的string对象的前面部分相匹配,则短的string对象小于长的string对象。
  • 如果两个string对象的字符不同,则比较第一个不匹配的字符。
    1
    2
    3
    string substr="hello";
    string phrase="hello world";
    string slang="hiya";
    则substr小于phrase,而slang则大于substr或phrase。

4.string对象的赋值

string对象,可以把一个string对象赋值给另一个string对象:

1
2
string str1,str2="the expense of spirit";
str1=str2;

5.两个string对象相加

string对象的加法被定义为连接(concatenation)。也就是说,两个(或多个)string对象可以通过使用加操作符+或者复合赋值操作符+=连接起来。给定两个string对象:

1
2
3
4
string s1("hello,");
string s2("world\n");
string s3=s1+s2;
s1+=s2;

6.和字符串字面值的连接

上面的字符对象s1s2直接包含了标点符号。也可以通过将string对象和字符串字面值混合得到同样的结果:

1
2
3
string s1("hello");
string s2("world");
string s3=s1+","+s2+"\n";

当进行string对象和字符串字面值混合连接操作时,+操作符的左右操作数必须至少有一个是string类型的:

1
2
string s5=s1+","+"world";
string s6="hello"+","+s2;

7.从string对象获取字符
string类型通过下标操作符([])来访问string对象中的单个字符。下标操作符需要取一个size_type类型的值,来标明要访问的位置。这个下标中的值通常被称为 “下标” 或 “索引(index)”。

注解:string对象的下标从0开始。如果s是一个string对象且s不空,则s[0]就是字符串的第一个字符,s[1]就表示第二个字符(如果有的话),而s[s.size()-1]则表示s的最后一个字符。
引用下标时如果超出下标作用范围就会引起溢出错误。

1
2
3
4
string s("hello world");
for(string::size_type n=0;n!=s.size();n++){
cout<<s[n]<<endl;
}

8.下标操作可用作左值

和变量一样,string对象的下标操作返回值也是左值。因此,下标操作可以放于赋值操作符的左边或右边。通过下面循环把str对象的每一个字符置为*

9.计算下标值

任何可产生整型值的表达式都可用作下标操作符的索引。

1
str[someotherval*someval]=someval;

虽然任何整型数值都可作为索引,但索引的实际数据类型却是unsigned类型string::size_type

建议:前面讲过,一个用string::size_type类型的变量接受size函数的返回值。在定义用作索引的比哪里时,出于同样的道理,string对象的索引变量最好也用string::size_type类型。

在使用下标索引string对象时,必须保证索引值 “在上下界范围内”。“在上下界范围内” 就是指索引值是一个赋值为size_type类型的值,其取值范围在0string对象长度减1之间。使用string::size_type类型或其他unsigned类型作为索引,就可以保证索引值不小于0,只要索引值是unsigned类型,就只需要检测它是否小于string对象的长度。

注意:标准库不要去检查索引值,所用索引的下标越界是没有定义的,这样往往会导致严重的运行时错误。

string对象中字符的处理

适用于string对象的字符(或其它char值)。

这些函数都在cctype头文件中定义。

cctype定义的函数
isalnum(c) 如果c是字母或数字,则为true。
isalpha(c) 如果c是字母,则为true。
iscntrl(c) 如果c是控制字符,则为true。
isdigit(c) 如果c是数字,则为true。
isgraph(c) 如果c不是空格,但可打印,则为true。
isprint(c) 如果c是可打印的字符,则为true。
ispunct(c) 如果c是标点符号,则为true。
isspace(c) 如果c是空白字符,则为true。
isupper(c) 如果c是大写字母,则为true。
isxdigit(c) 如果c是十六进制数,则为true。
tolower(c) 如果c是大写字母,则返回其小写字母形式,否则之间返回c。
toupper(c) 如果c是小写字母,则返回其大写字母形式,否则之间返回c。

表中的大部分函数是测试一个给定的字符是否符号条件,并返回一个int值作为真值。如果测试失败,则该函数返回0,否则返回一个(无意义的)非0值,表示被测字符符号条件。

表中的这些函数,可打印的字符是指那些可用显示表示的字符。空白字符则是空格、制表符、垂直制表符、回车符、换行符和进纸符的任意一种:标点符号则是除了数字、字母或(可打印的)空白字符(如空格)以外的其他可打印字符。

和返回真值的函数不同的是,tolowertoupper函数返回的是字符,返回实参字符本身或返回该字符相应的大小写字符。我们可用使用

标准库vector类型

vector是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。和string对象一样,标准库将负责管理与存储元素相关的内存。我们把vector称为容器,是因为它可以包含其他对象,一个容器中的所有对象都必须是同一种类型的。

使用vector之前,必须包含相应的头文件。

1
2
#include <vector>
using std::vector;

vector是一个类模板(class template)。使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。因此,我们可以定义保存string对象的vector,或保存int值的vector,又或是保存自定义的类类型对象的vector。

声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以vector为例,必须说明vector保存何种对象的类型,通过将类型放在类模板名称后面的尖括号中来指定类型:

1
2
vector<int> ivec;
vector<Sales_item> Sqles_vec;

和其他变量定义一样,定义vector对象要指定类型和一个变量的列表。上面的第一个定义,类型是vector<int>,该类型即是含有若干int类型对象的vector,变量名为ivec。第二个定义的变量名是Sales_vec,它保存的元素是Sales_item类型的对象。

注意:vector不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector类型的每一种都指定了其保存原始的类型。因此,vector<int>vector<string>都是数据类型。

vector对象的定义和初始化

vector类定义了好几种构造函数,用来定义和初始化vector对象.

几种初始化 vector 对象的方式
vector<T> v1; vector保存类型为T的对象,默认构造函数,v1为空
vector<T> v2(v1); v2 是 v1 的一个副本
vector<T> v3(n,i); v3 包含 n 个值为 i 的元素
vector<T> v4(n); v4 含有值初始化的元素的 n 个副本

1.创建确定个数的元素

若要创建非空的vector对象,必须给出初始化元素的值。当把一个vector对象复制到另一个vector对象时,新复制的vector中每一个元素都初始化为原vector中相应元素的副本。但这两个vector对象必须保存同一种元素类型:

1
2
3
vector<int> ivec1;
vector<int> ivec2(ivec1);
vector<string> svec; //error

可以用元素个数和元素值对vector对象进行初始化。构造函数用元素个数来决定vector对象保存元素的个数,元素值指定每个元素的初始值:

1
2
vector<int> ivec4(10,-1);
vector<int> svec(10,"hi!");

关键概念:vector对象动态增长
vector对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。

注意:虽然可以对给定元素个数的vector对象预先分配内存,但更有效的方法是先初始化一个空vector对象,然后再动态地增加元素。

2.值初始化

如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化(value initializationd)。这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在vector中元素的数据类型。

如果vector保存内置类型(如int型)的元素,那么标准库将用0值创建元素初始化式:

1
vector<int> fvec(10);

如果vector保存的是含有构造函数的类类型(如string)的元素,标准库将用该类型的默认构造函数创建元素初始化式:

1
vector<string> svec(10);

还有第三种可能性:元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。

vector对象的操作

vector标准库提供了许多类似于string对象的操作,下表列出了几种最重要的vector操作。

vector操作
v.empty() 如果 v 为空,则返回 true,否则返回 false。
v.size() 返回 v 中元素的个数。
v.push_back(t) 在 v 的末尾增加一个值为 t 的元素。
v[n] 返回 v 中位置为 n 的元素。
v1=v2 把 v1 的元素替换为 v2 中元素的副本。
v1==v2 如果 v1 与 v2 相等,则返回 false。
!=, < , <=, >, >= 保持这些操作符惯有的含义。

1.vector对象的size

emptysize操作类似于string类型的相关操作。成员函数size返回相应vector类定义的size_type的值。

注解:使用size_type类型时,必须指出该类型是在哪里定义的。vector类型总是包括vector的元素类型:
vector<int>::size_type

2.向vector添加元素

push_back()操作接受一个元素值,并将它作为一个新的元素添加到vector对象的后面,也就是 “插入(push)” 到vector对象的 “后面(back)”:

1
2
3
4
5
string word;
vector<string> text;
while(cin>>word){
test.push_back(word);
}

3.vector的下标操作

vector中的对象是没有命名的,可以按vector中对象的位置来访问它们。通常使用下标操作符来获取元素。vector的下标操作类似于string类型的下标操作。

vector的下标操作符接受一个值,并返回vector中该对应位置的元素。vector元素的位置从 0 开始。

1
2
3
for(vector<int>::size_type ix=0;ix!=ivec.size();ix++){
ivec[ix]=0;
}

string类型的下标操作符一样,vector下标操作的结果作为左值,因此可以像循环体中所做的那样实现写入。另外,和string对象的下标操作类似,这里用size_type类型作为vector下标的类型。

4.下标操作不添加元素

初学C++的程序员可能会认为vector的下标操作符可以添加元素,其实不然:

1
2
3
4
vector<int> ivec;
for(vector<int>::size_type ix=0;ix!=10;ix++){
ivec[ix]=ix;
}

这里 ivec 是空的vector对象,而且下标只能用于获取已存在的元素。

正确写法:

1
2
for(vector<int>::size_type ix=0;ix!=10;ix++){
ivec.push_back(ix);

注意:必须是已存在的元素才能用下标操作符进行索引。通过下标操作进行赋值时,不会添加到任何元素。

迭代器简介

除了使用下标来访问vector对象的元素外,标准库还提供了另一种访问元素的方法:使用迭代器(iterator)。迭代器是一种检查容器内元素并遍历元素的数据类型。

标准库为每一种标准容器(包括vector)定义了一种迭代器类型。迭代器类型提供了比下标操作更通用化的方法:所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。因为迭代器对所有的容器都适用,现代C++程序更倾向于适用迭代器而不是下标操作访问容器元素,即使对支持下标操作的vector类型也是这样。

1.容器的 iterator类型

每种容器类型都定义了自己的迭代器类型,如vector

1
vector<int>::iterator iter;

这条语句定义了一个名为 iter 的变量,它的数据类型是由vector<int>定义的iterator类型。每个标准库容器类型都定义了一个名为iterator的成员,这里的iterator与迭代器实际类型的含义相同。

2.begin和end操作

每种容器都定义了一对名为beginend的函数,用于返回迭代器。如果容器中有元素的化,由begin返回的迭代器指向第一个元素:

1
vector<int>::iterator iter=ivec.begin();

上述语句把 iter 初始化为名为beginvector操作返回的值。假设vector不空,初始化后,iter即指该元素为ivec[0]

end操作返回的迭代器指向vector的 “末端元素的下一个”。通常称为超出末端迭代器(off-the-end iterator),表明它指向了一个不存在的元素。如果vector为空,begin返回的迭代器与end返回的迭代器相同。

注解:由end操作返回的迭代器并不指向vector中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完vector中所有元素。

3.vector迭代器的自增和引用运算

迭代器类型定义了一些操作来获取迭代器所指向的元素,并允许程序员将迭代器从一个元素移动到另一个元素。

迭代器类型可使用解引用操作符*操作符)来访问迭代器所指向的元素:

1
*iter=0;

解引用操作符返回迭代器当前所指向的元素。假设iter指向vector对象ivec的第一个元素,那么*iterivec[0]就是指向同一个元素。上面这个语句的效果就是把这个元素的值赋为0;

迭代器使用自增操作符向前移动迭代器指向容器中下一个元素。从逻辑上说,迭代器的自增操作和int型对象的自增操作类似。对int对象来说,操作结果就是把int型值 “加1”,而对迭代器对象则是把容器中的迭代器 “向前移动一个位置”。因此,如果iter指向第一个元素,则++iter指向第二个元素。

注解:由于end操作返回的迭代器不这些任何元素,因此不能对它进行解引用或自增操作。

4.迭代器的其他操作

另一对可执行于迭代器的操作就是比较:用==!=操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。

**5.迭代器应用的程序示例

假设已声明了一个vector<int>型的ivec变量,要把它所有元素值重置为0,可以用下标操作来完成:

1
2
for(vector<int>::size_type ix=0;ix!=ivec.size();ix++)
ivec[ix]=0;

用迭代器来编写循环:

1
2
3
for(vector<int>::iterator iter=ivec.begin();
iter!=ivec.end();iter++)
*iter=0;

for循环首先定义了iter,并将它初始化为指向itec的第一个元素。for循环的条件测试itec是否于end操作返回的迭代器不等。每次迭代iter都自增1,这个for循环的效果是从ivec第一个元素开始,顺序处理vector中的每一个元素。最后,iter将指向ivec中的最好一个元素,处理完最好一个元素后,iter再增加1,就会与end操作的返回值相等,在这种情况下,循环终止。

for循环体内的语句用解引用操作符来访问当前元素的值。和下标操作符一样,解引用操作符的返回值是一个左值,因此可以对它进行赋值来改变它的值。上述循环的效果就是把ivec中所有元素都赋值为0。

通过上述对代码的详细分析,可以看出这段程序与用下标操作符的版本达到相同的操作效果:从vector的第一个元素开始,把vector中每个元素都置为0.

6.const_iterator

前面的程序用vector::iterator改变vector中的元素值。每种容器类型还定义了一种名为const_iterator的类型,该类型只能用于读取容器内元素,但不能改变其值。

当我们对普通iterator类型解引用时,得到对某个元素的非const引用。而如果我们对const_iterator类型解引用时,则可以得到一个指向const对象的引用,如同任何常量一样,该对象不能进行重写。

例如,如果 text 是vector<string>类型,程序员想要遍历它,输出每个元素,可以这样编写程序:

1
2
3
for(vector<string>::const_iterator iter=text.begin();
iter!=test.end();iter++)
cout<<*iter<<endl;

迭代器的算术操作

除了一次移动迭代器的一个元素的增量操作符外,wector迭代器(其他标准库容器迭代器很少)也支持其他的算术操作。这些操作称为迭代器算术操作(iterator arithmetic),包括:

  • iter+n
  • iter1-iter2

注意:任何改变vector长度的操作都会使已存在的迭代器失效。例如,在调用push_back之后,就不能再信赖指向vector的迭代器的值了。

标准库bitset类型

有些程序要处理二进制位的有序集,每个位困难包含0(关)值或1(开)值。位是用来保存一组项或条件的yes/no信息(有时也称标志)的简洁方法。标准库提供的bitset类简化了位集的处理。要使用bitset类就必须包含相关的头文件。

1
2
#include <bitset>
using std::bitset;

bitset对象的定义和初始化

下标列出了bitset的构造函数。类似于vectorbitset类是一种类模板;而与vector不一样的是bitset类型对象的区别仅在于其长度而不在其类型。在定义bitset时,要明确bitset含有多少位,须在尖括号内给出它的长度值:

1
bitset<32> bitvec; //32位,全为0

给出的长度值必须是常量表达式。正如这里给出的,长度值必须定义为整型字面值常量或是已用常量值初始化的整型的const对象。

这条语句把bitvec定义为含有32个位的bitset对象。喝vector的元素一样,bitset中的位是没有命名的,程序员只能按位置来访问它们。位集合的位置编码从0开始,因此,bitvec的位序是从0到31。以0位开始的位串是低阶位(low-order bit),以31位结束的位串是高阶位(high-order bit)。

初始化bitset对象的方法
bitset<n> b; b有n位,每位都为0
bitset<n> b(u); b是unsigned long型u的一个副本
bitset<n> b(s); b是string对象s中含有的位串的副本
bitset<n> b(s,pos,n); b是s中从位置pos开始的n个位的副本

1.用unsigned值初始化bitset对象

当用unsigned long值作为bitset对象的初始值时,该值将转换为二进制的位模式。而bitset对象中的位集作为这种位模式的副本。如果bitset类型长度大于unsigned long值的二进制位数,则其余的高阶位将置为0;如果bitset类型长度小于unsigned long值的二进制位数,则只使用unsigned值中的低阶位,超过bitset类型长度的高阶位将被丢弃。

在32位

2.用string对象初始化bitset对象

当用string对象初始化bitset对象时,string对象直接表示为位模式。从string对象读入位集的顺序是从右向左:

1
2
string strval("1100");
bitset<32> bitvec4(strval);

bitvec4的位模式中第2和3的位置为1,其余位置都为0。如果string对象的字符个数小于bitset类型的长度,则高阶位将置为0。

string对象和bitset对象之间是反向转化的:string对象的最右字符(即下标最低的哪个字符)用来初始化bitset对象的低阶位(即下标为0的位)。当用string对象初始化bitset对象时,记住这一差别很重要。

不一定要把整个string对象都作为bitset对象的初始值。相反,可以只用某个子串作为初始值:

1
2
3
string str("1111111000000011001101");
bitset<32> bitvec5(str,5,4);
bitset<32> bitvec6(str,str.size()-4);

bitset对象上的操作

bitset操作
b.any() b中是否存在置为1的二进制位?
b.none() b中存在置为1的二进制位吗?
b.count() b中置为1的二进制位的个数
b.size() b中二进制位的个数
b[pos] 访问b中在pos处的二进制位
b.test(pos) b中在pos处的二进制位是否为1?
b.set() 把b中所有二进制位置为1
b.set(pos) 把b中在pos处的二进制位置为1
b.reset() 把b中所有二进制位都置为0
b.reset(pos) 把b中在pos处的二进制位置为0
b.flip() 把b中所有二进制位诸位取反
b.flip(pos) 把b中在pos处的二进制位取反
b.to_ulong() 用b中同样的二进制位返回一个unsigned long
os<<b 把b中的位集输出到os流

1.测试整个bitset对象
2.访问bitset对象中的位
3.对整个bitset对象进行设置
4.获取bitset对象的值
5.输出二进制位
6.使用位操作符