C++重点内容
文章目录
进阶编程
1. 模板
针对C++泛型编程和STL技术
1.1 模板的概念
通用的模具,提高复用性,将类型参数化
C++另一种编程思想就是泛型编程,主要技术就是模板
模板机制:类模板、函数模板
1.2 函数模板
语法:template<typename T> 函数声明或定义
typename表明其后面的符号是一种数据类型,可用class代替;T为通用的数据类型
作用:建立一个通用函数,返回值和类型不具体指定,用一个虚拟的类型T代表
使用:
- ①自动类型推导:
func(a,b);
直接传入参数,编译器会自动推导传入参数的数据类型 - ②显式指定类型:
func<数据类型>(a,b);
显式告诉编译器T的类型
tips:
- 自动类型推导,必须推导出一致的数据类型T才能使用
- 模板必须要确定出T的数据类型才能使用
1.2.1 普通函数与函数模板的区别
普通函数调用时可以发生自动类型转换(隐式)
函数模板调用时,如果利用自动类型推导不会发生隐式类型转换、利用显式指定类型可以发生隐式类型转换
模板函数建议使用显示指定类型
int func1(int a, int b)//普通函数
{
return a + b;
}
template<class T>
T func2(T a, T b)//模板函数
{
retrun a + b;
}
void test()
{
int a = 1;
int b = 2;
int c = 'd';
func1(a+b);//3,参数都为int,正常调用
func2(a+b);//3,参数类型一致,可以推导出T为int类型
func1(a+c);//100,函数会将c的char类型自动转换为int类型
func2(a+c);//报错,模板函数利用自动类型推导时不会发生隐式类型转换
func2<int>(a+c);//100,模板函数利用显式指定类型时可以发生隐式类型转换
}
1.2.2 普通函数与函数模板的调用规则
当函数模板和普通函数都可以实现时,优先调用普通函数
可以通过空模板参数列表强制调用模板函数
函数模板也可以重载
如果函数模板能产生更好的匹配,则调动函数模板
提供函数模板后最好不要提供普通函数,容易存在二义性
void func(int a, int b)
{
cout<<"normal"<<endl;
}
template<class T>
void func(T a, T b)
{
cout<<"template1"<<endl;
}
template<class T>
void func(T a, T b, T c)
{
cout<<"template2"<<endl;
}
void test()
{
int a = 1;
int b = 2;
int c = 3;
func(a,b);//normal,调用的是普通函数
func<>(a,b);//template1,利用空模板强制调用模板函数
func(a,b,c);//template2,函数模板发生重载,调用对应版本的函数
char c1 = 'a';
char c2 = 'b';
func(c1,c2);//template1,调用的是模板函数,因为不会发生隐式类型转换,更为简单
}
1.2.3 模板的局限性
有些特定的数据类型需要提供具体化的模板做特殊实现,如数组的赋值操作、自定义数据类型的大小比较
template<class T>
bool eql(T &a, T &b)
{
if(a == b)
{
return true;
}
return false;
}
template<> bool eql(Person &a, Person &b)//重载模板函数
{
if(a.name == b.name)
{
return true;
}
return false;
}
1.3 类模板
语法:template<class Type1, class Type2...> 类
模板类中可能有多种数据类型,所以可以在定义模板类时用多种通用类型来代替
1.3.1 类模板和函数模板区别
类模板没有自动类型推导的使用方式
类模板在模板参数列表中可以有默认参数
template<class T = string, class E = int>//类模板可以有默认参数
class Person
{
public:
Person(T a, E b)
{
this->a = a;
this->b = b;
}
T a;
E b;
};
void test()
{
Person p("Tom", 10);//错误,类模板不能自动推导类型
Person<string, int>p("Tom", 10);//正确,必须使用显式指定类型
Person<string>p("Tom", 10);//正确,模板参数有默认类型
Person<>p("Tom", 10);//正确,甚至可以让所有模板参数都具有默认类型
}
1.3.2 类模板中成员函数创建时机
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建,调用时才根据对象数据类型来判断是否能调用此成员函数
1.3.3 类模板对象做函数参数
类模板实例化出的对象,向函数传参:
- 指定传入的方式,直接显示对象的数据类型,最常用
- 参数模板化,将对象中的参数变为模板进行传递
- 整个类模板化,将这个对象类型,模板化进行传递
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
T1 name;
T2 age;
};
void func1(Person<string, int> &p)//传参时直接指定传入类型
{
return;
}
template<class T1, class T2>
void func2(Person<T1, T2> &p)//将对象的模板参数也进行模板化操作
{
return;
}
template<class T>
void func3(T &p)//将对象的整个类进行模板化
{
return;
}
void test()
{
Person<string, int>p("Tom",10);
func1(p);
func2(p);
func3(p);
}
1.3.4 类模板与继承
类模板遇到继承需要注意:
- 子类继承的父类是一个类模板时,子类在声明的时候,要指出父类中T的类型。如果不指出,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需变为类模板
template<class T>
class Base
{
T a;
};
class Son : public Base<int>//必须指出T的具体类型
{
int b;
};
template<class T1, class T2>
class Son2 : public Base<T2>//此时子类也是一个类模板
{
T1 c;
};
1.3.5 类模板成员函数类外实现
重点是在函数前面声明函数模板,加上类作用域,并在类名后加上模板的参数列表
语法template<class T1, class T2,...> 返回值 类名<T1,T2,...>::func(args){}
template<class T1, class T2>
class Person
{
public:
T1 name;
T2 age;
Person(T1 name, T2 age);
void func();
};
//构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
//成员函数类外实现
template<class T1, class T2>
void Person<T1, T2>::func()
{
return;
}
1.3.6 类模板分文件编写
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方法:①直接包含.cpp源文件
#include"person.cpp"//直接包含源文件,源文件中包含头文件#include"person.h"
②将声明和实现写到同一个文件中,后缀改为.hpp
#include"person.hpp"//包含.hpp即可,hpp文件中包含头文件和源文件的所有内容
1.3.7 类模板与友元
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在
//类外实现 因为函数定义在类之前,并且使用了类,所以需要在函数定义之前先声明该类的存在
template<class T1, class T2>
class Person;
//类外实现 由于需要让编译器提前知道该函数,所以需要写在类定义的前面,声明该函数的存在
template<class T1, class T2>
void print2<>(Person<T1,T2> p)
{
cout<<p.a<<p.b<<endl;
}
template<class T1, class T2>
class Person
{
//全局函数 类内实现
friend void print1(Person<T1,T2> p)
{
cout<<p.a<<p.b<<endl;
}
//全局函数 类外实现
//需要在函数名后加上空模板参数列表,即<>
friend void print2<>(Person<T1,T2> p)
public:
Person(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
private:
T1 a;
T2 b;
};
2. STL初识
2.1 STL基本概念
定义:标准模板库
广义上分为:容器、算法、迭代器
容器和算法之间通过迭代器进行无缝连接
STL几乎所有的代码都采用了模板技术
STL六大组件:
- **容器:**各种数据结构,如vector、list、deque、set、map等用来存数据
- **算法:**各种常用的算法,如sort、find、copy、for_each等
- **迭代器:**扮演了容器和算法之间的胶合剂
- **仿函数:**行为类似函数,可作为算法的某种策略
- **适配器:**一种用来修饰容器或者仿函数或迭代器接口的东西
- **空间配置器:**负责空间的配置与管理
2.2 STL中容器、算法、迭代器简介
2.2.1 容器
STL容器:就是将运用最广泛的一些数据结构实现出来,如数组、链表、树、栈、队列、集合、映射等
分类:序列式容器和关联式容器
序列式容器:强调值的排序,序列式容器中每个元素都有固定的位置
关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
2.2.2 算法
STL算法:有限的步骤,解决逻辑或数学上的问题
分类:质变算法和非质变算法
质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝、替换、删除等
非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等
2.2.3 迭代器
STL迭代器:容器和算法之间粘合剂,算法要通过迭代器才能访问容器中的数据
提供一种方法,使之能够依序寻访某个容器中所含的各个元素,而又无需暴露该容器的内部表示方式
每个容器都有自己专属的迭代器
迭代器使用类似于指针
种类:
种类 | 功能 | 支持运算 |
---|---|---|
输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= |
输出迭代器 | 对数据的只写访问 | 只写,支持++ |
前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= |
双向迭代器 | 读写操作,并能向前向后操作 | 读写,支持++、==、!= |
随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持++、–、[n]、-n、<、<=、>、>= |
常用的容器中迭代器种类为双向迭代器和随机访问迭代器
2.3 容器算法迭代器初识
最常用的容器为vector,可以理解为数组
2.3.1 vector存放内置数据类型
容器:vector
,需要导入头文件#include<vector>
算法:for_each
,需要导入头文件#include<algorithm>
迭代器:vector<int>::iterator
,可使用*迭代器名
来取其指向的值
**函数指针:**指向函数的指针变量,可用于调用函数、传递参数;声明返回值 (* 指针变量名)(参数类型列表)
。调用指针变量名(实参列表)
回调函数:函数指针作为某个函数的参数,回调函数就通过函数指针调用的函数。简单讲就是由别人的函数执行时调用你实现的函数
#include<alogrithm>
#include<vector>
void print(int var)
{
cout<<var<<endl;
}
int main()
{
vector<int> a ={0 ,1 , 2, 3};
for_each(a.begin(),a.end(),print);
}
存放自定义数据类型时同理
3. STL常用容器
3.1 string容器
string本质是一个类,表示C++中的字符串
string和char *的区别:char *是一个指针;string是一个类,但类内部封装了char *,本质和char *一样
string管理char *所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
3.1.1 string构造函数
原型:
string()
:无参构造,创建一个空字符串string(const char* s)
:有参构造,使用字符串s初始化string(const string &str)
:使用一个string对象初始化string(int n, char c)
:使用n个字符c初始化
3.1.2 string赋值操作
原型:
string &operator=(const char* s)
:把char *类型字符串赋值给当前的字符串string &operator=(const string &s)
:把字符串s赋给当前的字符串string &operator=(char c)
:把字符c赋给当前的字符串string &assign(const char* s)
:把char *类型字符串赋值给当前字符串string &assign(const char* s, int n)
:把字符串s的前n个字符赋给当前字符串string &assign(const string &s)
:把字符串s赋给当前字符串string &assign(int n, char c)
:把n个字符c赋给当前字符串
3.1.3 string字符串拼接
实现在字符串末尾拼接字符串
原型:
string &operator+=(const char* str)
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个字符添加到当前字符串末尾
3.1.4 string查找和替换
查找指定字符串是否存在
在指定的位置替换字符串
原型:
int find(const string &s, int pos = 0) const
:查找s第一次出现的位置,从pos开始查找int find(const char* s, int pos = 0) const
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 &s, int pos = npos)
:从pos开始查找s最后一次位置int rfind(cosnt char* s, int pos = npos) const
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 &s)
:替换从pos开始n个字符为字符串sstring &replace(int pos, int n, const char* s)
rfind从右往左查找,find从左往右查找
英文字符占用1个字节,中文字符占用2个字节
3.1.5 字符串比较
字符串比较是按字符从ASCII码进行对比。返回0表示=;1表示>;-1表示<
原型:
int compare(const string &s) const
int compare(const char* s) const
3.1.6 string字符存取
原型:
char &operator[](int n)
:通过[]方式取字符char &at(int n)
:通过at方式获取字符
两种方法都可以对单个字符进行读写操作
3.1.7 string插入和删除
原型:
string &insert(int pos, const char* s)
:插入字符串string &insert(int pos, const string &str)
string &insert(int pos, int n, char c)
:在指定位置插入n个字符cstring &erase(int pos, int n = npos)
:删除从pos开始的n个字符
3.1.8 string子串
原型:
string substr(int pos = 0, int n = npos) const
:返回由pos开始的n个字符组成的字符串
3.2 vector容器
vector数据结构和数组非常相似,称为单端数组
vector和数组的区别:数组是静态空间,vector可以动态扩展空间
**动态扩展:**并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝至新空间中,释放原空间
vector容器的迭代器是支持随机访问的迭代器
3.2.1 vector构造函数
原型:
vector<T> v
:采用模板类实现,默认构造函数vector(v.begin(),v.end())
:将v[begin(),end()]区间内的元素拷贝给当前向量vector(n,elem)
:构造函数将n个elem拷贝给自身vector(const vector &vec)
:拷贝构造函数
3.2.2 vector赋值操作
原型:
vector &operator=(const vector &vec)
:重载等号运算符assign(beg, end)
:将[beg, end)区间中的数据拷贝赋值给当前向量assign(n, elem)
:将n个elem拷贝赋值给当前向量
3.2.3 vector容量和大小
原型:
empty()
:判断容器是否为空capacity()
:容器的容量size()
:返回容器中元素的个数resize(int num)
:重新指定容器的长度num,若容器变长,则以默认值填充新位置;若变短,则末尾超出长度的元素被删除resize(int num, elem)
:若变长则以elem填充新位置
3.2.4 vector插入和删除
原型:
push_back(elem)
:尾部插入元素elempop_back()
:删除并返回最后一个元素insert(const_iterator pos, elem)
:迭代器指向位置pos插入eleminsert(const_iterator pos, int count , elem)
:迭代器指向位置pos插入count个elemerase(const_iterator pos)
:删除迭代器指向的元素erase(const_iterator start, const_iterator end)
:删除迭代器从start到end之间的元素clear()
:删除容器中所有元素
3.2.5 vector数据存取
原型:
at(int idx)
:返回索引idx指向的数据operator[]
:通过已经封装好的重载运算符[]
返回索引idx所指的数据front()
:返回容器中的第一个元素back()
:返回容器中的最后一个元素
3.2.6 vector互换容器
原型:
swap(vec)
:将vec和本身的元素互换
利用swap收缩空间:vector<int>(vec).swap(vec)
:vector<int>(vec)
表示的是匿名对象,是调用拷贝构造函数,利用vec构造的一个匿名对象,匿名对象的size()和capacity()和vec的size()相同。然后调用swap函数相当于实现了一个容器互换,将匿名对象的容器和vec的容器进行互换,最后匿名对象在的当前行执行完毕后由系统自动回收,从而实现空间收缩
3.2.7 预留空间
原型:
reserve(int len)
:容器预留len个元素长度,预留位置不初始化,不可访问
当vector中需要存的数据量特别大时,在元素插入过程中vector会不断因为capacity不够大而寻找新的更大的内存空间。此时可以调用reserve函数使vector一开始就寻找到一块足够大的内存,从而避免重复扩容
3.3 deque容器
3.3.1 deque基本概念
双端数组:可以对头端进行插入删除操作
deque和vector区别:
- vector对于头部的插入删除效率低,数据量越大,效率越低
- deque相对而言,对头部的插入和删除比vector快
- vector访问元素时的速度会比deque快,这和两者内部实现有关
数据结构:
内部原理:内部有一个中控器,维护每段缓冲区的内容,缓冲区中存放真实数据。中控器维护的是每个缓冲区的地址,使得使用deque时像一个连续的内存空间。因为deque访问元素时可能需要通过中控器寻找下一块缓冲区的地址,所以deque访问速度没有vector快
deque的迭代器支持随机访问
3.3.2 deque构造函数
原型:
deque<T> deq
:默认构造deque(beg, end)
:拷贝beg和end之间的元素deque(n, elem)
:拷贝n个elem元素deque(const deque &deq)
:拷贝构造函数
3.3.3 deque赋值操作
原型:
deque& operatpr=(const deque &deq)
:重载=
运算符assign(beg,end)
:拷贝beg到end区间中数据assign(n,elem)
:拷贝n个elem
3.3.4 deque大小操作
原型:
deque.empty()
:判断容器是否为空deque.size()
:返回容器中元素的个数deque.resize(n)
:重新指定容器长度为n,若容器变长,则用默认值填充新位置;若容器变短,则末尾超出容器长度的部分被删除deque.resize(n,elem)
:用元素elem填充新位置
3.3.5 deque插入删除
原型:
两端插入操作:
push_back(elem)
:在容器尾部添加一个元素elempush_front(elem)
:在容器头部添加一个元素elempop_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位置的元素,返回下一个元素的位置
3.3.6 deque数据存取
原型:
at(int idx)
:返回索引idx指向的元素operator[]
:返回索引idx指向的元素front()
:返回容器中第一个元素back()
:返回容器中最后一个元素
3.3.7 deque排序
原型:
sort(beg,end)
:对beg和end之间的所有元素进行排序
sort是STL提供了排序算法,使用时需要包括头文件#include<algorithm>
,也可以用于vector。对于支持随机访问的迭代器的容器,都可以使用sort算法排序
3.4 stack容器
3.4.1 stack基本概念
stack是一个先进先出的数据结构,只有一个出口
栈只有顶端元素可以被外界使用,不允许遍历
3.4.2 stack接口
原型:
stack<T> stk
:默认构造stack(const stack &stk)
:拷贝构造stack& operator=(const stack &stk)
:重载=
运算符push(elem)
:向栈顶添加元素pop()
:移除栈顶第一个元素top()
:返回栈顶元素empty()
:判断栈是否为空size()
:返回栈的大小
3.5 queue容器
3.5.1 基本概念
queue是一种先进先出的数据结构,有两个出口
队列容器允许一端新增元素,一端移除元素
队列中只有队头和队尾才可以被外界使用,不允许遍历
3.5.2 queue接口
原型:
queue<T> que
:默认构造queue(const queue &que)
:拷贝构造queue& operator=(const queue &que)
:重载=
运算符push(elem)
:往队尾添加元素pop()
:从队头移除元素back()
:返回最后一个元素front()
:返回第一个元素empty()
:判断队列是否为空size()
:返回栈的大小
3.6 list容器
3.6.1 list基本概念
将数据进行链式存储
链表是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
组成:由一系列结点组成
结点的组成:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
STL中的链表是一个双向循环链表
链表的迭代器是双向迭代器,只支持前移和后移
优点:可以对任意位置进行快速插入和删除元素;采用动态存储分配,不会造成内存浪费和溢出
缺点:容器遍历速度慢于数组,占用空间比数组大
3.6.2 list构造函数
原型:
list<T> lst
:默认构造list(beg,end)
:拷贝构造,将beg和end之间的元素拷贝过来list(n,elem)
:拷贝构造,将n个elem拷贝给对象list(const list& lst)
:拷贝构造
3.6.3 list赋值和交换
原型:
assign(beg,end)
:将beg和end之间的元素拷贝过来assign(n,elem)
:将n个elem拷贝赋值过来list& operator=(const list &lst)
:重载=
运算符swap(lst)
:将lst与本身元素互换
3.6.4 list大小操作
原型:
size()
:返回容器内元素的个数empty()
:判断容器是否为空resize(n)
:重新指定容器长度为n,如果容器变长,就用默认值填充新位置;如果容器变短,就删去末尾超出长度的元素resize(n,elem)
:如果容器变长,就用elem填充新位置
3.6.5 list插入和删除
原型:
push_back(elem)
:在容器尾部插入一个元素elempop_back()
:删除容器尾部第一个元素push_fornt(elem)
:在容器开头插入一个元素elempop_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.6.7 list数据存取
原型:
front()
:返回第一个元素back()
:返回最后一个元素
list不支持随机访问
3.6.8 list反转和排序
原型:
reverse()
:反转链表sort()
:链表排序。默认从小到大sort(callback)
:排序算法的重载,回调函数callback指定排序规则
这里的sort()算法是链表的成员函数,不是algorithm库中的标准算法sort()。因为所有不支持随机访问迭代器的容器不能用标准算法,但是其内部会提供一些算法
3.7 set容器
3.7.1 set/multiset基本概念
set/multiset属于关联式容器,底层是二叉树实现
set插入数据只能用insert
set和multiset区别:
- set不允许容器中有重复元素
- multiset允许容器中有重复元素
3.7.2 set构造和赋值
原型:
set<T> st
:默认构造set(const set &st)
:拷贝构造set& operator=(const set &st)
:重载=
运算符
特点:所有元素插入时自动排序;不允许插入重复值
3.7.3 set大小和交换
原型:
size()
:返回容器的元素数量empty()
:判断容器是否为空swap(st)
:交换两个容器
3.7.4 set插入和删除
原型:
insert(elem)
:在容器中插入元素clear()
:清除所有元素erase(pos)
:删除pos迭代器指向的位置,返回下一个元素的位置erase(beg,end)
:删除区间beg到end之间的所有元素,返回下一个元素的位置erase(elem)
:删除容器中值为elem的元素
set在调用成员函数insert()时返回的数据类型为pair<iterator,bool>
;而multiset调用insert()只返回迭代器,不返回布尔值,因为不用检测插入数据
3.7.5 set查找和统计
原型:
find(key)
:查找元素key是否存在,返回该元素的迭代器;若不存在则返回end()count(key)
:统计元素key的数目,一般用于multiset;用于set返回值只能是0或1
3.7.6 set和multiset区别
区别;
- set不会插入重复数据,而multiset可以
- set插入数据时会返回插入结果,表示插入是否成功
- multist插入时不会检测数据,所以可以插入重复数据
3.7.7 pair对组创建
成对出现的数据,利用对组pair可以返回两个数据
语法:
pair<type,type> p (value1,value2)
pair<type,type> p = make_pair(value1,value2)
make_pair()
可以创建一个对组,并且不用写模板参数,只需要传入实参即可
使用:p.first
访问对组的第一个值;p.second
访问对组的第二个值
3.7.8 set容器排序
利用仿函数指定排序规则
仿函数就是定义一个类,类中实现()
的重载,使类的使用和函数的使用方法一样
#include<set>
class MyCompare
{
public:
bool operator()(int v1, int v2) const//需要加一个const修饰符表示常函数
{
return v1 > v2;
}
};
set<int,MyCompare> st;//用仿函数的规则进行排序
3.8 map/multimap容器
3.8.1 map基本概念
map的每一个元素都是pair类型的,pair中的第一个值称为key(键值),用于索引,第二个值称为value,是实值
所有元素都会根据键值自动排序
map/multimap是关联式容器,底层结构用二叉树实现
map和multimap区别:
- map不允许重复key值
- multimap允许重复key值
3.8.2 map构造和赋值
原型:
map<T1, T2> mp
:默认构造map<const map &map> mp
:拷贝构造map& operator=(const map &mp)
:重载=
运算符,赋值
map中元素都是成对出现的,插入时需要使用对组pair进行插入
3.8.3 map大小和交换
原型:
size()
:返回容器中元素的数组empty()
:判断容器是否为空swap(st)
:交换两个容器
3.8.4 map插入和删除
原型:
insert(elem)
:在容器中插入元素clear()
:清空所有元素erase(pos)
:删除pos迭代器指向的元素,返回下一个元素的迭代器erase(beg,end)
:删除从beg到end之间的元素,返回下一个元素的迭代器erase(key)
:删除容器中键为key的元素
插入insert的重载:①insert(pair<int,int>(1,1))
,利用对组pair进行插入;②insert(make_pair(1,1))
,利用make_pair创建对组进行插入;③insert(map<int,int>::value_type(1,1))
,利用map下的一种数据类型value_type进行插入;④m[1] = 1
,利用重载运算符[]
来插入。缺点如果没有初始化,访问其value时会返回一个默认值。一般用于访问确定存在的value
3.8.5 map查找和统计
原型:
find(key)
:查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end()count(key)
:统计key的元素个数。使用map返回值只可能是0或1,只有使用multimap返回值才可能大于1
3.8.6 map容器排序
map中排序规则默认为按照key值从小到大排序
利用仿函数改变排序规则
#include<map>
class MyCompare
{
public:
bool operator()(int v1, int v2)
{
return v1 > v2;//降序
}
};
map<int, int, MyCompare> m;
4. STL函数对象
4.1 函数对象
4.1.1 函数对象概念
定义:重载函数调用操作符的类,其对象常称为函数对象
函数对象使用重载的()
时,行为类似函数调用,也叫仿函数
本质:函数对象(仿函数)是一个类,不是一个函数
4.1.2 函数对象使用
特点:
- 函数对象在使用时,可以项普通函数一样调用,可以有参数,可以有返回值
- 函数对象超出普通函数的概念,函数对象可以有自己的状态
- 函数对象可以作为参数传递
class MyAdd
{
public:
MyAdd()
{
count = 0;//在构造函数时对count进行初始化
}
int operator()(int a, int b)
{
count++;//因为是成员属性,所以对于同一个对象是可以累加的
return a+b;
}
int count;//内部成员属性
}
//函数对象作参数
void test(MyAdd & ma, int a, int b)
{
ma(a,b);//使用方法不变
}
int main()
{
MyAdd myadd;
int c = myadd(10,10);//c=20
test(myAdd,10,10);
}
4.2 谓词
4.2.1 谓词概念
概念:
- 返回类型为bool类型的仿函数称为谓词
- 如果operator()接受一个参数,那么称为一元谓词
- 如果operator()接受两个参数,那么称为二元谓词
STL算法模板中,参数名为Pred的一般就是谓词
4.2.2 一元谓词
使用:find_if(v.begin(),v.end(),MyAdd())
其中MyAdd()
是匿名函数对象,和匿名对象的用法一样,是一元谓词作为参数传入算法中
4.2.3 二元谓词
使用:sort(v.begin(),v.end(),MyCompare())
其中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>
:取反仿函数
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)
:小于等于
4.3.4 逻辑仿函数
实现逻辑运算
原型:
template<class T> bool logical_and<T>
:逻辑与template<class T> bool logical_or<T>
:逻辑或template<class T> bool logical_not<T>
:逻辑非
transform
算法:transform(v1.begin(), v1,end(), v2.begin(), Func())
作用是将v1容器中begin到end中的所有元素搬运到v2中begin之后,并在搬运过程中利用Func()对每个元素进行操作。注意搬运时v2容器的空间必须已经开辟,否则将搬运失败
5. STL常用算法
算法主要由头文件#include<algorithm>
、#include<functional>
、#include<numeric>
组成
algorithm:是所有STL中最大的一个,包含比较、交换、查找、遍历、复制、修改等
functional:体积很小,只包括几个序列上进行简单数学运算的模板
numeric:定义了一些模板类,用以声明函数对象
5.1 常用遍历算法
需要导入头文件#include<algorithm>
5.1.1 for_each
遍历容器
原型:for_each(beg,end,func)
:参数beg指起始迭代器,end指结束迭代器;func指函数或者函数对象,其中如果为函数,调用时只写函数名,如果为函数对象,需要写函数名加括号。因为函数名表示函数指针,实际传入的参数是指向该函数的指针,所以不加括号,函数对象加上括号才表示一个对象,所以需要加括号
5.1.2 transform
搬运容器
原型:transform(beg1,end1,beg2,func)
参数beg1指第一个容器的起始迭代器;end1指第一个容器的结束迭代器;beg2指第二个容器的起始迭代器;func指函数或者函数对象
目标容器需要提前开辟足量的空间,否则搬运失败target.resize(int)
5.2 常用查找算法
需要导入头文件#include<algorithm>
5.2.1 find
查找指定元素,返回指定元素的迭代器,找不到返回结束迭代器end
原型:find(beg,end,value)
参数beg指起始迭代器;end指结束迭代器;value指需要查找的元素
当寻找的元素为自定义数据类型时,需要重载==
符号
5.2.2 find_if
按条件查找元素,返回指定元素的迭代器,找不到返回结束迭代器end
原型:find_if(beg,end,Pred)
参数beg指起始迭代器;end指结束迭代器;Pred指函数或谓词
5.2.3 adjacent_find
查找相邻重复元素,返回相邻元素的第一个位置的迭代器,找不到返回结束迭代器end
原型:adjacent_find(beg,end)
参数beg指起始迭代器;end指结束迭代器
5.2.4 binary_search
查找指定元素是否存在,找到返回true,找不到返回false
二分查找,只能在有序序列中查找
原型:bool binary_search(beg,end,value)
参数beg指起始迭代器;end指结束迭代器;value指查找的元素
5.2.5 count
统计元素个数,返回元素个数
原型:count(beg,end,value)
参数beg指起始迭代器;end指结束迭代器;value指需要统计的元素
5.2.6 count_if
按条件统计元素个数,返回元素个数
原型:count(beg,end,Pred)
参数beg指起始迭代器;end指结束迭代器;Pred指函数对象
5.3 常用排序算法
需要导入头文件#include<algorithm>
5.3.1 sort
对容器内元素进行排序
原型:sort(beg,end,Pred)
参数beg指起始迭代器;end指结束迭代器;Pred指函数对象,不填默认从小到大排序
5.3.2 random_shuffle
洗牌,指定范围内的元素随机调整次序,打乱序列
原型:random_shuffle(beg,end)
参数beg指起始迭代器;end指结束迭代器
如果不加随机数种子,每次执行洗牌算法打乱的序列是一样的,所以可以利用系统时间构造随机数种子实现洗牌srand((unsigned int)time(NULL))
,调用time函数需要引入头文件#include<ctime>
5.3.3 merge
两个容器合并,并存储到另一个容器中
两个容器必须是有序的,合并后仍为有序序列
原型:merge(beg1,end1,beg2,end2,dest)
参数beg1指第一个容器的起始迭代器;end1指第一个容器的结束迭代器;beg2指第二个容器的起始迭代器;end2指第二个容器的结束迭代器;dest指目标容器起始迭代器
目标容器需要提前分配足够大的空间target.resize(int)
5.3.4 reverse
将容器元素顺序进行反转
原型:reverse(beg,end)
参数beg指起始迭代器;end指结束迭代器
5.4 常用拷贝和替换算法
需要导入头文件#include<algorithm>
5.4.1 copy
将容器内指定范围内的元素拷贝到另一个容器中
原型:copy(beg,end,dest)
参数beg是已知容器的起始迭代器;end是已知容器的结束迭代器;dest是目标容器的起始迭代器
需要为目标容器提前开辟足够大的空间,否则拷贝失败
5.4.2 replace
将容器内的指定范围的旧元素修改为新元素
原型:replace(beg,end,oldvalue,newvalue)
参数beg指起始迭代器;end指结束迭代器;oldvalue值旧元素的值;newvalue指新元素的值
会替换范围中的所有等于oldvalue的值
5.4.3 replace_if
将区间内满足条件的元素,替换成指定元素
原型:replace_if(beg,end,Pred,newvalue)
参数beg指起始迭代器;end指结束迭代器;Pred指谓词即函数对象;newvalue指新元素的值
5.4.4 swap
互换两个容器中的所有元素,只能进行同种容器之间的交换
原型:swap(c1,c2)
参数c1为第一个容器;c2为第二个容器
容器大小也会交换
5.5 常用算术生成算法
需要导入头文件#include<numeric>
5.5.1 accumulate
计算区间内容器元素累积总和,并返回
原型:accumulate(beg,end,value)
参数beg指起始迭代器;end指结束迭代器;value指起始值
5.5.2 fill
向容器中填充的元素
原型:fill(beg,end,value)
参数beg指起始迭代器;end指结束迭代器;value指填充的值
需要提前为容器开辟空间
5.6 常用集合算法
5.6.1 set_intersection
求两个容器的交集,并返回交集中最后一个元素的迭代器
两个容器必须是有序序列
原型:set_intersection(beg1,end1,beg2,end2,dest)
参数beg1指第一个容器的起始迭代器;end1指第一个容器的结束迭代器;beg2指第二个容器的起始迭代器;end2指第二个容器的结束迭代器;dest指目标容器的起始迭代器
目标容器需要提前开辟空间,大小为两个容器中较小的容器大小
遍历交集时,结束迭代器使用算法返回的迭代器而不用end,这样能够准确遍历交集
5.6.2 set_union
求两个集合的并集,返回并集的最后一个元素的迭代器
两个容器都必须是有序序列
原型:set_union(beg1,end1,beg2,end2,dest)
参数beg1指第一个容器的起始迭代器;end1指第一个容器的结束迭代器;beg2指第二个容器的起始迭代器;end2指第二个容器的结束迭代器;dest指目标容器的起始迭代器
目标容器需要提前开辟空间,大小为两个容器中较大的容器大小
遍历并集时,结束迭代器使用算法返回的迭代器而不用end,这样能够准确遍历并集
5.6.3 set_difference
求两个集合的差集,返回差集的最后一个元素的迭代器
两个容器都必须是有序序列
原型:set_difference(beg1,end1,beg2,end2,dest)
参数beg1指第一个容器的起始迭代器;end1指第一个容器的结束迭代器;beg2指第二个容器的起始迭代器;end2指第二个容器的结束迭代器;dest指目标容器的起始迭代器
目标容器需要提前开辟空间,大小为第一个容器的大小
遍历差集时,结束迭代器使用算法返回的迭代器而不用end,这样能够准确遍历差集