STL string类
1.1 为何需要字符串操作类
在C++中,字符串是一个字符数组,最简单的字符数组可这样定义:
char staticName [20];
该语句声明了一个名为staticName的字符数组(也叫字符串),其长度是固定的,包含20个元素。可以看到,这个缓冲区可存储一个长度有限的字符串,如果试图存储的字符串超出限制将溢出。不能调整静态数组的长度,为了避开这种限制,C++支持动态分配内存,因此可以如下定义更动态的字符数组:
char* dynamicName = new char [arrarLen];
这定义了一个动态分配的字符数组,其长度由变量arrayLen的值指定,而这种值是在运行阶段确定的,因此该数组的长度是可变的。然而,如果要在运行阶段改变数组的长度,必须首先释放以前分配给它的内存,再重新分配内存来存储数据。
STL字符串类std::string和std::wstring分别模拟量普通字符串和宽字符串,可提供如下帮助:
- 减少了程序员在创建和操作字符串方面需要做的工作;
- 在内部管理内存分配细节,从而提高了应用程序的稳定性;
- 提供了复制构造函数和赋值运算符,可确保成员字符串得以正确复制;
- 提供了帮助执行截短、查找和删除等操作的实用函数;
- 提供了用于比较的运算符;
- 让程序员能够将精力放在应用程序的主要需求而不是字符串操作细节上。
std::string 和 std::wstring 实际上是同一个模板类( std::basic_string<T> )的具体化,即分别针对类型 char 和 wchar 的具体化。学习使用其中一个后,就能使用这些方法和运算符用于另一个。
1.2 使用STL string类
最常见的字符串函数包括:
- 复制;
- 连接;
- 查找字符和子字符串;
- 截短;
- 使用标准模板库提供的算法实现字符串反转和大小写转换。
要使用STL string类,必须包含头文件<string>.
1.2.1 实例化和复制STL string
string类提供了很多重载的构造函数,因此可以多种方式进行实例化和初始化。例如,可使用常量字符串初始化STL string对象或将常量字符串赋给STL std::string对象:
const char* constCStyleString = "Hello String!";
std::string strFromConst (constCStyleString);
或
std::string strFromConst = constCStyleString;
上述代码与下面的代码类似:
std::string str2("Hello String!");
显然,实例化并初始化string对象时,无需关心字符串长度和内存分配细节。STL string类的构造函数将自动完成这些工作。
同样,可使用一个string对象来初始化另一个:
std::string str2Copy (str2);
可让string的构造函数只接受输入字符串的前n个字符:
std::string strPartialCopy (constCStyleString, 5);
还可以这样初始化string对象,即使其包含指定数量的特定字符:
std::string strRepeatChars (10, 'a');
1.2.2 访问std::string的字符内容
要访问STL string的字符内容,可使用迭代器,也可采用类似于数组的语法并使用下标运算符([])提供偏移量。要获得string对象的C风格表示,可使用成员函数c_str()。
1.2.3 拼接字符串
要拼接字符串,可使用运算符+=,也可使用成员函数append():
string sampleStr1 ("Hello");
string sampleStr2 (" String! ");
sampleStr1 += sampleStr2;
sampleStr1.append (sampleStr2);
1.2.4 在string中查找字符或子字符串
STL string类提供了成员函数find(),该函数有多个重载版本,可在给定string对象中查找字符或子字符串。
// find substring "day" in sampleStr, starting at position 0
size_t charPos = sampleStr.find("day", 0);
// check if the substring was found, compare against string::npos
if (charPos != string::npos)
cout << "First instance of \"day\" was found at position " << charPos;
else
cout << "First instance of \"day\" was found at position " << charPos;
std::string::npos表明没有找到要搜索的元素。
1.2.5 截短STL string
STL string类提供了erase()函数,具有一下用途。
- 在给定偏移位置和字符串时删除指定数目的字符。
string sampleStr ("Hello String! Wake up to a beautiful day!");
sampleStr.erase (13, 28); // Hello String!
- 在给定指向字符的迭代器时删除该字符。
sampleStr.erase (iCharS); // iterator points to a specific character
- 在给由两个迭代器指定的范围时删除该范围内的字符。
sampleStr.erase (sampleStr.begin (), sampleStr.end ()); // erase from begin to end
1.2.6 字符串反转
反转STL string很容易,只需使用泛型算法std::reverse():
string sampleStr ("Hello String! We will reverse you!");
reverse (sampleStr.begin (), sampleStr.end ());
1.2.7 字符串的大小写转换
要对字符串进行大小写转换,可使用算法std::transform(),它对集合中的每个元素执行一个用户指定的函数。
transform(inStr.begin(), inStr.end(), inStr.begin(), ::toupper);
transform(inStr.begin(), inStr.end(), inStr.begin(), ::tolower);
1.3 基于模板的STL string实现
std::string类实际上是STL模板类std::basic_string<T>的具体化。容器类basic_string的模板声明如下:
template<class _Elem,
class _Traits,
class _Ax>
class basic_string
在该模板定义中,最重要的参数是第一个: _Elem,它指定了basic_string对象将存储的数据类型。因此, std:string使用_Elem=char具体化模板basic_string的结果,而wstring使用_Elem=wchar具体化模板basic_string的结果。
换句话说,STL string类的定义如下:
typedef basic_string<char, char_traits<char>, allocator<char> >
string;
而STL wstring类的定义如下:
typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >
string;
STL动态数组类
2.1 std::vector的特点
vector是一个模板类,提供了动态数组的通用功能,具有如下特点:
- 在数组末尾添加元素所需的时间是固定的,即在末尾插入元素的所需时间不随数组大小而异,在末尾删除元素也如此;
- 在数组中间添加或删除元素所需的时间与该元素后面的元素个数成正比;
- 存储的元素数是动态的,而vector类负责管理内存。
vector是一种动态数组,其结构如下:
要使用std::vector类,需要包含下面的头文件:
#include <vector>
2.2 典型的vector操作
2.2.1 实例化vector
要实例化vector,需要指定要在该动态数组中存储的对象类型:
std::vector<int> dynIntArray; // vector containing integers
std::vector<float> dynFloatArray; // vector containing floats
std::vector<Tuna> dynTunaArray; // vector containing Tunas
要声明指向list中元素的迭代器,可以这样做:
std::vector<int>::const_iterator elementInVev;
如果需要可用于修改值或调用非const函数的迭代器,可使用iterator代替const_iterator。
可以在实例化vector时指定它开始应包含的元素数以及这些元素的初始值,还可使用vector的一部分来实例化另一个vector。
2.2.2 使用push_back()在末尾插入元素
实例化一个整型vector后,接下来需要在vector中插入元素(整数)。在vector中插入元素时,元素将插入到数组末尾,这时使用成员函数push_back()完成。
vector <int> integers; // declare a vector of type int
// Insert sample integers into the vector:
integers.push_back (50);
integers.push_back (1);
2.2.3 列表初始化
与大多数容器一样,std::vector也支持列表初始化,能够在实例化vector的同时指定其元素:
vector<int> integers = {50, 1, 987, 1001};
// alternatively:
vector<int> vecMoreIntegers {50, 1, 987, 1001};
2.2.4 使用insert()在指定位置插入元素
puah_back()在vector末尾插入元素,如果要在中间插入元素,可以使用insert()函数。该函数有多个重载版本。
其中一个版本能够指定插入位置:
// insert an element at the beginning
integers.insert (integers.begin (), 25);
另一个版本能够指定插入位置、要插入的元素数以及这些元素的值(都相同):
// Insert 2 elements of value 45 at the end
integers.insert (integers.end (), 2, 45);
还可将另一个vector的内容插入到指定位置:
// Another vector containing 2 elements of value 30
vector <int> another (2, 30);
// Insert two elements from another container in position [1]
integers.insert (integers.begin () + 1,
another.begin (), another.end ());
虽然函数vector::insert()用途广泛,但给vector添加元素时,应首选push_back()。这时因为将元素插入vector时,insert()可能是效率最低的。
2.2.5 使用数组语法访问vector中的元素
可使用下列方法访问vector的元素:
- 使用下标运算符([])以数组语法方式访问;
- 使用成员函数at();
- 使用迭代器。
2.2.6 使用指针语法访问vector中的元素
也可使用迭代器以类似于指针的语法访问vector中的元素。
#include <iostream>
#include <vector>
int main ()
{
using namespace std;
vector <int> integers{ 50, 1, 987, 1001 };
vector <int>::const_iterator element = integers.cbegin ();
// auto element = integers.cbegin (); // auto type deduction
while (element != integers.end ())
{
size_t index = distance (integers.cbegin (), element);
cout << "Element at position ";
cout << index << " is: " << *element << endl;
// move to the next element
++ element;
}
return 0;
}
2.2.7 删除vector中的元素
除支持使用push_back()函数在末尾插入元素外,vector还支持是pop_back()函数将末尾的元素删除。同样,使用pop_back()将元素从vector中删除所需的时间是固定的,即不随vector存储的元素个数而异。
2.3 理解大小和容量
vector的大小指的是实际存储的元素数,而vector的容量指的是在重新分配内存以存储更多元素前vector能够存储的元素数。因此,vector的大小小于或等于容量。
要查询vector当前存储的元素数,可调用size():
cout << "Size: " << integers.size ();
要查询vector的容量,可调用capacity():
cout << "Capacity: " << integers.capacity () << endl;
如果vector需要频繁地给其内部动态数组重新分配内存,将对性能造成一定的影响。在很大程度上说,这种问题可以通过使用成员函数reserve(number)来解决。reserve函数的功能基本上是增加分配给内部数组的内存,以免频繁地重新分配内存。通过减少重新分配内存的次数,还可减少复制对象的时间,从而提高性能,这取决于存储在vector中的对象类型。
2.4 STL deque类
deque是一个STL动态数组类,与vector非常类似,但支持在数组开头和末尾插入或删除元素。是实例化一个整型deque,如下:
// Define a deque of integers
std::deque <int> intDeque;
要使用std::deque,需要包含头文件<deque>;
deque的内部结构如下所示。
deque与vector极其相似,也支持使用函数push_back()和pop_back()在末尾插入和删除元素。与vector一样,deque也使用运算符[]以数组语法访问其元素。deque与vector的不同之处在于,它还允许使用push_front()和pop_front()在开头插入和删除元素。