标准模板库 STL(Standard Template Library)
是 C++ 标准库的一部分,不用单独安装,只需要#include 头文件。
C++ 对模板(Template)有很好的支持,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。
C++ 语言的核心优势之一就是便于软件的复用。
C++ 语言有两个方面体现了复用:
面向对象的继承和多态机制
通过模板的概念实现了对泛型程序设计的支持
C++中的模板,就好比英语作文的模板,只换主题,不换句式和结构。对应到C++模板,就是只换类型,不换方法。
STL六大部件
- 容器(Containers)
- 分配器(Allocators)
- 算法(Algorithm)
- 迭代器(Iterators)
- 适配器(Adapters)
- 仿函数(Functors)
我们来了解一下vector的用法
vector(矢量),是一种「变长数组」,即“自动改变数组长度的数组”。
值得一提的是,vector可以用来以邻接表的方式储存图,非常友好,非常简洁。
要使用vector,需要添加头文件:
#include <vector>
using namespace std;
vector的定义
像定义变量一样定义vector变量:
vector<类型名> 变量名;
类型名可以是int、double、char、struct,也可以是STL容器:vector、set、queue。
vector<int> name;
vector<double> name;
vector<char> name;
vector<struct node> name;
vector<vector<int> > name;//注意:> >之间要加空格
vector数组就是一个一维数组,如果定义成vector数组的数组,那就是二维数组。
vector<int> array[SZIE]; //二维变长数组
为什么说低维是高维的地址
例如二维数组中,它的一维形式就是地址:
#include <iostream>
using namespace std;
int main(){
int arr[3][2];//定义一个3行2列的地址
cout<<arr[0]<<endl; //输出arr第1行的地址
cout<<arr[1]<<endl; //输出arr第2行的地址
cout<<arr[2]<<endl; //输出arr第3行的地址
return 0;
}
输出为:
vector容器内元素的访问
vector一般分为两种访问方式:
1.通过下标访问
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> vi;
vi.push_back(1);
cout<<vi[0]<<endl;
return 0;
}
2 通过迭代器访问
迭代器(iterator)变量名;
vector<类型名>::iterator 变量名;
例如:
vector<int>::iterator it;
vector<double>::iterator it;
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v;
for (int i = 0; i < 5; i++)
{
v.push_back(i);
}
//v.begin()返回v的首元素地址
vector<int>::iterator it=v.begin();
for (int i = 0; i < v.size(); i++)
{
cout<<it[i]<<" ";
}
return 0;
}
vector<int> v;
vector<int>::iterator it = v.begin();
首先定义了一个int类型的向量;然后定义了一个具有int元素的迭代器类型。it的类型就是vector<int>::iterator
容器的iterator类型
每种容器类型都定义了自己的迭代器类型,如vector:
vector<int>::iterator iter;
这条语句定义了一个名为iter的变量,它的数据类型是由vector<int>定义的iterator类型。每个标准库容器类型都定义了一个名为iterator的成员,这里的iterator与迭代器实际类型的含义相同。
不同的容器类定义了自己的iterator类型,用于访问容器内的元素。换句话说,每个容器定义了一种名为iterator的类型,而这种类型支持(概念上的)迭代器的各种行为。
输出为:
0 1 2 3 4
for循环迭代部分也可以写成:
vector<int>::iterator it=v.begin();
for (int i = 0; i < v.size(); i++)
{
cout<<*(it+i)<<" ";
}
同时也是:
it[i] = *(it+i) //这两个写法等价
insert()
insert(__position,__x);
insert(要插入的地址,要插入的元素);
参数:
__position:– A const_iterator into the %vector.
__x:– Data to be inserted.
与push_back()无脑在尾部添加元素不同的是,insert()是根据指定位置在vector中插入元素。
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v;
for (int i = 0; i < 5; i++)
{
v.push_back(i);
}
for (int i = 0; i < v.size(); i++)
{
cout<<v[i]<<" ";
}
v.insert(v.begin()+2,-1); //将-1插入v[2]的位置
cout<<endl;
for (int i = 0; i < v.size(); i++)
{
cout<<v[i]<<" ";
}
return 0;
}
输出:
0 1 2 3 4
0 1 -1 2 3 4
erase():
erase(__position);
同样,与clear()简单粗暴清空vector不同的是erase(),删除指定位置的元素。
erase()有两种用法:
- 删除一个元素
- 删除一个区间内的元素
删除一个元素
erase(__position);
例为:
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v;
for (int i = 0; i < 5; i++)
{
v.push_back(i);
}
for (int i = 0; i < v.size(); i++)
{
cout<<v[i]<<" ";
}
//删除v[3]
v.erase(v.begin()+3);
cout<<endl;
for (int i = 0; i < v.size(); i++)
{
cout<<v[i]<<" ";
}
return 0;
}
输出:
0 1 2 3 4
0 1 2 4
删除一个区间内的元素
erase(__positionBegin,__positionEnd);
即是删除[ __positionBegin,__positionEnd )区间内的元素,注意:是左闭右开!
例如:
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v;
for (int i = 0; i < 5; i++)
{
v.push_back(i);
}
for (int i = 0; i < v.size(); i++)
{
cout<<v[i]<<" ";
}
//删除v[1]到v[4]的元素
v.erase(v.begin()+1,v.begin()+4);
cout<<endl;
for (int i = 0; i < v.size(); i++)
{
cout<<v[i]<<" ";
}
return 0;
}
输出为:
0 1 2 3 4
0 4
vector常见用途
用邻接表存储图
使用vector实现邻接表,更为简单。
储存数据
vector本身可以作为数组使用,而且在一些元素个数不确定的场合可以很好地节省空间利用率。
常用泛型算法
相关头文件
algorithm 一般算法
numeric 数值类
三个很重要的编程假定
算法永远不会直接操作元素。
算法通过一般的迭代器对元素做查询方面的工作;如果该用插入迭代器,的确可以改变元素的大小,但仍然是通过迭代器来实现的,算法本身不会改变元素的内容。
第三个参数是迭代器的算法,假定此迭代器对应的元素个数与前面的元素个数相同。
只读算法
- find(begin, end, val) => 找到了返回对应迭代器,否则返回第二个参数。
- find_if(begin, end, 谓词) => 查找第一个符合条件的元素,返回对应迭代器。
- accumulate(begin, end, val) => 将元素的值算个合计,val是累加器的初始值。#include <numeric>。
- equal(begin, end, begin2) => 对两个容器内元素进行比较,返回bool。
写算法
- fill(begin, end, val) => 用val值填充。
- fill_n(begin, n, val) => 在begin的位置开始填充n个val值;注意,这个元素要有>=n个元素才能开始填充。
- copy(begin, end, 目标元素) => 拷贝。
- replace(begin, end, val, new_val) => 替换。
- replace_copy(begin, end, back_inserter(result), val, new_val) => replace的增强版,将替换后的结果保存在result中,原来的元素不变。
- sort(begin, end) => 按字典排序。
- unique(begin, end) => 对已经排序的元素做唯一化,注意,必须已经排序了!返回值也很重要,是最后一个唯一的成员之后的位置。
- transform(begin, end, 目标位置, 谓词) => 用谓词转换迭代器间的元素,将结果保存在目标位置(一般就是第一个参数的迭代器)。
插入迭代器
back_inserter,对元素调用push_back()。
谓词种类
lambda,重载函数调用的类(学名:函数对象),函数,他们统称为可调用对象。
可调用对象举个例子,他们的基础类型是int(int, int),也就是接受两个int,返回一个int。
lambda结构
[捕获列表] (参数列表) -> 返回类型 {函数体}
必须永远包括捕获列表和函数体,参数列表和返回值类型可以省略。
使用lambda的一个原因是,lambda可以通过捕获列表来获取更多的局部变量,虽然很多时候函数可以代替lambda,但在泛型算法中谓词只有一元和二元谓词,多了的话函数接收不到应有的参数,除非使用标准库函数bind。
隐式捕获
[&]默认引用捕获,[=]默认值捕获。
如果显示隐式混用,必须显隐的捕获类型不同,默认捕获类型(隐式捕获)在[]的第一个参数。
可变lambda
默认情况[]中的值捕获,lambda不会在函数体内改变其内容的,要想改变必须加上mutable,形如[=] () mutable { return ++val; }。
标准库函数bind <functional>
bind函数可以成为函数适配器。
形式:auto newCallable = bind(callable, arg_list)
参数列表arg_list包含占位符std::placeholders::_1/_2/_3...。
那些不是占位符的参数涉及到引用的,加ref()/cref()。
算法格式
alg(beg, end)
一般alg_if会有一个谓词作为第三个参数,alg(beg, end, pred)。
一般alg_copy回有一个dest作为第三个参数,alg(beg, end, dest)。
如果alg_copy_if,alg(beg, end, dest, pred)。
list和forward_list独有的算法
为什么有独有的?因为通用的算法效率太低,毕竟链表的一些操作改变指针指向就可以了。
- lst.merge(lst2, [comp]) 将lst2合并入lst,lst2中的元素被删除。
- lst.remove(val) 删除掉给定val元素,进阶版本lst.remove_if(pred),删除谓词为真的。
- lst.reverse() 反转。
- lst.sort([comp/pred]) 排序。
- lst.unique([pred]) 排重。
function标准库类型
在上面谓词种类中,类对象调用和lambda有自己的类型,但不是函数类型,如果想转成函数类型,必须使用function。
加入lambda的类型是int (int, int),那么使用function<int (int, int> f = (class/lambda)来指定。
模板函数:
模板函数是泛型编程的重要思想之一
C++的STL库完全通过模板实现,对比函数重载,函数模板只需要通过一次函数定义就可以实现不同参数列表和参数类型的函数重载功能
#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T, typename Y>
void tfunc_1(T &t, Y &y)
{
cout << "t:" << typeid(t).name() << " " << t << endl;
cout << "y:" << typeid(y).name() << " " << y << endl;
}
T tfunc_2(T &t)
{
t = t + 1;
return t;
}
int main()
{
int n = 2; double f = 3.5;
tfunc_1(n, f);
cout << tfunc_2(n) << endl;
return 0;
}
// 运行结果:
// t:i 2
// y:d 3.5
// 3
// 说明:使用typeid().name()返回变量类型,i表示int,d表示double,依此类推.
函数模板具体化:
函数模板具体化是指如果要将某一个或某几个要处理的数据类型进行单独处理,需要额外定义对应数据类型的模板函数,形式是“ template <> void fun(type &t); ”,函数模板具体化和普通函数可同时存在,调用顺序为——普通函数 > 函数模板具体化 > 模板函数,这就类似于在类之外实现"多态"。
#include <iostream>
#include <typeinfo>
using namespace std;
struct Node
{
int val;
Node *next;
Node(int x) : val(x), next(NULL) {}
};
// 模板函数
template <typename T>
void tfunc(T &t)
{
cout << "tT:" << t << endl;
}
// 函数模板具体化(用于处理Node类型)
template <>
void tfunc(Node &node)
{
cout << "tNode val:" << node.val << endl;
}
// 普通函数
void tfunc(int &a)
{
cout << "tfunc():" << a << endl;
}
int main()
{
double a = 2.1;
tfunc(a);
int b = 1;
tfunc(b);
Node node(2);
tfunc(node);
return 0;
}
// 输出:
// tT:2.1
// tfunc():1
// tNode val:2
3、函数模板实例化:让编译器生成指定类型函数定义,不用定义函数实现,实例化示例为“template void fun(type &t);”,下面是一个简单的例子。(详细可参考[https://www.cnblogs.com/cthon/p/9203234.html])
// 说明:在函数调用时可直接显示实例化,而不适用显示实例化声明.
#include <iostream>
#include <typeinfo>
using namespace std;
template <typename T>
void tfunc(T &t)
{
cout << "tT:" << typeid(t).name() << " " << t << endl;
}
int main()
{
char i = 'A';
tfunc<char>(i); // 或写成 template void tfunc<char>(i);
return 0;
}
// 输出:tT:c A
函数模板实例化:
让编译器生成指定类型函数定义,不用定义函数实现,实例化示例为“template void fun(type &t);”,下面是一个简单的例子。(详细可参考[C++模板之函数模板实例化和具体化 - CTHON - 博客园模板声明 template<typename/class T>, typename比class最近后添加到C++标准。 常规模板,具体化模板,非模板函数的优先调用顺序。 非模板函数(普通函https://www.cnblogs.com/cthon/p/9203234.html])
类模板
类模板可以指定默认模板参数(函数模板不可以),跟函数参数的默认值一样,必须从右向左连续赋值默认类型,如果实例化对象时又传递了类型,则默认类型会被覆盖掉,跟函数参数是一样的。创建对象时需要传递模板参数列表,模板参数列表加在类名后面ClassName ; 如果类的模板参数列表有默认值,可以不传模板参数,但一定要加 <> 如 ClassName< > classN; 创建堆区对象的时候,所有的类名称后面都要加模板参数列表,如 ClassName< typename T >* classN = new ClassName< typename T>; 除了类内,其他地方出现 ClassName 的地方一般都要加模板参数列表。
#include <iostream>
#include <typeinfo>
using namespace std;
template <typename T = int, typename Y = char>
class Test
{
private:
T t;
Y y;
public:
Test(T t, Y y): t(t), y(y) {}
void tfunc();
};
template <typename T, typename Y> // 类模板的函数在类外实现,需要加上模板参数列表,但不需要加指定的默认模板参数
void Test<T, Y>::tfunc() // 类外使用Test需要加模板参数
{
cout << t << " " << y << endl;
}
int main()
{
int n = 2;
double d = 2.1;
Test<int, double> test(n, d);
// 使用默认模板参数:Test<> test(int(2), char('a'));
test.tfunc();
return 0;
}
// 输出:2 2.1
类模板的继承:
类模板被继承后参数的传递方式主要有两种,一种直接在子类继承父类的时候,为父类指定固定的类型,二是通过子类模板参数列表传递。
template <typename T, typename Y>
class A
{
public:
A(T t, Y y) {}
};
class Test: public A<int, double> // 父类是类模板,子类是普通类
{
public:
Test(): A<int, double>(2, 2.1) {}
};
main()
{
Test();
}
/*****************************************/
template <typename T1, typename Y1>
class B
{
public:
B(T1 t) {}
};
template <typename X, typename Z, typename P> // 父类为类模板,子类为类模板
class Test: public A<X, P>
{
public:
Test(X x, Z z, P p): A<X, P>(x) {}
};
main()
{
Test<int, double, char>(int(2), double(2.1), char('a'));
}
更多文献请参考[C++模板类(类模板)与继承]
类模板的多态:
在创建对象时,分为子类没有模板(CFather<short, char>*cf = new CSon;)和子类有模板(CFather<short, char> *cf = new CSon<short, int, char>)两种,子类和父类的模板参数列表可以不一样,但一定要对应好。
#include <iostream>
using namespace std;
template<typename T, typename Y>
class A
{
public:
virtual void tfunc(T t, Y y) = 0;
};
class Test: public A<int, double>
{
public:
virtual void tfunc(int n, double d)
{
cout << n << " " << d << endl;
}
};
// 父类是类模板,子类是普通类,在多态情况下只有父类需要指定模板参数
int main()
{
A<int, double> *a = new Test;
a->tfunc(2, 2.1);
return 0;
}
// 输出:2 2.1
#include <iostream>
using namespace std;
template<typename T, typename Y>
class A
{
public:
virtual void tfunc(T t, Y y) = 0;
};
template <typename X, typename Z, typename P>
class Test : public A<X, P>
{
public:
virtual void tfunc(X x, P p)
{
cout << x << " " << p << endl;
}
};
// 父类是类模板,子类是类模板,在多态情况下父类和子类都需要指定模板参数
int main()
{
A<int, double> *a = new Test<int, char, double>;
a->tfunc(2, 2.1);
return 0;
}
// 输出:2 2.1
成员模板
成员模板简单来讲就是模板中的模板,常见于模板类中构建模板函数
#include <iostream>
class Base1 {};
class Base2 {};
class Test1 : public Base1 {};
class Test2 : public Base2 {};
template <typename T1, typename T2>
class Pair
{
public:
T1 t1;
T2 t2;
Pair(T1 t1, T2 t2) : t1(t1), t2(t2) {}
// 类模板中的成员模板
template <typename U1, typename U2>
Pair(const Pair<U1, U2> &pair) : t1(pair.t1), t2(pair.t2) {}
};
int main()
{
Pair<Base1 *, Base2 *>(Pair<Test1 *, Test2 *>(new Test1, new Test2));
return 0;
}