一、模板
1.模板的概念
模板是泛型编程主要用到的技术。模板就是建立通用的模具,大大提高复用性。就像生活中那种照片、视频的模板,可以直接套用。
c++中主要有两种模板:函数模板和类模板
模板的特点:1.是一个框架,不能直接使用。2.通用性强,但不是万能的。
2.函数模板
作用:建立一个通用模板,其中函数返回类型和形参的类型都可以不具体设置,使用虚拟模板代替
语法:template<typename T>
函数声明或定义
template:声明创建模板,typename:可以用class代替,后面跟着一种数据类型。
T:通用的数据类型,可以替换,一般为大写字母。
eg:
正常实现多个数据交换函数
void swapint(int& a, int& b) {
int temp;
temp = a;
a = b;
b = temp;
}
void swapd(double& a, double& b) {
double temp;
temp = a;
a = b;
b = temp;
}
void test() {
int a = 10;
int b = 90;
swapint(a, b);
cout << "a=" << a << " b=" << b << endl;
double c=10.2;
double d=20.3;
swapd(c, d);
cout << "c=" << c << " d=" << d << endl;
}
int main() {
test();
system("pause");
return 0;
}
对于每一种类型的数据交换,都要专门写一个函数来实现,而c++中除了本来有的int double等类型以外,还有自定义的数据类型,所以全部都写函数代码量居多,所以这时候使用模板来实现。
模板实现数据交换函数
template<typename T>
void myswap(T& a, T& b) {
T temp;
temp = a;
a = b;
b = temp;
}
void test() {
int a = 10;
int b = 90;
myswap(a, b);
cout << "a=" << a << " b=" << b << endl;
double c=10.2;
double d=20.3;
myswap(c, d);
cout << "c=" << c << " d=" << d << endl;
}
int main() {
test();
system("pause");
return 0;
}
这就是具体的实现,减少了代码量还有可读性强。
其中函数模板的使用有两种方式:
直接使用(如上面代码),编译器自动识别类型:函数名(形参)
指定出类型:函数名<指定类型>(形参)
注意:这两种方法都必须确保通用数据类型正确,才能调用。
函数模板和普通函数的区别:主要是是否有自动类型转换
普通函数是有自动类型转换,在函数模板中,如果使用的是直接使用,自动识别类型,是不存在自动类型转换,但是指定出类型时是存在自动类型转换的。
可以通过空模板参数列表 函数名<>(形参) 来强制调用函数模板
函数模板也可以重载。
模板的局限性以及解决:
template<typename T>
void myswap(T& a, T& b) {
T temp;
temp = a;
a = b;
b = temp;
}
对于这个函数模板,如果传入的是一个自定义的类person,就会报错,编译器在调用过程无法识别person类=person类的操作,当然可以使用运算符重载解决,但是太过麻烦,可以使用另外一种办法解决,就是具体化参数类型以及函数定义里的内容。
template<>void myswap(person& a, person& b) {
person temp;
temp.one = a.one;
a.one = b.one;
b.one = temp.one;
}
3.类模板
作用:建立一个通用类模板,其中类的成员的数据类型都可以不具体设置,使用虚拟模板代替
语法:template<typename T>
类
template:声明创建模板,typename:可以用class代替,后面跟着一种数据类型。
T:通用的数据类型,可以替换,一般为大写字母。
eg:
简单的类模板
template<class A,class B>
class person {
public:
A name;
B age;
void show() {
cout << "name=" << name << "age=" << age << endl;
}
};
void test() {
person<string, int> p;
p.name = "小明";
p.age = 20;
p.show();
}
int main() {
test();
system("pause");
return 0;
}
当有多种类型需要替代时,每一个虚拟类型之间用,隔开。
在创建对象时用指定出类型的方法确定出类型:类名<具体的类型> 对象名
类模板使用没有自动推导类型,但是在类模板在声明时可以有默认类型设置,就像下面代码一样
template<class A,class B=int>
在类模板中的成员函数是在被调用时才会创建,而普通的类中的成员函数是一开始就创建好的。
类模板实例化的对象作函数参数的传入方式:
指定传入类型
void test(person<string, int>& p)
参数模板化
template<class T1,class T2>
void test(person<T1, T2>& p) {
}
整个类都模板化
template<class T>
void test(T& p) {
}
类与继承
当子类继承的父类是一个类模板时,在继承过程中必须指定出数据类型,要不然会报错
class test :public person<string, int> {
};
但是如果想灵活指定父类的数据类型,就需要把子类也写成类模板
template<class T1,class T2,class T3>
class test :public person<T2,T3> {
T3 age;
};
void test1() {
test<int, string, int> m;
}
T2和T3是继承中指定父类的数据类型,而T1是指定子类自己的数据类,这样就可以灵活应用。
类模板的成员函数类外实现:
template<class T1,class T2>
void person<T1, T2>::show() {
}
必须写出作用域,又以为是类模板所以要加上<T1,T2>,但是编译器在这里识别不了T1和T2,所以要加上template<class T1,class T2>。
类模板分文件编写
因为类模板成员函数是在调用时创建,如果声明和实现分文件写,编译器就找不到。
解决方法:1.直接包含.cpp文件
2.声明和实现写在一个文件,并且改后缀为.hpp
全局函数作类模板的友元的实现:
直接在类模板内实现(把函数实现写在类内)
在类外实现,需要提前声明全局函数(在实现类模板前)
二、STL
stl基本概念
STL的产生为了建立数据结构和算法的一套标准。
STL:标准模板库
STL广义上分为容器、算法、迭代器,容器和算法之间通过迭代器无缝衔接
STL几乎所有的代码都使用了函数模板或类模板
STL六大组件
STL大体分为六大组件:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
容器:各种数据结构,如vector,list,deque等,用来存放数据
算法:各种常用的算法,如sort,find
迭代器:扮演容器和算法之间的胶合剂
仿函数:行为类似函数,可作算法的某种策略
适配器:一种用来修饰容器或仿函数或迭代器接口的东西
空间配置器:负责空间的配置和管理
3.STL中的容器、算法、迭代器
容器
用于存放东西
STL容器就是把运用最广泛的数据结构实现出来
常见的数据结构:数组,链表,队列,栈,集合,映射表等
这些容器分为序列式容器和关联式容器:
序列式容器:强调值的排序。容器中每一个元素都有固定位置
关联式容器:二叉树结构,容器中各元素之间没有严格的物理上的关系
算法
问题的解法。用有限的步骤解决逻辑或数学上的问题,就叫做算法
算法分为质变算法和非质变算法
质变算法:是指运用过程中会更改区间内元素的内容。如拷贝,替换,删除等
非质变算法:是指运用过程中不会更改区间内元素的内容。如查找,计数,遍历等
迭代器
容器和算法之间的粘合剂
提供一种方法,使之能够依序的寻访某个容器内的各个元素,而又不会暴露容器内的内部表达方式
每一个容器都有自己专属的迭代器
迭代器类似于指定,前期可以把迭代器当作指针看
迭代器种类:

常用迭代器种类为:双向迭代器或随机访问迭代器
4.STL中的容器、算法、迭代器初始
容器分为三种:
顺序容器:vetor deque list
关联容器:set map multiset
容器适配器:stack queue priority_queue
vector
概念:vector数据结构和数组非常像,又叫单端数组。
vector与普通数组的区别:数组是静态空间,而vector可以动态拓展(找到更大的空间,把原来空间的东西复制过去并释放原来的空间)
1.vector的构造函数
功能:创建vector容器
函数原型:
·vector<T> v; //采用模板实现类实现,默认构造函数
·vector(v.begin() , v.end()); //将v{begin(),end()}区间的元素拷贝给本身
·vector(n,elem); //将n个elem拷贝给本身
·vector(const vetor &vec); //拷贝构造函数
vector存放内置数据类型并遍历这个容器
算法:for_each
迭代器:vector<int>::iterator
创建vector容器:vector<int> v;
向容器插入数据(10):v.push_back(10);
下面是创建容器、插入数据、遍历容器的简单代码演示:
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>//标准算法头文件
using namespace std;
void myprint(int val) {
cout << val << endl;
}
void test() {
//创建一个vector容器
vector<int> v;
//插入内置数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
//通过迭代器访问容器中的数据
//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;
//}
//第三种遍历方式
for_each(v.begin(), v.end(), myprint);
}
int main() {
test();
system("pause");
return 0;
创建vector容器需要包含头文件<vextor>,第三种遍历方式使用的算法需要包含头文件<algorithm>
第三种遍历方式的底层原理如下图

其实就是前面的方法封装成算法。
vector容器存放其他数据类型
vector容器存放自定义数据类型与存放内置数据类型相同。
也可以容器中嵌套容器,类似于二维数组。:vector<vector<int>> v;
在遍历这个容器时要写两个for循环,外层遍历每一个存放int的容器,内层遍历每个容器内的int类型数据
2.vector的赋值操作
功能:给vector容器赋值
函数原型:
·vector& operator=(const vector &vac); //重载等号操作符,直接=就行
·assign(big,end); //将【big,end】区间的数据拷贝赋值给本身
·assign(n,elem); //将n个elem拷贝赋值给本身
示例:
vector<int> v;
vector<int> v1;
v1 = v;
vector<int> v2;
v2.assign(v.begin(), v.end());
vector<int> v3;
v3.assign(5, 10);
3.vector容量和大小
功能:对vector容量和大小的操作
函数原型:
·empty(); //判断容器是否为空,如果为空返回1
·capacity(); //返回容器的容量
·size(); //返回容器中元素的个数
·resize(num); //重新指定容器长度为num,若容器变长,则默认以0填充新位置,若容器变短,则删除末尾位置元素
·resize(num,elem); //重新指定容器长度为num,若容器变长,则默认以elem填充新位置,若容器变短,则删除末尾位置元素
示例:
//创建一个vector容器
vector<int> v;
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
if (v.empty()) {
cout << "容器为空" << endl;
}
else {
cout << "容器不为空" << endl;
//输出容器容量
cout << "容器的容量为" << v.capacity() << endl;
//输出容器大小(元素个数)
cout << "容器的大小为" << v.size() << endl;
}
//重新设置容器容量,多余的位置用0补充
v.resize(15);
//重新设置容器容量,多余的位置用10补充
v.resize(15, 10);
vector插入和删除
功能:对vector容器进行插入和删除操作
函数原型:
·push_back(a); //尾部插入元素a
·pop_back(); //删除最后一个元素
·insert(const_iterator pos,a); //迭代器指向位置pos插入元素a
·insert(const_iterator pos,int coust,a); //迭代器指向位置pos插入coust个元素a
·erase(const_iterator pos); //删除迭代器指向的元素
·erase(const_iterator start, const_iterator end); //删除迭代器指向的从start到end之间的内容
·clear(); //删除容器中所有元素
示例:
//创建一个vector容器
vector<int> v;
//尾插
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
//尾删
v.pop_back();
//指定位置插入
v.insert(v.begin(), 100);
//指定位置插入多个元素
v.insert(v.begin(),2, 100);
//指定位置删除
v.erase(v.begin());
//指定区间删除与清空容器
v.erase(v.begin(), v.end());
v.clear();
vector数据存取
功能:对vector中的数据的存取操作
函数原型:
·at(num); //返回第num个元素的数据
·[num]; //返回第num个元素的数据
·front(); //返回容器中第一个元素的数据
·back(); //返回容器中最后一个元素的数据
示例:
//创建一个vector容器
vector<int> v;
//尾插
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
//打印容器第2个元素的数据
cout << v.at(2) << endl;
cout << v[2] << endl;//类似数组的操作
//打印容器第一个和最后一个元素数据
cout << v.front() << v.back() << endl;
vector互换容器
功能:实现两个容器互换
函数原型:
·swap(v1); //将v1与自身的元素互换
函数比较简单。一般使用这个函数来巧妙的减少空间使用。
首先按下面的代码进行容器的元素的减少
//创建一个vector容器
vector<int> v;
for (int i = 0; i < 1000; i++) {
v.push_back(i);
}
v.resize(3);
一开始的容器大小为1000,容量13000多,而使用resize减少元素数量为3的时候,容器空间(容量)还是13000多。
所以可以使用以下代码来防止这个情况
vector<int>(v).swap(v);
代码解析:vector<int> (v) //创建匿名对象,以v的元素大小为容量创建
.swap() //容器交换
这样之后就可以使v的容量和大小都变为3.
vector预留空间
功能:减少vector在动态扩展容量时的扩展次数
函数原型:
·reserve(num); //容器预留num个元素长度,预留位置不初始化,元素不可访问
set/multiset
1.set基本概念
简介:所有元素都会在插入时自动被排序
本质:set/multiset属于关联式容器,底层结构是用二叉树实现
set和multiset区别:
·set不允许容器中有重复元素
·multiset允许容器中有重复元素
2.set构造和赋值
功能:创建set容器以及赋值
构造:
·set<T> t; //默认构造函数
·set(const set &st); //拷贝构造函数
赋值:
·set& operator=(const set &st); //重载等号操作符,直接使用=
3.set插入和删除
功能:set容器进行插入和删除数据
函数原型:
·insert(a); //在容器中插入元素a
·clear(); //清除所有元素
·erase(const iterator pos); //删除pos迭代器指向的元素,返回下一个元素的迭代器
·erase(const iterator begin,const iterator end); //删除区间迭代器指向区间(begin,end)中所有元素,返回下一个元素的迭代器
·erase(num); //删除容器中值为num的元素;
set容器的创建赋值删除和遍历示例:
//创建一个vector容器
set<int> s;
for (int i = 10; i < 60; i+=10) {
s.insert(i);
}
通过迭代器访问容器中的数据
//set<int>::iterator itBegin = s.begin();//起始迭代器,指向容器第一个元素
//set<int>::iterator itEnd = s.end();//结束迭代器,指向容器最后一个元素的下一个位置
遍历容器
// //第一种遍历方式
//while (itBegin != itEnd) {
// cout << *itBegin << endl;
// itBegin++;
//}
//第二种遍历方式
//for (set<int>::iterator it = s.begin(); it != s.end(); it++){
// cout << *it << endl;
//}
//第三种遍历方式
for_each(s.begin(), s.end(), myprint);
set容器的迭代器和遍历使用的算法和vector是相同的。所以过程也差不多。
4.set大小和交换
功能
:统计set容器大小以及交换set容器
函数原型:
·size(); //返回容器中元素的数目
·empty(); //判断容器是否为空
·swap(st); //交换本身与st容器的元素
每个函数功能和操作和vector容器中的基本上都是一样的,就是唯一一点set容器不支持resize(重新指定大小)。
5.set查找和统计
功能:对set容器进行查找数据和统计数据
函数原型:
·find(key); //查找key是否存在,若存在返回该数据元素的迭代器,若不存在,返回set.end().
·count(key); //统计key的元素个数,返回个数(对于set容器只有0或1,而对于multiset就有不同的结果)
queue
1.queue基本概念
概念:queue是一种先进先出的数据结构(是队列容器),它有两个出口

队列容器允许从一端新增元素,从另一端移除元素
队列只有队头和队尾可以被外界使用,因此队列不允许有遍历行为
队列中进数据叫--入队 push
队列中出数据叫--出队 pop
2.queue常用接口
功能:栈容器常用的对外接口
构造函数:
·queue<T> q; //默认构造
·queue(const queue &q); //拷贝构造
赋值操作:
·queue& operator=(const queue& q); //重载等号操作符,直接使用=
数据存取:
·push(a); //往队尾添加元素
·pop(); //从队头移除第一个元素
·back(); //返回最后一个元素
·front(); //返回第一个元素
大小操作:
·empty(); //判读堆栈是否为空
·size(); //返回栈的大小