1.1 STL基本概念
StandardTemplate Library,标准模板库
从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码重用机会。STL(Standard Template Library)标准模板库,在我们 C++标准程序库中隶属于 STL 的占到了 80%以上。
1.2 STL六大组件简介
STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。
容器: 各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。
算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.
迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator*, operator-> , operator++,operator--等指针相关操作予以重载的classtemplate. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template
适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的classtempalte.
STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。
1.3 STL优点
STL 是 C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。1. STL 的一个重要特性是将数据和操作分离。数据由容器类别加以管理,操作则由可定制的算法定义。迭代器在两者之间充当“粘合剂”,以使算法可以和容器交互运作1111
2. 程序员可以不用思考 STL 具体的实现过程,只要能够熟练使用 STL 就 OK 了。这样他们就可以把精力放在程序开发的别的方面。
3. STL 具有高可重用性,高性能,高移植性,跨平台的优点。
4. 高可重用性:STL 中几乎所有的代码都采用了模板类和模版函数的方式实现,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。关于模板的知
识,已经给大家介绍了。
5. 高性能:如 map 可以高效地从十万条记录里面查找出指定的记录,因为 map 是采用红黑树的变体实现的。
6. 高移植性:如在项目 A 上用 STL 编写的模块,可以直接移植到项目 B 上。
算法
算法分为:质变算法和非质变算法。
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
迭代器
迭代器(iterator)是一种抽象的设计概念(可以理解为一个指针),现实程序语言中并没有直接对应于这个概念的实物。在<<Design Patterns>>一书中提供了23中设计模式的完整描述,其中iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
迭代器的设计思维-STL的关键所在,STL的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。从技术角度来看,容器和算法的泛型化并不困难,c++的class template和function template可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//STL 中的容器 算法 迭代器
void test01(){
vector<int> v; //STL 中的标准容器之一 :动态数组
v.push_back(1); //vector 容器提供的插入数据的方法
v.push_back(5);
v.push_back(3);
v.push_back(7);
//迭代器
vector<int>::iterator pStart = v.begin(); //vector 容器提供了 begin()方法 返回指向第一个元素的迭代器
vector<int>::iterator pEnd = v.end(); //vector 容器提供了 end()方法 返回指向最后一个元素下一个位置的迭代器
//通过迭代器遍历
while (pStart != pEnd){
cout << *pStart << " ";
pStart++;
}
cout << endl;
//算法 count 算法 用于统计元素的个数
int n = count(pStart, pEnd, 5);
cout << "n:" << n << endl;
}
//STL 容器不单单可以存储基础数据类型,也可以存储类对象
class Teacher
{
public:
Teacher(int age) :age(age){};
~Teacher(){};
public:
int age;
};
void test02(){
vector<Teacher> v; //存储 Teacher 类型数据的容器
Teacher t1(10), t2(20), t3(30);
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
vector<Teacher>::iterator pStart = v.begin();
vector<Teacher>::iterator pEnd = v.end();
//通过迭代器遍历
while (pStart != pEnd){
cout << pStart->age << " ";
pStart++;
}
cout << endl;
}
//存储 Teacher 类型指针
void test03(){
vector<Teacher*> v; //存储 Teacher 类型指针
Teacher* t1 = new Teacher(10);
Teacher* t2 = new Teacher(20);
Teacher* t3 = new Teacher(30);
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
//拿到容器迭代器
vector<Teacher*>::iterator pStart = v.begin();
vector<Teacher*>::iterator pEnd = v.end();
//通过迭代器遍历
while (pStart != pEnd){
cout << (*pStart)->age << " ";
pStart++;
}
cout << endl;
}
//容器嵌套容器 难点(不理解,可以跳过)
void test04()
{
vector< vector<int> > v;
vector<int>v1;
vector<int>v2;
vector<int>v3;
for (int i = 0; i < 5;i++)
{
v1.push_back(i);
v2.push_back(i * 10);
v3.push_back(i * 100);
}
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
for (vector< vector<int> >::iterator it = v.begin(); it != v.end();it++)
{
for (vector<int>::iterator subIt = (*it).begin(); subIt != (*it).end(); subIt ++)
{
cout << *subIt << " ";
}
cout << endl;
}
}
int main(){
//test01();
//test02();
//test03();
test04();
system("pause");
return EXIT_SUCCESS;
}
string容器
C风格字符串(以空字符结尾的字符数组)太过复杂难于掌握,不适合大程序的开发,所以C++标准库定义了一种string类,定义在头文件<string>。String和c风格字符串对比:
Char*是一个指针,String是一个类
string封装了char*,管理这个字符串,是一个char*型的容器。
String封装了很多实用的成员方法
查找find,拷贝copy,删除delete 替换replace,插入insert
不用考虑内存释放和越界
string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
//1. string初始化
/*
string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个string对象初始化另一个string对象
string(const char* s);//使用字符串s初始化
string(int n, char c);//使用n个字符c初始化
*/
void test01(){
string s1; //创建空字符串,调用无参构造函数
string s2(s1); //调用拷贝构造函数
//string s3 = s1; //调用拷贝构造函数
const char* str = "hello world!";//C风格的字符串
string s4(str); //把c_string转换成了string
string s5(10, 'a');
cout << s4 << endl;
cout << s5 << endl;
}
//2. 赋值操作
/*
string& operator=(const char* s);//char*类型字符串赋值给当前的字符串
string& operator=(const string &s);//把字符串s赋给当前的字符串
string& operator=(char c);//字符赋值给当前的字符串
string& assign(const char *s);//把字符串s赋给当前的字符串
string& assign(const char *s, int n);//把字符串s的前n个字符赋给当前的字符串
string& assign(const string &s, int start, int n);//将s从start开始n个字符赋值给字符串
string& assign(const string &s);//把字符串s赋给当前字符串
string& assign(int n, char c);//用n个字符c赋给当前字符串
*/
void test02(){
string s1 = "abcd";//把char* s 赋值给当前的字符串
s1 = "hello world!"; //赋值
s1 = 'a';//字符char c 赋值给当前的字符串
string s2 = "haha";
string s3 = s2;//把字符串string s 赋给当前的字符串
char* str1 = "hello world!";
string s4, s5;
s4.assign(s2);//把字符串string s 赋给当前的字符串
s5.assign(str1);//把char* s 赋值给当前的字符串
char* str2 = "hello";
string s6, s7;
s6.assign(str2, 4);//把char* s 的前n个字符赋给当前的字符串
/*将string s 从start(不包括start所在字符)开始的n个字符赋给当前字符串,
第三个参数不写则默认把start之后的所有字符赋给当前字符串*/
s7.assign(s2, 1, 2);
string s8;
s8.assign(10, 'a');//用n个字符c赋给当前字符串
cout << "s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
cout << "s6:" << s6 << endl;
cout << "s7:" << s7 << endl;
cout << "s8:" << s8 << endl;
}
//3. 字符存取
void test03(){
string s = "hello world!";
//[]和at 返回的是元素的引用
for (int i = 0; i < s.size();i++){
cout << s[i] << " ";
}
cout << endl;
for (int i = 0; i < s.size(); i ++){
//s.at(i) = 100;
cout << s.at(i) << " ";
}
cout << endl;
//[]和at区别。[]访问越界,直接挂掉。at访问越界会抛出一个out_of_range异常
try{
//cout << s[100] << endl;
cout << s.at(100) << endl;
}
catch (...){
cout << "访问越界!" << endl;
}
}
//4.字符串拼接
/*
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串s连接到当前字符串结尾
string& append(const char *s, int n);//把字符串s的前n个字符连接到当前字符串结尾
string& append(const string &s);//同operator+=()
string& append(const string &s, int pos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加n个字符c
*/
void test04(){
string s1 = "hello";
string s2 = " world";
s1 += s2;
cout << s1 << endl;
cout << s2 << endl;
string s3 = s1 + s2;
cout << s3 << endl;
//string + c_string
string s4 = s1 + " aaaaa";
cout << s4 << endl;
s4.append(" bbbbbb");
cout << s4 << endl;
s4.append(s1);
cout << s4 << endl;
string s5 = "start";
cout << s5 << endl;
s5.append("aaaaa", 3);
cout << s5 << endl;
s5.append("abcde", 2, 3);
cout << s5 << endl;
s5.append(6,'g');
cout << s5 << endl;
}
//5. 字符串查找
/*
int find(const string& str, int pos = 0) const; //查找str第一次出现位置,从pos开始查找
int find(const char* s, int pos = 0) const; //查找s第一次出现位置,从pos开始查找
int find(const char* s, int pos, int n) const; //从pos位置查找s的前n个字符第一次位置
int find(const char c, int pos = 0) const; //查找字符c第一次出现位置
int rfind(const string& str, int pos = npos) const;//查找str最后一次位置,从pos开始查找
int rfind(const char* s, int pos = npos) const;//查找s最后一次出现位置,从pos开始查找
int rfind(const char* s, int pos, int n) const;//从pos查找s的前n个字符最后一次位置
int rfind(const char c, int pos = 0) const; //查找字符c最后一次出现位置
string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
string& replace(int pos, int n, const char* s); //替换从pos开始的n个字符为字符串s
*/
void test05(){
//如果没有查找到返回-1
string s1 = "abcdefgth";
int pos = s1.find("def",4);
if (pos == -1){
cout << "没有查找到!" << endl;
}
else{
cout << "查找到:" << pos << endl;
}
s1.replace(1, 3, "111");
cout << s1 << endl;
string s2 = "abdefcdefgh";
cout << s2.find('f') << endl;
cout << s2.find("def") << endl;
cout << s2.rfind("def") << endl;
}
//6. 字符串比较
/*
compare函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的A比小写的a小。
int compare(const string &s) const;//与字符串s比较
int compare(const char *s) const;//与字符串s比较
*/
void test06(){
string s1 = "hello";
string s2 = "hello";
const char* str = "hallo";
if (s1.compare(s2) == 0){
cout << "相等!" << endl;
}
int n = s1.compare(str);//int n用来接收compare()的返回值
if (n == 0){
cout << "相等!" << endl;
}
else if(n < 0) {
cout << "s1小于str" << endl;
}
else {
cout << "s1大于str" << endl;
}
}
//7. 子串
//string substr(int pos = 0, int n = npos) const;//返回由pos开始的n个字符组成的字符串
void test07(){
//找出邮箱地址中的用户名字符串
string s = "hello@hotmal.com";
int pos = s.find("@");
string username = s.substr(0, pos);
cout << "username:" << username << endl;
}
//8.插入
/*
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入n个字符c
string& erase(int pos, int n = npos);//删除从pos开始的n个字符
*/
void test08(){
string s = "hello";
s.insert(1,"111");
cout << s << endl;
s.insert(4, 2, 'a');
cout << s << endl;
s.erase(1,3); //从1号位置开始3个字符
cout << s << endl;
}
//9. c_string和string转换
void test09(){
//c_string -> string
const char* str = "hello world";
string s(str); //调用string的带参数的构造函数
cout << "S:" << s << endl;
const char* str2 = s.c_str(); //string->c_string,返回值是const char* 类型的指针
cout << "str2:" << str2 << endl;
}
//10.string类型的对象实际上是通过const char*管理字符串,且将字符串存储在堆区,
//由const char*指向该堆区。当重新给string对象赋值时,若新值所需的空间大于原始空间
//该对象就会重新分配一块较大的空间
void test10(){
string s1 = "hello world!";
//将s1.c_str()的返回值const char*指针强制转换为int*指针以显示地址
cout << (int*)s1.c_str() << endl;
//at()和[]返回的都是元素的引用
char& a = s1[1];//对元素s1[1]取引用
char& b = s1[3];//对元素s1[3]取引用
a = '2';
b = '3';
cout << s1 << endl;
s1 = "aaaaa";
//由于新值所需的空间小于原始空间,所以不会重新分配空间,那么对原始空间的操作不会出错
cout << (int*)s1.c_str() << endl;
a = '3';//ok
b = '4';//ok
cout << s1 << endl;
s1 = "pppppppppppppppppppp";
//由于新值所需的空间大于原始空间,所以会重新分配空间,那么对原始空间的操作就会出错
cout << (int*)s1.c_str() << endl;
//a = '2';//error
//b = '3';//error
}
int main(void)
{
//test01();
//test02();
//test03();
//test04();
//test05();
//test06();
//test07();
//test08();
//test09();
test10();
return 0;
}
vector容器
vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。Array是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。Vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的array了。Vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦vector旧空间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是”配置新空间-数据移动-释放旧空间”的大工程,时间成本很高,应该加入某种未雨绸缪的考虑,稍后我们便可以看到vector的空间配置策略。
vector迭代器
Vector维护一个线性空间,所以不论元素的型别如何,普通指针都可以作为vector的迭代器,因为vector迭代器所需要的操作行为,如operaroe*, operator->, operator++, operator--, operator+, operator-, operator+=, operator-=, 普通指针天生具备。Vector支持随机存取,而普通指针正有着这样的能力。所以vector提供的是随机访问迭代器(Random Access Iterators).根据上述描述,如果我们写如下的代码:
Vector<int>::iterator it1;
Vector<Teacher>::iterator it2;
it1的型别其实就是Int*,it2的型别其实就是Teacher*.
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
using namespace std;
//1. 初始化
/*
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。
//例子 使用第二个构造函数 我们可以...
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
*/
//打印出vector容器对象v中的元素
void PrintVector(vector<int>& v){
for (vector<int>::iterator it = v.begin(); it != v.end(); it ++){
cout << *it << " ";
}
cout << endl;
}
void test01(){
//调用无参构造函数
vector<int> v1;
int arr[] = { 2, 3, 4, 1, 9 };
//调用有参构造函数
vector<int> v2(arr, arr + sizeof(arr) / sizeof(int));
vector<int> v3(v2.begin(),v2.end());
vector<int> v4(10,0);
//调用拷贝构造函数
vector<int> v5(v4);
PrintVector(v2);
PrintVector(v3);
PrintVector(v4);
PrintVector(v5);
}
//2. 赋值操作
/*
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。
*/
void test02(){
int arr[] = { 2, 3, 4, 1, 9 };
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
vector<int> v2;
vector<int> v3;
vector<int> v4;
v2.assign(v1.begin(),v1.end());
v3 = v2;
v4.assign(10, 6);
PrintVector(v1);
PrintVector(v2);
PrintVector(v3);
PrintVector(v4);
//swap交换
int arr1[] = { 1, 2, 3,4, 5 };
vector<int> v5(arr1, arr1 + sizeof(arr1) / sizeof(int));
cout << "交换前:" << endl;
PrintVector(v1);
PrintVector(v5);
v1.swap(v5);
cout << "交换后:" << endl;
PrintVector(v1);
PrintVector(v5);
}
//3 .大小操作
/*
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。
*/
void test03(){
int arr[] = { 2, 3, 4, 1, 9 };
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));//调用有参构造函数初始化vector容器
if (v1.empty()){
cout << "空!" << endl;
}
else{
cout << "不空!" << endl;
}
cout << "size:" << v1.size() << endl;
v1.resize(10);
PrintVector(v1);
cout << "size:" << v1.size() << endl;
v1.resize(4);
PrintVector(v1);
cout << "size:" << v1.size() << endl;
v1.resize(10,1);
PrintVector(v1);
cout << "size:" << v1.size() << endl;
}
//4. 巧用swap收缩内存
void test04(){
vector<int> v;
//利用for循环向容器中添加元素
for (int i = 0; i < 100000;i++){
v.push_back(i);
}
cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;
//通过resize改变容器大小
v.resize(3);
cout << "size:" << v.size() << endl;//只有元素个数改变了
cout << "capacity:" << v.capacity() << endl;//容量并没有改变
//收缩内存
//vector<int>(v).swap(v);
vector<int>(v).swap(v); //匿名对象
cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;
}
//5.reserve
void test05(){
vector<int> v;
//预先开辟空间
v.reserve(100000);
int num = 0;
int* p = NULL;
for (int i = 0; i < 100000;i++){
v.push_back(i);
if (p != &v[0]){
p = &v[0];
num++;
}
}
cout << "size:" << v.size() << endl;
cout << "capacity:" << v.capacity() << endl;
cout << "num:" << num << endl;
}
//6. 插入和删除
/*
insert(const_iterator pos, int count, ele);//迭代器指向位置pos插入count个元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素
*/
void test06(){
int arr[] = { 2, 3, 4, 1, 9 };
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
v1.push_back(100);//尾插法
v1.insert(v1.begin(),3,200); //头插法,若插入的ele个数不写,默认为1个
PrintVector(v1);
v1.pop_back();//尾删法
cout << "尾删法之后:" << endl;
PrintVector(v1);
v1.erase(v1.begin());//头删法
cout << "头删法之后:" << endl;
PrintVector(v1);
//v1.clear();//删除全部元素
v1.erase(v1.begin(), v1.end()); //相当于全部删除 v1.clear();
cout << "size:" << v1.size() << endl;
cout << "capacity:" << v1.capacity() << endl;
}
//7.注:vector容器支持随机访问和逆序遍历
void test07(){
//什么随机迭代器? vector容器支持随机访问
int arr[] = { 2, 3, 4, 1, 9 };
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
vector<int>::iterator it = v1.begin();
cout << *(it + 2) << endl;
cout << v1[2] << endl;
//逆序遍历
PrintVector(v1);
for (vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend();it ++){
cout << *it << " ";
}
cout << endl;
}
int main(void)
{
//test01();
//test02();
//test03();
//test04();
//test05();
//test06();
test07();
return EXIT_SUCCESS;
}
注意:
所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。这是程序员容易犯的一个错误,务必小心。 |
deque容器
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。Deque容器和vector容器最大的差异,一在于deque允许使用常数项时间对头端进行元素的插入和删除操作。二在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样,”旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的。也因此,deque没有必须要提供所谓的空间保留(reserve)功能.
虽然deque容器也提供了Random Access Iterator,但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque.
deque容器实现原理
Deque容器是连续的空间,至少逻辑上看来如此,连续现行空间总是令我们联想到array和vector,array无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间 三步骤,如果不是vector每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。
Deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。
Deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<deque>
#include<algorithm>
using namespace std;
//每个容器都提供自己的迭代器
void PrintDeque(const deque<int> d){
//const_iterator 只读迭代器,告诉编译器,我不会通过迭代器去修改容器中元素的值
//reverse_iterator 逆序迭代器
//iterator 最普通的正向迭代器
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it ++){
cout << *it << " ";
//*it = 100;//error,const修饰的只读迭代器不能用来修改容器中的元素的值
}
cout << endl;
}
//deque:double end queue
//1.deque构造函数
/*
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
*/
void test01(){
deque<int> d1; //无参构造函数
int arr[] = { 6, 2, 8, 1, 0 };
deque<int> d2(arr,arr + sizeof(arr)/sizeof(int));
deque<int> d3(d2.begin(), d2.end());
deque<int> d4(10, 0);
deque<int> d5(d2);
PrintDeque(d2);
PrintDeque(d3);
PrintDeque(d4);
PrintDeque(d5);
}
//2. 赋值操作
/*
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符
swap(deq);// 将deq与本身的元素互换
*/
void test02(){
//调用无参构造函数
deque<int> d1,d2,d3,d4;
//利用for循环给deque容器d1添加元素
for (int i = 0; i < 6; i++) {
d1.push_back(i + 3);
}
PrintDeque(d1);
//assign赋值
d2.assign(d1.begin(), d1.end());
PrintDeque(d2);
d3.assign(10, 10);
d4.assign(10, 20);
//swap交换两个deque容器的元素
cout << "交换之前:" << endl;
PrintDeque(d3);
PrintDeque(d4);
cout << "---------" << endl;
d3.swap(d4);
cout << "交换之后:" << endl;
PrintDeque(d3);
PrintDeque(d4);
cout << "---------" << endl;
//=号赋值
d3 = d4;
PrintDeque(d3);
PrintDeque(d4);
}
//3. 大小操作
/*
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
*/
void test03(){
deque<int> d1;
d1.assign(10, 100);
cout << "size:" << d1.size() << endl;
if (d1.empty()){
cout << "空!" << endl;
}
else{
cout << "不空!" << endl;
}
d1.resize(15, 1);
PrintDeque(d1);
}
//4. 两端插入和删除
/*
push_back(elem);//在容器尾部添加一个数据
push_front(elem);//在容器头部插入一个数据
pop_back();//删除容器最后一个数据
pop_front();//删除容器第一个数据
*/
void test04(){
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
PrintDeque(d);
d.pop_back();
d.pop_front();
PrintDeque(d);
}
//5. 存取操作
/*
at(idx);//返回索引idx所指的数据,如果idx越界,抛出out_of_range。
operator[];//返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据
*/
void test05(){
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
for (int i = 0; i < d.size();i ++){
cout << d[i] << " ";
}
cout << endl;
for (int i = 0; i < d.size(); i++){
cout << d.at(i) << " ";
}
cout << endl;
cout << "front:" << d.front() << endl;
d.front() = 100;//front()返回的是第一个元素的引用,所以既可做右值,也可以当左值
cout << "front:" << d.front() << endl;
cout << "back:" << d.back() << endl;
d.back() = 200;//back()返回的是第一个元素的引用,所以既可做右值,也可以当左值
cout << "back:" << d.back() << endl;
}
//6. 插入和删除
/*
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位置的数据,返回下一个数据的位置。
*/
void test06(){
deque<int> d1;
d1.push_back(10);
d1.push_back(20);
d1.push_front(100);
d1.push_front(200);
d1.insert(d1.begin(),500);
d1.insert(d1.end(), 500);
PrintDeque(d1);
deque<int> d2;
d2.push_back(0);
d2.push_back(1);
d2.push_back(2);
d1.insert(d1.begin() + 1, d2.begin(), d2.end());
//500 0 1 2 200 100 10 20 500
PrintDeque(d1);
//删除
d1.erase(d1.begin()); //头删
PrintDeque(d1);
d1.clear();
//d1.erase(d1.begin(), d1.end()); //等价于 d1.clear();
cout << "size:" << d1.size() << endl;
}
//比较两个变量值的大小
bool MyCompare(int v1,int v2){
if (v1 < v2)
return true;
else
return false;
}
void test07(){
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_front(100);
d.push_front(200);
PrintDeque(d);
//即使不提供自定义排序函数,sort也会默认提供一个从小到大的排序函数
sort(d.begin(), d.end(), MyCompare);
PrintDeque(d);
}
int main(void)
{
//test01();
//test02();
//test03();
//test04();
//test05();
//test06();
test07();
return EXIT_SUCCESS;
}