C++提高编程

C++提高编程

注意:

STL不同容器,算法的头文件以及遍历输入等为了方便进行了省略,请在实际应用时注意添加

主要包括泛型编程STL技术

1 模板

1.1 模板概念

​ 模板的特点:

​ 1、模板不能直接使用,她只是一个框架

​ 2、模板的通用并不是万能的

​ C++的另一种编程思想称为泛型编程,主要利用模板技术

​ C++提供两种模板机制:函数模板和类模板

1.2 函数模板

1.2.1 函数模板语法

​ 函数模板作用:建立通用函数,函数的返回值类型和形参类型可以不确定,用虚拟类型代表

​ 语法:

template<typename T>
//接函数声明或定义

template——声明创建模板

typename——表面其后面的符号是一种数据类型,可以用class代替

T——通用的数据类型,可以替换,通常用T

template<typename T>//声明模板,告诉编译器后面的T不要报错
void Swap(T &a,T &b)
{
    T temp;
    temp=a;a=b;b=temp;
}

int main()
{
    int a=10,b=20;
    //两种调用方式
    //自动类型推导
    Swap(a,b);
    //显示指定类型
    Swap<int>(a,b);
}
1.2.2 函数模板注意事项

​ 1)自动类型推导,必须推出一致T才可以使用

​ 2)模板必须要能够确定出T的具体数据类型,才可以使用

1.2.3 普通函数和模板函数的区别

​ 普通函数调用可以发生隐式类型转换

​ 模板函数要用显示指定类型法去调用才能隐式类型转换,如果用自动类型推导则不能隐式类型转换,因为有歧义

int myAdd(int a,int b)//普通函数
{
    return a+b;
}

template<class T>
T myAdd(T a,T b)
{
    return a+b;
}

int main()
{
    int a=10;
    char c='c';
    //调用普通函数时
    myAdd(a,c);//可以运行,char字符会隐式类型转换成ascii码
    //自动推导类型调用时
    myAdd(a,c);//报错
    //显示指定类型调用时
    myAdd<int>(a,c);//合法,结果和普通函数运行结果相同
}
1.2.4 普通函数和函数模板调用规则

​ 1)若函数模板和普通函数都可以实现,优先调用普通函数,即使函数只有声明(会报错)

​ 2)可以通过空模板参数列表(eg.maAdd<>(a,b))来强制调用函数模板

​ 3)函数模板也可以发生重载

​ 4)若函数模板可以更好的匹配,优先调用函数模板

1.2.5 模板的局限性

​ 模板的通用性并不是万能的,对于一些自定义数据类型的,需要具体化的实现(如Person类是否相等的比较)

template<class T>
bool myCompare(T a,T b)//如果用自定义类调用这个模板函数是不行的
{
    if(a==b)
    {
        return true;
    }
    else
    {
        return false;
    }
}

template<> bool myCompare(Person &p1,Person &p2)//对于特殊类型的具体化实现
{
    //具体化的实现
    if(p1.age==p2.age&&p1.id==p2.id)
    {
        return true;
    }
    else
    {
        return false;
    }
}

学习模板不是为了写模板,而是在STL能够运用系统提供的模板

1.3 类模板

1.3.1 类模板语法

​ 作用:建立通用类,类中成员类型可以不具体定制,用虚拟类型代表

template <class NameType,class AgeType>//多个不确定类型
class Person
{
public:
    Person(NmaeTypr name,AgeType age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    
    NameType m_name;
    AgeType m_age;
}

int main()
{
    Person<string,int>p("aaa",1111);//调用
}
1.3.2 类模板和函数模板区别

​ 1)类模板没有自动类型推导的调用方式

​ 2)类模板的模板参数列表可以有默认值

template <class NameType,class AgeType=int>//模板参数列表可以设置默认值
class Person
{
public:
    Person(NmaeTypr name,AgeType age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    
    NameType m_name;
    AgeType m_age;
}

int main()
{
    Person<string>p("aaa",1111);//此处不写int同样合法
}
1.3.3 类模板中成员函数创建时机

​ 普通类中成员函数和类模板中的成员函数调用时机有区别:

​ 普通类中的成员函数一开始就创建

​ 模板类中的成员函数在调用时才创建,因为在调用前编译器不能确定具体的类型

1.3.4 类模板对象做函数参数

​ 类模板实例化出的对象 作为函数参数传递的方式

​ 最常用:指定转入的类型

template <class NameType,class AgeType=int>//模板参数列表可以设置默认值
class Person
{
public:
    Person(NmaeTypr name,AgeType age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    
    void showPerson()
    {
        //省略内容
    }
    NameType m_name;
    AgeType m_age;
}

//方法一、指定传入的类型(最常用!!)
void showInfo( Person<string,int> &p)
{
    p.showPerson();
}

//方法二、参数模板化
template<class T1,class T2>
void showInfo(Person<T1,T2> &p)
{
    p.showPerson();
}

//方法三、整个类模板化
template<class T>
void showInfo(T &p)
{
    p.showPerson();
}

int main()
{
    Person<string,int>p("qqq",11);
    showInfo(p);
}

​ 补充:查看参数的具体类型:typeid(T).name();

1.3.5 类模板与继承

​ 子类继承的父类时模板类时,子类声明时必须要指定出父类中T的类型,否则会报错

​ 如果想灵活指定父类中T的类型,子类也需要时类模板

template<class T>
class Base
{
public:
    T a;
}

class Son1:public Base<int>//必须指定出父类T的类型,不能灵活指定类型
{
    
}

template<class T1,class T2>
class Son2:public Base<T1>//子类也变成类模板,就可以灵活指定父类T的类型
{
public:
    T2 b;
}
1.3.6 类模板成员函数类外实现

​ 类模板中成员函数类外实现时,需要在作用域后加上模板参数列表

template <class T1,class T2>
class Person
{
public:
    Person(T1 name,T2 age)//类内声明
    T1 m_name;
    T2 m_age;
}

//成员函数类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)//交代函数作用域
{
    //具体实现
}
1.3.7 类模板分文件编写

​ 类模板中成员函数创建时机是调用阶段,导致分文件编写时链接不到

​ 有两种方法解决:

​ 1)直接#include .cpp源文件

​ 2)将声明和实现写到同一个文件夹,并更改后缀名为.hpp==(常用解决方法)==

//以下是Person.hpp的代码
#pragma once
#include<iostream>
using namespace std;
#include<string>

template <class T1,class T2>
class Person
{
public:
    Person(T1 name,T2 age)
    T1 m_name;
    T2 m_age;
};


template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
    //具体实现
}

//在源文件中#include"Person.hpp"就可以调用类模板了
1.3.8 类模板与友元

​ 全局函数类内实现:直接在类内声明友元即可==(建议使用这种)==

​ 全局函数类内声明类外实现:需要提前让编译器知道全局函数的存在

template<class T1,class T2>class Person//让编译器提前知道Person类存在

template<class T1,class T2>
void Printp(Person<T1,T2> &p)//2.全局函数做友元类外实现,需要让编译器提前知道类模板和全局函数模板存在
{
    //具体实现
}
    
template<class T1,class T2>
class Person
{
    friend void Printp(Person<T1,T2> &p)//1.全局函数作为友元类内实现
    {
        //具体实现
    }
    
    friend void Printp<>(Person<T1,T2> &p)//2.全局函数做友元类内声明,加空模板参数列表
};

2 STL初识

2.1 STL诞生

​ C++的面向对象和泛型编程的思想,目的是为了提升复用性

​ 为了建立数据结构和算法的一套标准,诞生了STL

2.2 STL基本概念

​ STL(Standard Template Library,标准模板库)

​ STL几乎所有代码都采用模板类或模板函数

2.3 STL六大组件

容器,算法,迭代器,仿函数,适配器,空间适配器

​ 适配器:一种用来修饰容器或仿函数或迭代器接口的东西

​ 空间适配器:负责空间的配置与管理

​ (两个不会涉及到具体内容)

2.4 STL中容器、算法、迭代器

容器:就是将运用最广泛的一些数据结构实现出来

​ 容器分为:序列式容器和关联式容器

​ 序列式容器:每个元素均有固定的位置,强调值得排序

​ 关联式容器:二叉树结构,各元素间没有严格得物理上得顺序关系

​ 算法:分为质变算法和非质变算法

​ 质变算法:运算过程中会更改区间内元素的内容,如拷贝,插入,删除等

​ 非质变算法:运算过程中不会更改区间内元素的内容,如查找,计数,遍历等

​ 迭代器:容器和算法间的粘合剂

​ 提供一种方法,使能够依序访问某个容器中的各个元素,而又无需暴露该容器的内部表示方式

​ 每个容器偶有专属的迭代器

​ 迭代器使用类似指针,可以暂时理解为指针

​ 迭代器种类:输入迭代器,输出迭代器,向前迭代器,双向迭代器,随机访问迭代器

​ 常用的两种:

​ 双向迭代器:读写操作,并能向前向后操作(++,–)

​ 随机访问迭代器:读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器

2.5 容器算法迭代器初识

​ STL中最常用的容器为vector,可以理解为数组

2.5.1 vector存放内置数据类型

​ 容器:vector

​ 算法:for_each

​ 迭代器:vector<int>::iterator

#include<vector>//引入vector头文件
#include<algorithm>//引入算法头文件

void myPrint(int a)
{
    cout<<a<<endl;
}

int main()
{
    vector<int> v;//vector定义
    v.push_back(10);//vector尾插元素
    v.push_back(20);
    v.push_back(30);
    
    //创建迭代器
    vector<int>::iterator pBegin=v.Begin();//v.Begin()返回迭代器,指向容器的第一个数据
    vector<int>::iterator pEnd=v.End();//v.End()返回迭代器,指向容器最后一个元素的下一个位置
    
    //第一种遍历方式
    while(pBegin!=pEnd)
    {
        cout<<*pBegin<<endl;
        pBegin++;
    }
    
    //第二种遍历方式
    for(vector<int>::iterator it=v.Begin();it!=v.End();it++)
    {
        cout<<*it<<endl;
    }
    
    //第三种方式,使用STL提供的标准遍历算法,需要头文件<algorithm>
    for_each(v.Begin(),v.End(),myPrint)//不用加()
    
}
2.5.2 Vector存放自定义数据类型
class Person
{
public:
    Person(string name,int age)
    {
        this->m_name=name;
        this->m_age=age;
    }
    string m_name;
    int m_age;
}

int main()
{
    vector<Person> v;
    Person p1("aaa",111);
    Person p1("baa",211);
    
    v.push_back(p1);
    v.push_back(p2);
    
    for(vector<Person>::iterator it=v.Begin();it!=v.End();it++)
    {
        cout<<(*it).m_name<<(*it).m_age<<endl;
    }
}
2.5.3 Vector容器嵌套容器
int main()
{
    vector<vector<int>> v;
    
    vector<int> v1;
    vector<int> v2;
    vector<int> v3;
    
    for(i=0;i<4;i++)
    {
        v1.push_back(i+1);
        v2.push_back(i+2);
        v3.push_back(i+3);
    }
    
    for(vector<vector<int>>::iterator it=v.Begin();it!=v.End();it++)
    {
        for(vector<int>::iterator mit=(*it).Begin();mit!=(*it).End();mit++)
        {
            cout<<*vit<<" ";
        }
        cout<<endl;
    }
}

3 STL常用容器

3.1 String容器

3.1.1 string基本概念

​ 本质:string是C++风格的字符串,而string本质上是一个类

​ string和char *区别:

​ char *是一个指针

​ string是一个类,是一个char *的容器,类内封装了char *,管理这个字符串

​ 特点:

​ string类内部封装了很多成员方法,如find,copy,delete,replace,insert

​ string管理char *所分配的内存,不用担心复制越界和取值越界等,由类内部负责

3.1.2 string构造函数
int main()
{
    string s1;//string()创建空字符串
    
    //string(const char* s)使用字符串初始化
    const char* str="asd";//"asd"就是一个字符数组const char*
    string s2(str);
    
    //string(const string &s)使用一个string对象初始化另一个string对象
    string s3(s2);
    
    //string(int n,char c)使用n个字符c初始化
    string(10,'a');
}
3.1.3 string赋值操作
int main()
{
    //使用operator=(常用)
    string s1="hewaeae";
    
    string s2=s1;
    
    string s3='a';
    
    //使用assign()
    string s4;
    s4.assgin("ihdqoid");
    
    string s5;
    s5.assgin("sddqwd",5);//前5个字符
        
    string s6;
    s6.assgin(s5);//拷贝
    
    string s7;
    s7.assgin(10,'a');//几个几
}
3.1.4 string字符串拼接

​ 实现在字符串末尾拼接字符

int main()
{
    string s1="aaaa";
    //使用+=操作符
    s1+="weqeewe";
    
    s1+='w';
    
    string s2="weeeee";
    s1+=s2;
    
    //使用append()
    s1.append("asda");
    
    s1.append("saddd",4);//前4个字符
    
    s1.append(s2);
    
    s1.append(s2,0,3);//s2从第0个位置开始的后面3个
}

3.1.5 string 查找和替换
int main()
{
    string s1="abcdefg";
    
    //find(),返回位置
    int pos=s1.find("de");
    
    //rfind()从右往左找到第一个符合条件
    pos=s1.rfind("de");
    
    //替换,s1的第一个位置开始后面三个字符被换成"weqwewe"
    s1.replace(1,3,"weqwewe");
}
3.1.6 string比较

​ 字符串比较按照ASCII码进行对比

​ = 返回 0

​ > 返回 1

​ < 返回 -1

int main()
{
    string s1="qwer";
    string s2="qqqq";
    
    if(s1.compare(s2)==0)//compare()比较字符串
    {
        cout<<"相等"<<endl;
    }
}
3.1.7 string字符存取
int main()
{
	string s1="qwer";
    
    //1.使用[]获取字符
    for(i=0;i<s1.size();i++)//size()可以获取字符串长度
    {
        cout<<s1[i]<<endl;
    }
    s1[1]='a';//可以通过[]修改字符
    
    //2.使用at()获取字符
    for(i=0;i<s1.size();i++)
    {
        cout<<s1.at(i)<<endl;
    }
    s1.at(1)='a';//可以通过at修改单个字符
}
3.1.8 string插入和删除
int main()
{
    string s1="qqwe";
    //插入
    s1.insert(1,"qwe");//第一个位置插入"qwe"
    
    //删除
    s1.erase(1.3);//从第一个位置删除后面3个字符
}
3.1.9 string子串

从字符串中获取想要的字符

int main()
{
    //提取用户名
    string email="qing@163.com";
    int pos=email.find("@");
    string subStr=email.substr(0,pos);//substr()返回从0开始后面pos个字符的子串
}

3.2 Vector容器

3.2.1 vector基本概念

​ vector和数组非常相似,又叫单端数组

​ vector和普通数组的区别:

​ 普通数组是静态空间,而vector可以动态扩展

​ 动态扩展:

并不是在原空间之后继续接新空间,而是寻找更大的内存空间,然后将原数据拷贝进新空间,释放原空间

​ vector容器的迭代器是支持随机访问的迭代器

3.2.2 vector构造函数

​ 创建vector容器

int main()
{
    vector<int>v1;//默认无参构造(常用)
    
    vector<int>v2(v1.Begin(),v1.End());//通过区间方式进行构造,区间左闭右开
    
    vector<int>v3(10,100);//10个100
        
    vector<int>v4(v3);//拷贝构造函数(常用)
}
3.2.3 vector赋值操作
int main()
{
    vector<int>v1;
    for(i=0;i<10;i++)
    {
        v1.push_back(i);
    }
    
    //等号操作符赋值
    vector<int>v2;
    v2=v1;
    
    //assign()区间数据拷贝赋值
    vector<int> v3;
    v3.assign(v1.Begin(),v1.End());//左闭右开
    
    //n个elem赋值
    vector<int>v4;
    v4.assign(10,100);//10个100
}
3.2.4 vector容量和大小
int main()
{
    vector<int>v1;
    for(i=0;i<10;i++)
    {
        v1.push_back(i);
    }
    
    v1.empty();//返回容器是否为空
    v1.capacity();//返回容器的容量
    v1.size();//返回容器中元素个数
    v1.resize(4);//重新指定容器大小,比原来大会用0填充;比原来小就把后面的删除,但容器容量不会变
    v1.resize(11,100);//比原来大用100填充
}
3.2.5 vector插入和删除
int main()
{
    vector<int> v1;
    //尾插
    v1.push_back(10);
    
    //尾删
    v1.pop_back();
    
    //插入,第一个参数是迭代器
    v1.insert(v1.Begin(),100);
    v1.insert(v1.Begin(),2,1);//插入2个1
    
    //删除
    v1.erase(v1.Begin(),v1.End());
    
    //清空
    v1.clear();
}
3.2.6 vector数据存储
int main()
{
    vector<int>v1;
    int i;
    
    //[]访问单个元素
    v1[i];
    
    //at()访问单个元素
    v1.at(i);
    
    //访问第一个元素
    v1.front();
    
    //访问最后一个元素
    v1.back();
}
3.2.7 vector互换容器
int main()
{
    vector<int>v1;
    for(i=0;i<10000;i++)
    {
        v1.push_back(i);
    }
    
    vector<int>v2;
    
    //交换
    v1.swap(v2);
    
    //巧用交换可以收缩内存
    vector<int>(v1).swap(v1);//利用匿名对象和拷贝构造
}
3.2.8 vector预留空间
int main()
{
    vector<int>v;
    //reserve()预留空间,预留位置不初始化,无法访问
    v.reserve(1000);
}

3.3 deque容器

3.3.1 deque基本概念

​ 双端数组,头尾都可以插入删除操作

​ deque和vector的区别:

​ vector头部插入效率低

​ deque头部插入删除的速度比vector快

​ vector访问速度比deque快

​ deque内部工作原理:

​ deque内部有一个中控器,维护每段缓冲区中的内容,缓冲区存放真实内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2GW6kMMO-1654777689271)(C:\Users\qingl\Downloads\微信截图_20220520134843.png)]

​ deque的迭代器是支持随机访问的

3.3.2 deque构造函数
void Print(const deque<int>& d)//const防止函数对传入参数修改,需要把迭代器改成const_iterator
{
    for(deque<int>::const_iterator it=d.Begin();it!=d.End();it++)
    {
        cout<<*it<<" ";
    }
    cout<<endl;
}

int main()
{
    //无参构造
    deque<int>d1;
    for(i=0;i<10;i++)
    {
        d1.push_back(i);
    }
    Print(d1);
    
    //区间构造
    deque<int>d2(d1.Begin(),d1.End());
    
    //几个几构造
    deque<int>d3(10,100);//10个100
    
    //拷贝构造
    deque<int>d4(d3);
}
3.3.3 deque赋值操作
int main()
{
    deque<int>d1;
    //等号赋值
    deque<int>d2;
    d2=d1;
    //assign区间赋值
    d2.assign(d1.Begin(),d1.End());
    //assign几个几赋值
    d2.assign(10,100);
}
3.3.4 deque大小操作
int main()
{
    deque<int>d1;
    //判断容器是否为空,返回bool
    d1.empty();
    //返回容器中元素的个数
    d1.size();
    //重新指定容器的长度,长度边长用0填充,变短删除末尾
    d1.resize(5);
    d1.resize(5,1);//用1填充
}
3.3.5 deque插入删除
int main()
{
    deque<int>d1;
    //两端操作
    //头插
    d1.push_front(10);
    //尾插
    d1.push_back(20);
    //头删
    d1.pop_front();
    //尾删
    d1.pop_back();
    
    //指定位置操作
    //指定位置插入一个
    d1.insert(d1.Begin,100);
    //指定位置插入几个几
    d1.insert(d1.Begin(),2,100);
    //指定位置插入另一个区间的数
    deque<int>d2;
    d1.insert(d1.Begin(),d2.Begin(),d2.End());//第一个参数是位置,后面两个参数是区间,左闭右开
    //清空容器内容
    d1.clear();
    //删除指定区间,返回下一个数据位置
    d1.erase(d1.Begin(),d1.End());
    //删除指定位置,返回下一个数据位置
    deque<int>::iterator it=d.Begin();
    it++;//位移位置
    d1.erase(it);
}
3.3.6 deque数据存取
int main()
{
    deque<int>d1;
    //[]访问指定位置元素
    d1[i];
    //at访问指定位置元素
    d1.at(i);
    //访问第一个元素
    d1.front();
    //访问最后一个元素
    d1.back();
}
3.3.7 deque排序操作
#include<algorithm>//排序属于算法,要包含算法头文件
int main()
{
    deque<int>d1;
    sort(d1.Begin(),d1.End());//默认升序
}

​ 注意:支持随机访问的迭代器的容器,都可以用sor算法直接对其进行排序 如vector也可以用sort排序

补充:随机数
#include<ctime>//使用time需要包含该头文件
int main()
{
	srand((unsigned int)time(NULL));//随机数种子
    
    int a=rand()%41+60;//rand()返回0到RAND_MAX之间的随机数
}

3.5 stack容器

3.5.1 stack基本概念

​ stack先进后出,只有一个出口

​ 栈中只有栈顶元素可以被外界使用,因此栈不允许有遍历行为

3.5.2 stack常用接口
#include<stack>
int main()
{
    stack<int>s;
    //入栈
    s.push(10);
    //出栈
    s.pop();
    //查看栈顶元素
    s.top();
    //判断栈是否为空
    s.empty();
    //判断栈大小
    s.size();
}

3.6 queue容器

3.6.1 queue基本概念

​ 队列 先进先出

​ 只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为

3.6.2 queue常用接口
#include<queue>
int main()
{
    queue<int>q;
    //拷贝构造
    queue<int>q1;
    q1=q;
    //入队
    q.push(10);
    //出队
    q.pop();
    //第一个元素
    q.front();
    //最后一个元素
    q.back();
    //是否为空
    q.empty();
    //返回大小
    q.size();
}

3.7 list容器

3.7.1 list基本概念

​ 链表:将数据进行链式储存

​ STL中的链表是一个双向循环链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3W5aSyf8-1654777689272)(C:\Users\qingl\Downloads\微信截图_20220520155012.png)]

​ 由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

​ list优点:

​ 采用动态存储分配,不会造成内存浪费和溢出

​ 链表执行插入和删除方便

​ list缺点:

​ 链表灵活,但空间(指针域)和时间(遍历)消耗较大

list重要性质:插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的

3.7.2 list构造函数
#include<list>
int main()
{
    //无参构造
    list<int>l1;
    //赋值
    l1.push_back(10);
    //区间构造
    list<int>l2(l1.Begin(),l1.End());
    //拷贝构造
    list<int>l3(l1);
    //几个几构造
    list<int>l4(10,100);
}
3.7.3 list赋值和交换
int main()
{
    list<int>l1;
    //等号赋值
    list<int>l2;
    l2=l1;
    //区间赋值
    list<int>l3;
    l3.assign(l1.Begin(),l1.End());
    //几个几赋值
    list<int>l4;
    l4.assign(10,100);
    
    //交换
    l1.swap(l2);
}
3.7.4 list大小操作
int main()
{
    list<int>l1;
    //容器大小
    l1.size();
    //容器是否为空
    l1.empty();
    //容器重新指定大小,规则和vector等一样
    l1.resize(5);
    l1.resize(5,10);//5个10
}
3.7.5 list插入删除
int main()
{
    list<int>l1;
    //头插
    l1.push_front(10);
    //尾插
    l1.push_back(20);
    //头删
    l1.pop_front();
    //尾删
    l1.pop_back();
    //插入
    l1.insert(l1.Begin(),100);
    //删除
    l1.erase(l1.Begin());
    //移除
    l1.remove(100);//删除所有是100的元素
    //清空
    l1.clear();
}
3.7.6 list数据存取
int main()
{
    list<int>l1;
    //list不是连续线性空间存储数据,迭代器也不支持随机访问,因此没有[]和at()访问法
    //只能访问头和尾
    l1.front();
    l1.back();
    //验证迭代器不支持随机访问
    list<int>::iterator it=l1.Begin();
    it++;//合法
    it=it+1;//非法
}
3.7.7 list反转和排序
void myCompare(int v1,int v2)
{
    return v1>v2;//降序就让第一个数大于第二个数
}
int main()
{
    list<int>l1;
    //反转
    l1.reverse();
    //排序
    sort(l1.Begin(),l1.End());//报错,对于不支持随机访问迭代器的容器,不可以用标准算法
    //不支持随机访问迭代器的容器,内部会提供对应算法
    l1.sort();//升序
    l1.sort(myCompare);//降序
}
3.7.8 list自定义类排序
class Person
{
public:
    Person(int age,int height)
    {
        this->m_age=age;
        this->m_height=height;
    }
    int m_age;
    int m_height;
};

void myCompare(Person &p1,Person &p2)
{
    if(p1.m_age==p2.m_age)
    {
        return p1.m_height>p2.m_height;//年龄相同 按身高的降序
    }
    else
    {
        return p1.m_age<p2.m_age//年龄升序
    }
}

int main()
{
    list<Person>l1;
    l1.sort(myCompare);//通过自定义函数规定排序规则
}

3.8 set/multiset容器

3.8.1 set基本概念

​ 特点:所有元素都会在插入时被自动排序

​ 本质:set/multiset属于关联式容器,底层结构是二叉树

​ set和multiset区别:

​ set不允许容器有重复元素

​ multiset允许容器中有重复元素

3.8.2 set构造和赋值
#include<set>
int main()
{
    //无参构造
    set<int> st1;
    
    //拷贝构造
    set<int> st2(st1);
    
    //赋值
    st2=st1;
    
    //插入数据,只有insert方法
    st1.insert(10);
    //set容器会自动排序,不允许插入重复值
}
3.8.3 set大小和交换
int main()
{
    set<int> s1;
    //大小
    s1.size();
    //是否为空
    s1.empty();
    //交换
    set<int> s2;
    s1.swap(s2);
}
3.8.4 set插入删除
int main()
{
    set<int>s1;
    //插入
    s1.insert(10);
    //删除指定位置
    s1.erase(s1.Begin());
    //删除指定元素
    s1.erase(10);
    //删除区间
    s1.(s1.Begin(),s1.End());
}
3.8.5 set查找统计
int main()
{
    set<int> s1;
    
    //查找,返回迭代器
    set<int>::iterstor pos=s1.find(30);
    
    //统计指定元素个数
    int num=s1.count(30);
}
3.8.6 set和multiset区别

​ set不能元素重复 multiset可以元素重复

int main()
{
    set<int> s1;
    //insert会返回一个对组,第一个是插入位置的迭代器,第二个是是否插入成功
    pair<set<int>::iterator bool> ret=s1.insert(10);
}
3.8.7 pair对组创建

​ 特点:成对出现的数据,利用对组可以返回两个数据

//pair不用包含头文件
//创建对组
int main()
{
    //创建方法一
    pair<string,int>p1("aaa",111);
    //创建方法二
    pair<string,int>p2=make_pair("aaaa",111);
    //获取内容
    p1.first;
    p1.second;
}
3.8.8 set容器排序

​ set容器默认从小到大排序,利用仿函数修改排序规则

class myCompare//仿函数本质上是一个类
{
    bool operator()(int v1,int v2)//重载()操作符
    {
        return v1>v2;
    }
};

int main()
{
    set<int,myCompare>s1;
}

注意:自定义类型(如Person)都需要指定排序规则

3.9 map/multimap容器

3.9.1 map基本概念

​ 特点:

​ map所有元素都是pair

​ pair中第一个元素为键值key,起到索引作用,第二个元素为实际值value

​ 所有元素都会根据元素键值自动排序

​ 本质:

​ map/multimap属于关联式容器,底层是二叉树

​ 优点:

​ 可以根据key值快速找到value值

​ map和multimap区别:

​ map不允许容器有重复key值

​ multimap允许容器有重复key值

3.9.2 map构造和赋值
#include<map>
int main()
{
    //无参构造
    map<int,int>m1;
    //插入
    m1.insert(pair<int,int>(1,10));
    //拷贝构造
    map<int,int>m2(m1);
    //赋值
    map<int,int>m3;
    m3=m2;
    //访问
    int res=m1[20];
 }
3.9.3 map大小和交换
int main()
{
    map<int,int> m1;
    //大小
    m1.size();
    //是否为空
    m1.empty();
    //交换
    map<int,int> m2;
    m1.swap(m2);
}
3.9.4 map插入和删除
int main()
{
    map<int,int>m1;
    //第一种
    map.insert(pair<int,int>(1,10));//插入对组
    //第二种
    map.insert(make_pair(1,10));
    //第三种
    map.insert(map<int,int>::valuetype(1,10));
    //第四种
    m1[4]=10;//不建议,因为如果不存在key为4,则会多出一对pair(4,0)。但可以用[]访问map元素
    //删除
    //删除指定位置
    m1.erase(m1.Begin());
    //按照指定键值删除
    m1.erase(3);
    //删除区间
    m1.erase(m1.Begin(),m1.End());
    //清空
    m1.clear();
}
3.9.5 map查找和统计
int main()
{
    map<int,int>m1;
    
    //按照键值查找,返回迭代器
    map<int,int>::iterstor pos=m1.find(3);
    
    //按照键值统计指定pair个数
    int num=m1.count(3);
}
3.9.6 map容器排序

​ 利用仿函数,改变map排序规则

class myCompare//仿函数本质上是一个类
{
    bool operator()(int v1,int v2)//重载()操作符,括号内是键值的类型
    {
        return v1>v2;
    }
};

int main()
{
     map<int,int,myCompare>m1;
}

4 STL 函数对象

4.1 函数对象

4.1.1 函数对象概念

​ 概念:

重载函数调用操作符的类,其对象被称为函数对象

函数对象使用重载的()时,行为类似函数调用,也叫仿函数

​ 本质:

​ 函数对象(仿函数)是一个类,不是一个函数

4.1.2 函数对象使用

​ 特点:

​ 1)函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值

​ 2)函数对象超出普通函数的概念,函数对象可以有自己的状态

​ 3)函数对象可以作为参数传递

class myAdd
{
	int num;//函数对象超出不同函数的概念,函数对象可以有自己的状态
    myAdd()
    {
        this->num=0;
    }
    //函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
    int operator()(int a,int b)
    {
        return a+b;
        num++;
    }
};

void Print(myAdd myadd)//函数对象可以作为参数传递
{
    //省略内容
}
    
int main()
{
    myAdd myadd;
    myadd(10,10);
    cout<<myadd.num<<endl;
    Print(myadd);
}

4.2 谓词

4.2.1 谓词概念

​ 概念:

返回bool类型的仿函数叫谓词

​ operator()()只接受一个参数,叫一元谓词

​ operator()()接受两个参数,叫二元谓词

//一元谓词
#include<algorithm>//find_if
class myFind
{
    bool operator()(int val)
    {
        return val>5;//查找容器有没有大于5的
    }
};
int main()
{
    vector<int>v1;
    //myFind()匿名函数对象
    vector<int>::iterator it=find_if(v1.begin(),v1.end(),myFind());
    if(it==v1.end())
    {
        //没找到
    }
    else
    {
        //找到
    }
}
//二元谓词
class myCompare
{
    bool operator()(int v1,int v2)
    {
        return v1>v2;
    }
};

int main()
{
    vector<int>v1;
    //myCompare()匿名函数对象
    sort(v1.begin(),b1.end(),myCompare());
}

4.3 内建函数对象

4.3.1 内建函数对象意义

​ 概念:STL内建了一些函数对象

​ 分类:

​ 算术仿函数

​ 关系仿函数

​ 逻辑仿函数

​ 用法:

​ 用法和一般函数完全相同

​ 使用内建函数对象,需要引入头文件#include<functional>

4.3.2 算术仿函数

​ 功能:实现四则运算

#include<functional>
int main()
{
    //取反仿函数
    negate<int>(10);
    //加法仿函数
    plus<int>(10,20);
    //减法仿函数
    minus<int>(20,10);
    //乘法仿函数
    multiplies<int>(10,10);
    //除法仿函数
    divides<int>(10,2);
    //取模%仿函数
    modulus<int>(10,3);
}
4.3.3 关系仿函数
int main()
{
    vector<int>v1;
    sort(v.begin(),v.end(),greater<int>());//大于,即降序排列
    //其他关系仿函数
    //等于
    equal_to<int>();
    //不等于
    not_equal_to<int>();
    //大于等于
    greater_equal<int>();
    //小于
    less<int>();
    //小于等于
    less_equal<int>();
}
4.3.4 逻辑仿函数(不常用)
int main()
{
    vector<bool>v1;
    
    vector<bool>v2;
    v2.resize(v1.size());//搬运前必须要先指定大小
    transform(v1.begin(),v1.end(),v2.begin(),logical_not<bool>());//将容器v1搬运到容器v2中,并执行取反操作
    //其他逻辑仿函数
    //逻辑与
    logical_and<bool>();
    //逻辑或
    logical_or<bool>();
}

5 STL常用算法

概述:

​ 算法主要是由头文件<algorithm>,<functional>,<numeric>组成

5.1 常用遍历算法

5.5.1 for_each
#include<algorithm>
void myPrint1(int val)//普通函数
{
    cout<<val<<" ";
}

class myPrint2//仿函数
{
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
    vector<int>v1;
    
    for_each(v.begin(),v.end(),myPrint1);//普通函数,不用加()
    for_each(v.begin(),v.end(),myPrint2());//仿函数,匿名函数对象,要加()
}
5.1.2 transform

​ 搬运容器到另一个容器

class Transform
{
    int operator()(int val)
    {
        return val+10;//搬运的同时可以进行一些操作
    }
};
int main()
{
    vector<int>v1;
    
    vector<int>v2;
    v2.resize(v1.size());//搬运前必须要先指定大小
    transform(v1.begin(),v1.end(),v2.begin(),Transform());
}

5.2 常用查找算法

5.2.1 find
class Person
{
public:
    Person(int age,int height)
    {
        this->m_age=age;
        this->m_height=height;
    }
    int m_age;
    int m_height;
    //重载== 让底层find知道如何对比自定义类
    bool operator==(const Person &p)//const防止修改
    {
        if(this->m_age==p.m_age&&this->.m_height==p.m_height)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

int main()
{
    vector<Person>v1;
    Person p(111,222);//要找的对象
    vector<Person>::iterator it=find(v1.begin(),v1.end(),p);
    if(it==v1.end())
    {
        //没有找到
    }
    else
    {
        //找到
    }
}
5.2.2 find_if

​ 按条件查找元素

class Person
{
public:
    Person(int age,int height)
    {
        this->m_age=age;
        this->m_height=height;
    }
    int m_age;
    int m_height;
};

class Greater20
{
    bool operator()(const Person &p)//const防止修改
    {
        if(p.m_age>20)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

int main()
{
    vector<Person>v1;
    Person p(111,222);//要找的对象
    vector<Person>::iterator it=find_if(v1.begin(),v1.end(),Greater20());
    if(it==v1.end())
    {
        //没有找到
    }
    else
    {
        //找到
    }
}
5.2.3 adjacent_find

​ 查找相邻重复元素

int main()
{
    vector<int>v1;
    vector<int>::iterator it=adjacent_find(v1,begin(),v1.end());
    if(it==v1.end())
    {
        //没有找到
    }
    else
    {
        //找到
    }
}

​ 注意:如果有两组相同的,就要从第一组的位置到最后在查找一次

5.2.4 binary search(二分查找)

​ 查找指定元素是否存在,返回bool

无序序列不能用

int main()
{
    vector<int>v1;//必须有序
    bool ret=binary_search(v1,begin(),v1.end(),9);
}
5.2.5 count

​ 统计元素个数

class Person
{
public:
    Person(int age,int height)
    {
        this->m_age=age;
        this->m_height=height;
    }
    int m_age;
    int m_height;
    //重载== 让底层find知道如何对比自定义类
    bool operator==(const Person &p)//const防止修改
    {
        if(this->m_age==p.m_age&&this->.m_height==p.m_height)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

int main()
{
    vector<Person>v1;
    Person p(111,222);//要找的对象
    int num=count(v1.begin(),v1.end(),p);
}
5.2.6 count_if

​ 按照条件统计元素个数

class Person
{
public:
    Person(int age,int height)
    {
        this->m_age=age;
        this->m_height=height;
    }
    int m_age;
    int m_height;
};

class Greater20
{
    bool operator()(const Person &p)//const防止修改
    {
        if(p.m_age>20)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

int main()
{
    vector<Person>v1;
    Person p(111,222);//要找的对象
    int num=find(v1.begin(),v1.end(),Greater20());
}

5.3 常用排序算法

5.3.1 sort
int main()
{
    vector<int>v1;
    sort(v1.begin(),v1.end());//默认升序
    sort(v.begin(),v.end(),greater<int>());//大于,即降序排列
}
5.3.2 random_shuffle

​ 重新打乱顺序

int main()
{
    vector<int>v1;
    random_shuffle(v1,begin(),v1.end());
}
5.3.3 merge

​ 两个容器的元素合并,并储存到另一个容器

两个容器必须是有序的

int main()
{
    vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    v2.resize(v1.size()+v2.size);//必须要先指定大小
    merge(v1.begin(),v1.end(),v1.begin(),v2.end(),targetv.begin());
}
5.3.4 reverse

​ 容器内元素反转

int main()
{
    vector<int>v1;
    reverse(v1,begin(),v1.end());
}

5.4 常用拷贝和替换算法

5.4.1 copy
int main()
{
    vector<int>v1;
    vector<int>v2;
    v2.resize(v1.size());
    copy(v1.begin(),v1.end(),v2.begin());
}
5.4.2 replace

​ 将指定范围内指定的旧元素改为新元素

int main()
{
    vector<int>v1;
    replace(v1.begin(),v1.end(),20,2000);//把区间内所有20替换成2000
}
5.4.3 replace_if

​ 将指定范围内满足条件的旧元素改为新元素

class Greater30
{
    bool operator()(int val)
    {
        return val>=30
    }
};

int main()
{
    vector<int>v1;
    replace_if(v1.begin(),v1.end(),Greater30(),3000);//把大于等于30的替换为3000
}
5.4.4 swap

​ 互换两个容器的元素

int main()
{
    vector<int>v1;
    vector<int>v2;
    swap(v1,v2);
}

5.5 常用算术生成算法

​ 属于小型算法,使用时包含头文件#include<numeric>

5.5.1 accumulate
#include<numeric>
int main()
{
    vector<int>v1;
    int total=accumulate(v1.begin(),v1.end(),0);//最后一个参数是起始累加值,有返回值
}
5.5.2 fill

​ 向容器内填充指定元素

int main()
{
    vector<int>v1;
    fill(v1.begin(),v1.end(),100);//用100填充
}

5.6 常用集合算法

5.6.1 set_intersection

​ 求两个容器的交集

两个原容器必须是有序序列

class myPrint
{
public:
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
	vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    targetv.resize(min(v1.size(),v2.size()));//min取最小值,要包含头文件algorithm
    vector<int>::iterator itEnd=set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),targetv.begin());//返回结                                                                                                           束位置
    //遍历
    for_each(targetv.begin(),itEnd,myPrint());//要用itEnd,否则迭代器位置不对,会有多的0
    cout<<endl;
}
5.6.2 set_union

​ 求并集

​ 两个容器必须是有序的

class myPrint
{
public:
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
	vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    targetv.resize(v1.size()+v2.size());
    vector<int>::iterator itEnd=set_union(v1.begin(),v1.end(),v2.begin(),v2.end(),targetv.begin());//返回结束位置
    //遍历
    for_each(targetv.begin(),itEnd,myPrint());//要用itEnd,否则迭代器位置不对,会有多的0
    cout<<endl;
}
5.6.3 set_difference

​ 求两个集合差集

class myPrint
{
public:
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
	vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    targetv.resize(max(v1.size(),v2.size()));//max取最大值,要包含头文件algorithm
    //v1和v2的差集
    vector<int>::iterator itEnd=set_difference(v1.begin(),v1.end(),v2.begin(),v2.end(),targetv.begin());//返回结束		位置
    //遍历
    for_each(targetv.begin(),itEnd,myPrint());//要用itEnd,否则迭代器位置不对,会有多的0
    cout<<endl;
    //v2和v1的差集
    vector<int>::iterator itEnd=set_difference(v2.begin(),v2.end(),v1.begin(),v1.end(),targetv.begin());
}

v1;
replace_if(v1.begin(),v1.end(),Greater30(),3000);//把大于等于30的替换为3000
}




#### 5.4.4 swap

​	互换两个容器的元素

```c++
int main()
{
    vector<int>v1;
    vector<int>v2;
    swap(v1,v2);
}

5.5 常用算术生成算法

​ 属于小型算法,使用时包含头文件#include<numeric>

5.5.1 accumulate
#include<numeric>
int main()
{
    vector<int>v1;
    int total=accumulate(v1.begin(),v1.end(),0);//最后一个参数是起始累加值,有返回值
}
5.5.2 fill

​ 向容器内填充指定元素

int main()
{
    vector<int>v1;
    fill(v1.begin(),v1.end(),100);//用100填充
}

5.6 常用集合算法

5.6.1 set_intersection

​ 求两个容器的交集

两个原容器必须是有序序列

class myPrint
{
public:
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
	vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    targetv.resize(min(v1.size(),v2.size()));//min取最小值,要包含头文件algorithm
    vector<int>::iterator itEnd=set_intersection(v1.begin(),v1.end(),v2.begin(),v2.end(),targetv.begin());//返回结                                                                                                           束位置
    //遍历
    for_each(targetv.begin(),itEnd,myPrint());//要用itEnd,否则迭代器位置不对,会有多的0
    cout<<endl;
}
5.6.2 set_union

​ 求并集

​ 两个容器必须是有序的

class myPrint
{
public:
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
	vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    targetv.resize(v1.size()+v2.size());
    vector<int>::iterator itEnd=set_union(v1.begin(),v1.end(),v2.begin(),v2.end(),targetv.begin());//返回结束位置
    //遍历
    for_each(targetv.begin(),itEnd,myPrint());//要用itEnd,否则迭代器位置不对,会有多的0
    cout<<endl;
}
5.6.3 set_difference

​ 求两个集合差集

class myPrint
{
public:
    void operator()(int val)
    {
        cout<<val<<" ";
    }
};
int main()
{
	vector<int>v1;
    vector<int>v2;
    vector<int>targetv;
    targetv.resize(max(v1.size(),v2.size()));//max取最大值,要包含头文件algorithm
    //v1和v2的差集
    vector<int>::iterator itEnd=set_difference(v1.begin(),v1.end(),v2.begin(),v2.end(),targetv.begin());//返回结束		位置
    //遍历
    for_each(targetv.begin(),itEnd,myPrint());//要用itEnd,否则迭代器位置不对,会有多的0
    cout<<endl;
    //v2和v1的差集
    vector<int>::iterator itEnd=set_difference(v2.begin(),v2.end(),v1.begin(),v1.end(),targetv.begin());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值