一、string类
1. 构造函数
常用的构造函数如下。
构造函数原型 | 含义 |
---|---|
string() | 默认构造函数。创建一个默认string对象,长度为0 |
string(const string &s) | 拷贝构造函数。用一个string对象初始化另一个string对象 |
string(const char *s) | 用字符串常量构建string对象 |
string(size_n n, ch ar c) | 构造一个包含n个元素的string对象,其中每个字符都被初始化为字符c |
字符串常量,也就是C-风格的字符串,例如char str[] = "hello"
。
string类虽然看起来使用方法与C中的字符串数组差不多,但它并不是基于char类型实现的,而是基于模板类basic_string<char>
的一个typedef。
#include <string>
string str;
string str1("hello world");
string str2(str1);
string str3(3, 'a'); // string对象为3个字符'a'
2. 运算符重载
string类中重载了一些运算符,使字符串操作更方便。
重载运算符 | 含义 |
---|---|
>> | cin字符串输入 |
<< | cout字符串输出 |
= | 将一个字符串赋给另一个字符串,可以附加字符串常量、单个char字符或string对象 |
+ | 将两个字符串连在一起,操作对象可以是字符串常量、单个char字符或string对象 |
+= | 将一个字符串附加到另一个字符串后面,可以附加字符串常量、单个char字符或string对象 |
[] | 访问字符串中的各个字符 |
> >= < <= == != | 比较字符串的大小,比较对象可以是string类或字符串常量 |
3. 常见成员函数
#include <string>
string str;
1. 获取字符串的长度
int len1 = str.length()
int len2 = str.size()
length()来自较早的string类,size()成员为STL兼容性添加的
2. 在字符串中搜索指定的字符或子字符串
函数原型:size_type find(const string & str, size_type pos = 0)const
从字符串的迫使、位置开始,查找字符串str
如果找到,则返回该子字符串首次出现时首个字符的索引;否则,返回string::npos
第一个参数也可以是char型(单个字符)或const char *型(字符串常量)
3. 容量操作
返回分配给字符串的内存块的大小:int cap = str.capacity()
规定请求内存块的最小长度:str.reserve(10)
4. 类型转换
将string字符串转换成c风格的字符串常量:const char *s = str.c_str()
npos是string类的静态成员,它的值是string对象能存储的最大字符数。由于索引从0开始,所以它比最大的索引值大1,因此可以使用它来表示没有查找到字符或字符串。
字符串在内存中占用一段连续的地址空间。如果进行将一个字符串连接到另一个字符串末尾的操作,则占用的空间变大,但相邻的内存可能已经被占用了,因此,需要分配一块新的连续内存块给连接后的字符串,字符串的内容复制到新的内存单元中。如果大量执行这样的操作,效率将非常低。因此,很多C++实现分配一个比实际字符串大的内存块,满足扩充字符串长度的需求。如果字符串长度依旧超出原有的范围,则程序将分配一个大小为原来两倍的新内存块,以提供足够大的空间。
举个例子
string small = "bit";
cout << small.capacity() << endl; // 输出15
small.reserve(50);
cout << small.capacity() << endl; // 输出63
二、标准库模板
1. 迭代器
所谓的迭代器,是一个广义指针。迭代器能够为STL的各种不同的容器类提供统一处理的接口,这些类通常都是简单指针无法处理的。每个容器类都定义了一个合适的迭代器,该迭代器的类型是一个名为iterator的typedef,其作用域为整个类。例如,声明一个double类型的vector容器的代码如下。
vector<double>::iterator pd;
2. STL通用方法
指向T的迭代器类型:X::iterator
T的类型:X::value_type
返回容器中元素的个数:a.size()
交换a和b的内容:a.swap(b)
返回指向容器中第一个元素的迭代器:a.begin()
返回指向容器超过容器尾的迭代器:a.end()
比较a和b,需要满足a和b长度相同,且每个元素都相等:a == b
比较a和b,不相等情况:a != b
3. vector矢量
传统的数组array是一块连续、固定的空间,并不能自由地改变array的大小,也就难以在数组中插入、删除元素。为了解决这个问题,C++定义了vector容器。
vector所采用的数据结构是线性连续空间,但是允许动态改变空间地大小。如果要换大一点或者小一点的空间,首先配置一块新的空间,然后将旧空间的数据拷贝到新空间,再释放原来的空间。
为了降低空间配置时的速度成本,vector实际配置的大小可能比用户实际需求大一些,以备将来可能的扩充,这边是容量(capacity)的概念。换句话说,一个vector的容量永远大于或等于其大小,一旦容量等于大小,便是满载,如果再有新增元素,整个vector容器就要在内存中再寻找一块满足容量大小地新居所,并将所有数据拷贝过去。
动态增加大小并不是在原空间之后续直接接新空间,因为无法保证原空间之后的空间没有被其他数据占用。因此找更大的内存空间,然后将原数据拷贝新空间,释放原空间。通常会新分配的空间是原空间大小的两倍。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
因为vector维护的是一个连续空间,因此支持随机存取,提供随机访问迭代器(Random Access Iterators)。vector迭代器的功能可以完全覆盖普通指针的各种操作,如operator*, operator->, operator++, operator–, operator+, operator-, operator+=, operator-=,这些运算符的使用方式与普通指针完全相同。
创建vector类对象。
#include <vector>
创建vector对象,一维
不指定大小:vector<int> vec;
指定大小为5:vector<int> vec(5);
创建vector对象,二维
不指定大小:vector<vector<int>> vec;
指定大小,5行5列:
vector<int> temp(5);
vector<vector<int>> vec(5, temp));
vector类的操作。
#include <vector>
vector<int> vec;
容量操作:
获取数据个数:int size = vec.size()
获取容量大小:int cap = vec.capacity()
判断是否为空:vec.empty()
改变vector的size:vec.resize()
改变vector的capacity:vec.reserve()
增删查改:
int a;
int pos;
尾插:vec.push_back(a)
尾删:a = vec.pop_back()
查找:find
在pos之前插入元素a:vec.insert(pos, a);
删除pos位置的元素:vec.erase(pos);
vector的非成员函数。
对容器中的某一段元素进行遍历:for_each(起始位置,结束位置,函数指针)
例如
vector<Review>::iterator pr;
for (pr = books.begin(); pr != books.end(); pr++)
ShowReview(*pr);
改写成for_each函数为
for_each(books.begin(), books.end(), ShowReview);
随机排列指定区间内的元素:Random_shuffle(起始位置,结束位置)
例如Random_shuffle(books.begin(), books.end());
对指定区间内的元素进行排序:sort(起始位置,结束位置)
如果容器元素是用户自定义的对象,则必须对运算符<进行重定义operator<()。
4. deque双端队列
vector容器是单向开口的连续内存空间,允许在尾部插入新元素,尽管vector容器也可以在头部或中间插入元素,但效率极低,因为需要挨个向后面挪动元素。
deque容器则是一种双向开口的连续线性空间,头尾两端都可以做元素插入和删除操作。deque容器在逻辑空间上看是连续的,但实际上物理空间并不连续。每段数据空间内部是连续的,而每段数据空间之间则不一定连续。
deque由若干段的缓冲区构成,缓冲区中存放真实数据,同时使用一个中控器,维护每段缓冲区的地址。中控器是一种map(注意,不是STL的map容器),是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。如下图所示。
deque除了维护一个指向map的指针外,还维护start、finish两个迭代器,分别指向第一缓冲区的第一个元素和最后缓冲区的最后一个元素(的下一位置)。此外,也必须记住目前的map大小,因为一旦map的所提供的空间不足,它将需要重新配置一个更大的空间,依然是经过三个步骤:配置更大的、拷贝原来的、释放原空间。
deque容器和vector容器的差异:
- deque能够在在常数项时间内在头部进行插入和删除操作,vector头插需要挨个挪动后面的元素。
- 在于deque没有容量的概念,因为它以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来。deque不需要像vector那样预留一部分空间来减少重新分配内存并拷贝数据的次数。
- deque容器也提供了随机访问迭代器,但是它的迭代器并不是普通的指针,因此也比vector的迭代器实现起来更复杂。因此,除非有必要,我们应该尽可能的使用vector,而不是deque。
deque的常用操作函数如下。
#include <deque>
deque<int> elem;
两端插入删除:
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器的第一个数据
指定位置操作:
insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值
clear();//清空容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置
erase(pos);//删除pos位置的数据,返回下一个数据的位置
5. stack栈
栈是一种先进后出的数据结构,stack容器的底层实现是deque,stack没有迭代器。
#include <stack>
stack<int> st;
int a;
把元素a入栈:st.push(a)
栈顶元素出栈(没有返回值):st.pop()
返回栈顶元素(栈顶元素不出栈):a = st.top()
判断栈是否为空(为空则返回true):st.empty()
6. queue队列
队列是一种先进先出的数据结构,queue容器的底层实现是deque,queue没有迭代器。
#include <queue>
queue<int> q;
int a;
在队尾添加a元素:q.push(a)
移除队头元素(没有返回值):q.pop()
返回队头元素(队头元素不出队):a = q.front()
返回队尾元素(队尾元素不出队):a = q.back()
判断队列是否为空(为空则返回true):q.empty()
7. list双向循环链表
list的数据结构是双向循环链表,在逻辑空间上是连续的,但在物理空间上是非连续、非顺序的。它可以在常数时间内完成元素的插入和删除,但不支持随机访问,
list容器的迭代器是一种双向迭代器(Bidirectional Iterator),有能力进行正确的递增、递减、取值、成员存取操作:递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员。
list的元素插入操作和删除操作都不会造成原有list迭代器的失效。这在vector是不成立的,因为vector的插入操作可能造成记忆体重新配置,导致原有的迭代器全部失效,但list不会。
list的常用操作函数如下。
#include <list>
list<int> l;
int a;
在链表头添加a元素:l.push_front(a)
从链表头移除元素(没有返回值):l.pop_front()
在链表尾添加a元素:l.push_back(a)
从链表尾移除元素(没有返回值):l.pop_back()
insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值
clear();//清空容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置
erase(pos);//删除pos位置的数据,返回下一个数据的位置