C++11新特性
移动语义
概念理解
可以取地址的是左值,不能取地址的就是右值。右值短暂的存在于栈上。
右值包括:临时对象、匿名对象、字面值常量
const左值引用可以绑定到左值与右值上面。正因如此,也就无法区分传进来的参数是左值还是右值。
右值引用只能绑定到右值,不能绑定到左值。所以可以区分出传进来的参数到底是左值还是右值,进而可以区分。
字面值常量:10 是个右值
字符串常量:"hello" 是个左值
String s3 = "hello"; String s4 = String("world");
移动语义的背景:
String("world")
是一个临时对象,临时对象开辟出一块空间,s4深拷贝后,临时对象开辟的空间立马销毁。但是如果临时对象开辟的空间直接转移给s4,s4也不用深拷贝了,这不就省下开销了吗,对吧。
那如何将临时对象开辟的空间转移给s4呢?那肯定先要识别临时对象是个右值还是右值,如果是右值就进行操作。
结论:非const的左值引用是不能绑定右值的;而const的左值引用既可以绑定左值,也可以绑定右值,但是无法区分左值、右值
C++98困境:不能完成通过代码区分左值与右值
C++11提出概念:右值引用
int &&rref = 10; int &&rref = a ;//不可以,报错
右值引用可以绑定到右值上,但是不能绑定左值。
移动构造函数
将拷贝构造函数以及赋值运算符函数称为具有复制控制语义的函数。
将移动构造函数以及移动赋值运算符函数称为具有移动语义的函数。
1、移动语义的函数优先于具有拷贝语义的函数的执行
2、具有移动语义的函数如果不写的话,编译器是不会自动生成,必须手写
String(String &&rhs) : _pstr(rhs._pstr)//将左值指向右值的空间 { cout << "String(String &&)" << endl; rhs._pstr = nullptr; //右值空间被转移后,使命完成,为避免共同指向销毁,右值指向为空 }
移动赋值运算符函数
String &operator=(String &&rhs) { cout << "String &operator=(String &&)" << endl; if(this != &rhs)//1、自移动 { delete [] _pstr;//2、释放左操作数 _pstr = nullptr; _pstr = rhs._pstr;//3、浅拷贝,指向临时开辟的空间 rhs._pstr = nullptr; //改变临时的指向,防止临时销毁后空间也跟着销毁 } return *this;//4、返回*this }
std::move 函数
将左值转换为右值,在内部其实上是做了一个强制转换,static_cast<T &&>(lvaule)
//s1是左值,现在将s1转换成右值,右值s1 = s1 s1 = std::move(s1); cout << "s1 = " << s1 << endl;// 执行为空 //在移动赋值运算函数中,s1指向nullptr,那右边的s1也就为空了,再去给s1赋值,那其实大家都是空指针了嘛,所以打印为空
几个问题注意一下:
1、String("world") = String("world")
这里不存在自复制,因为这里虽然字符串一样,但是确实两个不一样的临时对象,且这样做无意义
2、自移动或者自复制这一步操作还是要有的,就是为了避免出现 s1 = std::move(s1)
这样的操作。
右值引用
右值引用的作用是识别出右值,但是其本身是一个左值。但是右值引用可不可以是一个右值呢?
左值:可以进行取地址的就是左值。
右值:不能进行取地址的就是右值。右值包括:临时变量、临时对象、匿名变量、匿名对象、字面值常量(10)
左值引用:int &ref;左值引用可以绑定到左值,不能绑定到右值。可以识别左值。
const左值引用:既可以绑定到左值,也可以绑定到右值,所以,const左值引用不能区分出左值与右值。
右值引用:int &&rref;右值引用可以绑定到右值,但是不能绑定到左值。可以识别右值。
右值引用的特点是:可以识别右值,可以绑定到右值。但是右值引用本身既可以是左值,也可以是右值。比如:当我们将右值引用作为函数参数的时候,在移动构造函数与移动赋值函数中就是左值,但是如果将函数的返回类型设置为右值引用的时候,那么右值引用本身就是一个右值了。
编译器优化技术—RVO
RVO(Return Value Optimization)
当函数需要返回一个对象的时候,如果自己创建一个临时对象用户返回,那么这个临时对象会消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy Constructor)以及一个析构函数(Destructor)的调用的代价。
而如果稍微做一点优化,就可以将成本降低到一个构造函数的代价
http://blog.csdn.net/zwvista/article/details/6845020
http://www.cnblogs.com/xkfz007/articles/2506022.html
资源管理
C的难点背景:
void UseFile(char const* fn){ FILE* f = fopen(fn, “r”); // 获取资源 // 使用资源 if (!g()) { fclose(f); return; }//关闭文件 // ... if (!h()) { fclose(f); return; }//关闭文件 // ... fclose(f); // 释放资源 }
用于释放资源的代码需要在不同的位置重复书写多次。如果再加入异常处理,fclose(f)情况会变得更加复杂。
为了应对这种问题,c++11提出了 RAII技术(Resource Acquisition Is Initialization)。
RAII 是一种由 C++创造者 Bjarne Stroustrup 提出的, 利用栈对象生命周期管理程序资源(包括内存、文件句柄、锁等)的技术。
RAII 思想
利用栈对象的生命周期管理资源(内存资源、文件描述符、文件、锁)。
四个基本特征
1、在构造函数中初始化资源,或者称为托管资源
2、在析构函数中释放资源
3、提供若干访问资源的方法(比如:读写文件)
4、一般不允许复制或者赋值。(对象语义)
对象语义:不允许复制或者赋值。
- 1、将拷贝构造函数或者赋值运算符函数=delete
- 2、将拷贝构造函数或者赋值运算符函数设置为私有的
- 3、可以使用继承的思想,将基类拷贝构造函数与赋值运算符函数删除(设为私有),让派生类继承基类
值语义:可以进行复制或者赋值。
使用 RAII 时,一般在资源获得的同时构造对象, 在对象生存期间,资源一直保持有效;对象析构时,资源被释放。
关键:要保证资源的释放顺序与获取顺序严格相反。
#include <iostream> #include <string> using std::cout; using std::endl; using std::string; class SafeFile{ public: //在构造函数中初始化资源(托管资源) SafeFile(FILE *fp) : _fp(fp){ cout << "SafeFile(FILE *)" << endl; } //提供若干访问资源的方法(对文件进行写操作) //用C的方法向文件中写数据 void write(const string &msg){ fwrite(msg.c_str(), 1, msg.size(), _fp); } //在析构函数中释放资源 ~SafeFile(){ cout << "~SafeFile()" << endl; if(_fp){ fclose(_fp); cout << "fclose(_fp)" << endl; } } private: FILE *_fp; }; void test(){ string msg = "hello,world\n"; SafeFile sf(fopen("wd.txt", "a+"));//sf是栈对象 sf.write(msg); //栈对象随着声明周期的结束,会自动的调用析构函数,完成对文件的close操作 } int main(int argc, char **argv){ test(); return 0; }
RAII 实现
#include <iostream> using std::cout; using std::endl; class Point { public: Point(int ix = 0, int iy = 0) : _ix(ix) , _iy(iy) { cout << "Point(int = 0, int = 0)" << endl; } void print() const { cout << "(" << _ix << ", " << _iy << ")" << endl; } ~Point() { cout << "~Point()" << endl; } private: int _ix; int _iy; }; template <typename T> //T加相当于任何数据类型有 class RAII { public: //1、在构造函数中初始化资源 RAII(T *data) : _data(data) { cout <<"RAII(T *)" << endl; } //2、在析构函数中释放资源 ~RAII() { cout << "~RAII()" << endl; if(_data) { delete _data; _data = nullptr; } } //3、提供若干访问资源的方法 T *operator->() { return _data; } T &operator*() { return *_data; } T *get() const { return _data; } //重置 void reset(T *data) { if(_data) { delete _data; _data = nullptr; } _data = data; } //4、不允许复制或者赋值 RAII(const RAII &rhs) = delete; RAII &operator=(const RAII &rhs) = delete; private: T *_data; /* int *_data; */ /* double *_data; */ /* string *_data; */ /* Point *_data; */ }; void test() { //利用栈对象的生命周期结束的时候,会执行析构函数,完成资源的清理 RAII<int> raii(new int(10));//栈对象raii,在尖括号中声明T是int类型,托管堆申请的空间 cout << endl; //pt本身不是一个指针,但是pt的使用就与一个指针完全一样 //可以托管资源(new int(10),new Point(1, 2)),但是可以 //不用直接执行delete就可以将资源进行回收,资源的回收靠的 //就是栈对象pt的销毁执行析构函数 RAII<Point> pt(new Point(1, 2));//栈对象pt,在尖括号中声明T是Point类型,托管堆申请的空间 pt->print(); (*pt).print(); /* pt.operator->()->print(); */ } int main(int argc, char **argv) { test(); return 0; }
智能指针
C++11提供了以下几种智能指针,位于头文件
智能指针(Smart Pointer):
- 是存储指向动态分配(堆)对象指针的类
- 在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象
- RAII类模拟智能指针,可以利用一个对象在离开一个域中会调用析构函数的特性,在构造函数中完成初始化,在析构函数中完成清理工作,将需要操作和保护的指针作为成员变量放入RAII中。
auto_ptr的使用
#include <iostream> #include <memory> using std::cout; using std::endl; using std::auto_ptr; void test(){ int *pInt = new int(10); auto_ptr<int> ap(pInt); cout << "*ap = " << *ap << endl; cout << "*pInt = " << *pInt << endl; cout << endl; auto_ptr<int> ap2(ap); cout << "*ap2 = " << *ap2 << endl; cout << "*ap = " << *ap << endl; //报错,Segmentation fault (core dumped) } int main(){ test(); return 0; }
看一眼关于auto_ptr的源码
auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} template <class _Tp> class auto_ptr { private: _Tp* _M_ptr; public: //auto_ptr ap2(ap); //auto_ptr &__a = ap auto_ptr(auto_ptr& __a) : _M_ptr(__a.release()) { } _Tp* release(){ //将ap托管的空间转移给了ap2 _Tp* __tmp = _M_ptr; _M_ptr = 0; //ap自身指向为空 return __tmp; } };
auto_ptr的缺点:一般都不使用这种指针
表面上执行的是拷贝操作,但是底层己经将右操作数 ap 所托管的堆空间的控制权己经交给了左操作数ap2,并且ap底层的数据成员己经被置位空了,该拷贝操作存在隐患,所以该拷贝操作是存在缺陷的。在拷贝构造或赋值操作时,会发生所有权的转移。
unique_ptr 的使用
明确表明是独享所有权的智能指针,无法进行复制与赋值。
保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象。
具有移动语义,可以作为容器的元素(容器中可以存unique_ptr 的类型)
void test() { unique_ptr<int> up(new int(10)); cout << "*up = " << *up << endl; cout << "up.get() = " << up.get() << endl;//获取托管的指针的值 cout << endl ; /* unique_ptr<int> up2(up);//error,独享资源的所有权,不能进行拷贝复制 */ cout << endl ; unique_ptr<int> up3(new int(10)); /* up3 = up;//error,不能进行赋值操作 */ //独享所有权的智能指针,对托管的空间独立拥有。在语法层面就将auto_ptr的缺陷摒弃掉了,具有对象语义 /*------------------------------------------------------------------------------------------*/ cout << endl ; unique_ptr<int> up4(std::move(up));//通过移动语义move转移up的所有权,初始化智能指针up4 cout << "*up4 = " << *up4 << endl; cout << "up4.get() = " << up4.get() << endl; cout << endl ; unique_ptr<Point> up5(new Point(3, 4)); vector<unique_ptr<Point>> vect; vect.push_back(std::move(up5));//不能传左值,但是可以传右值 //或者直接构建右值,显式调用unique_ptr的构造函数 vect.push_back(unique_ptr<Point>(new Point(1, 2))); }
shared_ptr 的使用
是一个共享所有权的智能指针,允许对象之间进行复制或者赋值,展示出来的就是值语义。使用引用计数的观点。当对象之间进行复制或者赋值的时候,引用计数会加+1,当最后一个对象销毁的时候,引用计数减为0的时候,会负责回收托管的空间。
void test() { shared_ptr<int> sp(new int(10)); cout << "*sp = " << *sp << endl; cout << "sp.get() = " << sp.get() << endl; cout << "sp.use_count() = " << sp.use_count() << endl; //引用计数 cout << endl; shared_ptr<int> sp2 = sp;//拷贝复制ok cout << "*sp = " << *sp << endl; cout << "*sp2 = " << *sp2 << endl; cout << "sp.get() = " << sp.get() << endl; cout << "sp2.get() = " << sp2.get() << endl; cout << "sp.use_count() = " << sp.use_count() << endl; cout << "sp2.use_count() = " << sp2.use_count() << endl; cout << endl; shared_ptr<int> sp3(new int(20)); sp3 = sp;//赋值ok cout << "*sp = " << *sp << endl; cout << "*sp2 = " << *sp2 << endl; cout << "*sp3 = " << *sp3 << endl; cout << "sp.get() = " << sp.get() << endl; cout << "sp2.get() = " << sp2.get() << endl; cout << "sp3.get() = " << sp3.get() << endl; cout << "sp.use_count() = " << sp.use_count() << endl; cout << "sp2.use_count() = " << sp2.use_count() << endl; cout << "sp3.use_count() = " << sp3.use_count() << endl; cout << endl; vector<shared_ptr<Point>> vec; //作为容器的元素 shared_ptr<Point> sp4(new Point(10, 20)); vec.push_back(sp4); //可以传左值 vec.push_back(std::move(sp4)); //也可以传右值 vec.push_back(shared_ptr<Point>(new Point(1, 3)));//类名声明一个临时对象指针 }

问题:存在循环引用?
该智能指针在使用的时候,会使得引用计数增加,从而会出现循环引用的问题?两个shared_ptr智能指针互指,导致引用计数增加,不能靠对象的销毁使得引用计数变为0,从而导致内存泄漏。。。
#include <iostream> #include <memory> using std::cout; using std::endl; using std::shared_ptr; class Child;//类的前向声明 class Parent{ public: Parent(){ cout << "Parent()" << endl; } ~Parent(){ cout << "~Parent()" << endl; } shared_ptr<Child> parent; }; class Child{ public: Child(){ cout << "Child()" << endl; } ~Child(){ cout << "~Child()" << endl; } shared_ptr<Parent> child; }; void test() { shared_ptr<Parent> pParent(new Parent()); shared_ptr<Child> pChild(new Child()); cout << "pParent.use_count() = " << pParent.use_count() << endl; cout << "pchild.use_count() = " << pChild.use_count() << endl; cout << endl; pParent->parent = pChild; pChild->child = pParent; cout << "pParent.use_count() = " << pParent.use_count() << endl; cout << "pchild.use_count() = " << pChild.use_count() << endl; //此代码存在内存泄漏 } int main(int argc, char **argv){ test(); return 0; }

模板
1、简化程序,少写代码,维持结构的清晰,大大提高程序的效率。
2、解决强类型语言的严格性和灵活性之间的冲突。。
2.1、带参数的宏定义(原样替换)
2.2、函数重载(函数名字相同,参数不同)
2.3、模板(将数据类型作为参数)
模板定义
template <typename T> //模板参数列表 template <class T> //模板参数列表
注意:class与typename在此没有任何区别,完全一致。class出现的时间比较早,通用性更好一些,typename是后来才添加了,对于早期的编译器可能识别不了typename关键字。
模板类型
函数模板与类模板。通过参数实例化构造出具体的函数或者类,称为模板函数或者模板类。
函数模板和类模板的区别在于:
1,函数模板有自动类型推导,但是类模板没有自动类型推导,必须指定模板参数类型;
2,函数模板不可以有默认参数,但是类模板允许有默认参数,当类模板中有默认参数的时候,就可以把显示指定类型的参数去掉。
函数模板
template <模板参数表> //模板参数列表,此处模板参数列表不能为空
返回类型 函数名(参数列表)
{ //函数体 }
template <typename T>//模板参数列表 T add(T x, T y) { cout << "T add(T, T)" << endl; return x + y; }
模板参数推导 (在实参传递的时候进行推导)
函数模板------------------------------------------------------------------------模板函数
实例化
https://gitee.com/demoCanon/cpp-gpt/blob/master/Project/week4/functionTemplate.cc
实例化:隐式实例化与显示实例化
cout << "add(ia, ib) = " << add(ia, ib) << endl;//隐式实例化,没有明确说明类型,T只能靠编译器推导 cout << "add(da, db) = " << add<double>(da, db) << endl;//显示实例化,显式写出参数类型T为double
函数模板、普通函数间的关系
1、函数模板与普通函数是可以进行重载的
2、普通函数优先于函数模板执行
3、函数模板与函数模板之间也是可以进行重载的
模板头文件与实现文件
模板不能写成头文件与实现文件形式(类型inline函数),或者说不能将声明与实现分开,这样会导致编译报错。
分开可以编译,但是在链接的时候是有问题的。
建议在头文件给出模板的声明后,立即include引入实现文件

模板的特化:偏特化与全特化
template <> //全部特化出来就是全特化 //因为模板的所有参数都以特殊形式写出来了,所以模板列表写为空 const char *add(const char *pstr1, const char *pstr2) { size_t len = strlen(pstr1) + strlen(pstr2) + 1; char *ptmp = new char [len](); strcpy(ptmp, pstr1); strcat(ptmp, pstr2); return ptmp; }
注意:函数模板是支持全特化的,但是不同的编译器,不同的语法版本,可能不支持偏特化,不支持部分特化。
函数模板的参数类型
1、类型参数,class T 这种就是类型参数
2、非类型参数 常量表达式,整型:bool/char/short/int/long/size_t, 注意:float/double这些就不是整型
https://gitee.com/demoCanon/cpp-gpt/blob/master/Project/week4/templatePara.cc#
成员函数模板
#include <iostream> using std::cout; using std::endl; class Point{ public: Point(double dx = 0.0, double dy = 0.0) : _dx(dx) , _dy(dy) { cout << "Point(double,double)" << endl; } //普通函数与成员函数都可以设置为模板形式 template <typename T = double> T func(){ return (T)_dx; } ~Point(){ cout << "~Point()" << endl; } private: double _dx; double _dy; }; void test(){ Point pt(1.1, 2.2); cout << "pt.func() = " << pt.func<int>() << endl;//使用传入参数int cout << "pt.func() = " << pt.func() << endl;//使用类型参数默认值double } int main() { test(); return 0; }
可变模板参数
可以表示0个到任意个参数,参数的个数不确定,参数的类型也不确定。可以类比printf进行理解。
https://gitee.com/demoCanon/cpp-gpt/blob/master/Project/week4/variTemplate.cc



类模板
template <typename T> class Stack { private: T *_data; //int *_data; //double *_data; //Point *_data; };
注意:成员模版不能声明为虚函数。写成模板相当于写了好多个函数,如果再将这些函数设计为虚函数,而虚函数会入虚表,那么都不知道模板可以推导出多少个函数,那么虚表就不知道分配多少空间。
模板是可以进行嵌套的。
#include <iostream> #include <string> using std::cout; using std::endl; using std::string; //泛型编程 template <typename T = int, size_t sz = 10> class Stack { public: Stack() : _top(-1) , _data(new T[sz]()) { } ~Stack(); bool empty() const; bool full() const; void push(const T &value); void pop(); T top() const; private: int _top; T *_data; }; template <typename T, size_t sz> //头必须写出来,但是默认值前面已经给出,无需初始化 Stack<T, sz>::~Stack() //用类声明的时候也需要将模板参数显式亮出来 { if(_data) { delete [] _data; _data = nullptr; } } template <typename T, size_t sz> bool Stack<T, sz>::empty() const { return -1 == _top; } template <typename T, size_t sz> bool Stack<T, sz>::full() const { return _top == sz - 1; } template <typename T, size_t sz> void Stack<T, sz>::push(const T &value) { if(!full()) { _data[++_top] = value; } else { cout << "The stack is full, cannot push any data" << endl; return; } } template <typename T, size_t sz> void Stack<T, sz>::pop() { if(!empty()) { --_top; } else { cout << "The stack is empty, cannot pop any data" << endl; return; } } template <typename T, size_t sz> T Stack<T, sz>::top() const { return _data[_top]; } void test() { Stack<int> st; cout << "栈是不是空的 ?" << st.empty() << endl; st.push(1); cout << "栈是不是满的 ?" << st.full() << endl; for(size_t idx = 2; idx != 15; ++idx) { st.push(idx); } cout << "栈是不是满的 ?" << st.full() << endl; while(!st.empty()) { cout << st.top() << " "; st.pop(); } cout << endl; cout << "栈是不是空的 ?" << st.empty() << endl; } void test2() { Stack<string, 20> st; cout << "栈是不是空的 ?" << st.empty() << endl; st.push(string("aa")); cout << "栈是不是满的 ?" << st.full() << endl; for(size_t idx = 1; idx != 15; ++idx) { st.push(string(2, 'a' + idx)); } cout << "栈是不是满的 ?" << st.full() << endl; while(!st.empty()) { cout << st.top() << " "; st.pop(); } cout << endl; cout << "栈是不是空的 ?" << st.empty() << endl; } int main(int argc, char **argv) { test2(); return 0; }
容器
Standard Template Library标准模板库,有6大组件
1、容器(数据结构):用来存数据的。
- 序列式容器:vector 、 deque 、list
- 关联式容器:set 、 map
- 无序关联式容器:unordered_set 、 unordered_map
2、迭代器:看是一种指针,称为泛型指针。
3、算法:可以对容器中的数据进行相应的操作
4、适配器
- 容器适配器 优先级队列
- 算法适配器
- 迭代器的适配器(源码)
5、函数对象(仿函数):做定制化操作
6、空间配置器 ( Allocator ) :空间的申请与释放。(研究基本使用+原理+源码)
程序 = 容器(数据结构)+ 算法
dynamic动态
deque双端队列
序列式容器

初始化
三种序列式容器vector、deque、list完全支持五种初始化的方式
void test(){ //-----------------------------初始化------------------------------------- //1、创建空对象 vector<int> number; //2、创建count个value vector<int> number2(10,3); //3是10个元素的初始化值 //3、迭代器范围 int arr[10] = {1,3,5,7,9,10,8,6,4,2}; vector<int> number3(arr,arr + 10);//左闭右开的区间[,)这里的10指的是数组长度 //4、拷贝构造函数与移动构造函数 vector<int> number4_1 (number2); //拷贝构造函数 vector<int> number4_2 (std::move(number2));//移动构造函数 //5、大括号 vector<int> number5 = {1,3,5,9,7,2,4,6,8}; //只需要将vector替换为对应的deque 与 list
遍历
4种遍历方式
三种序列式容器,其中:list是没有下标的,所以不支持下标访问运算符。其他的三种遍历的方式,三种容器都支持。
//只需要将vector替换为对应的deque 与 list vector<int> number2(10,3); void test(){ //----------------------------遍历操作------------------------------------ #if 0 //1、使用下标进行遍历,只有list不支持下标访问 for(size_t idx = 0; idx != number2.size();++idx){ cout << number2[idx] << " "; } cout << endl; #endif //2、迭代器:泛型指针 vector<int>::iterator it; //没有初始化迭代器it for(it = number.begin();it != number.end(); ++it){ cout << *it << " "; } cout << endl; //3、迭代器(对迭代器进行初始化) vector<int>::iterator it2 = number.begin(); for(;it2 != number.end(); ++it2){ cout << *it2 << " "; } cout << endl; //4、for与auto,C++11 for(auto &elem : number){ //&是引用的意思,减少拷贝操作 cout << elem << " "; } cout << endl; }
注意:list不支持下标

//display 打印函数 template <typename Container> void display(const Container &con) { for(auto &elem : con) { cout << elem << " "; } cout << endl; }
在尾部进行插入与删除
三种容器在尾部进行插入与删除的使用是完全一样的。
void test() { cout << endl << "----------------初始化vector容器-----------------" << endl; vector<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; display(number); //打印容器number的值 cout << "number.size() = " << number.size() << endl //打印容器存储量 << "number.capacity() = " << number.capacity() << endl ; //打印容器空间量 cout << endl << "----------------在vector的尾部进行插入与删除------------------" << endl; cout << "在vector的尾部push_back插入2个元素,打印如下:" << endl; number.push_back(33); number.push_back(77); display(number); cout << "number.size() = " << number.size() << endl << "number.capacity() = " << number.capacity() << endl << endl; cout << "在vector的尾部pop_back删除元素,打印如下:" << endl; number.pop_back(); //删除尾部元素 display(number); cout << "number.size() = " << number.size() << endl << "number.capacity() = " << number.capacity() << endl; cout << endl; } void test2(){ cout << endl << "----------------初始化deque容器-----------------" << endl; deque<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; display(number); cout << endl << "在deque的尾部push_back进行插入" << endl; number.push_back(33); number.push_back(77); display(number); cout << endl << "在deque的尾部pop_back进行删除" << endl; number.pop_back(); display(number); cout << endl; } void test3(){ cout << endl << "----------------初始化list容器-----------------" << endl; list<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; display(number); cout << endl << "在list的尾部push_back进行插入" << endl; number.push_back(33); number.push_back(77); display(number); cout << endl << "在list的尾部pop_back进行删除" << endl; number.pop_back(); display(number); cout << endl; }
在头部进行插入与删除
deque与list支持在头部进行插入与删除,但是vector没有在头部进行插入与删除的操作。
void test2(){ cout << endl << "----------------初始化deque容器-----------------" << endl; deque<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; display(number); cout << endl << "在deque的头部push_front进行插入" << endl; number.push_front(100); number.push_front(200); display(number); cout << endl << "在deque的尾部pop_front进行删除" << endl; number.pop_front(); display(number); cout << endl; } void test3(){ cout << endl << "----------------初始化list容器-----------------" << endl; list<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; display(number); cout << endl << "在list的头部push_front进行插入" << endl; number.push_front(100); number.push_front(200); display(number); cout << endl << "在list的尾部pop_front进行删除" << endl; number.pop_front(); display(number); cout << endl; }
Q:为什么vector不支持在头部进行插入与删除?
A:因为vector中的元素是连续的,如果在头部删除一个元素,那么后面的元素都会前移;如果在头部进行插入一个元素,那么后面的所有的元素都会后移,这样会导致一个元素的插入与删除,使得整个vector中的元素都要变动位置,时间复杂度O(N)
//Q:如何获取vector第一个元素的地址? &number; error,获取不到第一个元素的地址 */ &number[0]; ok,可以获取第一个元素的地址 */ &*number.begin(); ok,可以获取第一个元素的地址 */ int *pdate = number.data(); ok,可以获取第一个元素的地址 */

vector的原理图

_M_start
:指向第一个元素的位置
_M_finish
:指向最后一个元素的下一个位置
_M_end_of_storage
:指向当前分配空间的最后一个位置的下一个位置
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) > class vector { public: //类型萃取(重要) typedef _Tp value_type; typedef value_type* pointer; typedef const value_type* const_pointer; typedef value_type* iterator; typedef const value_type* const_iterator; typedef value_type& reference; typedef const value_type& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; //为了严格表明_Base::allocator_type是一种类型,所以加上typename关键字进行强调 typedef typename _Base::allocator_type allocator_type; //vector的下标访问运算符的重载 reference operator[](size_type __n) { return *(begin() + __n); } void _M_range_check(size_type __n) const { if (__n >= this->size()) __stl_throw_range_error("vector"); } //总结:通过下标获取元素,可以使用at,也可以直接使用下标,但是at比下标访问更安全,因为有范围检查 reference at(size_type __n) { _M_range_check(__n); return (*this)[__n]; } };

deque的原理图


注意:deque是由很多小片段组成的,片段内部是连续的,片段之间是不连续。所以可以这么说,逻辑上是连续的,但是物理上是不连续。可以通过中控器数组进行调控。
在任意位置进行插入
三种序列式容器在任意位置插入元素的四种方法

void test3(){ list<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; cout << endl << "在list的任意位置进行插入元素" << endl; list<int>::iterator it = number.begin(); ++it; ++it; //迭代器向后移动2个位置 cout << "*it = " << *it << endl; number.insert(it, 100); //1、找一个位置,插入一个元素 display(number); cout << "*it = " << *it << endl; cout << endl << endl; number.insert(it, 30, 200);//2、找一个位置,插入count30个相同元素 display(number); cout << "*it = " << *it << endl; cout << endl << endl; vector<int> vec = {77, 99, 33, 55}; number.insert(it, vec.begin(), vec.end()); //3、找一个位置,插入迭代器范围的元素 display(number); cout << "*it = " << *it << endl; cout << endl << endl; number.insert(it, {111, 444, 666, 888, 333}); //4、找一个位置,插入一个大括号范围的元素 display(number); cout << "*it = " << *it << endl; }
总结:对于list而言,插入的时候,迭代器是跟着结点走的,它在使用的时候,不会存在什么问题。但是对于vector而言,插入元素的大小会影响vector底层的扩容,有可能会导致迭代器失效。所以为了解决这个问题,每次操作迭代器时候,最好可以提前更新迭代器的位置,让迭代器指向新的空间的位置,这样就保证不会出错。
特意的,对于deque而言,去进行insert操作的时候,也有可能迭代器失效,所以最好还是可以每次使用迭代器的时候,像vector一样,将迭代器的位置进行更新,指向新的空间的位置。
插入一个链接
vector删除连续重复元素
vector删除连续重复元素的隐患:
void test() { vector<int> number = {1, 3, 6, 6, 6, 6, 7, 5}; display(number); //这种方式不能删除连续重复元素 for(auto it = number.begin(); it != number.end(); ++it) { if(6 == *it) { number.erase(it); } } display(number); }

原因是:每删除一个元素,vector的元素便向前移动位置,但是迭代器的位置不变,迭代器所在位置的数值发生变化,接着进入下一次的循环更新迭代器的位置,导致连续重复的元素逃过了判断,从而漏掉。
正确解决办法:
void test2() { vector<int> number = {1, 3, 6, 6, 6, 6, 7, 5}; display(number); for(auto it = number.begin(); it != number.end(); ) //不用在更新迭代器的位置 { if(6 == *it) { number.erase(it); } else //如果不是该数字,才会去更新迭代器的位置 { ++it; } } display(number); } void test3() { vector<int> number = {1, 3, 6, 6, 6, 6, 7, 5}; display(number); for(auto it = number.begin(); it != number.end(); ++it) { while(6 == *it) //循环检测要删除的元素,如果不是连续元素就跳出(不更新迭代器的位置,在原位置上检测) { number.erase(it); } } display(number); }
deque与list就不会出现,因为vector是属于动态数组的,deque与list是属于链表的。
元素的清空
cout << endl << "清空vector容器中的元素" << endl; number.clear();//只是将元素清空了,但是没有回收内存 number.shrink_to_fit();//将剩余的空间进行了回收 cout << "number.size() = " << number.size() << endl << "number.capacity() = " << number.capacity() << endl; //------------------------------------------------------------------ cout << endl << "清空deque容器中的元素" << endl; number.clear();//只是将元素清空了,但是没有回收内存 number.shrink_to_fit();//将剩余的空间进行了回收 cout << "number.size() = " << number.size() << endl; //----------------------------------------------------------------------- //对于list而言,元素删除了也会将结点删除,就不存在回收空间函数 cout << endl << "清空list容器中的元素" << endl; number.clear(); cout << "number.size() = " << number.size() << endl;
emplace_back的使用
在容器末尾就地构造元素
void test() { vector<Point> number; Point pt(1, 2); number.push_back(Point(1, 2)); //number.push_back(1, 2); error! number.push_back((1, 2)); number.push_back({1, 2}); number.push_back(pt); number.emplace_back(3, 4); //免去大括号与Point声明 }
size()获取容器中元素个数,三者都有该函数
capacity()获取容器申请的空间大小,只有vector有这个函数。
list的特殊用法
排序函数sort

存在两种形式的sort函数
void test() { list<int> number = {1, 3, 5, 5, 9, 9, 8, 6, 2}; display(number); cout << endl << "测试list的sort函数" << endl; number.sort();//1、默认情况,会按照从小到大的顺序进行排序 //std::less<T> 如果lhs < rhs 就返回true,否则返回false std::less<int> les; //从小到大进行排序 number.sort(std::less<int>()); //传递右值 number.sort(les); //传递左值 //std::greater<T> 如果lhs > rhs 就返回true,否则返回false number.sort(std::greater<int>()); //从大到小进行排序 display(number); }
Compare是个类,比较的是对象。
可以看一下源码:


那_StrictWeakOrdering
是个什么东西呢? 翻译为:严格弱排序。
严格是说在判断的时候会用"<",而不是"<=",弱排序是因为,一旦"<"成立便认为存在"<"关系,返回ture,而忽略了"="关系和">"区别,把它们归结为false。
https://www.boost.org/sgi/stl/StrictWeakOrdering.html
严格弱排序是一个二元谓词,它对两个对象进行比较,如果第一个对象在第二个对象之前,则返回真。这个谓词必须满足严格弱排序的标准数学定义。准确的要求在下面说明,但它们的大致意思是,严格弱排序的行为方式必须是 "小于 "的行为方式:如果a小于b,则b不小于a,如果a小于b且b小于c,则a小于c,以此类推。

所以我们了解到无参sort的排序是按照严格弱排序的,而有参sort给出了一个模板,_StrictWeakOrdering告诉你,sort也应该是弱排序,但毕竟是模板,可以传入的不一定非要是严格弱排序,也可以是其他数学定义的排序方式。所以也可以传入严格强排序。
template <class _Tp> //严格弱排序 struct less : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x < __y; } // < 运算符重载 };
template <class _Tp> //严格强排序 struct greater : public binary_function<_Tp,_Tp,bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x > __y; } // > 运算符重载 };
实现list的sort
//自定义函数对象(仿函数) template <typename T> struct CompareList //模仿less和greater的定义 { bool operator()(const T &lhs, const T &rhs) const { cout << "bool operator()(const T &, const T &) const" << endl; return lhs < rhs; } }; void test() { list<int> number = {1, 3, 5, 5, 9, 9, 8, 6, 2}; display(number); cout << endl << "测试list的sort函数" << endl; number.sort(CompareList<int>()); //自己实现比较原则 display(number); }
函数对象:
函数对象是重载函数调用操作符的类的对象。即函数对象是行为类似函数的对象,又称仿函数,是一个能被当做普通函数来调用的对象。
reverse 反转序列 与 unique去重元素
void test() { list<int> number = {1, 3, 5, 5, 9, 9, 8, 6, 2, 5, 9, 5}; display(number); cout << endl << "测试list的reverse函数" << endl; //reverse实现的是反转容器序列 number.reverse(); display(number); cout << endl << "测试list的unique函数" << endl; number.sort(); number.unique();//需要先将元素进行排序,然后才能去除重复元素 display(number); }
merge 合并vector
两个链表要进行merge的时候,需要先各自进行相同的排序,然后再进行merge,否则不排序后的合并没有意义。
void test() { cout << endl << "---------测试list的merge函数-----------" << endl; list<int> number2 = {11, 99, 88, 33, 7}; number2.sort(); cout << "number2链表: "; display(number2); cout << "number 链表: "; display(number); //两个链表要进行merge的时候,需要先分别将两个链表进行从小 //到大排序,然后再进行merge,否则就达不到合并后排序的效果 cout << "合并操作: number.merge(number2); " << endl; number.merge(number2); cout << "number2链表: "; display(number2); cout << "number链表: "; display(number); cout << endl << "从大到小合并:"<< endl; number.sort(std::greater<int>()); number2.sort(std::greater<int>()); number.merge(number2); cout << "number2链表: "; display(number2); cout << "number链表: "; display(number); }

splice操作
从一个 list 转移元素给另一个。不复制或移动元素,仅重指向链表结点的内部指针。
1、移动一个容器内的所有元素
void test(){ cout << endl << "测试splice函数" << endl; list<int> number = {1,2,3,5,6,8,9 }; list<int> number3 = {111, 555, 777, 333, 666}; display(number3); auto it = number.begin(); ++it; ++it; cout << "*it = " << *it << endl; number.splice(it, number3); display(number); display(number3); }

结果分析:splice也如同merge一样,将number3的元素移动到number后,number3自身的元素啥也没有了。和merge不一样的是,splice可以在指定的位置下进行转移元素。
2、移动一个容器内的一个元素
void test(){ cout << endl << "测试splice函数" << endl; list<int> number = {1,2,3,5,6,8,9 }; list<int> number3 = {111, 555, 777, 333, 666}; cout << "number链表: "; display(number); cout << "number3链表: "; display(number3); auto it = number.begin(); ++it; ++it; cout << "*it = " << *it << endl; auto it2 = number3.begin(); ++it2; cout << "*it2 = " << *it2 << endl; number.splice(it, number3, it2); //将number3中it2位置下的元素移动到number中的it位置下 cout << "number链表: "; display(number); cout << "number3链表: "; display(number3); }

3、移动一个容器内的范围元素
void test() { cout << endl << "测试splice函数" << endl; list<int> number = {1,2,3,5,6,8,9 }; list<int> number3 = {111, 555, 777, 333, 666}; cout << "number链表: "; display(number); cout << "number3链表: "; display(number3); auto it = number.begin(); ++it; ++it; cout << "*it = " << *it << endl; auto it2 = number3.begin(); ++it2; cout << "*it2 = " << *it2 << endl; auto it3 = number3.end(); --it3; cout << "*it3 = " << *it3 << endl; //将number3中it2到it3范围内的元素移动到number中的it位置下 number.splice(it, number3, it2, it3);//[it2, it3)左闭右开 cout << "number链表: "; display(number); cout << "number3链表: "; display(number3); }

4、移动容器自身内的元素 (避免使用,会出现问题)
void test(){ auto it4 = number.begin(); ++it4; ++it4; cout << "*it4 = " << *it4 << endl; auto it5 = number.end(); --it5; --it5; cout << "*it5 = " << *it5 << endl; number.splice(it4, number, it5); //迭代器it4与it5都是number自身的元素 display(number); }
vector的insert插入扩容(了解)
void test() { vector<int> number = {1, 3, 5, 9, 7, 4, 6, 2, 10, 3}; display(number); cout << "number.size() = " << number.size() << endl << "number.capacity() = " << number.capacity() << endl; cout << endl << "在vector的尾部进行插入与删除" << endl; number.push_back(33); display(number); cout << "number.size() = " << number.size() << endl << "number.capacity() = " << number.capacity() << endl; cout << endl << "在vector的任意位置进行插入元素" << endl; vector<int>::iterator it = number.begin(); ++it; ++it; cout << "*it = " << *it << endl; number.insert(it, 100); display(number); cout << "number.size() = " << number.size() << endl << "number.capacity() = " << number.capacity() << endl; cout << "*it = " << *it << endl; }

结果分析:因为push_back每次只能插入一个元素,所以按照2 * capacity进行扩容是没有任何问题的,但是insert进行插入元素的时候,插入元素
的个数是不确定的,所以不能按照2 * capacity进行扩容
设置 size() = m = 12
, capacity() = n = 20
, 待插入的元素个数t
:
- 当插入元素的个数
t < n - m
,此时就不会扩容 - 当插入元素的个数
n - m < t < m
,此时就按照2 * m
进行扩容 - 当插入元素的个数
n - m < t, m < t < n
,此时就按照t + m
进行扩容 - 当插入元素的个数
n - m < t, t > n
,此时就按照t + m
进行扩容
你可以试一下。
关联式容器
关联容器实现能快速查找(O(log n) 复杂度)的数据结构。
set | 唯一键的集合,按照键排序 (类模板) |
---|---|
map | 键值对的集合,按照键排序,键是唯一的 (类模板) |
multiset | 键的集合,按照键排序 (类模板) |
multimap | 键值对的集合,按照键排序 (类模板) |
set 使用
set的特征:
- key值是唯一的,不能重复
- 默认情况下,会按照key值升序排列
- set的底层使用的是红黑树数据结构
const 与 find 查找
void test(){ cout << "------------set的初始化------------" << endl; set<int> number = {1, 3, 5, 5, 7, 5, 9, 2, 4}; /* set<int, std::greater<int>> number = {1, 3, 5, 5, 7, 5, 9, 2, 4}; */ display(number); cout << endl ; cout << "----------set中的count查找----------" << endl; size_t cnt1 = number.count(7); cout << "查找为7的元素是否在容器内? cnt1为1表示存在, 为0表示不存在" << endl; cout << "cnt1 = " << cnt1 << endl << endl; cout << "----------set中的find查找----------" << endl; /* set<int>::const_iterator it = number.find(10); //OK的*/ set<int>::iterator it = number.find(10); if(it == number.end()) //迭代器判空的独特方法,不能用nullptr { cout << "该为10的元素不存在set中, 查找失败" << endl; } else{ cout << "该为10的元素存在set中" << *it << endl; } cout << endl; }

insert 插入
注意:set容器内如果插入已经存在的元素,则不会重复插入,会被过滤掉。
void test(){ set<int> number = {1, 3, 5, 5, 7, 5, 9, 2, 4}; cout << "----------set中的insert插入----------" << endl; cout << "原number的元素有: "; display(number); //set的insert单独插入一个数返回的是pair迭代器类型的 pair<set<int>::iterator, bool> ret = number.insert(8); if(ret.second) { cout << "该元素插入成功 " << *ret.first << endl; } else { cout << "该元素插入失败,该元素已经存在set中" << endl; } display(number); cout << endl ; //set的insert插入另一个容器范围的元素 vector<int> vec = {1, 3, 7, 9, 11, 45, 7, 9, 5, 7}; number.insert(vec.begin(), vec.end()); display(number); cout << endl ; //set的insert插入大括号内的元素 number.insert({13, 56, 78, 23, 11, 14}); display(number); }

下标和修改操作
void test(){ set<int> number = {1, 3, 5, 5, 7, 5, 9, 2, 4}; cout << endl << "set的下标操作" << endl; //set是不支持下标的 /* cout << "number[1] = " << number[1] << endl; */ cout << endl << "set的修改操作" << endl; it = number.end(); --it; --it; cout << "*it = " << *it << endl; /* *it = 123;//error, 不能修改 */ }
实现 set 的类对象类型元素的排序
先了解一下
template< class Key, class Compare = std::less<Key>, class Allocator = std::allocator<Key> > class set; //看一下构造函数有关于大括号的构造 set( std::initializer_list<value_type> init, const Compare& comp = Compare(), const Allocator& alloc = Allocator() ); //less的比较运算符重载 constexpr bool operator()(const T &lhs, const T &rhs) const { return lhs < rhs; // }
#include <math.h> #include <iostream> #include <set> #include <utility> #include <vector> using std::cout; using std::endl; using std::set; using std::pair; using std::vector; template <typename Container> void display(const Container &con) { for(auto &elem : con) { cout << elem << " "; } cout << endl; } class Point { public: Point(int ix = 0, int iy = 0) : _ix(ix) , _iy(iy) { /* cout << "Point(int = 0, int = 0)" << endl; */ } double getDistance() const { return hypot(_ix, _iy); } void print() const { cout << "(" << _ix << ", " << _iy << ")"; } ~Point() { /* cout << "~Point()" << endl; */ } friend std::ostream &operator<<(std::ostream &os, const Point &rhs); friend bool operator<(const Point &lhs, const Point &rhs); private: int _ix; int _iy; }; std::ostream &operator<<(std::ostream &os, const Point &rhs) { os << "(" << rhs._ix << ", " << rhs._iy << ")"; return os; } bool operator<(const Point &lhs, const Point &rhs) //会在less的函数对象中触发 < { cout << "bool operator<(const Point &, const Point &)" << endl; //按照距离的远近进行比较 if(lhs.getDistance() < rhs.getDistance()) { return true; } else if(lhs.getDistance() == rhs.getDistance()) { if(lhs._ix < rhs._ix) { return true; } else if(lhs._ix == rhs._ix) { if(lhs._iy < rhs._iy) { return true; } else { return false; } } else { return false; } } else { return false; } } void test2() { set<Point> number = { Point(-1, 2), Point(1, -2), Point(1, 2), Point(2, -2), Point(3, 5), Point(1, 2) }; display(number); } int main(int argc, char **argv) { test2(); return 0; }

运行结果:之所以set中插入Point对象数据后,能够排序,是因为在less中比较两个对象时,触发了 < 比较运算符的我们自己写的重载。第二种方式:当然你也可以用函数对象去实现。不过要设计成友元的方式,不然无法访问内部私有成员。

第3种方式,对std::less
的模板进行特化处理。
namespace std { //模板的特化 template<> struct less <Point> { bool operator()(const Point &lhs,const Point &rhs){ ..... //不过要用get方法去获取私有成员 } }; } //end of namespace std
针对于自定义类型的三种形式(重要)
1、模板的特化

2、函数对象的形式

3、运算符重载形式

multiset的使用
multiset的特征:
- key值是不唯一的,可以重复
- 默认情况下,会按照key值升序排列
- multiset的底层使用的是红黑树数据结构
除去insert的返回值是pair类型的双迭代器,其他的操作均与set效果相同。看一下multiset独有的bound函数
bound函数


注意:set与multiset针对于自定义类型的写法完全等同,都可以针对于自定义类型的三种形式完全一样。
map 使用
map的特征
- map中存放的是pair,也就是key-value类型,key值是惟一的
- 不能重复,但是value是可以重复的,也可以不重复
- 默认情况下,也会按照key值进行升序排列
- 底层实现也是使用的红黑树
初始化
template <typename Container> void display(const Container &con) { for(auto &elem : con) { cout << elem.first << " " << elem.second << endl; } } void test() { /* map<int, string, std::greater<int>> number = { */ map<int, string> number = { pair<int, string>(8, "wuhan"), pair<int, string>(6, "wuhan"), pair<int, string>(9, "hubei"), {1, "beijing"}, {1, "dongjing"}, {2, "nanjing"}, make_pair(3, "tianjin"), make_pair(8, "wuhan"), make_pair(10, "shenzhen") }; display(number); }
map的查找
void test() { cout << endl << "map的查找操作" << endl; //返回拥有关键 key 的元素数,但key是唯一的,只会返回 0或1 size_t cnt = number.count(8); cout << "cnt = " << cnt << endl; cout << endl; //查找key为10的键值对 map<int, string>::iterator it = number.find(10); //指向键等于 key 的元素的迭代器。若找不到这种元素,则返回尾后(见 end() )迭代器。 if(it == number.end()) { cout << "该元素不存在map中" << endl; } else { cout << "该元素存在map中 " << it->first << " " << it->second << endl; } }
map的插入
std::pair<iterator, bool> insert( const value_type& value );
map的插入是一个pair类型,不是一个key,所以需要构建pair类型
void test() { cout << endl << "map的insert操作" << endl; pair<map<int, string>::iterator, bool> // 3种插入方式:1、pair类型数据 2、打括号插入 3、make_pair插入 /* ret = number.insert(pair<int, string>(4, "chongqing")); */ /* ret = number.insert({4, "chongqing"}); */ ret = number.insert(make_pair(4, "chongqing")); if(ret.second) //pair第二个元素类型为bool,若成功插入则为true { cout << "插入成功 " << ret.first->first << " " << ret.first->second << endl; } else { cout << "插入失败, 证明该元素已经存在map中" << endl; } display(number); cout << endl << endl; map<int, string> number2 = { {7, "sichuan"}, {5, "sichuan"}, }; //insert的范围插入 number.insert(number2.begin(), number2.end()); display(number); cout << endl << endl; //insert的大括号多个插入大括号嵌套大括号(一个个都是pair类型) number.insert({ {11, "hello"}, {2, "nanjing"}, {13, "hangzhou"} }); display(number); }
下标和修改操作
支持下标操作和修改操作。
void test() { cout << endl << "map的下标操作" << endl; //map的下标中传的是key类型,然后返回的是value类型 cout << "number[1] = " << number[1] << endl;//查找 //如果下标操作访问的key不存在,就会变为插入操作,value为空 cout << "number[12] = " << number[12] << endl;//插入 display(number); cout << endl << endl; //T &operator[](key) number.operator[](12) = "kiki"; /* number[12] = "kiki";//修改 等价于上面*/ number[5] = "kiki"; display(number); /* const map<int, string> number3 = {make_pair(1, "hehhe")}; */ /* number3[1]; error 因为是const是不允许通过下标访问修改value*/ }
针对于自定义类型
如果自定义类型是string,是不需要重载小于符号的,因为其可以进行直接比较。
void test2() { /* map<Point, string> number = {//error */ //之所以报错是因为,map会自动对key进行排序,但Point没有实现对比较运算符的重载,所以会报错 map<string, Point> number = { {"hello", Point(1, 2)}, {"world", Point(2, 2)}, make_pair("wuhan", Point(3, 2)), make_pair("wuhan", Point(3, 2)), {"nanjing", Point(1, 2)}, {"dongjing", Point(1, 2)}, }; display(number); }

multimap 的使用
multimap 特征

不支持下标(重点)

无序关联式容器
无序关联容器提供能快速查找(均摊 O(1) ,最坏情况 O(n) 的复杂度)的无序(哈希)数据结构。
基本概念
底层使用的是哈希。
哈希函数:通过key值找到key值存放的位置。size_t index = H(key)
哈希函数的构建:目的就是为了设计哈希函数,尽量能减少冲突。
哈希冲突:H(key1) = H(key2)
,key值不一样,但是key值对应的位置值index是一样。
解决哈希冲突:开放地址法、在散列法、建立公共溢出区、链地址法(STL中使用就是这种方法)

装载因子
装填因子。实际的数据个数/表的长度 = m。m越大的话,那么冲突的概率就比较高,但是空间的利用率也比较高。m越小的话,冲突的概率也会比较小,空间的利用率也会比较小。尽量将装载因子的大小设计在[0.5, 0.75]. 可以将数组看成是完美的哈希。
unordered_set的使用

总结:其他的查找方法与set完全相同,insert插入的方法、以及erase方法与set完全相同。也没有下标访问运算符。
针对自定义类型
template< class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>, class Allocator = std::allocator<Key> > class unordered_set;
自定义实现:
https://gitee.com/boldness2012/cpp47_code/blob/master/20230413/unordered_set.cc#

针对于std::hash的写法:


针对于std::equal_to的写法:


unordered_multiset的使用

注意:unordered_multiset的查找、插入、删除,可以参照set与multiset之间的区别。针对于自定义类型,完全与unordered_set等价。
unordered_map的使用
特征:

下标访问(重点):

unordered_multimap的使用
基本特征

不支持下标:

优先级队列
template< class T, class Container = std::vector<T>, class Compare = std::less<typename Container::value_type> //类型萃取 > class priority_queue;

针对于自定义类型
针对于自定义类型的时候,需要根据第二个模板参数中容器的value_type进行std::less的改写。std::less有常规的三种形式,模板的特化、函数对象的形式、运算符重载的形式(在前面讲set的时候重点讲解过)。
容器的选择
如果说容器可以取下标?
vector、deque、map、unordered_map
总结:所有只要是带set的都没有下标,所有带multi的都与下标没有关系,list没有下标。
元素是否有序?
关联式容器是有顺序,无序关联式容器没有,对于序列式容器而言,默认情况下与顺序没有关系,但是可以借助于对应排序函数进行排序。
时间复杂度?
无序关联式容器:底层使用的是哈希,如果没有冲突的话,时间复杂度O(1).
关联式容器:底层使用的是红黑树,时间复杂度O(logN).
序列式容器:时间复杂度O(N)
迭代器的类型?
随机访问迭代器:vector、deque
双向迭代器:list、关联式容器。
前向迭代器:无序关联式容器。
没有迭代器:容器适配器stack、queue、priority_queue
迭代器
输入输出流之所以可以看成是容器,因为会对应的有缓冲区,而缓冲区是可以存数据的,就可以当容器看待。
迭代器的头文件在 #include


输出流迭代器

理解的要点是将输入/输出流作为容器看待。因此,任何接受迭代器参数的算法都可以和流一起工作。使用流迭代器必须要包含头文件
先介绍一个算法库的copy函数
Defined in header <algorithm> template< class InputIt, class OutputIt > OutputIt copy( InputIt first, InputIt last, OutputIt d_first );
摘取的ostream_iterator源码部分实现细节
class ostream_iterator { public: //ostream_iterator<int> osi(cout, "\n"); //ostream_type& __s = cout; _M_stream = &cout; //const _CharT* __c = "\n";_M_string = "\n" ostream_iterator(ostream_type& __s, const _CharT* __c) : _M_stream(&__s) //取得是输出流地址 , _M_string(__c) { } //osi = 1 osi对象内实现对赋值运算符的重载 //__Tp(int) = __value(1) ostream_iterator<_Tp>& operator=(const _Tp& __value) { *_M_stream << __value;//cout << 1 if (_M_string) //_M_string如果不为空就输入到cout内 *_M_stream << _M_string;//cout << "\n" return *this; } ostream_iterator<_Tp>& operator*() { return *this; } ostream_iterator<_Tp>& operator++() { return *this; } ostream_iterator<_Tp>& operator++(int) { return *this; } private: ostream_type* _M_stream; const _CharT* _M_string; };
摘取的copy源码部分实现细节
//copy()函数内实现细节: template <class _InputIter, class _OutputIter> inline _OutputIter copy(_InputIter __first, _InputIter __last, _OutputIter __result) { return __copy_aux(__first, __last, __result, __VALUE_TYPE(__first));//走到__copy_aux()函数内实现 } //__copy_aux()函数内实现细节: template <class _InputIter, class _OutputIter, class _Tp> inline _OutputIter __copy_aux(_InputIter __first, _InputIter __last, _OutputIter __result, _Tp*) { typedef typename __type_traits<_Tp>::has_trivial_assignment_operator _Trivial; return __copy_aux2(__first, __last, __result, _Trivial()); //走到__copy_aux2()函数内实现 } //__copy_aux2()函数内实现细节: template <class _InputIter, class _OutputIter> inline _OutputIter __copy_aux2(_InputIter __first, _InputIter __last, _OutputIter __result, __false_type) { return __copy(__first, __last, __result,//走到__copy()函数内实现 __ITERATOR_CATEGORY(__first), __DISTANCE_TYPE(__first)); } //__copy()函数内实现细节: template <class _InputIter, class _OutputIter, class _Distance> inline _OutputIter __copy(_InputIter __first, _InputIter __last, _OutputIter __result, input_iterator_tag, _Distance*) { for ( ; __first != __last; ++__result, ++__first) *__result = *__first; return __result; } //__copy函数实现剖析: //vector<int> vec = {1, 3, 5, 9, 7}; //copy(vec.begin(), vec.end(), osi) __first = vec.begin(); __last = vec.end(); __result = osi _OutputIter __copy(_InputIter __first, _InputIter __last, _OutputIter __result, input_iterator_tag, _Distance*) { for ( ; __first != __last; ++__result, ++__first) *__result = *__first; //osi = 3 请看上面osi对赋值运算符的重载 return __result; } last 1, 3, 5, 9, 7 f //osi = 1 //__value = 1;3 //__Tp = int ostream_iterator<_Tp>& operator=(const _Tp& __value) { *_M_stream << __value;//cout << 3 if (_M_string) *_M_stream << _M_string;//cout << "\n" return *this; }
输入流迭代器

gdb的使用
list,简写为l,查看代码;breakpoint,简写为b,设置断点的;run,简写为r,让程序跑起来;step,简写为s,单步调试;next,简写为n,下一步;info b,查看断点;bt打印堆栈信息;print,简写为p,可以打印变量的值;ptype,可以打印变量类型。
摘取的ostream_iterator源码部分实现细节
class istream_iterator { public: istream_iterator() : _M_stream(0) , _M_ok(false) {} //istream_iterator<int> isi(cin) //istream_type& __s = cin; //_M_stream = &cin; istream_iterator(istream_type& __s) : _M_stream(&__s) { _M_read(); } void _M_read() { // &cin && cin _M_ok = (_M_stream && *_M_stream) ? true : false; if (_M_ok) { *_M_stream >> _M_value;//cin >> _M_value 2 _M_ok = *_M_stream ? true : false; } } reference operator*() const { return _M_value; } istream_iterator& operator++() { _M_read(); return *this; } istream_iterator operator++(int) { istream_iterator __tmp = *this; _M_read(); return __tmp; } //isi //__x = istream_iterator<int>() bool _M_equal(const istream_iterator& __x) const { //_M_ok = 0; return (_M_ok == __x._M_ok) && (!_M_ok || _M_stream == __x._M_stream); } private: istream_type* _M_stream; _Tp _M_value; bool _M_ok; }; //__first = isi = __x; //__last = istream_iterator<int>() = __y; template <class _Tp, class _CharT, class _Traits, class _Dist> inline bool operator!=(const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __x, const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __y) { return !__x._M_equal(__y); }
摘取的copy源码部分实现细节
//__first = isi; //__last = istream_iterator<int>(); //__result = back_insertter(vec); _OutputIter __copy(_InputIter __first, _InputIter __last, _OutputIter __result, input_iterator_tag, _Distance*) { for ( ; __first != __last; ++__result, ++__first) *__result = *__first; return __result; } *__result = 1; *__result = 2; template <class _Container> inline back_insert_iterator<_Container> back_inserter(_Container& __x) { return back_insert_iterator<_Container>(__x); } Point back_inserter(_Container& __x) { return Point(x); }
摘取的back_insert源码部分实现细节
注意:
void test() { istream_iterator<int> isi(cin); vector<int> vec; /* copy(isi, istream_iterator<int>(), vec.begin()); */ //对于vector而言,如果没有预留空间的话,最好使用push_back插入元素 copy(isi, istream_iterator<int>(), std::back_inserter(vec)); //可以看成是遍历vector copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " ")); cout << endl; //看打印是否成功 }
class back_insert_iterator { public: explicit back_insert_iterator(_Container& __x) : container(&__x) {} back_insert_iterator<_Container>& operator=(const typename _Container::value_type& __value) { container->push_back(__value); //插入元素 return *this; } back_insert_iterator<_Container>& operator*() { return *this; } back_insert_iterator<_Container>& operator++() { return *this; } back_insert_iterator<_Container>& operator++(int) { return *this; } protected: _Container* container; }
迭代适配器
插入迭代器
void test() { vector<int> vecNumber= {1, 3, 7, 9}; list<int> listNumber = {11, 55, 88, 66}; //back_inserter与back_insert_iterator底层执行了push_back函数 /* 1、第一种方式,使用函数模板back_inserter copy(listNumber.begin(), listNumber.end(), back_inserter(vecNumber)); */ // 2、第二种方式,使用类模板back_insert_iterator copy(listNumber.begin(), listNumber.end(), back_insert_iterator<vector<int>>(vecNumber)); copy(vecNumber.begin(), vecNumber.end(), ostream_iterator<int>(cout, " ")); cout << endl; cout << endl << endl; //front_inserter与front_insert_iterator底层执行了push_front函数 copy(vecNumber.begin(), vecNumber.end(), front_insert_iterator<list<int>>(listNumber)); copy(listNumber.begin(), listNumber.end(), ostream_iterator<int>(cout, " ")); cout << endl; cout << endl << endl; //将vector中的数据插入到set中 set<int> setNumber = {55, 33, 2, 8, 12, 19}; auto it = setNumber.begin(); //inserter与insert_iterator底层执行了insert函数 copy(vecNumber.begin(), vecNumber.end(), insert_iterator<set<int>>(setNumber, it)); copy(setNumber.begin(), setNumber.end(), ostream_iterator<int>(cout, " ")); cout << endl; }
本文作者:负笈远游
本文链接:https://www.cnblogs.com/BrokenSnow/p/17323112.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步