本阶段主要针对 C++泛型编程 和STD技术做详细学习,探讨 C++ 更深层次的使用。
一、 模板
模板就是建立通用的模具,大大提高复用性。学习模板是为了会用别人提供的模板。
- C++ 另一种编程思想称为泛型编程,主要利用的技术就是模板
- C++ 提高两种模板机制:函数模板和类模板
1.1 函数模板
1.1.1 函数模板定义
函数模板:建立一个通用函数,其函数返回类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:
template<typename T>     // 注意这里没有分号;
void func(T &a){...}     // 函数声明或定义解释:
- template — 声明创建模板
- typename — 表明其后面的符号是一种数据类型,可以用 class 代替
- T — 通用的数据类型,名称可以替换,通常为大写字母
template<typename T>  // 声明一个模板,告诉编译器 T 为通用数据类型,不要报错
void mySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
int main() {
    float a = 1.1;
    float b = 2.2;
    mySwap(a, b);             // 第一种调用:自动类型推导
    cout << a << endl;
    cout << b << endl;
    int c = 10;
    int d = 20;
    mySwap<int>(c, d);       // 第二种调用:显示指定类型
    cout << c << endl;
    cout << d << endl;
    system("pause");
}
总结:
- 函数模板用关键字 template
- 使用函数模板有两种方式:自动类型推导、显示指定类型
- 模板的目的时为了提高函数复用性,将类型参数化。
1.1.2 函数模板注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型 T,才可使用 
- 模板必须要确定出 T 的数据类型,才可以使用 - template<typename T> // 声明了 T, 紧接着的 test()函数却没有使用 void test() { cout << "test()调用 " << endl; } void mySwap(T &a, T &b){ // 这个会报错 T, 上面的 T 声明仅对 T 声明下面的第一个函数有效 cout << " 交换 " << endl; } int main() { test(); // 这里调用就会报错 test<int>() // 这随意指定一个 int,就没问题了 system("pause"); }
1.1.3 函数模板案例
案例描述:
- 利用函数模板封装一个排序的函数,可以对不同的数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用 char 数组和 int 数组进行测试
template<typename T>
void mySwap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
template<typename T>  
void mySort(T arr[], int len) {
    for (int i = 0; i < len; ++i) {
        int max = i;
        for (int j = i + 1; j < len; ++j) {
            if (arr[max] < arr[j]) {
                max = j;
            }
        }
        if (max != i) {
            mySwap(arr[max], arr[i]);
        }
    }
}
void test() {
    char cha[] = "dhsjas";
    //int cha[] = { 3,4,2,5,1,6,8,7 };
    int len = sizeof(cha) / sizeof(cha[0]);
    mySort(cha, len);
    for (int i = 0; i < len; ++i) {
        cout << cha[i] << " ";
    }
}
int main() {
    test();
    system("pause");
}1.1.4 普通函数与函数模板的区别
普通函数与函数模板区别(主要是是否可以发生隐式类型转换):
- 普通函数调用时,可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
int addtest1(int a, int b) {
    return a + b;
}
template<typename T>
T addtest2(T a, T b) {
    return a + b;
}
int main() {
    int a = 10;
    char c = 'c'; //(int)c = 99
    cout << addtest1(a, c) << endl;
    // 自动类型推导
    //cout << addtest2(a, c) << endl; 报错
    // 显示指定类型
    cout << addtest2<int>(a, c) << endl;
    system("pause");
}
1.1.5 普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先使用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
int test1(int a, int b);
template<typename T>
void test1(T a, T b) {
    cout << " 调用模板函数 " << endl;
}
int main() {
    int a = 10;
    int b = 20;
    // 报错,调用普通函数,然而普通函数只有声明没有实现
    //test1(a, b);
    // 通过空模板参数列表, 强制调用函数模板
    test1<>(a, b) ;
    // 函数模板可以产生更好的匹配,优先调用函数模板
    char c1 = 'a';
    char c2 = 'b';
    test1(c1, c2);
    system("pause");
}1.1.6 模板的局限性 / 具体化的模板
有时候通用模板并不能解决所有问题,这时候可以利用具体化的模板,去解决自定义类型的通用化。
class Person {
public:
    Person(string name, int age) {
        this->m_name = name;
        this->m_age = age;
    }
    string m_name;
    int m_age;
};
template<typename T>
bool compare(T a, T b) {
    if (a == b) {
        return true;
    }
    else {
        return false;
    }
};
// 具体化模板
template<> bool compare(Person &p1, Person &p2) {
    if (p1.m_name == p2.m_name && p1.m_age == p2.m_age) {
        return true;
    }
    else {
        return false;
    }
}
int main() {
    Person p1(" 张三 ", 25);
    Person p2(" 张三 ", 21);
    bool ret = compare(p1, p2);
    system("pause");
}1.2 类模板
1.2.1 类模板语法
类模板作用:建立一个通用类,类中的成员、数据类型可以不具体制定,用一个虚函数类型来代表。
语法:
template<typename T>
class A{...}                   // 类的声明或定义尖括号 <> 里可以定义多个通用数据类型,以逗号隔开。
template<class NameType,class AgeType>
class Person {
public:
    Person(NameType name, AgeType age) {
        this->m_name = name;
        this->m_age = age;
    }
    void show_Person() {
        cout << this->m_name << endl;
        cout << this->m_age << endl;
    }
    NameType m_name;
    AgeType m_age;
};
void test() {
    Person<string, int> p1("Tom", 23);  //<string,int> 叫模板的参数列表,必须指定
    p1.show_Person();
}
int main() {
    test();
    system("pause");
}1.2.2 类模板与函数模板区别
类模板与函数模板区别:
- 类模板没有自动类型推导的使用方式。
- 类模板在模板参数列表中可以有默认参数。
template<class NameType, class AgeType=int>
class Person {
public:
    Person(NameType name, AgeType age) {
        this->m_name = name;
        this->m_age = age;
    }
    NameType m_name;
    AgeType m_age;
};
// 类模板没有自动类型推导的使用方式
void test1() {
    Person p1("Tom", 23);  // 报错,必须指定类型
}
// 类模板在模板参数列表中可以有默认参数
void test2() {
    Person<string> p2("Tomo", 22);  //template 中已经指定了第二个参数的默认类型
}
1.2.3 类模板中的成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建。
- 类模板中的成员函数在调用时才创建。
class Person1 {
public:
    void show1() {
        cout << "Person1 成员函数的调用 " << endl;
    }
};
class Person2 {
public:
    void show2() {
        cout << "Person2 成员函数的调用 " << endl;
    }
};
template<class T> 
class Person {
public:
    T obj;
    // 类模板中的成员函数
    void func1() {
        obj.show1();
    }
    void func2() {
        obj.show2();
    }
};
void test() {
    Person<Person1> p;       
    p.func1();             // 这里并不会报错,调用 func1()只会创建 func1()
    p.func2();             // 报错
}
int main() {
    test();
    system("pause");
}1.2.4 类模板对象做函数参数
一共有三种传入方式:
- 指定传入的类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型模板化进行传递
template <class T1,class T2>
class Person {
public:
    Person(T1 name, T2 age) {
        this->m_name = name;
        this->m_age = age;
    }
    void show() {
        cout << m_name << endl;
        cout << m_age << endl;
    }
    T1 m_name;
    T2 m_age;
};
// 指定传入的类型      
void test1(Person<string, int>&p) {
    p.show();
}
// 参数模板化
template<class T1, class T2>
void test2(Person<T1, T2>& p) {
    p.show();
}
// 整个类模板化
template<class T>
void test3(T &p) {
    p.show();
    cout << typeid(T).name() << endl;  // 输出
}
int main() {
    Person<string, int>p("Tom", 200);
    test1(p);
    Person<string, int>p2("Tomo", 20);
    test2(p2);
    Person<string, int>p3("July", 50);
    test3(p3);
    system("pause");
}使用最广泛的是第一种:指定传入的类型。
1.2.5 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定父类中 T 的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中 T 的类型,子类也需要变为类模板
template <class T>
class Base {
    T m;
};
// 在继承父类时,父类模板必须要指定类型
class Son1 :public Base<int> {...};
// 也可以灵活指定,子类也需要变为类模板
template<class T1,class T2>
class Son2 :public Base<T1> {  
public:
    T2 obj;
    Son2() {
        cout << typeid(T1).name() << endl;
        cout << typeid(T2).name() << endl;
    }
};
void test() {
    Son1 s1;
    Son2<int, char> s2;
}
int main() {
    test();
    system("pause");
}1.2.6 类模板成员函数类外实现
template <class T1, class T2>
class Person {
public:
    Person(T1 name, T2 age);
    void showPerson();
    T1 m_name;
    T2 m_age;
};
// 构造函数类外实现
template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age) {
    this->m_name = name;
    this->m_age = age;
}
// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
    cout << this->m_name << endl;
    cout << this->m_age << endl;
}1.2.7 类模板成员函数类外实现
当我们写类模板时,成员函数的调用时机是在调用阶段,导致文件编写时链接不到
有两种解决方式:
- 解决方式 1:直接包含.cpp 源文件
- 解决方式 2:将声明和实现写到同一个文件中,并更改后缀名为.hpp。hpp 是约定的名称,并不是强制
1.2.8 类模板与友元
全局函数类内实现:直接在类内声明友元即可。
全局函数类外实现:需要提前让编译器知道全局函数的存在。
template <class T1, class T2>
class Person;
// 类外实现
template <class T1, class T2>
void showPerson2(Person<T1, T2> p) {
    cout << p.m_name << endl;
    cout << p.m_age << endl;
}
template <class T1, class T2>
class Person {
    // 全局函数在类内实现
    friend void showPerson(Person<T1, T2> p) {
        cout << p.m_name << endl;
        cout << p.m_age << endl;
    };
    // 全局函数类外实现
    friend void showPerson2<>(Person<T1, T2> p);  // 实现的代码要写在该类的前面
public:
    Person(T1 name, T2 age) {
        this->m_name = name;
        this->m_age = age;
    };
private:
    T1 m_name;
    T2 m_age;
};
void test() {
    Person<string, int> p1("Tom", 25);
    showPerson(p1);   // 全局函数在类内实现
    showPerson2(p1);  // 全局函数在类外实现
}
int main() {
    test();
    system("pause");
}一般建议全局函数模板类内实现,类外实现太复杂了。
1.2.9 类模板案例
实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
首先定义一个数组模板类,文件命名为 Array.hpp:
#include<iostream>
#include<string>
using namespace std;
template<class T>
class Array {
public:
    // 有参构造函数,用于初始化数组容量
    Array(int capacity) {
        this->mSize = 0;
        this->mCapacity = capacity;
        this->mAddress = new T[this->mCapacity];
    }
    // 拷贝构造函数
    Array(const Array& arr) {
        this->mCapacity = arr.mCapacity;
        this->mSize = arr.mSize;
        this->mAddress = new T[this->mCapacity];
        for (int i = 0; i < this->mSize; ++i) {
            this->mAddress[i] = arr.mAddress[i];
        }
    }
    // 重载 = 操作符,深拷贝
    Array& operator=(const Array& arr) {
        if (this->mAddress != NULL) {
            delete[] this->mAddress;
            this->mSize = 0;
            this->mCapacity = 0;
        }
        this->mCapacity = arr.mCapacity;
        this->mSize = arr.mSize;
        this->mAddress = new T[this->mCapacity];
        for (int i = 0; i < this->mSize; ++i) {
            this->mAddress[i] = arr.mAddress[i];
        }
        return *this;
    }
    // 尾部插入数据
    void Push_Back(const T& val) {
        if (this->mCapacity == this->mSize) {
            return;
        }
        this->mAddress[this->mSize] = val;
        this->mSize++;
    }
    // 从尾部删除数据
    void Pop_Back() {
        if (this->mSize == 0) {
            return;
        }
        this->mSize--;  // 逻辑删除,让用户访问不到
    }
    T& operator[](int index) {
        return this->mAddress[index];// 暂未考虑越界的问题
    }
    int getCapacity() {
        return this->mCapacity;
    }
    int getSize() {
        return this->mSize;
    }
    ~Array() {
        if (this->mAddress != NULL) {
            delete[] this->mAddress;
            this->mAddress = NULL;
            this->mCapacity = 0;
            this->mSize = 0;
        }
    }
private:
    T* mAddress;    // 指针指向堆区开辟的真实数组
    int mCapacity;  // 数组容量
    int mSize;      // 数组当前的大小
};测试案例,命名为 ArrayTest.cpp:
#include<iostream>
#include<string>
#include"Array.hpp"
using namespace std;
void printIntArray(Array<int>& arr) {
    for (int i = 0; i < arr.getSize(); ++i) {
        cout << arr[i] << " ";
    }
    cout << endl;
}
// 测试 1
void test1() {
    Array<int> arr1(6);
    Array<int> arr3(100);
    arr1 = arr3;
    for (int i = 0; i < 6; ++i) {
        arr1.Push_Back(i);
    }
    printIntArray(arr1);
    cout << "arr1 的大小:" << arr1.getSize() << endl;
    cout << "arr1 的容量:" << arr1.getCapacity() << endl;
    Array<int> arr2(arr1);
    printIntArray(arr2);
    cout << "arr2 的大小:" << arr2.getSize() << endl;
    cout << "arr2 的容量:" << arr2.getCapacity() << endl;
    arr2.Pop_Back();
    printIntArray(arr2);
    cout << "arr2 的大小:" << arr2.getSize() << endl;
    cout << "arr2 的容量:" << arr2.getCapacity() << endl;
}
// 测试自定义数据类型
class Person {
public:
    Person() {};
    Person(string name, int age) {
        this->m_name = name;
        this->m_age = age;
    }
    string m_name;
    int m_age;
};
void printPersonArray(Array<Person>& arr) {
    for (int i = 0; i < arr.getSize(); ++i) {
        cout << arr[i].m_name + " " << arr[i].m_age << endl;
    }
    cout << endl;
}
// 测试自定义的数据类型
void test2() {
    cout << "\n--------------test2------------" << endl;
    Array<Person> arr(10);
    Person p1(" 孙悟空 ", 999);
    Person p2(" 韩信 ", 24);
    Person p3(" 妲己 ", 23);
    Person p4(" 赵云 ", 26);
    Person p5(" 鲁班 ", 12);
    arr.Push_Back(p1);
    arr.Push_Back(p2);
    arr.Push_Back(p3);
    arr.Push_Back(p4);
    arr.Push_Back(p5);
    printPersonArray(arr);
    cout << "arr 的大小:" << arr.getSize() << endl;
    cout << "arr 的容量:" << arr.getCapacity() << endl;
}
int main() {
    test1();
    test2();
    system("pause");
} 1.3 写模板的坑
推荐把模板的实现和声明都写在头文件!否则编译会报错。
还有一种方式,可以分离声明与实现:
h 头文件:
template <typename T>
T add(const T &a, const T &b);cpp 实现:
#include "add.h"
template <typename T>
T add(const T &a, const T &b)
{
    return a + b;
}
template int add(const int &a, const int &b);最后一行要指定模板的实际类型。
二、STL 初识
2.1 STL 的诞生
- 长久以来,人们一直希望建立一种可以重复利用的东西
- C++ 面向对象编程和泛型编程思想,目的就是复用性的提升
- 大多情况下,数据结构和算法都未有一套标准,导致被迫从事大量重复工作
- 为了建立数据结构和算法的一套标准,诞生了 STL(Standard Template Library, 标准模板库)
2.2 STL 的介绍
- STL 从广义上分为:容器 (container), 算法 (algorithm), 迭代器(iterator)
- 容器 和算法 之间通过 迭代器 进行无缝链接
- STL 几乎所有的代码都采用了模板类或者模板函数
2.3 STL 六大组件
STL 大体分为六大组件,分别是容器,算法,迭代器,仿函数,适配器(配接器),空间配置器
- 容器:各种数据结构,如 vector, list, deque, set, map 等,用来存放数据
- 算法:各种常用的算法,如 sort, find, copy, for_each 等。
- 迭代器:扮演了容器与算法之间的胶合剂
- 仿函数,行为类似函数,可作为算法的某种策略
- 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
- 空间配置器:负责用来空间的配置与管理
2.4 STL 中的容器、算法、迭代器
容器:置物之所也。
STL 容器就是将运用最广泛的一些数据结构实现出来,常用的数据结构:数组,链表,树,栈,队列,集合,映射表等。
这些容器分为序列式容器和关联式容器两种:
- 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
- 关联式容器:二叉树结构,各元素之间没有严格的物理意义上的顺序关系。只能一步步偏移取值,不能跳转。
算法:问题值解法也。
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)。
算法分为:质变算法和非质变算法。
- 质变算法:是指运算过程中,会更改区间元素的内容,例如拷贝,替换,删除等。
- 非质变算法:是指运算过程中,不会更改区间内的元素内容,例如查找,计数,遍历,寻找极值等等。
迭代器:容器和算法之间的粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器。
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针。
迭代器种类:
| 种类 | 功能 | 支持运算 | 
|---|---|---|
| 输入迭代器 | 对数据的只读访问 | 只读,支持 ++,==,!= | 
| 输出迭代器 | 对数据的只写访问 | 只写,支持 ++ | 
| 前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持 ++,==,!= | 
| 双向迭代器 | 读写操作,并能向前向后操作 | 读写,支持 ++,– | 
| 随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持 ++,–,[n],-n,<,<=, >,>= | 
常用的容器迭代器种类为 双向迭代器 和随机访问迭代器
2.5 容器算法迭代器初识
STL 中最常用的容器为 Vector,可以理解为数组。
2.5.1 vector 存放内置数据类型
 容器:vector
算法:for_each
迭代器:vector<int>::interator
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void Mprint(int val) {
    cout << val << endl;
}
void test() {
    vector<int> v;    
    v.push_back(10);  // 插入数据
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);
    // 通过迭代器访问容器中的数据
    vector<int>::iterator itBegin = v.begin();  // 起始迭代器,指向容器的第一个元素
    vector<int>::iterator itEnd = v.end();  // 起始迭代器,指向容器的最后一个元素的下一个位置
    // 第一种遍历方式
    while (itBegin != itEnd) {
        cout << *itBegin << endl;  // 迭代器是一个指针
        itBegin++;
    }
    // 第二种遍历方式
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        cout << *it << endl;
    }
    // 第三种遍历方式,利用 STL 提供的遍历算法
    for_each(v.begin(), v.end(), Mprint);
}
int main() {
    test();
    system("pause");
}2.5.2 vector 存放自定义数据类型
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
class Person {
public:
    Person(string name, int age) {
        this->m_name = name;
        this->m_age = age;
    }
    string m_name;
    int m_age;
};
// 传入自定义的数据类型
void test1() {
    vector<Person> v;
    Person p1("Tom", 23);
    Person p2("Jack", 18);
    Person p3("Judy", 23);
    Person p4("Mark", 23);
    v.push_back(p1);
    v.push_back(p2);
    v.push_back(p3);
    v.push_back(p4);
    for (vector<Person>::iterator it = v.begin(); it != v.end(); ++it) {
        //cout << (*it).m_name << " " << (*it).m_age << endl;  // *it 是 Perosn 类型的数据
        cout << it->m_name << " " << it->m_age << endl; // 推荐这种
    }
}
// 传入自定义的数据类型的指针
void test2() {
    vector<Person*> v;   //v 用来储存指针(指针指向 person 类型)
    Person p1("Tom", 23);
    Person p2("Jack", 18);
    Person p3("Judy", 23);
    Person p4("Mark", 23);
    v.push_back(&p1);
    v.push_back(&p2);
    v.push_back(&p3);
    v.push_back(&p4);
    for (vector<Person*>::iterator it = v.begin(); it != v.end(); ++it) {
        cout << (*it)->m_name << " " << (*it)->m_age << endl;  // *it 是 Perosn 类型的数据    
    }
}
int main() {
    test1();
    test2();
    system("pause");
}2.5.3 vector 容器嵌套容器
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
void test1() {
    vector<vector<int>> v;
    // 创建小容器
    vector<int> v1;
    vector<int> v2;
    vector<int> v3;
    // 向小容器添加数据
    for (int i = 0; i < 3; ++i) {
        v1.push_back(i + 1);
        v2.push_back(i + 2);
        v3.push_back(i + 3);
    }
    // 将小容器插入到大容器中
    v.push_back(v1);
    v.push_back(v2);
    v.push_back(v3);
    // 通过大容器,把所有数据遍历一遍
    for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); ++it) {
        for (vector<int>::iterator jt = (*it).begin(); jt != (*it).end(); ++jt) {
            cout << *jt << " ";
        }
        cout << endl;
    }
}
int main() {
    test1();
    system("pause");
}三、STL 常用容器
STL 中list、vector、map 是三个最常被使用的容器。
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 构造函数
构造函数原型:
- string();// 创建一个空的字符串,例如 string str;
- string(const char* s);// 使用字符串 s 初始化
- string(const string& str);// 使用一个 string 对象初始化另一个 string 对象
- string(int n, char c);// 使用 n 个字符 c 初始化
3.1.3 string 赋值操作
string 赋值操作:即给 string 字符串进行赋值
赋值的函数原型:
- string& operator=(const char* s);//char* 类型字符串,赋值给当前的字符串
- string& operator=(const string& s);// 把字符串 s 赋值给当前的字符串
- string& operator=(char s);// 把字符赋值给当前的字符串
- string& assign(const char* s);// 把字符串 s 赋值给当前的字符串
- string& assign(const char* s, int n);// 把字符串 s 的前 n 个字符赋值给当前的字符串
- string& assign(const string& s);// 把字符串 s 赋值给当前的字符串
- string& assign(int n, char s);// 用 n 个字符 s 赋值给当前的字符串
3.1.4 string 字符串拼接
在字符串末尾拼接字符串
函数原型:
- string& operator+=(const char* str);// 重载 += 操作符
- string& operator+=(const char c);// 重载 += 操作符
- string& operator+=(const string& str);// 重载 += 操作符
- string& append(const char *s);// 把字符串 s 连接到当前字符串结尾
- string& append(const char *s, int n);// 把字符串 s 的前 n 个字符连接到当前字符串结尾
- string& append(const string &s);// 同 operator+=(const string& str)
- string& append(const string &s, int pos, int n);// 字符串 s 中从 pos 开始的 n 个字符连接到字符串结尾
- string& append(int n, char c);// 在字符串结尾拼接 n 个 c
// 字符串拼接
void test01(){
    string str1 = " 我 ";
    // += 拼接
    str1 += " 爱玩游戏 ";
    cout << "str1 = " << str1 << endl;
    string str2 = "LOL DNF";
    str1 += str2;
    cout << "str1 = " << str1 << endl;
    string str3 = "I";
     // append 拼接
    str3.append(" love ");
    str3.append("game abcde", 4);
    //append 指定位置拼接
    str3.append(str2, 4, 3); // 从下标 4 位置开始 ,截取 3 个字符,拼接到字符串末尾
    cout << "str3 = " << str3 << endl;
}
3.1.5 string 查找和替换
功能描述:
- 查找:查找指定字符串是否存在
- 替换:在指定的位置替换字符串
函数原型:
- int find(const string& str, int pos = 0) const;// 查找 str 第一次出现位置, 从 pos 开始查找
- int find(const char* s, int pos = 0) const;// 查找 s 第一次出现位置, 从 pos 开始查找
- int find(const char* s, int pos, int n) const;// 从 pos 位置查找 s 的前 n 个字符第一次位置
- int find(const char c, int pos = 0) const;// 查找字符 c 第一次出现位置
- int rfind(const string& str, int pos = npos) const;// 查找 str 最后一次位置, 从 pos 开始查找
- int rfind(const char* s, int pos = npos) const;// 查找 s 最后一次出现位置, 从 pos 开始查找
- int rfind(const char* s, int pos, int n) const;// 从 pos 查找 s 的前 n 个字符最后一次位置
- int rfind(const char c, int pos = 0) const;// 查找字符 c 最后一次出现位置
- string& replace(int pos, int n, const string& str);// 替换从 pos 开始 n 个字符为字符串 str
- string& replace(int pos, int n,const char* s);// 替换从 pos 开始的 n 个字符为字符串 s
// 查找
void test01(){
    string str1 = "abcdefgde";
    //find 找第一次出现的位置
    int pos = str1.find("de");
    if (pos == -1){
        cout << " 未找到 " << endl;
    }
    else{
        cout << "pos = " << pos << endl;
    }
    //rfind 找最后一次出现的位置
    pos = str1.rfind("de");
    cout << "pos = " << pos << endl;
}
// 替换
void test02(){
    string str1 = "abcdefgde";
    str1.replace(1, 3, "1111");    // 从位置 1 开始的共 3 个字符替换为 1111        
    cout << "str1 = " << str1 << endl;
}3.1.6 string 字符串比较
功能描述:
- 字符串之间的比较
比较方式:
- 字符串比较是按字符的 ASCII 码进行对比
= 返回 0
> 返回 1
< 返回 -1
函数原型:
- int compare(const string &s) const;// 与字符串 s 比较
- int compare(const char *s) const;// 与字符串 s 比较
void test01(){
    string s1 = "hello";
    string s2 = "aello";
    int ret = s1.compare(s2);
    if (ret == 0) {
        cout << "s1 等于 s2" << endl;
    }
    else if (ret > 0)
    {
        cout << "s1 大于 s2" << endl;
    }
    else
    {
        cout << "s1 小于 s2" << endl;
    }
}3.1.7 string 字符存取
string 中单个字符存取方式有两种
- char& operator[](int n);// 通过 [] 方式取字符
- char& at(int n);// 通过 at 方法获取字符
void test01(){
    string str = "hello world";
    // 获取字符
    for (int i = 0; i < str.size(); i++)
    {
        cout << str[i] << " ";
    }
    cout << endl;
    for (int i = 0; i < str.size(); i++)
    {
        cout << str.at(i) << " ";
    }
    cout << endl;
    // 字符修改
    str[0] = 'x';
    str.at(1) = 'x';
    cout << str << endl;
}[]:下标操作符 [] 在使用时不检查索引的有效性,如果下标超出字符的长度范围,会示导致未定义行为。
at:函数 at() 在使用时会检查下标是否有效。如果给定的下标超出字符的长度范围,系统会抛出 out_of_range 异常。
3.1.8 string 插入和删除
功能描述:
- 对 string 字符串进行插入和删除字符操作
函数原型:
- string& insert(int pos, const char* s);// 插入字符串
- string& insert(int pos, const string& str);// 插入字符串
- string& insert(int pos, int n, char c);// 在指定位置插入 n 个字符 c
- string& erase(int pos, int n = npos);// 删除从 Pos 开始的 n 个字符
// 字符串插入和删除
void test01()
{
    string str = "hello";
    //insert 插入
    str.insert(1, "111");
    cout << str << endl;    //
    //erase 删除
    str.erase(1, 3);  // 从 1 号位置开始 3 个字符, 第二个数字可以不写,意为删除从第 1 个到最后一个
    cout << str << endl;
}3.1.9 string 子串
功能描述:
- 从字符串中获取想要的子串
函数原型:
- string substr(int pos = 0, int n = npos) const;// 返回由 pos 开始的 n 个字符组成的字符串
void test01(){
    string str = "abcdefg";
    // 获取子串
    string subStr = str.substr(1, 3);       // 若第二个数字可以不写,意为取从第 1 个到最后一个
    cout << "subStr = " << subStr << endl;
    string email = "hello@sina.com";
    int pos = email.find("@");
    string username = email.substr(0, pos);
    cout << "username: " << username << endl;
}3.2 vector 容器
3.2.1 vector 基本概念
vector 数据结构和数组非常相似,也称为单端数组。
vector 与普通数组区别:数组是静态空间,而 vector 可以动态扩展。数组 array 在定义时必须显式或隐式指定其 size;vector 则不用。
动态扩展:并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝至新空间,释放原空间。
vector 容器的迭代器是支持随机访问的迭代器。
3.2.2 vector 构造函数
函数原型:
- vector<T> v;// 采用模板实现类实现,默认构造函数
- vector(v.begin(), v.end());// 将 v[begin(), end()]区间中的元素拷贝给本身。
- vector(n, elem);// 构造函数将 n 个 elem 拷贝给本身。
- vector(const vector &vec);// 拷贝构造函数。
#include <vector>
void printVector(vector<int>& v) {
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
}
void test01(){
    vector<int> v1; // 无参构造
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
    }
    printVector(v1);
    vector<int> v2(v1.begin(), v1.end());  // 将 v1 的所有数据传给 v2
    printVector(v2);
    vector<int> v3(10, 100);   // 传入 10 个 100 给 V3
    printVector(v3);
    vector<int> v4(v3);  // 拷贝构造
    printVector(v4);
}vector 数组快速声明与初始化:
// 初始化数组长度
vector<int> v1(10);
// 初始化数组大小和值
vector<int> v2(10, 100);
// 直接赋值
vector<int> v3 = {1,2,3,4,5};
// 使用数组初始化
int arr[] = {1,2,3,4};
vector<int> v4(arr, arr+4);
vector<int> v5(std::begin(arr), std::end(arr));3.2.3 vector 赋值操作
函数原型:
- vector& operator=(const vector &vec);// 重载等号操作符
- assign(beg, end);// 将 [beg, end) 区间中的数据拷贝赋值给本身。
- assign(n, elem);// 将 n 个 elem 拷贝赋值给本身。
#include <vector>
void printVector(vector<int>& v) {
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
}
void test01()
{
    vector<int> v1; 
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
    }
    printVector(v1);
    vector<int>v2;
    v2 = v1;                 // = 号赋值
    printVector(v2);
    vector<int>v3;
    v3.assign(v1.begin(), v1.end());    //assign 赋值
    printVector(v3);
    vector<int>v4;
    v4.assign(10, 100);      //assign 赋数值
    printVector(v4);
}3.2.4 vector 容量和大小
函数原型:
- empty();// 判断容器是否为空
- capacity();// 容器的 容量
- size();// 返回容器中 元素的个数
- resize(int num);// 重新指定容器的长度为 num,若容器变长,则以默认值 0 填充新位置。-  // 如果容器变短,则末尾超出容器长度的元素被删除。 
- resize(int num, elem);// 重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。-  // 如果容器变短,则末尾超出容器长度的元素被删除 
void test01()
{
    vector<int> v1;
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
    }
    //printVector(v1);
    //empty 使用
    if (v1.empty())
    {
        cout << "v1 为空 " << endl;
    }
    else
    {
        cout << "v1 不为空 " << endl;
        cout << "v1 的容量 = " << v1.capacity() << endl;
        cout << "v1 的大小 = " << v1.size() << endl;
    }
    //resize 重新指定大小为 15,用 10 补齐
    v1.resize(15, 10);
    //printVector(v1);
    //resize 重新指定大小,超出 5 个元素部分删除
    v1.resize(5);
    //printVector(v1);
}3.2.5 vector 插入和删除
函数原型:
- push_back(ele);// 尾部插入元素 ele
- pop_back();// 删除最后一个元素
- insert(const_iterator pos, ele);// 迭代器指向位置 pos 插入元素 ele
- insert(const_iterator pos, int count,ele);// 迭代器指向位置 pos 插入 count 个元素 ele
- erase(const_iterator pos);// 删除迭代器指向的元素
- erase(const_iterator start, const_iterator end);// 删除迭代器从 start 到 end 之间的元素
- clear();// 删除容器中所有元素
#include <vector>
void test01()
{
    vector<int> v1;
    // 尾插
    v1.push_back(10);
    v1.push_back(20);
    v1.push_back(30);
    v1.push_back(40);
    v1.push_back(50);
    //printVector(v1);
    // 尾删
    v1.pop_back();
    //printVector(v1);
    // 插入
    v1.insert(v1.begin(), 100);  
    //printVector(v1);               //100 10 20 30 40 50
    v1.insert(v1.begin(), 2, 1000);
    //printVector(v1);
    // 删除
    v1.erase(v1.begin());
    printVector(v1);
    // 清空
    v1.erase(v1.begin(), v1.end());
    v1.clear();
    //printVector(v1);
}当 vector 容器为空时,必须采用 push_back()方法来插入元素。但如果 vector 容器初始化有值,可以用随机访问的方法重新赋值:
void test() {
    vector<int> m(10,0);        // 初始化 m 容器为 10 个 0
    // 修改元素
    for (int i = 0; i < 10; ++i) {
        m[i] = i;
    }
    // 查看元素
    for (int num : m) {
        cout << num;
    }
}3.2.6 vector 数据存取
函数原型:
- at(int idx);// 返回索引 idx 所指的数据
- operator[idx];// 返回索引 idx 所指的数据
- front();// 返回容器中第一个数据元素
- back();// 返回容器中最后一个数据元素
#include <vector>
void test01()
{
    vector<int>v1;
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
    }
    for (int i = 0; i < v1.size(); i++)
    {
        cout << v1[i] << " ";
    }
    cout << endl;
    for (int i = 0; i < v1.size(); i++)
    {
        cout << v1.at(i) << " ";
    }
    cout << endl;
    cout << "v1 的第一个元素为: " << v1.front() << endl;
    cout << "v1 的最后一个元素为: " << v1.back() << endl;
}3.2.7 vector 互换容器
函数原型:
- swap(vec);// 将 vec 与本身的元素互换
void test01()
{
    vector<int>v1;
    for (int i = 0; i < 10; i++)
    {
        v1.push_back(i);
    }
    printVector(v1);
    vector<int>v2;
    for (int i = 10; i > 0; i--)
    {
        v2.push_back(i);
    }
    printVector(v2);
    // 互换容器
    cout << " 互换后 " << endl;
    v1.swap(v2);
    printVector(v1);
    printVector(v2);
}当然 swap 函数更重要的作用是:收缩容量,节省空间
void test02()
{
    vector<int> v;
    for (int i = 0; i < 10000; i++) {
        v.push_back(i);
    }
    cout << "v 的容量为:" << v.capacity() << endl;   // 输出 12138
    cout << "v 的大小为:" << v.size() << endl;       // 输出 10000
    v.resize(3);
    cout << "v 的容量为:" << v.capacity() << endl;   // 输出 12138
    cout << "v 的大小为:" << v.size() << endl;       // 输出 3
    // 收缩容量(内存)
    vector<int>(v).swap(v); //vector<int>(v)匿名对象
    cout << "v 的容量为:" << v.capacity() << endl;   // 输出 3
    cout << "v 的大小为:" << v.size() << endl;       // 输出 3
}3.2.8 vector 预留空间
减少 vector 在动态扩展容量时的扩展次数。
函数原型:
- reserve(int len);// 容器预留 len 个元素长度,预留位置不初始化,元素不可访问。
void test01()
{
    vector<int> v;
    // 预留空间
    v.reserve(100000);  // 加与不加这行,后面计算的 num 有差别
    int num = 0;
    int* p = NULL;
    for (int i = 0; i < 100000; i++) {
        v.push_back(i);
        if (p != &v[0]) {
            p = &v[0];
            num++;
        }
    }
    cout << "num:" << num << endl;  // 输出:1
}3.3 Deque 容器
3.3.1 deque 容器基本概念
deque(音 /dɛk/)容器是一种双端数组,可以对头部和尾部进行插入删除操作。
deque 与 vector 区别:
- vector 对于头部的插入和删除效率低下,数据量越大,效率越低
- deque 相对而言,对头部的插入删除速度会比 vector 快
- vector 访问元素时的速度会比 deque 快,这和内部实现有关。
deque 内部工作原理:
deuqe 内部有个中控器,维护每段缓冲区中的内容,缓冲区存放真实数据。
中控器维护的是每个缓冲区的地址,使得使用 deque 时像一片连续的内存空间。
deque 容器的迭代器是支持随机访问的迭代器。
3.3.2 deque 构造函数
函数原型:
- deque<T>deqT; // 默认构造形式
- deque(beg, end);// 构造函数将 [beg, end) 区间中的元素拷贝给本身。
- deque(n, elem);// 构造函数将 n 个 elem 拷贝给本身。
- deque(const deque &deq);// 拷贝构造函数
void printDeque(const deque<int>& d)
{
    for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {  // 注意这里的 const_iterator,只读迭代器
        cout << *it << " ";
    }
    cout << endl;
}
void test01() {
    deque<int> d1; 
    for (int i = 0; i < 10; i++)
    {
        d1.push_back(i);
    }
    printDeque(d1);
    deque<int> d2(d1.begin(), d1.end());
    printDeque(d2);
    deque<int>d3(10, 100);
    printDeque(d3);
    deque<int>d4 = d3;
    printDeque(d4);
}总结:deque 容器和 vector 容器的构造方式几乎一致,灵活使用即可
3.3.3 deque 赋值操作
函数原型:
- deque& operator=(const deque &deq);// 重载等号操作符
- assign(beg, end);// 将 [beg, end) 区间中的数据拷贝赋值给本身。
- assign(n, elem);// 将 n 个 elem 拷贝赋值给本身。
void test01()
{
    deque<int> d1;
    for (int i = 0; i < 10; i++)
    {
        d1.push_back(i);
    }
    printDeque(d1);
    deque<int>d2;
    d2 = d1;                //= 号赋值
    printDeque(d2);
    deque<int>d3;
    d3.assign(d1.begin(), d1.end());   //assign 赋值
    printDeque(d3);
    deque<int>d4;
    d4.assign(10, 100);
    printDeque(d4);
}3.3.4 deque 大小操作
函数原型:
- deque.empty();// 判断容器是否为空
- deque.size();// 返回容器中元素的个数
- deque.resize(num);// 重新指定容器的长度为 num, 若容器变长,则以默认值填充新位置。-  // 如果容器变短,则末尾超出容器长度的元素被删除。 
- deque.resize(num, elem);// 重新指定容器的长度为 num, 若容器变长,则以 elem 值填充新位置。-  // 如果容器变短,则末尾超出容器长度的元素被删除。 
其用法与 vector 完全一样。
3.3.5 deque 插入和删除
函数原型:
两端插入操作:
- push_back(elem);// 在容器尾部添加一个数据
- push_front(elem);// 在容器头部插入一个数据
- pop_back();// 删除容器最后一个数据
- pop_front();// 删除容器第一个数据
指定位置操作:
- insert(pos,elem);// 在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。
- insert(pos,n,elem);// 在 pos 位置插入 n 个 elem 数据,无返回值。
- insert(pos,beg,end);// 在 pos 位置插入 [beg,end) 区间的数据,无返回值。
- clear();// 清空容器的所有数据
- erase(beg,end);// 删除 [beg,end) 区间的数据,返回下一个数据的位置。
- erase(pos);// 删除 pos 位置的数据,返回下一个数据的位置。
用法类似 vector。比 vector 多的是可以进行头部插入和删除。
3.3.6 deque 数据存取
函数原型:
- at(int idx);// 返回索引 idx 所指的数据
- operator[];// 返回索引 idx 所指的数据
- front();// 返回容器中第一个数据元素
- back();// 返回容器中最后一个数据元素
其用法与 vector 完全一样。
3.3.7 deque 排序
算法:
- sort(iterator beg, iterator end)// 对 beg 和 end 区间内元素进行排序
#include <deque>
#include <algorithm>   // 标准算法库
void test01(){
    deque<int> d;
    d.push_back(10);
    d.push_back(20);
    d.push_front(100);
    d.push_front(200);
    printDeque(d);
    // 排序
    sort(d.begin(), d.end());
    printDeque(d);
}vector 也支持用 sort 进行排序。总结一句话:只要是支持随机访问的迭代器容器都可以用 sort 进行排序。
3.4 案例 - 评委打分
3.4.1 案例描述
有 5 名选手,选手 ABCDE,10 个评委分别对每一位选手打分,去除最高分,去除评委中最低分,取平均分。
实现步骤:
- 创建五名选手,放到 vector 容器中
- 遍历 vector 容器,取出来每一位选手,执行 for 循环,可以把 10 个评分打分存到 deque 容器中
- sort 算法对 deque 容器中分数排序,去除最高分和最低分
- deque 容器遍历一遍,累加总分
- 获取平均分
3.4.2 代码实现
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include<vector>
#include<algorithm>
#include <deque>
class Person {
public:
    Person(string name, int score) {
        this->m_Name = name;
        this->m_score = score;
    }
    string m_Name;
    int m_score;
};
void creatPerson(vector<Person>& v) {
    string seed = "ABCDE";
    for (int i = 0; i < seed.size(); ++i) {
        string name = " 选手 " ;
        name += +seed[i];
        int score = 0;
        Person p(name, score);
        v.push_back(p);
    }
}
void setScore(vector<Person>& v) {
    for (vector<Person>::iterator it = v.begin(); it != v.end(); ++it) {
        deque<int> d;  // 用来存放 10 个分数
        for (int i = 0; i < 10; ++i) {
            int score = rand() % 40 + 61;
            d.push_back(score);
        }
        sort(d.begin(), d.end());
        d.pop_back();
        d.pop_front();
        // 计算平均分
        int sum = 0;
        for (deque<int>::iterator dit = d.begin(); dit != d.end(); ++dit) {
            sum += *dit;
        }
        int avg = sum / d.size();
        it->m_score = avg;
    }
}
int main() {
    srand((unsigned int)time(NULL)); // 随机数种子
    vector<Person> v;
    creatPerson(v);
    setScore(v);
    for (vector<Person>::iterator it = v.begin();it!=v.end();++it) {
        cout << it->m_Name << " " << it->m_score << endl;
    }
    system("pause");
    return 0;
}3.5 stack 容器
3.5.1 基本概念
stack(栈)容器是一种先进后出的数据结构容器,它只有一个出口。

栈内元素只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为。
栈中进入数据称为—入栈push
栈中弹出数据称为—出栈pop
3.5.2 stack 常用接口
构造函数:
- stack<T> stk// 采用模板类实现,stack 对象的默认构造形式
- stack(const stack &stk)// 拷贝构造函数
赋值操作:
- stack& operator=(const stack &stk)// 重载等号操作符
数据存取:
- push(elem);// 向栈顶添加元素
- pop();// 从栈顶移除元素
- top();// 返回栈顶元素
大小操作:
- empty();// 判断栈是否为空
- size();// 返回栈的大小
3.6 queue 容器
3.6.1 基本概念
queue 是一种先进先出的数据结构,它有两个出口:

队列容器允许从一端新增元素,从另一端移除元素。犹如人,嘴巴进,屁股出。
队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为
队列中进数据称为—入队push
队列中出数据称为—出队pop
3.6.2 常用接口
构造函数:
- queue<T> que;//queue 采用模板类实现,queue 对象默认构造形式
- queue(const queue &que);// 拷贝构造函数
赋值操作:
- queue& operator=(const queue &que);// 重载等号操作符
数据存取:
- push(elem);// 往队尾添加元素
- pop();// 从队头移除第一个元素
- back();// 返回最后一个元素
- front();// 返回第一个元素
大小操作:
- empty();// 判断容器是否为空
- size();// 返回容器的大小
3.7 list 容器
3.7.1 基本概念
list 容器的存储方式为链式存储。
链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。
链表的组成:链表由一系列结点组成。
结点的组成:一个是存储数据元素的数据域,另一个是储存下一个结点地址的指针域。
 
STL 中的链表是一个 双向循环 链表。
- 双向 是指每一个结点有两个指针,一个指针 next 指向下一个结点的地址,一个指针 prev 指向上一个结点的地址;
- 循环 是指第一个结点 prev 指向最后一个结点的地址,最后一个结点 next 指向第一个结点的地址。

由于链表的存储方式并不是连续的内存空间,因此链表 list 中的迭代器只支持前移和后移,属于 双向迭代器。
list 的优点:
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
list 的缺点:
- 链表灵活,但是空间(指针域)和时间(遍历)额外耗费较大。
List 有一个重要的性质,插入和删除操作都不会造成原有 list 迭代器的失效,这在 vector 是不成立的。
3.7.2 list 构造函数
函数原型:
- list<T> lst;// list 采用采用模板类实现, 对象的默认构造形式:
- list(beg,end);// 构造函数将 [beg, end) 区间中的元素拷贝给本身。
- list(n,elem);// 构造函数将 n 个 elem 拷贝给本身。
- list(const list &lst);// 拷贝构造函数。
#include<list>
void printList(const list<int>&L) {
    for (list<int>::const_iterator it = L.begin(); it != L.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
}
void test1() {
    // 创建 list 容器 --- 默认构造
    list<int> L1;
    L1.push_back(10);
    L1.push_back(20);
    L1.push_back(30);
    L1.push_back(40);
    printList(L1);
    // 创建 list 容器 --- 区间方式构造
    list<int> L2(L1.begin(),L1.end());
    // 创建 list 容器 --- 拷贝构造
    list<int> L3(L2);
    // 创建 list 容器 ---n 个 elem
    list<int> L4(10,100);
    printList(L4);
}
int main() {
    test1();
    system("pause");
    return 0;
}3.7.3 list 赋值和交换
函数原型:
- assign(beg, end);// 将 [beg, end) 区间中的数据拷贝赋值给本身。
- assign(n, elem);// 将 n 个 elem 拷贝赋值给本身。
- list& operator=(const list &lst);// 重载等号操作符
- swap(lst);// 将 lst 与本身的元素互换。
3.7.4 list 大小操作
函数原型:
- size();// 返回容器中元素的个数
- empty();// 判断容器是否为空
- resize(num);// 重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。-  // 如果容器变短,则末尾超出容器长度的元素被删除。 
- resize(num, elem);// 重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置。变短则删除。
3.7.5 list 插入和删除
函数原型:
- push_back(elem);// 在容器尾部加入一个元素
- pop_back();// 删除容器中最后一个元素
- push_front(elem);// 在容器开头插入一个元素
- pop_front();// 从容器开头移除第一个元素
- insert(pos,elem);// 在 pos 位置插 elem 元素的拷贝,返回新数据的位置。
- insert(pos,n,elem);// 在 pos 位置插入 n 个 elem 数据,无返回值。
- insert(pos,beg,end);// 在 pos 位置插入 [beg,end) 区间的数据,无返回值。
- clear();// 移除容器的所有数据
- erase(beg,end);// 删除 [beg,end) 区间的数据,返回下一个数据的位置。
- erase(pos);// 删除 pos 位置的数据,返回下一个数据的位置。
- remove(elem);// 删除容器中所有与 elem 值匹配的元素。
3.7.6 list 数据存取
函数原型:
- front();// 返回第一个元素。
- back();// 返回最后一个元素。
3.7.7 list 反转和排序
函数原型:
- reverse();// 反转链表
- sort();// 链表排序,升序
#include<list>
void printList(const list<int>&L) {
    for (list<int>::const_iterator it = L.begin(); it != L.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
}
bool myCompare(int v1, int v2) {
    return v1 > v2;
}
void test1() {
    list<int> L1;
    L1.push_back(20);
    L1.push_back(10);
    L1.push_back(40);
    L1.push_back(30);
    L1.sort();  // 默认升序排列
    printList(L1);
    L1.sort(myCompare);   // 降序排列
    printList(L1);
}
int main() {
    test1();
    system("pause");
    return 0;
}3.7.8 排序案例
#include<list>
class Person {
public:
    Person(string name, int age, int height) {
        this->m_Name = name;
        this->m_Age = age;
        this->m_Height = height;
    }
    string m_Name;
    int m_Age;
    int m_Height;
};
void printList(const list<Person>&L) {
    for (list<Person>::const_iterator it = L.begin(); it != L.end(); ++it) {
        cout << it->m_Name << " ";
        cout << it->m_Age << " ";
        cout << it->m_Height << endl;
    }
}
bool sortRule(Person &p1, Person &p2) {
    if (p1.m_Age == p2.m_Age) {
        return p1.m_Height > p2.m_Height;
    }
    return p1.m_Age < p2.m_Age;
}
void test1() {
    list<Person> L1;
    Person p1(" 张飞 ", 23, 178);
    Person p2(" 周瑜 ", 28, 175);
    Person p3(" 孙权 ", 12, 132);
    Person p4(" 曹操 ", 34, 175);
    Person p5(" 关羽 ", 25, 182);
    Person p6(" 刘备 ", 28, 180);
    L1.push_back(p1);
    L1.push_back(p2);
    L1.push_back(p3);
    L1.push_back(p4);
    L1.push_back(p5);
    L1.push_back(p6);
    printList(L1);
    cout << endl;
    L1.sort(sortRule);
    printList(L1);
}对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序。
高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂。
3.8 set/multiset 容器
3.8.1 基本概念
集合容器,所有元素在插入时会自动被排序。set/multiset 属于 关联式容器,底层结构用二叉树实现。
set 和 multiset 区别:
- set 不允许容器中有重复的元素,multiset 允许容器中有重复的元素
- set 插入数据的同时会返回插入结果,表示插入是否成功;multiset 无返回值
set 和 multiset 的头文件都是#include<set>
构造函数
- set<T> st;// 默认构造函数:
- set(const set &st);// 拷贝构造函数
赋值
- set& operator=(const set &st);// 重载等号操作符
3.8.2 set 大小和交换
函数原型:
- size();// 返回容器中元素的数目
- empty();// 判断容器是否为空
- swap(st);// 交换两个集合容器
3.8.3 set 插入和删除
函数原型:
- insert(elem);// 在容器中插入元素。
- clear();// 清除所有元素
- erase(pos);// 删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
- erase(beg, end);// 删除区间 [beg,end) 的所有元素 ,返回下一个元素的迭代器。
- erase(elem);// 删除容器中值为 elem 的元素。
3.8.5 set 查找和统计
函数原型:
- find(key);// 查找 key 是否存在, 若存在,返回该键的元素的迭代器;若不存在,返回 set.end();
- count(key);// 统计 key 的元素个数(整型 int)
#include<set>
void test1() {
    set<int> s1;
    s1.insert(20);
    s1.insert(50);
    s1.insert(10);
    s1.insert(40);
    s1.insert(30);
    set<int>::iterator pos = s1.find(30);
    if (pos != s1.end()) {
        cout << " 找到 " << endl;
    }
    else {
        cout << " 没找到 " << endl;
    }
    int num = s1.count(10);
}3.8.6 set 返回值
set 插入数据的同时会返回插入结果,表示插入是否成功。返回值是一个 pair 类型的数据
#include<set>
void test1() {
    set<int> s1;
    pair<set<int>::iterator, bool> ret = s1.insert(20);
    if (ret.second) {
        cout << " 插入成功!" << endl;
    }
    else {
        cout << " 插入失败!" << endl;
    }
    pair<set<int>::iterator, bool> ret2 = s1.insert(20);
    if (ret2.second) {
        cout << " 插入成功!" << endl;
    }
    else {
        cout << " 插入失败!" << endl;
    }
}3.8.7 pair 对组创建
成对出现的数据,利用对组可以返回两个数据。
两种创建方式:
- pair<type, type> p (value1, value2);
- pair<type, type> p = make_pair(value1, value2);
pair<string, int> p("Tom", 20);
cout << " 姓名: " <<  p.first << " 年龄: " << p.second << endl;3.8.8 set 容器排序
学习目标:
- set 容器默认排序规则为从小到大,掌握如何改变排序规则
主要技术点:
- 利用仿函数,可以改变排序规则
示例一:内置的数据类型排序
#include<set>
class SortRule {
public:
    bool operator()(int v1, int v2) const {       // 这里加个 const,不然 vs2019 会报错
        return v1 > v2;
    }
};
void printSet(set<int, SortRule>&s) {
    for (set<int, SortRule>::iterator it = s.begin(); it != s.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
}
void test1() {
    set<int, SortRule> s1;
    s1.insert(20);
    s1.insert(30);
    s1.insert(10);
    s1.insert(50);
    s1.insert(40);
    printSet(s1);
}示例二:自定义数据类型排序
#include<set>
class Person {
public:
    Person(string name, int age, int height) {
        this->m_Name = name;
        this->m_Age = age;
        this->m_Height = height;
    }
    string m_Name;
    int m_Age;
    int m_Height;
};
class SortRule {
public:
    bool operator()(const Person& p1, const Person& p2) const{
        return p1.m_Age > p2.m_Age;
    }
};
void printSet(set<Person, SortRule>&s) {
    for (set<Person, SortRule>::iterator it = s.begin(); it != s.end(); ++it) {
        cout << it->m_Name << " ";
        cout << it->m_Age << " ";
        cout << it->m_Height << endl;
    }
}
void test1() {
    set<Person, SortRule> s1;
    Person p1(" 张飞 ", 25, 180);
    Person p2(" 周瑜 ", 26, 175);
    Person p3(" 刘备 ", 28, 179);
    Person p4(" 孙权 ", 16, 158);
    Person p5(" 关羽 ", 27, 182);
    s1.insert(p1);
    s1.insert(p2);
    s1.insert(p3);
    s1.insert(p4);
    s1.insert(p5);
    printSet(s1);
}3.9 map/multimap 容器
3.9.1 map 基本概念
map/multimap 属于关联式容器,底层结构是用二叉树实现。
- map 中所有元素都是 pair
- pair 中第一个元素为 key(键值),起索引作用,第二个元素为 value(实值)
- 所有元素都会根据元素的键值自动排序
优点:
- 可以根据 key 值快速找到 value 值
map/multimap 区别:
- map 不允许容器中有重复 key 值元素。
- multimap 允许容器中有重复 key 值元素。
3.9.2 map 构造和赋值
函数原型:
构造:
- map<T1, T2> mp;//map 默认构造函数:
- map(const map &mp);// 拷贝构造函数
赋值:
- map& operator=(const map &mp);// 重载等号操作符
#include<map>
void printMap(map<int, int>& m) {
    for (map<int, int>::iterator it = m.begin(); it != m.end(); ++it) {
        cout << (*it).first << " " << (*it).second << endl;
    }
}
void test1() {
    // 默认构造
    map<int, int>m;
    m.insert(pair<int,int>(1, 10));
    m.insert(pair<int, int>(4, 40));
    m.insert(pair<int, int>(3, 30));
    m.insert(pair<int, int>(2, 200));
    printMap(m);
    // 拷贝构造
    map<int, int>m2(m);
    printMap(m2);
    // 赋值
    map<int, int>m3 = m;
    printMap(m3);
    // 按照 key 取值
    cout << m[2] << endl;  
    // 多层 map
    map<string, map<int, int>> mm;
    mm.insert(make_pair("No1", m));   //mm["No1"]=m
}map 取值有三种:[],find,at
- find 有就是有,没有就是没有,需要判断 find 的返回结果,才知道有没有。 
- [] 不管有没有,都是有。因为没有就是 0,字符串就是空。反正就是给它是初始值。如果原先不存在该 key,则插入,如果存在,则覆盖插入。 
- at 用于取值,但它是进行越界检测,这会损失效率。 - 相比于 find,能少敲键盘 - 相比于[],更安全 - 性能会有损失 - 所以常用于待查找的 key 都应该存在的情况,如果查找的 key 不存在,则说明程序有问题了。 
以上三种虽然都能达到取值的目的,但用法上的些微差别可能导致使用上的 bug,所以一般性的建议如下:
如果纯粹是查找取值,不要用 [] 操作符,因为它会自动创建不存在的元素
正因为上面的原因,[]仅用于非 const 的情形下。在 const map 下使用 [] 会导致编译期错误
如果不确定待查找的值是否存在,且不存在也是合理的,则使用 find,判断返回值是否是 end()
如果待查找的值肯定存在,如果不存在就是逻辑上的错误,则使用 at,处理 out_of_range 异常
所以,对 map 根据 key 取值,不要用[]!!!
3.9.3 map 大小和交换
函数原型:
- size();// 返回容器中元素的数目
- empty();// 判断容器是否为空
- swap(st);// 交换两个集合容器
3.9.4 map 插入和删除
函数原型:
- insert(elem);// 在容器中插入元素。
- clear();// 清除所有元素
- erase(pos);// 删除 pos 迭代器所指的元素,返回下一个元素的迭代器。
- erase(beg, end);// 删除区间 [beg,end) 的所有元素 ,返回下一个元素的迭代器。
- erase(key);// 删除容器中值为 key 的元素。
3.9.5 map 查找和统计
函数原型:
- find(key);// 查找 key 是否存在,若存在,返回该键的元素的迭代器;若不存在,返回 map.end();
- count(key);// 统计 key 的元素个数,返回 int 整型数据
#include<map>
void test1() {
    map<int, int>m;
    m.insert(make_pair(1, 10));
    m.insert(make_pair(2, 20));
    m.insert(make_pair(3, 30));
    m.insert(make_pair(4, 40));
    map<int, int>::iterator pos = m.find(2);
    cout << pos->second << endl;
}3.9.6 map 容器排序
map 排序函数与 set 类似。
class SortRule {
public:
    bool operator()(int v1, int v2) const {       // 这里加个 const,不然 vs2019 会报错
        return v1 > v2;
    }
};
void test1() {
    map<int, int, SortRule> m;    //SortRule 指明按照从大到小排列
    m.insert(make_pair(1, 10));
    m.insert(make_pair(2, 20));
    m.insert(make_pair(3, 30));
    m.insert(make_pair(4, 40));
    m.insert(make_pair(5, 50));
}3.10 案例 - 员工分组
3.10.1 案例描述
- 公司今天招聘了 10 个员工(ABCDEFGHIJ),10 名员工进入公司之后,需要指派员工在那个部门工作
- 员工信息有: 姓名 工资组成;部门分为:策划、美术、研发
- 随机给 10 名员工分配部门和工资
- 通过 multimap 进行信息的插入 key(部门编号) value(员工)
- 分部门显示员工信息
3.10.2 代码实现
#include<vector>
#include<map>
class Worker {
public:
    string m_Name;
    int m_Salary;
};
void createWorker(vector<Worker>& v) {
    string nameseed = "ABCDEFGH";
    for (int i = 0; i < nameseed.size(); ++i) {
        Worker worker;
        worker.m_Name = " 员工 ";
        worker.m_Name += nameseed[i];
        worker.m_Salary = rand() % 10000 + 10000;
        v.push_back(worker);
    }
}
void setGroup(vector<Worker>& v, multimap<int, Worker>& m) {
    for (vector<Worker>::iterator it = v.begin(); it != v.end(); ++it) {
        int dpart = rand() % 3 + 1;
        m.insert(pair<int, Worker>(dpart, *it));
    }
}
void showWorkerByGroup(multimap<int, Worker>& m) {
    for (int i = 1; i < 4; ++i) {
        switch (i)
        {
        case 1:
            cout << " 策划部门员工:" << endl;
            break;
        case 2:
            cout << " 美术部门员工:" << endl;
            break;
        case 3:
            cout << " 研发部门员工:" << endl;
            break;
        default:
            break;
        }
        multimap<int, Worker>::iterator pos = m.find(i);
        int num = m.count(i);
        int index = 0;
        for (; pos != m.end() && index < num; ++pos, index++) {
            cout << pos->second.m_Name << " " << pos->second.m_Salary << endl;
        }
        cout << endl;
    }
}
void test1() {
    srand((unsigned int)time(NULL));
    vector<Worker> vWorker;
    createWorker(vWorker);
    multimap<int, Worker>mWorker;
    setGroup(vWorker, mWorker);
    showWorkerByGroup(mWorker);
    /*for (vector<Worker>::iterator it = vWorker.begin(); it != vWorker.end(); ++it) {
        cout << it->m_Name << " " << it->m_Salary << endl;
    }*/    
}四、STL- 函数对象
4.1 函数对象
4.1.1 函数对象概念
概念:
- 重载函数调用操作符的类,其对象常称为函数对象。
- 函数对象使用重载的 () 时,行为类似函数调用,也叫仿函数。
本质:函数对象(仿函数)是一个类,不是一个函数。
4.1.2 函数对象使用
特点:
- 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值 - // 函数对象(仿函数) class MyAdd { public: int operator()(int v1, int v2) { return v1 + v2; } }; void test1() { MyAdd add; int ret = add(10, 20); // 类对象的调用类似函数 cout << ret << endl; }
- 函数对象超出普通函数的概念,函数对象可以有自己的状态 - class MyPrint { public: MyPrint() { this->count = 0; } void operator()(string test) { cout << test << endl; this->count++; } int count; }; void test1() { MyPrint print; print("hello world"); print("hello world"); print("hello world"); cout << "print 调用次数:" << print.count << endl; }- 这里仿函数可以记录自己调用的次数 
- 函数对象可以作为参数传递 - class MyPrint { public: MyPrint() { this->count = 0; } void operator()(string test) { cout << test << endl; this->count++; } int count; }; void doPrint(MyPrint& mp, string test) { mp(test); } void test1() { MyPrint print; doPrint(print, "Hello C++"); //here }
4.2 谓词 Pred
返回 bool 类型的仿函数称为谓词:
- 如果 operator()接受一个参数,那么就叫一元谓词
- 如果 operator()接受两个参数,那么就叫二元谓词
#include <vector>
#include <algorithm>
//1. 一元谓词
struct GreaterFive{
    bool operator()(int val) {
        return val > 5;
    }
};
void test01() {
    vector<int> v;
    for (int i = 0; i < 10; i++)
    {
        v.push_back(i);
    }
    // 利用 find_if 标准算法
    vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());
    if (it == v.end()) {
        cout << " 没找到!" << endl;
    }
    else {
        cout << " 找到:" << *it << endl;
    }
}
// 二元谓词
class MyCompare
{
public:
    bool operator()(int num1, int num2)
    {
        return num1 > num2;
    }
};
void test(){
    vector<int> v;
    v.push_back(10);
    v.push_back(20);
    sort(v.begin(), v.end(), MyCompare);
}4.3 内建函数对象
4.3.1 基本概念
STL 内建了一些函数对象,其中包括:
- 算术仿函数
- 关系仿函数
- 逻辑仿函数
用法:
- 这些仿函数所产生的对象,用法和一般函数完全相同
- 使用内建函数对象,需要引入头文件#include<functional>
4.3.2 算术仿函数
功能描述:
- 实现四则运算
- 其中 negate 是一元运算,其他都是二元运算
仿函数原型:
- template<class T> T plus<T>// 加法仿函数
- template<class T> T minus<T>// 减法仿函数
- template<class T> T multiplies<T>// 乘法仿函数
- template<class T> T divides<T>// 除法仿函数
- template<class T> T modulus<T>// 取模仿函数
- template<class T> T negate<T>// 取反仿函数
#include<functional>
void test1() {
    negate<int> ne;               // 取反
    cout << ne(50) << endl;
    plus<int> p;                  // 加法
    cout << p(10, 20) << endl;
}4.3.3 关系仿函数
功能描述:
- 实现关系对比
仿函数原型:
- template<class T> bool equal_to<T>// 等于
- template<class T> bool not_equal_to<T>// 不等于
- template<class T> bool greater<T>// 大于
- template<class T> bool greater_equal<T>// 大于等于
- template<class T> bool less<T>// 小于
- template<class T> bool less_equal<T>// 小于等于
#include<algorithm>
#include<vector>
#include<functional>
void test1() {
    vector<int> v;
    v.push_back(10);
    v.push_back(30);
    v.push_back(20);
    sort(v.begin(), v.end(), greater<int>());    // 内建的 greater<int>()函数对象,改变默认的排序方式
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        cout << *it << endl;
    }
}4.3.4 逻辑仿函数
功能描述:
- 实现逻辑运算
函数原型:
- template<class T> bool logical_and<T>// 逻辑与
- template<class T> bool logical_or<T>// 逻辑或
- template<class T> bool logical_not<T>// 逻辑非
#include<algorithm>
#include<vector>
#include<functional>
void test1() {
    vector<bool> v;
    v.push_back(true);
    v.push_back(false);
    v.push_back(true);
    // 利用逻辑非,将容器 v 搬运到容器 v2 中,并执行取反操作
    vector<bool> v2;
    v2.resize(v.size());
    transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());
    for (vector<bool>::iterator it = v2.begin(); it != v2.end(); ++it) {
        cout << *it << endl;
    }
}此部分内容仅做了解即可,实际使用较少
五、STL 常用算法
概述:
- 算法主要是由头文件 - <algorithm>- <functional>- <numeric>组成。
- <algorithm>是所有 STL 头文件中最大的一个,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等
- <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数
- <functional>定义了一些模板类, 用以声明函数对象。
5.1 常用遍历算法
算法简介:
- for_each// 遍历容器
- transform// 搬运容器到另一个容器中
这两个算法包含在 <algorithm> 中。
5.1.1 for_each
功能描述:
- 实现遍历容器
函数原型:
- for_each(iterator_beg, iterator_end, _func);- // 遍历算法 遍历容器元素 - // beg 开始迭代器 - // end 结束迭代器 - // _func 函数或者函数对象 
#include<algorithm>
#include<vector>
// 普通函数
void print1(int val) {
    cout << val << " ";
}
// 仿函数
class print2 {
public:
    void operator()(int val) {
        cout << val << " ";
    }
};
void test1() {
    vector<int> v;
    v.push_back(10);
    v.push_back(30);
    v.push_back(20);
    v.push_back(40);
    for_each(v.begin(), v.end(), print1);  // 传入普通函数,遍历出每个元素 
    cout << endl;
    for_each(v.begin(), v.end(), print2());  // 传入仿函数(),遍历出每个元素
}5.1.2 transform
功能描述:
- 搬运容器到另一个容器中
函数原型:
- transform(iterator_beg1, iterator_end1, iterator_beg2, _func);
//beg1 源容器开始迭代器
//end1 源容器结束迭代器
//beg2 目标容器开始迭代器
//_func 函数或者函数对象
#include<algorithm>
#include<vector>
void print1(int val) {
    cout << val << " ";
}
int trans(int v) {
    return v;
}
void test1() {
    vector<int> v;
    v.push_back(10);
    v.push_back(30);
    v.push_back(20);
    v.push_back(40);
    vector<int> v2;
    v2.resize(v.size());  // 先要开辟空间
    transform(v.begin(), v.end(), v2.begin(), trans);
    for_each(v2.begin(), v2.end(), print1);
}5.2 常用查找算法
算法简介:
- find(iterator beg, iterator end, value);- // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 - // beg 开始迭代器 - // end 结束迭代器 - // value 查找的元素 
- find_if(iterator beg, iterator end, _Pred);- // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 - // beg 开始迭代器 - // end 结束迭代器 - // _Pred 函数或者谓词(返回 bool 类型的仿函数) 
- adjacent_find(iterator beg, iterator end);- // 查找相邻重复元素, 返回相邻元素的第一个位置的迭代器 - // beg 开始迭代器 - // end 结束迭代器 
- bool binary_search(iterator beg, iterator end, value);- // 查找指定的元素,查到 返回 true 否则 false - // 注意: 在 无序序列中不可用 - // beg 开始迭代器 - // end 结束迭代器 - // value 查找的元素 
- count(iterator beg, iterator end, value);- // 统计元素出现次数,返回 int 类型数据 - // beg 开始迭代器 - // end 结束迭代器 - // value 统计的元素 
- count_if(iterator beg, iterator end, _Pred);- // 按条件统计元素出现次数,返回 int 类型数据 - // beg 开始迭代器 - // end 结束迭代器 - // _Pred 谓词 
一个 find_if 的例子:
class Greater30 {
public:
    bool operator()(int val) {
        return val > 30;
    }
};
void test1() {
    vector<int> v;
    v.push_back(10);
    v.push_back(30);
    v.push_back(20);
    v.push_back(40);
    vector<int>::iterator it=find_if(v.begin(), v.end(), Greater30());
    if (it == v.end()) {
        cout << " 没找到 " << endl;
    }
    else {
        cout << " 找到 " << endl;
    }
    cout << *it << endl;  // 输出 30,找到第一个满足条件的即停止查找
}5.3 常用排序算法
算法简介:
- sort(iterator beg, iterator end, _Pred);- // 排序 - // beg 开始迭代器 - // end 结束迭代器 - // _Pred 谓词 
- random_shuffle(iterator beg, iterator end);- // 指定范围内的元素随机调整次序 - // beg 开始迭代器 - // end 结束迭代器 
- merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);- // 容器元素合并,并存储到另一容器中 - // 注意: 两个容器必须是 有序的 - // beg1 容器 1 开始迭代器 
 // end1 容器 1 结束迭代器
 // beg2 容器 2 开始迭代器
 // end2 容器 2 结束迭代器
 // dest 目标容器开始迭代器
- reverse(iterator beg, iterator end);- // 反转指定范围的元素 - // beg 开始迭代器 - // end 结束迭代器 
5.4 常用拷贝和替换算法
算法简介:
- copy(iterator beg, iterator end, iterator dest);- // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 - // beg 开始迭代器 - // end 结束迭代器 - // dest 目标起始迭代器 
- replace(iterator beg, iterator end, oldvalue, newvalue);- // 将区间内旧元素 替换成 新元素 - // beg 开始迭代器 - // end 结束迭代器 - // oldvalue 旧元素 - // newvalue 新元素 
- replace_if(iterator beg, iterator end, _pred, newvalue);- // 按条件替换元素,满足条件的替换成指定元素 - // beg 开始迭代器 - // end 结束迭代器 - // _pred 谓词 - // newvalue 替换的新元素 
- swap(container c1, container c2);- // 互换两个容器的元素 - // c1 容器 1 - // c2 容器 2 
5.5 常用算术生成算法
注意:
- 算术生成算法属于小型算法,使用时包含的头文件为 #include <numeric>
算法简介:
- accumulate(iterator beg, iterator end, value);- // 计算容器元素累计总和,返回总和 - // beg 开始迭代器 - // end 结束迭代器 - // value 起始值 
- fill(iterator beg, iterator end, value);- // 向容器中填充元素 - // beg 开始迭代器 - // end 结束迭代器 - // value 填充的值 
5.6 常用集合算法
算法简介:
- set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);- // 求两个集合的交集 - // 注意: 两个集合必须是有序序列 - // beg1 容器 1 开始迭代器 
 // end1 容器 1 结束迭代器
 // beg2 容器 2 开始迭代器
 // end2 容器 2 结束迭代器
 // dest 目标容器开始迭代器,目标容器开辟空间需要从 两个容器中取小值
- set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);- // 求两个集合的并集 - // 注意: 两个集合必须是有序序列 - // beg1 容器 1 开始迭代器 
 // end1 容器 1 结束迭代器
 // beg2 容器 2 开始迭代器
 // end2 容器 2 结束迭代器
 // dest 目标容器开始迭代器,目标容器开辟空间需要 两个容器相加
- set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);- // 求两个集合的差集 - // 注意: 两个集合必须是有序序列 - // beg1 容器 1 开始迭代器 
 // end1 容器 1 结束迭代器
 // beg2 容器 2 开始迭代器
 // end2 容器 2 结束迭代器
 // dest 目标容器开始迭代器,目标容器开辟空间需要从 两个容器取较大值
六、异常处理
程序有时会遇到运行阶段错误,导致程序无法正常地运行下去,C++ 为此提供了异常处理工具。其基本思想是:函数 A 在执行过程中发现异常时可以不加处理,而只是”拋出一个异常”给 A 的调用者,函数 B 可以选择捕获 A 拋出的异常进行处理,也可以选择置之不理。如果置之不理,这个异常就会被拋给 B 的调用者,以此类推。如果一层层的函数都不处理异常,异常最终会被拋给最外层的 main 函数。main 函数应该处理异常。如果 main 函数也不处理异常,那么程序就会立即异常地中止。
6.1 基本的异常处理
1、调用 std::abort() 函数
using namespace std;
double cal(double a, double b) {
    if (a == -b) {
        cout << " 两数之和不可以等于 0" << endl;
        abort();       // 直接调用
    }
    else {
        return 1 / (a + b);
    }
}当运行到 abort(),程序会终止,避免崩溃。
2、返回错误码
可以使用指针 / 引用参数来将值返回给调用程序,并使用函数的返回值来指出成功还是失败。
bool cal(double a, double b, double * ans) {
    if (a == -b) {
        *ans = 0.0;
        return false;
    }
    else {
        *ans = 1 / (a + b);
        return true;
    }
}
void test1() {
    double x, y, ans;
    cout << " 请输入 x,y 的值 " << endl;
    cin >> x >> y;
    if (cal(x, y, &ans)) {
        cout << ans << endl;
    }
    else {
        cout << "error code 1" << endl;
    }
}6.2 异常机制
C++ 异常是对程序运行过程中发生异常情况的一种响应,对异常的处理分为 3 个组成部分:
- 引发异常
- 使用处理程序捕获异常
- 使用 try 块
C++ 异常处理涉及到三个关键字:try、catch、throw。
如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
捕获异常
// 捕获异常
try {
   // 保护代码
}
catch(ExceptionName e1)
{
   // 处理 ExceptionName 异常的代码
}
catch(ExceptionName e2)
{
   // 处理 ExceptionName 异常的代码
}如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 …,如下所示:
try {}
catch (...){}  // 捕获任何异常抛出异常
我们可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
double division(int a, int b)
{
   if(b == 0)
   {
      throw "Division by zero condition!";  // 抛出异常
   }
   return (a/b);
}6.3 案例
#include <iostream>
using namespace std;
double division(int a, int b)
{
   if(b == 0)
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}
int main ()
{
   int x = 50;
   int y = 0;
   double z = 0;
   try {
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {
     cerr << msg << endl;
   }
   return 0;
}由于我们抛出了一个类型为字符串数组(”Division by zero condition!”)的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char* 接收。
6.4 异常类型
6.4.1 标准异常
C++ 提供了一系列标准的异常,定义在 <exception> 中。
我们可以在程序中使用这些标准的异常。

下面举一个 std::bad_alloc 例子:
#include<iostream>
using namespace std;
int main()
{
    try {
        char* p = new char[0x7fffffff];  // 无法分配这么多空间,会抛出异常
    }
    catch (exception& e) {
        cout << e.what() << endl;
    }
    catch (bad_alloc& e) {
        cout << e.what() << endl;
    }
    return 0;
}
// 第 8 行的 catch 捕获到了 bad allocation 输出,第二次 catch 为空(即使就是 bad_alloc 类型的异常)what() 是异常类提供的一个公共方法,将返回异常产生的原因
异常只捕获一次
6.4.2 自定义异常
可以通过继承和重载 exception 类来定义新的异常。
#include <iostream>
#include <exception>
using namespace std;
class MyException : public exception
{
public:
    const char* what() const throw ()
    {
        return "C++ Exception";
    }
};
int main()
{
    try
    {
        throw MyException();
        cout << "look here" << endl;
    }
    catch (MyException& e)
    {
        cout << "MyException caught" << endl;
        cout << e.what() << endl;
    }
    catch (exception& e)
    {
        // 其他的错误
    }
}
/* 输出
MyException caught
C++ Exception
*/欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net