主要内容:
- 标准C++ string类
- 模板auto_ptr、unique_ptr和shared_ptr
- 标准模板库(STL)
- 容器类
- 迭代器
- 函数对象(functor)
- STL算法
- 模板initializer_list(C++11)
1. string类
string类由头文件string支持,string类包含大量的方法,包括若干构造函数、用于将字符串赋给变量、合并字符串、比较字符串、访问各个元素的重载运算符、用于在字符串中查找字符和子字符串的工具。
1.1 构造字符串
string的构造函数:string有7个构造函数,C++11新增2个构造函数。
使用构造函数时进行了简化:
(1)string实际上是模板具体化basic_string<char>的一个typedef;
(2)省略了与内存管理相关的参数
size_type是一个依赖于实现的整型,是在头文件string中定义的。
string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值。
下表中使用NBTS(null-terminated string)来表示以空字符结束的字符串——传统的C字符串。
构造函数 | 描述 |
---|---|
string(const char* s) | 将string对象初始化为s指向的NBTS |
string(size_type n, char c) | 创建一个包含n个元素的string对象,其中每个元素都被初始化为字符c |
string(const string& str) | 将一个string对象初始化为string对象str(复制构造函数) |
string() | 创建一个默认的string对象,长度为0(默认构造函数) |
string(const char* s, size_type n) | 将string对象初始化为s指向的NBTS的前n个字符,即使超过了NBTS结尾 |
template<class Iter> string(Iter begin, Iter end) | 将string对象初始化为区间[begin, end)内的字符,其中begin和end的行为就像指针,用于指定位置,范围包括begin在内,但不包括end |
string(const string& str, string size_type pos = 0, size_type n = npos) | 将一个string对象初始化为对象str中从位置pos开始到结尾的字符,或从位置pos开始的n个字符 |
string(string&& str) noexcept | C++11新增,将一个string对象初始化为string对象str,并可能修改str(移动构造函数) |
string(initializer_list<char> il) | C++11新增,将一个string对象初始化为初始化列表il中的字符 |
// 程序清单16.1 str1.cpp
// str1.cpp -- introducing the string class
#include <iostream>
#include <string>
// using string constructors
int main()
{
using namespace std;
string one("Lottery Winner!"); // ctor #1
cout << one << endl; // overloaded <<
string two(20, '$'); // ctor #2
cout << two << endl;
string three(one); // ctor #3
cout << three << endl;
one += " Oops!"; // overloaded +=
cout << one << endl;
two = "Sorry! That was";
three[0] = 'P';
string four; // ctor #4
four = two + three; // overloaded +, =
cout << four << endl;
char alls[] = "All's well that ends well";
string five(alls, 20); // ctor #5
cout << five << "!\n";
string six(alls+6, alls+10); // ctor #6
cout << six << ", ";
string seven(&five[6], &five[10]); // ctor #6 again
cout << seven << "...\n";
string eight(four, 7, 16); // ctor #7
cout << eight << " in motion!" << endl;
return 0;
}
1.2 string类输入
对于类,很有帮助的一点是,知道有哪些输入方式可用。
对于C-风格字符串,有3种方式:
char info[100];
cin >> info;
cin.getline(info, 100); //read a line, 丢弃\n
cin.get(info, 100); //read a line, leave \n in queue
对于string对象,有2种方式:
string stuff;
cin >> stuff;
getline(cin, stuff); //read a line, 丢弃\n
两个版本的getline()都有一个可选参数,用于指定使用哪个字符来确定输入的边界:
cin.getline(info, 100, ':'); //read up to :, 丢弃:
getline(cin, stuff, ':'); //read up to :, 丢弃:
string版本的getline()将自动调整目标string对象的大小,使之刚好能够存储输入的字符:
string版本的getline()函数从输入中读取字符,并将其存储到目标string中,直到发生下列三种情况之一:
- 到达文件尾,此时输入流的eofbit将被设置,这意味着方法fail()和eof()都将返回true;
- 遇到分界字符(默认为\n),此时将把分界字符从输入流中删除,但不存储它;
- 读取的字符数达到最大允许值(string::npos和可供分配的内存字节数中较小的一个),此时将设置输入流的failbit,这意味着方法fail()将返回true。
string版本的operator>>()函数的行为与此类似,只是它不断读取,直到遇到空白字符并将其留在输入队列中,而不是不断读取,直到遇到分界字符并将其丢弃。空白字符指空格、换行符和制表符。
// strfile.cpp -- read strings from a file
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
int main()
{
using namespace std;
ifstream fin;
fin.open("tobuy.txt");
if(fin.is_open() == false)
{
cerr << "Can;t open file. Bye.\n";
exit(EXIT_FAILURE);
}
string item;
int count = 0;
getline(fin, item, ':');
while(fin) // while input is good
{
++count;
cout << count << ": " << item << endl;
getline(fin, item, ':');
}
cout << "Done\n";
fin.close();
return 0;
}
注意:通常,对于程序要查找的文本文件,应将其放在可执行程序或项目文件所在的目录中,否则必须提供完整的路径名。在Windows系统中,C-风格字符串中的转义序列\\表示一个斜杠:
fin.open("C:\\CPP\\Progs\\tobuy.txt"); //file = C:\CPP\Progs\tobuy.txt
注意:将 : 指定为分界字符后,换行符将被视为常规字符。
下面是文件tobuy.txt的内容:
sardines:chocolate ice cream:pop corn:leeks:
cottage cheese:olive oil:butter:tofu:
下面是程序的输出:
1: sardines
2: chocolate ice cream
3: pop corn
4: leeks
5:
cottage cheese
6: olive oil
7: butter
8: tofu
9:
Done
1.3 使用字符串
- 可以使用不同方式来创建string对象、显示string对象的内容、将数据读取和附加到string对象中、给string对象赋值、将两个string对象连结起来;
- 可以比较字符串;
- 可以确定字符串的长度。size()和length()成员函数都返回字符串中的字符数(length()成员来自较早版本的string类,size()则是为提供STL兼容性而添加的);
- 可以以多种不同的方式在字符串中搜索给定的子字符串或字符,表16.2描述了find()方法的4个重载版本(string::npos是字符串可存储的最大字符数,通常是无符号int或无符号long的最大取值);
- string库还提供了相关的方法:rfind()、find_first_of()、find_last_of()、find_first_not_of()和find_last_not_of(),它们的重载特征标都与find()方法相同。
rfind()方法查找子字符串或字符最后一次出现的位置;
find_first_of()方法在字符串中查找参数中任何一个字符首次出现的位置;
find_last_of()方法功能与此相同,只是它查找最后一次出现的位置;
find_first_not_of()方法在字符串中查找第一个不包含在参数中的字符;
方法原型 | 描述 |
---|---|
size_type find(const string& str, size_type pos = 0) const | 从字符串的pos位置开始,查找子字符串str。如果找到,则返回该子字符串首次出现时其首字母的索引;否则,返回string::npos |
size_type find(const char* s, size_type pos = 0)const | 从字符串的pos位置开始,查找子字符串s。如果找到,则返回该子字符串首次出现时其首字母的索引;否则,返回string::npos |
size_type find(const char* s, size_type pos = 0, size_type n) | 从字符串的pos位置开始,查找s的前n个字符组成的子字符串。如果找到,则返回该子字符串首次出现时其首字母的索引;否则,返回string::npos |
size_type find(char ch, size_type pos = 0)const | 从字符串的pos位置开始,查找字符ch。如果找到,则返回该字符首次出现的位置;否则,返回string::npos |
程序清单16.3建立了一个非图形版本的Hangman拼字游戏。
// 程序清单16.3 -- hangman.cpp
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;
const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal", "danger", "ensign", "florid"}; //还有好多次懒得写了,注意需要26个单词
int main()
{
using std::cout;
using std::cin;
using std::tolower;
using std::endl;
std::srand(std::time(0));
char play;
cout << "Will you play a word game? <y/n> ";
cin >> play;
play = tolower(play);
while(play == 'y')
{
string target = wordlist[std::rand() % NUM];
int length = target.length();
string attempt(length, '-'); //记录玩家猜对的字母
string badchars;
int guesses = 6; //玩家有6次机会
cout << "Guess my secret word. It has " << length << " letters, and you guess\n"
<< "one letter at a time. You get " << guesses << " wrong guesses.\n";
cout << "Your word: " << atempt << endl;
while(guesses > 0 && attempt != target)
{
char letter;
cout << "Guess a letter: ";
cin >> letter;
if(badchars.find(letter) != string::npos || attempt.find(letter) != string::npos)
{
cout << "You already guessed that. Try again.\n";
continue;
}
int loc = target.find(letter);
if(loc == string::npos)
{
cout << "Oh, bad guess!\n";
--guesses;
badchars += letter; //add to string
}
else
{
cout << "Good guess!\n";
attempt[loc] = letter;
// check if letter appears again
loc = target.find(letter, loc+1);
while(loc != string::npos)
{
attempt[loc] = letter;
loc = target.find(letter, loc+1);
}
}
cout << "Your word: " << attempt << endl;
if(attempt != target)
{
if(badchars.length() > 0)
{
cout << "Bad choices: " << badchars << endl;
}
cout << guesses << " bad guesses left\n";
}
}
if(guesses > 0)
{
cout << "That's right!\n";
}
else
{
cout << "Sorry, the word is " << target << ".\n";
cout << "Will you play another? <y/n>";
cin >> play;
play = tolower(play);
}
}
cout << "Bye\n";
return 0;
}
1.4 string还提供了哪些功能
(1)自动调整大小功能
每当程序将一个字母附加到字符串末尾时,不能仅仅将已有的字符串加大,因为相邻的内存可能被占用了。因此,可能需要分配一个新的内存块,这样效率低。因此很多C++实现分配一个比实际字符串大的内存块,为字符串提供了增大空间。如果字符串不断增大,超过了内存块的大小,则程序将分配一个大小为原来两倍的新内存块,以提供足够的增大空间。
方法capacity()返回当前分配给字符串的内存块的大小,reserve()方法让您能够请求内存块的最小长度。
//程序清单16.4 str2.cpp
// str2.cpp -- capacity() and reserve()
#include <iostream>
#include <string>
int main()
{
using namespace std;
string empty;
string small = "bit";
string larger = "Elephants are a girl's best friend";
cout << "Sizes:\n";
cout << "\tempty: " << empty.size() << endl;
cout << "\tsmall: " << small.size() << endl;
cout << "\tlarger: " << larger.size() << endl;
cout << "Capacities:\n";
cout << "\tempty: " << empty.capacity() << endl;
cout << "\tsmall: " << small.capacity() << endl;
cout << "\tlarger: " << larger.capacity() << endl;
empty.reserve(50);
cout << "Capacity after empty reserve(50): " << empty.capacity() << endl;
return 0;
}
下面是使用某种C++实现时,16.4程序的输出:
Size:
empty: 0
small: 3
larger: 34
Capacirties:
empty: 15
small: 15
larger:47
Capacity after empty reserve(50): 63
注意,该实现使用的最小容量为15个字符,这比标准容量选择(16的倍数)小1。
由string对象转换为C-风格字符串:使用c_str()方法。 fout.open(filename.c_str());
1.5 字符串种类
本节将string类看作是基于char类型的,string库实际上是基于一个模板类:
template<class charT, class traits = char_traits<charT>, class Allocator = Allocator<charT> >
basic_string {
// ...
}
模板basic_string有4个具体化,每个具体化都有一个typedef名称:
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string; //C++11
typedef basic_string<char32_t> u32string; //C++11
traits类描述关于选定字符类型的特定情况,如如何对值进行比较。对于wchar_t、char16_t、char32_t和char类型,有预定义的char_traits模板具体化,它们都是traits的默认值。
Allocator是一个管理内存分配的类。对于各种字符类型,都有预定义的allocator模板具体化,它们都是默认的。它们使用new和delete。