这一章主要介绍了三个重要的标准库类型string、vector和bitset类型。
标准库string类型
string类型支持长度可变的字符串。下面是几种初始化string对象的方式:
string s1; //默认构造函数,s1为空串
string s2(s1); //将s2初始化为s1的一个副本
string s3(“value”); //将s3初始化为一个字符串字面值的副本
string s4(n, ‘c’); //将s4初始化为字符’c’的n个副本
在使用string类型之前,要包含string头文件(#include <string>)和using std::string。
可以从标准输入中读取string对象,并把读入的串存储在string对象中,有两个规则:
1、读取并忽略开关所有的空白字符(如空格、换行符、制表符)。
2、读取字符直到再次遇到空白字符,读取终止。
如果你想读取整行输入文本,可以使用getline函数。getline函数接受两个参数:一个输入流对象和一个string对象。
下面列出了常用的string对象操作:
s.empty() 如果s为空串,则返回true,否则返回false
s.size() 返回s中字符的个数,包括空字符(空格、换行符、制表符等)
s[n] 返回s中位置为n的字符,位置从0开始计数,即s[0]返回s的第一个字符
s1 + s2 把s1和s2连接成一个新字符串,返回新生成的字符串
s1 = s2 把s1的内容替换为s2的副本
s1 == s2 比较s1和s2的内容,相等则返回true,否则返回false
!=, <, <=, >, >= 保持这些关系操作符惯有的含义
这里要注意的是,任何存储s.size()操作结果的变量必须为string::size_type类型,它是由string类定义的一种特殊类型,可以保证足够大能够存储任意string对象的长度。特别重要的是,不要把size()的返回值赋给一个int变量。
String对象的下标从0开始,如果s是一个string对象且s不空,则s[0]就是字符串的第一个字符,s[1]就表示第二个字符(如果有的话),而s[s.size() - 1]则表示s的最后一个字符。引用下标时如果超出下标作用范围就会引起溢出错误。
关系操作符比较两个string对象是采用了和(大小写敏感的)字典排序相同的策略:
1、 如果两个string对象长度不同,且短的string对象与长的string对象的前面部分相匹配,则短的string对象小于长的string对象。
2、 如果两个string对象的字符不同,则比较第一个不匹配的字符。
我们经常要对string对象中的单个字符进行处理。下面是cctype头文件中字义的处理字符的函数:
isalnum(c) 如果c是字母或数字,则返回true
isalpha(c) 如果c是字母,则返回true
iscntrl(c) 如果c是控制字符,则返回true
isdigit(c) 如果c是数字,则返回true
isgraph(c) 如果c不是空格,但可打印,则返回true
islower(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
我编写了一个小程序来加深对上面这些内容的理解:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1; // 默认s1为空
string s2("test"); // 将s2初始化为字符串"test"的副本
string s3(s2); // 将s3初始化为s2的副本,此时s3保存的字符串就是"test"
string s4(5,'t'); // 将s4初始化为字符't'的5个副本,即字符串"ttttt"
// 下面我们将s1 s2 s3 s4的值以及长度输出到屏幕
if(s1.empty()){
cout << "s1 is empty!" << endl;
}
cout << "s1 : " << s1 << "/tthe size is " << s1.size()
<< "/ns2 : " << s2 << "/tthe size is " << s2.size()
<< "/ns3 : " << s3 << "/tthe size is " << s3.size()
<< "/ns4 : " << s4 << "/tthe size is " << s4.size() << endl;
// 交换s3和s4的内容
string temp(s3);
s3 = s4;
s4 = temp;
// 接下来提示用户输入字符串,并把用户输入的字符串保存到s1和s2中
cout << "Please input some words :" << endl;
cin >> s1;
getline(cin,s2);
// 再次把s1 s2 s3 s4 输出到屏幕,查看结果,注意s1和s2的区别
cout << "s1 : " << s1 << "/tthe size is " << s1.size()
<< "/ns2 : " << s2 << "/tthe size is " << s2.size()
<< "/ns3 : " << s3 << "/tthe size is " << s3.size()
<< "/ns4 : " << s4 << "/tthe size is " << s4.size() << endl;
return 0;
}
执行程序时,我输入的是“how are you!”,运行结果如下图:
思考:保存用户输入的时候,为什么s1只保存了how,而s2却没有保存how ?
标准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 个副本
在使用vector之前,要#include <vector>和using std::vector。
vector对象(以及其它标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。因为vector增长的效率高,在元素值已知的情况下,最好是动态地添加元素。下面是一些vector对象的操作:
v.empty(); // 如果v为空,则返回true,否则返回false
v.size(); // 返回v中元素的个数,vector<T>::size_type类型
v.push_back(t) // 在v的末尾增加一个值为t的元素
v[n] // 返回v中位置为n的元素
v1 = v2 // 把v1的元素替换为v2中元素的副本
v1 == v2 // 如果v1与v2相等,则返回true
!=, <, <=, >, >= // 保持这些关系操作符惯有的含义
vector中的对象是没有命名的,可以按vector中对象的位置来访问它们,即通过使用下标操作来获取或更改元素,但是不能用下标操作来添加元素。
标准库提供的另一种访问元素的方法是使用迭代器(iterator)。Iterator是一种检查容器内元素并遍历元素的数据类型。因为迭代器对所有的容器都适用,现代C++程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的vector类型也是这样。
每种容器都定义了一对命名为begin和end的函数,用于返回迭代器。如果容器中有元素的话,由begin返回的迭代器指向第一个元素,由end返回的迭代器指向vector的“末端元素的下一个”,通常称为超出末端迭代器,表明它指向了一个不存在的元素。如果vector为空,begin和end返回的迭代器相同。迭代器的解引用操作符*返回迭代器当前所指向的元素。每种容器类型还定义了一种名为const_iterator的类型,该类型只能用于读取容器内元素,但不能改变其值。
动手做:自己编写程序,加深对vector的理解,下面是我写的一个小程序
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
//--------------------------------------------------
// 这段代码的功能是用一个容器保存用户输入的单词
//--------------------------------------------------
string word;
// 定义一个string类型的容器
vector<string> text;
// 指示用户输入单词,按回车结束输入
cout << "Please input your words:/n(press enter as end)" << endl;
while ( cin.peek() != '/n' && cin >> word ){
text.push_back(word); // 保存用户输入的单词
}
// 遍历text容器,把保存的单词输出到屏幕
if(text.empty()){ // 如果容器为空,即用户没有输入任何单词
cout << "You have not input any words!" << endl;
}
else{ // 把用户输入的单词输出到屏幕,并统计单词个数
cout << "There are the words you input:" << endl;
// 用户输入的单词不允许被修改,这里定义一个const_iterator
for (vector<string>::const_iterator iter = text.begin();
iter != text.end(); ++iter ){
cout << *iter << " ";
}
cout << "/nand there are " << text.size() << " words!" << endl;
}
//----------------------------------------------------
// 下面这段代码的功能是对用户输入的数字按从小到大排序
//----------------------------------------------------
int num;
// 定义一个int类型的容器
vector<int> ivec;
cout << "Please input the numbers you want to sort:"
"/n(press enter as end)" << endl;
cin.clear();
cin.ignore(); // 清空输入流缓冲区
while( cin.peek() != '/n' && cin >> num ){
ivec.push_back( num );
}
if( ivec.empty()){
cout << "You have not input any numbers!" << endl;
}
else{
cout << "There are the numbers you input:" << endl;
vector<int>::iterator iter;
for( iter = ivec.begin(); iter != ivec.end(); ++ iter){
cout << *iter << " ";
}
cout << "/nBy sort ascending:" << endl;
// 用插入排序法对容器内的元素排序
vector<int>::iterator tempiter; // 定义一个iterator(思考为什么不是const_iterator)
for( iter = ivec.begin() + 1; iter != ivec.end(); ++ iter){
num = *iter;
tempiter = iter;
while( tempiter != ivec.begin() && *(tempiter - 1) > num){
*tempiter = *(tempiter - 1);
-- tempiter;
}
*tempiter = num;
}
// 输出排序后的容器
for( iter = ivec.begin(); iter != ivec.end(); ++ iter){
cout << *iter << " ";
}
cout << endl;
}
return 0;
}
运行结果如下图:
思考:修改程序,用下标操作实现程序的功能。说说下标操作和迭代器有什么区别?
注意:标准库vector类型中并没有定义输出到os流的操作,任何试图通过容器名输出容器内容的操作都是错误的,例如cout << ivec;就是错误的。
标准库bitset类型
bitset类型提供了对二进制位的有序集的操作方法。下面是初始化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个位的副本
同样,在使用bitset之前,要#include <bitset>和using std::bitset。
值得注意的是,当用unsigned long值作为bitset对象的初始值是,该值将转化为二进制的位模式保存在bitset对象中。如果bitset类型长度n大于unsigned long值的二进制位数,则其余的高阶位将置为0;如果bitset类型长度n小于unsigned long值的二进制位数,则只使用unsigned值中的低阶位,超过bitset类型长度的高阶位将被丢弃。
特别要注意的是,当用string对象初始化bitset对象时,string对象直接表示为位模式,从string对象读入位集的顺序是从右向左。即string对象和bitset对象之间是反向转化的:string对象的最右边字符(即下标最大的那个字符)用来初始化bitset对象的低阶位(即下标为0的位)。同时还要注意,string字符串只能是包含0和1的字符串,含有其它字符,将出现错误。
下面是一些bitset常用的操作:
b.any() 如果b中存在值为1的二进制位,则返回true
b.none() 如果b中不存在值为1的二进制位,则返回true
b.count() 返回b中值为1的二进制位的个数
b.size() 返回b中二进制位的位数
b[pos] 访问b中在pos处的二进制位
b.test(pos) 如果b中在pos处的二进制位是1,则返回true
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流
其中b.size()操作的返回值类型是标准库中命名为size_t的类型。size_t类型定义在cstddef头文件中,它是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。