1.模板
1.1概念
就是建立通用的模具,提高复用性
1.2函数模板
- C++的一种编程思想是:泛型编程,主要利用的技术是模板
- C++提供两种模板机制:函数模板和类模板
函数模板作用:
- 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代替。
语法:
template<typename T>
// 函数声明或定义
/*
解释:
template:声明创建模板
typename:表示其后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母
*/
// 普通的交换函数
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
// 使用模板的交换函数
// 先声明模板
template<typename T> // 这里可以用typename或class
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void test() {
int a = 1;
int b = 2;
swapInt(a, b);
cout << "a = " << a << "\tb = " << b << endl;
double c = 1.1;
double d = 2.2;
swapDouble(c, d);
cout << "c = " << c << "\td = " << d << endl;
// 模板的交换函数的使用
// 两种方式使用函数模板
// 1.自动类型推导
// mySwap(a, b);
// 2.显示指定类型
// mySwap<int>(a, b);
// mySwap<double>(c, d);
mySwap(a, b);
mySwap(c, d);
cout << "a = " << a << "\tb = " << b << endl;
cout << "c = " << c << "\td = " << d << endl;
}
使用模板是为了提高代码复用性,将类型参数化
注意:
- 自动类型推导,必须推导出的类型都一致才能使用。(就是说使用模板的函数,它的使用模板的类型都应该是一样的才可以)
- 模板必须确定出T的数据类型,才可以使用。
template<typename T>
void func1(T a, T b){
}
template<typename T> // 直接注释掉这句话也可以运行。
void func2(){
}
void test(){
int a = 1;
char b = ' ';
func1(); // 报错,因为a和b类型不一致
func2(); // 报错,因为编译器确定不了T的类型。(func2<int>(); // 这样调用就不会报错,因为这样就直接告诉编译器T的类型了)
}
普通函数和函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
// 普通函数
int myAdd1(int a, int b){
return a+b;
}
// 函数模板
template<typename T>
T myAdd2(T a, T b){
return a + b;
}
void test(){
int a = 1;
int b = 2;
char c = 'a';
// 正常的调用与返回
cout << myAdd1(a,b) << endl;
// 发生隐式类型转换,自动的把c转换为了int类型
cout << myAdd1(a,c) << endl;
// 报错
cout << myAdd2(a,c) << endl;
// 发生隐式类型转换,正常运行
cout << myAdd2<int>(a,c) << endl;
cout << myAdd2<char>(a, c) << endl;
}
推荐使用指定显示类型的方法来调用函数模板
普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板。(func<>(); 就可以了)
- 函数模板可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
- 既然提供了函数模板就不要提供普通函数了,容易产生二义性
模板具有局限性
为了解决模板的局限性,提供了模板的重载,可以为这些特定的类型提供具体化的模板。
class Person {
public:
int m_Age;
string m_Name;
Person(int age, string name) {
this->m_Age = age;
this->m_Name = name;
}
};
template<typename T>
bool myCompare(T a, T b) {
if (a == b) {
return true;
}
else {
return false;
}
}
// 这样的模板函数没办法处理传进来的参数是Person类型的,所以对它进行重载
template<> bool myCompare(Person p1, Person p2) {
if (p1.m_Age == p2.m_Age) {
return true;
}
else {
return false;
}
}
void test() {
Person p1(10, "Tom");
Person p2(13, "Jack");
bool ret = myCompare(p1, p2);
}
- 利用具体化的模板可以解决自定义类型的通用化
- 学习模板并不是为了写模板,而是能够在STL运用系统提供的模板
1.3类模板
作用
- 建立一个通用类,类中的成员、数据类型可以不具体制定,用一个虚拟的类型来表示。
语法
(和函数模板语法一致)
template<typename T>
// 类
类模板和函数模板的区别:
- 类模板没有自动类型推导的方式
- 类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType = int>
class Person
{
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << this->m_Name << ":" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test51() {
// Person<string, int> p1("张三", 10);
// 前面设置了:template<class NameType,class AgeType = int>,所以在使用的时候就可以不加int
Person<string> p1("张三", 10);
p1.showPerson();
}
类模板中成员函数和普通类中成员函数创建时机
- 普通类中的成员函数一开始就会创建。
- 类模板中成员函数在调用的时候才创建。
类模板对象作函数参数
有三种传入方式:
- 指定传入的类型 – 直接显示对象的数据类型 (最常用)
- 参数模板化 – 将对象中的参数变为模板进行传递
- 整个类模板化 – 将这个对象类型模板化进行传递
template<class NameType,class AgeType>
class Person
{
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << this->m_Name << ":" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
// 方法1:指定传入的类型
void printShowPerson(Person<string, int> &p){
p.showPerson();
}
void test1() {
Person<string, int> p1("张三", 10);
printShowPerson(p1);
}
// 方法2:将参数模板化
template<class T1,class T2>
void printShowPerson2(Person<T1, T2> &p){
p.showPerson();
}
void test2(){
Person<string, int> p2("张三", 10);
printShowPerson2(p2);
}
// 方法3:将整个类模板化
template<class T1>
void printShowPerson3(T &p){
// 查看数据类型
cout << "T的数据类型为:" << typeid(T).name() << endl;
p.showPerson();
}
void test2(){
Person<string, int> p3("张三", 10);
printShowPerson3(p3);
}
类模板与继承
注意:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
template<class T>
class Base
{
T m;
};
// class Son: public Base 错误,不知道父类中的T类型
class Son: public Base<int> // 在声明时指定父类中T的类型。
{};
template<class T1, class T2> // 将子类也变换成类模板,可以灵活的指定父类中T的类型
class Son2: public Base<T1>
{
T1 m1;
T2 m2;
};
// 调用
void test()
{
Base<int> b1;
Son<char> s1;
Son2<char, bool> s2;
}
类模板成员函数类外实现
- 注意:类模板成员函数类外实现,需要加上模板参数列表
// 构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){}
// 成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson(){}
类模板分文件编写
问题: 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。
解决:
- 方法1:直接包含.cpp源文件
- 方法2:将声明和实现写在同一个文件中,并更改后缀名为.hpp, .hpp是约定的名称,并非强制的。
// person.h的代码:
#pragma once
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
showPerson();
};
// person.cpp中的代码:
#include "person.h"
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){}
template<class T1, class T2>
void Person<T1, T2>::showPerson(){}
// person.hpp的代码:
#pragma once
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
showPerson();
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){}
template<class T1, class T2>
void Person<T1, T2>::showPerson(){}
// 主文件中的代码:
#include "person.h" // 当仍然和普通的分文件编写一致时,会报错
#include "person.cpp" // 仍然和普通的份文件编写一致,但是导入的是源文件,就不会报错
#include "person.hpp" // 将person.h与person.cpp中的内容写到一起,然后将后缀名改为.hpp
类模板与友元
template<class T1, class T2>
class Person;
template<class T1, class T2>
void printPerson2(Person<T1, T2> &p)
{}
template<class T1, class T2>
class Person
{
// 全局函数类内实现
friend void printPerson(Person<string, int> &p){}
// 全局函数类外实现
// 如果是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson2<>(Person<string, int> &p);
public:
Person(T1 name, T2 age){
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
类模板案例
实现一个通用的数组类,描述:
- 可以对内置数据类型以及自定义数据类型的数据进行存储。
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除。
- 可以通过下标的方式访问数组中的元素。
- 可以获取数组中当前元素个数和数组的容量。
// 自己的通用的数组类
#pragma once
#include <iostream>
using namespace std;
template<class T>
class MyArray
{
public:
// 有参构造,参数 容量
MyArray(int capacity)
{
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
// 拷贝构造
MyArray(const MyArray& arr) {
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
// 深拷贝
this->pAddress = new T[this->m_Capacity];
// 把数据拷贝过来
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
}
// operator= 防止浅拷贝问题
MyArray& operator=(const MyArray& arr) {
// 先判断原来堆区是否有数据,如果有就先释放
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
// 深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Capacity; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
// 尾插法
void Push_Back(const T & value) {
// 判断还未到达最大容量
if (this->m_Size <= this->m_Capacity) {
this->pAddress[this->m_Size++] = value;
}
}
// 尾删法
void Pop_Back() {
// 让用户访问不到最后一个元素
if (this->m_Size != 0) {
this->m_Size--;
}
}
// 通过下标的方式访问数组中的元素
T& operator[](int index) {
return this->pAddress[index];
}
// 返回数组容量
int getCapacity() {
return this->m_Capacity;
}
// 返回数组大小
int getSize() {
return this->m_Size;
}
// 析构函数
~MyArray() {
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress; // 指针指向堆区开辟的真实数组
int m_Capacity; // 数组容量
int m_Size; // 数组大小
};
2.STL
2.1STL
2.1.1 STL基本概念
- STL(Standard Template Library,标准模板库)
- STL从广义上分为:容器、算法、迭代器
- 容器和算法之间通过迭代器进行无缝连接
- STL几乎所有的代码都采用了模板类或者模板函数
2.1.2STL六大组件
容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 容器:各种数据结构,如vector、list、deque、set、map等
- 算法:各种常用的算法,如sort、find、copy、for_each等
- 迭代器:容器和算法之间的胶合剂
- 仿函数:行为类似函数,可作为算法的某种策略
- 适配器:用来修饰容器、仿函数、迭代器接口的东西
- 空间适配器:负责空间的配置和管理
2.1.3STL中的容器、算法、迭代器
容器: 将运用最广泛的一些数据结构实现出来。
- 常用的数据结构:数组、链表、树、栈、队列、集合、映射表等。
- 这些容器分为序列式容器和关联式容器两种:
- 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
- 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系。
算法: 使用有限步骤解决逻辑(数学)问题
- 分为质变算法和非质变算法:
- 质变算法:指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等。
- 非质变算法:指运算过程中不会更改区间内的元素的内容。例如查找、计数、遍历、寻找极值等等。
迭代器: 容器和算法间的粘合剂
- 提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
- 每个容器都有自己专属的迭代器。
- 迭代器的使用类似于指针的使用。
迭代器种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、– |
随机访问迭代器 | 读写操作,可以以跳跃的方式随机访问任意数据,功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
常用的迭代器为双向迭代器和随机访问迭代器
2.2STL-常用容器
2.2.1string容器
本质:
- string是C++风格的字符串,而string本质上是一个类。
string和char*的区别:
- char*是一个指针。
- string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。
特点:
- string类内部封装了很多成员方法
- string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责。
string构造函数:
构造函数原型:
- string();创建一个空的字符串,例如:string str;
- string(const char* s);使用字符串s初始化
- string(const string& s);使用一个string对象初始化另一个string对象
- string(int n,char c);使用n个字符c初始化
string赋值操作:
赋值的函数原型:
- string& operator=(const char* s);char*类型字符串赋值给当前的字符串
- string& operator=(const string& s);把字符串s赋值给当前的字符串
- string& operator=(char c);把字符赋值给当前的字符串
- string& assign(const char* s);把字符串赋值给当前的字符串
- string& assign(const char* s,int n);把字符串s的前n个字符赋值给当前的字符串
- string& assign(const string& s);把字符串s赋值给当前的字符串
- string& assign(int n,char c);用n个字符c赋值给当前的字符串
string字符串拼接:
描述:实现在字符串末尾拼接字符串
函数原型:
- string& operator+=(const char* s);
- string& operator+=(const char c);
- string& operator+=(const string& s);
- string& append(const char* s);
- string& append(const char* s,int n);把字符串s的前n个字符拼接到当前字符串末尾
- string& append(const string& s);
- string& append(const string& s,int pos,int n);把字符串s从第pos位置开始的n个字符拼接到当前字符末尾
string查找和替换:
查找:查找指定字符串是否存在
替换:在指定的位置替换字符串
函数原型:(找不到返回-1,找到返回位置)
- int find(const string& str,int pos = 0) const;查找str第一次出现的位置,从pos开始查找
- int find(const char* str,int pos = 0) const;同上
- int find(const char* str,int pos,int n) const;从pos位置开始查找str的前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* str,int pos = npos) const;同上
- int rfind(const char* str,int pos,int n) const;从pos位置开始查找str前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* str);同上
string字符串比较:
比较方式:从前到后,一个一个的按照字符的ASCII码进行比较
- 等于:返回 0
- 大于:返回 1
- 小于:返回 -1
函数原型:
- int compare(const string& str) const;
- int compare(const char* str) const;
string字符存取:
单个字符存取的两种方式:
- char& operator[](int n);通过[]存取字符
- char& at(int n);通过at方法获取字符
string插入和删除:
函数原型:
- string& insert(int pos,const string& str);从pos位置插入字符串
- string& insert(int pos,const char* str);同上
- string& insert(int pos,int n,char c);在pos位置插入n个字符c
- string& erase(int pos,int n = npos);删除从pos开始的n个字符
string子串:
从字符串中获取想要的子串
函数原型:
- string substr(int pos = 0,int n = npos) const;返回由pos开始的n个字符组成的字符串
2.2.2vector容器(数组)
功能: vector的数据结构类似于数组,被称为单端数组
vector与普通数组的区别: 数组是静态空间,vector可以动态扩展
动态扩展:
- 从新找一块更大的内存空间,将原数据拷贝到新空间,释放原空间
- vector容器的迭代器是支持随机访问的迭代器
vector构造函数:
创建vector容器
函数原型:
- vector< T> v;采用 模板实现类 实现,默认构造参数
- vector(v.begin(),v.end());将v.begin()和v.end()区间中的元素拷贝给本身
- vector(n,elem);构造函数将n个elem拷贝给本身
- vector(const vector& vec);拷贝构造函数
vector赋值操作:
函数原型:
- vector& operator=(const vector& vec);重载等号操作符
- assign(beg,end);将[beg,end]区间中的数据拷贝赋值给本身
- assign(n,elem);将n个elem拷贝赋值给本身
vector容量和大小:
函数原型:
- empty();判断容器是否为空
- capacity();容器的容量
- size();返回容器中元素的个数
- resize(int num);重新指定容器的长度num,若容器变长,则以默认值填充新位置。若变短,超出部分则删除。
- resize(int num,elem);若变长,则变长部分以elem填充。
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();删除容器中所有元素
vector数据存取:
- at(int idx);返回索引idx所指向的数据
- operator[];
- front();返回容器中第一个数据元素
- back();返回容器中最后一个数据元素
vector互换容器:
函数原型:
- swap(vec);将vec与本身的元素互换
用处:收缩内存空间。
例如:刚开始一个很大的数组,系统给它分配一片很大的内存空间,使用resize()直接把这个数组减小到很小,但是它的空间还是非常大。
vector<int>(v).swap(v); // 就可以解决这个问题
// vector<int>(v) 这是个匿名对象,它是通过拷贝构造来的,匿名对象会在本句执行完毕释放
// v 这是原来的对象
// swap 将匿名对象的指向和原来对象的指向交换
// 结果也就是匿名对象指向原来的大空间,被释放,而原来对象指向匿名对象的空间,被保留下来
vector预留空间:
功能:减少vector在动态扩展容量时的扩展次数
函数原型:
- reserve(int len);容器预留len个元素长度,预留位置不初始化,元素不可访问
2.2.3deque容器
功能: 双端数组,可以对头端进行插入删除操作
deque与vector比较:
- vector对头部插入删除的效率低,数据量越大,效率越低
- deque相对而言对头部插入删除速度快
- vector访问元素的速度比deque快
原理: 在内部有中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据。中控器维护每个缓冲区的地址。
deque的迭代器支持随机访问
deque的构造函数:
函数原型:(都类似于vector)
- deque< T> deqT;默认构造
- deque(beg,end);
- deque(n,elem);
- deque(const deque& deqT);
deque赋值操作:
函数原型:
- deque& operator=(const deque& deq);
- assign(beg,end);
- assign(n,elem);
deque容器大小操作:
函数原型:(和vector一样)
- empty();
- size();
- resize(num);
- resize(num,elem);
- deque容器没有容量的概念
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位置的数据,返回下一个数据的位置
deque数据存取:
- at(int idx);
- operator[];
- front();返回容器中第一个数据元素
- back();最后一个
deque排序操作:
算法:(默认升序)
- sort(iterator beg,iterator end);对beg和end区间内元素进行排序
2.2.4stack容器(栈容器)
特点: 是一种先进后出的数据结构,它只有一个出口
栈不允许遍历
stack常用接口:
构造函数:
- stack< T> stk;
- stack(const stack& stk);拷贝构造函数
赋值操作:
- stack operator=(const stack& stk);
数据存取:
- push(elem);向栈顶添加元素
- pop();从栈顶移除第一个元素
- top();返回栈顶元素
大小操作:
- empty();判断栈堆是否为空
- size();返回栈的大小
2.2.5queue容器(队列容器)
特点: 先进先出的数据结构,有一个入口一个出口
queue常用接口:
构造函数:
- queue< T> que;
- queue(const queue& que);
赋值操作:
- queue operator=(const queue& que);
数据存取:
- push(elem);队尾添加元素
- pop();对头移出第一个元素
- back();返回队尾元素
- front();返回队头元素
大小操作:
- empty();
- size();
2.2.6list容器(链表)
功能: 将数据进行链式存储
链表: 逻辑上通过指针连续,物理上非连续
组成: 由一系列结点组成。
- 结点由数据域和指针域组成
- 数据域存储此结点的数据
- 指针域存储下一个节点的地址
不支持随机访问
优点: 可以在链表的任意位置快速的插入或删除数据、动态分配空间
缺点: 遍历慢、空间大
STL中的链表是一个双向循环链表
插入操作和删除操作不会使原有的list迭代器失效
构造函数:
- list< T> lst;
- list(beg,end);
- list(n,elem);将n个elem拷贝给本身
- list(const list &lst);
赋值和交换:
- assign(beg,end);类似第二个构造
- assign(n,elem);
- list& operator=(const list& lst);
- swap(lst);将lst与本身的元素互换
大小操作:
- size();
- empty();
- resize(num);重新指定大小
- resize(num,elem);
插入和删除:
- 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);删除区间的数据,返回下一个数据的位置
- erase(pos);删除pos位置数据,返回下一个数据的位置
- remove(elem);删除容器中所有与elem值匹配的元素
数据存取:
- front();返回第一个元素
- back();返回最后一个元素
翻转和排序:
- reverse();翻转链表
- sort();排序链表,排序规则自己制定规则方法,将它作为参数。
所有不支持随机访问迭代器的容器,不可以用标准算法,它的内部会提供相应的一些算法
自定义类型排序时一定要自己指定规则。
2.2.7set容器
特点: 元素会在插入时自动排序
本质: set/multiset属于关联式容器,底层结构是用二叉树实现的。
set和multiset的区别:
- set不允许容器中有重复的元素
- multiset允许容器中有重复的元素
- set插入数据的同时会返回插入结果,表示插入是否成功
- multiset不会检测数据,因此可以插入重复数据
构造函数:
- set< T> st;
- set(const set& st);
赋值:
- set& operator=(const set& st);
大小和交换:
- size();
- empty();
- swap(st);交换两个集合容器
插入和删除:
- insert(elem);在容器中插入元素
- clear();
- erase(pos);删除pos指向元素,返回下一个元素的迭代器
- erase(beg,end);删除区间内元素,返回下一个元素的迭代器
- erase(elem);删除容器中值为elem的元素
查找和统计:
- find(key);查找key是否存在,若存在返回该元素的迭代器。若不存在,返回set.end()。
- count(key);统计key的元素个数
pair对组创建:
- pair<type,type> p (value1,value2);
- pair<type,type> p = make_pair(value1,value2);
- 访问对组数据
- p.first:第一个数据
- p.second:第二个数据
set容器排序:
- 利用仿函数,改变排序规则
- 自定义的数据类型都需要指定排序规则
class MyCompare
{
public:
bool operator()(int v1, int v2){
return v1 > v2;
}
};
class ComparePerson
{
public:
bool operator()(const Person& p1, const Person& p2){
return p1.m_Age > p2.m_Age;
}
};
class Person
{
public:
Person(string name, int age){
this->m_Name = name;
this->m_Age = age;
}
private:
string m_Name;
int m_Age;
};
void test1(){
set<int, MyCompare> s;
s.insert(10);
s.insert(40);
s.insert(70);
s.insert(30);
s.insert(20);
s.insert(60);
for(set<int, MyCompare>::iterator it = s.begin();it != s.end();it++){
cout << *it << " ";
}
cout << endl;
}
void test2(){
set<Person, ComparePerson> s;
s.insert("a",10);
s.insert("b",40);
s.insert("c",70);
s.insert("d",30);
s.insert("e",20);
s.insert(60);
for(set<int, ComparePerson>::iterator it = s.begin();it != s.end();it++){
cout << *it << " ";
}
cout << endl;
}
2.2.8map容器
特点:
- map中所有元素都是pair
- pair中第一个元素为key(键值),起到索引作用。第二个元素为value(实值)
- 所有元素都会根据元素的键值自动排序
本质: map/multimap属于关联式容器,底层结构是二叉树实现。
优点: 可以根据key值快速找到value值
map和multimap的区别:
- map不允许容器中有重复的key值
- multimap允许容器中有重复的key值
map构造和赋值:
- map<T1,T2> m;
- map(const map &m);
- map& operator=(const map& m);
大小和交换:
- size();
- empty();
- swap(mp);
插入和删除:
- insert(elem);
- insert(pair<T1,T2>(elem1,elem2));
- insert(make_pair(elem1,elem2));
- insert(map<T1,T2>::value_type(elem1,elem2));
- mp[key] = value;
- clear();
- erase(pos);
- erase(beg,end);
- erase(key);
查找和统计:
- find(key);查找key是否存在,存在返回该迭代器,不存在返回map.end()迭代器
- count(key);统计key的个数
排序:
- 默认按照key值进行从小到大排序
- 利用仿函数改变排序规则
2.3STL-函数对象
2.3.1函数对象
概念:
- 重载函数调用操作符的类,其对象称为函数对象
- 函数对象使用重载的()时,行为类似函数调用,也被称为仿函数
本质:
- 函数对象是一个类,不是一个函数
函数对象使用特点:
- 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
- 函数对象超出普通函数的概念,函数对象可以有自己的状态,就是它可以有自己的成员属性及方法
- 函数对象可以作为参数传递
2.3.2谓词
概念:
- 返回bool类型的仿函数称为谓词。
- 如果operator()接收一个参数,那么叫做一元谓词
- 如果operator()接收两个参数,那么叫做二元谓词
2.3.3内建函数对象
概念:
- STL内建了一些函数对象
分类:
- 算数仿函数
- 关系仿函数
- 逻辑仿函数
用法:
- 这些仿函数所产生的对象,用法和一般函数完全相同
- 使用内建函数对象,需要引入头文件#include < functional>
算数仿函数:
功能描述:
- 实现四则运算
- 其中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> 取反仿函数
关系仿函数
功能描述:
- 实现关系对比
仿函数原型:
- 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> 小于等于
逻辑仿函数
功能描述:
- 实现逻辑运算
仿函数原型:
- template <class,T> bool logical_and< T> 逻辑与
- template <class,T> bool logical_or< T> 逻辑或
- template <class,T> bool logical_not< T> 逻辑非
2.4STL-常用算法
概述:
- 算法主要是由头文件< algorithm> < functional> < numeric> 组成
- < algorithm>是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等
- < numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数。
- < functional>定义了一些模板类,用以声明函数对象
2.4.1遍历算法
- for_each(iterator beg,iterator end,_func); 遍历容器
- transform(iterator beg1,iterator end1,iterator beg2,_func); 搬运容器到另一个容器中
2.4.2查找算法
- find(iterator beg,iterator end,value);查找元素, 找不到返回迭代器end()
- find_if(iterator beg,iterator end,_Pred);按条件查找元素,_Pred:函数或者谓词
- adjacent_find(iterator beg,iterator end);查找相邻重复元素
- bool binary_search(iterator beg,iterator end,value);二分查找法,找到返回true,否则返回false,在无序序列中不可使用
- count(iterator beg,iterator end,value);统计元素个数
- count_if(iterator beg,iterator end,_Pred);按条件同级元素个数
2.4.3排序算法
- sort(iterator beg,iterator end,_Pred);对容器内元素进行排序
- random_shuffle(iterator beg,iterator end);洗牌,指定范围内的元素随机调整次序
- merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);容器元素合并,并存储到另一容器中,两个容器必须是有序的
- reverse(iterator beg,iterator end);反转指定范围的元素
2.4.4拷贝和替换算法
- copy(iterator beg,iterator end,iterator dest);将容器内指定范围的元素拷贝到另一容器中
- replace(iterator beg,iterator end,oldvalue,newvalue);将指定范围内的旧元素改为新元素
- replace_if(iterator beg,iterator end,_Pred,newvalue);满足条件的。
- swap(container c1,container c2);互换两个容器的元素,要交换的容器是同种类型
2.4.5算数生成算法
- accumulate(iterator beg,iterator end,value);计算容器元素累计总和,参数value是起始的累加值
- fill(iterator beg,iterator end,value);向容器中填充指定的元素
2.4.6集合算法
- set_intersection(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);求两个容器的交集
- set_union(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);求两个容器的并集
- set_difference(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest);求两个容器的差集