移动语义
概念理解
可以取地址的是左值,不能取地址的就是右值。右值短暂的存在于栈上。
右值包括:临时对象、匿名对象、字面值常量
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;
}
标签:11,__,const,cout,iterator,number,特性,C++,模板
From: https://www.cnblogs.com/BrokenSnow/p/17323112.html