什么是STL?
C++标准库:标准模板库
通俗说:对常见的数据结构的封装+通用的算法(算法可以操作任意类型的数据,因为这些数据都是按照模板的方式给
出的,可以是内置类型的数据,也可以是自定义类型的数据。而且这些算法对于各种数据结构都是适用的,无论你将
数据承载在哪种数据结构中都可以进行处理)
sort:排序算法,无论你对vector,还是list都是通用的
STL六大组件:
容器:
组织数据,对常见数据结构的封装
序列式容器:
string:深浅拷贝问题,底层是由动态类型的字符串封装的,默认给出的是15个有效的,静态的字符空间,再需要扩容时以动态的字符串进行扩容
vector:底层是由动态的顺序表封装的,考虑扩容机制
list:带头结点的双向的循环的链表:
queue:底层由deque实现
stack:底层由deque实现
dueue,
priority_queue优先级队列(默认是大堆)
forward_list(带头结点的循环单链表c++11),
array(c++11新增的,静态的顺序表)
关联式容器:
接口分类:
构造与销毁:
元素访问:
容量操作:
元素修改操作:
迭代器:
特殊操作:(不是所有的容器都有特殊操作)
关于算法的简单的介绍:STL通用的类型的算法---与数据类型无关,与数据结构无关。
与数据类型无关靠的是:template模板
与数据结构无关靠的是:迭代器
如何给一个类增加迭代器:
1.根据容器底层的数据结构封装一个迭代器类
实际上是对指针进行封装
构造函数 / * / -> / == / ++ / --(非必须,因为单链表就不需要) / !=,在迭代器类中给出这些操作,
2.在容器类中给迭代器类型取别名;其实不取也可以,取的话就会显得统一:typedef迭代器类型 iterator;
3.在容器类中给出begin()和end()操作接口
所以算法就可以通过迭代器透明化(不必知道容器底层的数据结构)的去操作容器中的数据
迭代器分类:
原生态的指针
对原生态指针的封装
关于迭代器失效:
迭代器失效:迭代器对应的指针失效,指针为野指针,指针指向的 内容不存在
解决的办法就是给迭代器重新赋值
vector和list失效的场景?前面说过了
算法可以定制功能:
仿函数(函数对象):可以将一个类的对象按照函数的方式进行使用。仿函数的功能就是可以使算法,或者一个类更加的灵活。
在仿函数中就是定义一个类,在类中对()进行重载,
比如有一个算法for_ward(v.begin(),v.end(),op)意思就是在v的这段区间上的每一个元素,进行op
操作,op就是你所要进行的操作,可以是一个仿函数,为了定制这个函数的功能,你可以先定义一个类,
在类中对()重载,在函数体内是你要对每个元素进行的操作。
适配器:
比如我们平时用的stack和queue,就是对deque的封装
priority_queue:堆(默认大堆)想要使用小堆就要<存放的元素类型,vector<存放的元素类型>,great<存放的元素类型>>
注意:优先级队列中存放的必须是内置类型的话优先级队列可以直接使用
加入存放的是自定义的数据类型则必须要对>或者<进行重载,但是有的时候重载之后还是不行,那么可能就需要通过仿函数来进行完成了
模板参数分为:非类型模板参数,类型模板参数
template<class T,size_t N>
这个T就是类型模板参数,N就是非类型的模板参数
class array
{
public:
...
private:
T _array[N]; //N是常量
size_t _size;
};
array<int,10> arr; //定义类型的时候必须要都给出,例如这个还要指定大小
注意:
1.浮点数,类对象以及字符串是不允许作为非类型的模板参数的(自定义的类型也不可以)
2.非类型的模板参数必须要求在编译期就能确定结果,否则就是失败
模板的特化:
#include<iostream>
using namespace std;
template<class T>
T& MAX(T& left,T& right)
{
return left > right ? left : right;
}
class Date
{
friend ostream& operator<<(ostream& _cout, Date& p);
public:
Date(int year,int month,int day)
:_year(year)
, _month(month)
, _day(day)
{}
bool operator>(Date& p)
{
if (_year > p._year || _year == p._year && _month > p._month || _year == p._year && _month == p._month&&_day > p._day)
return true;
return false;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, Date& p)
{
_cout << p._year << " " << p._month << " " << p._day << " ";
return _cout;
}
int main()
{
int a = 10;
int b = 20;
cout << MAX(a, b) << endl; //内置类型可以直接比较
cout << MAX(b, a) << endl;
Date d1(2019, 10, 22); //自定义类型需要在类中将比较方式给出,那么就可以使用MAX函数来进行比较了
Date d2(2019, 10, 23);
cout << MAX(d1, d2) << endl;
cout << MAX(d2, d1) << endl;
//但是有的类型不能处理,或者处理出来就是错误
//通常情况下可以使用模板实现一些与类型无关的代码,但是对于一些特殊类型得到的就是错误的情况
char* p1 = "world";
char* p2 = "hello";
cout << MAX(p1, p2) << endl; //打印结果出错
cout << MAX(p2, p1) << endl;
return 0;
}
可以看出对于char*类型的变量的比较是存在错误的
模板的特化又分为函数模板的特化和类模板的特化:
函数模板一般特化的比较少
对于上面的函数来说就是MAX函数对于char*类型的变量在比较的时候会存在错误,所以接着我们就对char*类型来进行特化
在上面的代码段中插入下面的代码,那么在调用char*类型的函数的时候就会自动的去调用我们特化的MAX函数
//模板的特化
template<>
char*& MAX<char*>(char*& left, char*& right)
{
if (strcmp(left, right) == 1) ///strcmp大于返回1,小于返回-1
return left;
return right;
}
特化后的打印结果:
函数模板特化的注意事项:
如果要进行特化,那么首先就得先有一个模板函数(例如上面第一次的MAX函数),其次是我们要清楚,已经给出的模板对于哪种类型是有问题的(比如char*),这样才知道要对哪种类型,进行特化。
//普通模板和特化模板的比较
template<class T>
T& MAX(T& left,T& right)
{
return left > right ? left : right;
}
template<>
char*& MAX<char*>(char*& left, char*& right)
{
if (strcmp(left, right) == 1) ///strcmp大于返回1,小于返回-1
return left;
return right;
}
但是一般函数模板是不需要特化的,我们对于上面的情况不是进行特化,而是直接将不能处理类的函数直接给出(例如直接将char*类型的比较函数给出),因为直接给出的话比较简单,容易,因为某些函数的特化很难搞定,或者说根本搞不定。
char* MAX(char* left,char* right)
{
if (strcmp(left, right) == 1) ///strcmp大于返回1,小于返回-1
return left;
return right;
}
反正就是函数特化,有时候容易出状况,就算你把特化版本写出来有时也不会调,所以说函数模板对于哪一种类型有问题,就直接把哪一种类型的具体函数给出来就可以了,特化容易出错。
2》 void func(const T& p) //那么按照常理来推测就是const int*& p ,也就是说const修饰的是p所只想的空间里面的内容不能修改
{
3》 *p = 100; //但是这里修改了
int b = 20;
p = b; //这一步反而会报错
}
int main()
{
int a = 10;
int* p = &a;
1》 func(p); //这里传的是int* 类型
4》 cout << a << endl; //并且在外部还可以正常打印出100
return 0;
}
由上可见其实对于函数参数的类型的推演,也就是第二步我们就判断错误了,
const int a = 10;
int const b = 10;
对于上面两个const修饰的都是变量a,b和位置并没有关系,也就是说在参数列表中T 就仅仅只是一个变量,
别管是int* ,int,int**,反正const修饰的只是后面的变量。
模板特化:
全特化:对所有类型的参数进行特化
template<class T1,class T2>
class Date
{
public:
Date()
{}
private:
T1 _year;
T2 _month;
};
///上面类的特化
template<>
class Date
{
public:
Date()
{}
private:
int _year;
int _month;
};
Date<int,int> m; //调用特化版本
Date<int,double> w; // 调用模板类
偏特化:
又包含部分特化:—》将模板参数中的部分类型进行特化
template<class T1, class T2>
class Date
{
public:
Date()
{}
private:
T1 _year;
T2 _month;
};
///上面类的偏特化
//意思就是只要在定义类对象的时候,给出的第二个参数是int类型的就调用偏特化版本
template<class T1, int>
class Date
{
public:
Date()
{}
private:
T1 _year;
int _month;
};
Date<int, int> m; //调用偏特化
Date<int, double> w; // 调用模板类
非类型模板参数必须在编译器就确认其结果,
template<class T, size_t N>
class array
{
public:
...
private:
T _array[N]; //N是常量
size_t _size;
};
int a = 10;
int b = 20
array<int,a + b > arr;//必须在编译期给出结果,这里会编译报错
偏特化的第二个特性:让米板参数列表中的类型更加的严格
//假如我们想让模板只能处理指针类型的参数,那么就可以这样做
//让模板参数列表的限制更加的严格
template<class T1, class T2>
class Date<T1*,T2*>
{
public:
Date()
{}
private:
T1* _year;
T2* _month;
};
Date<int*, int*> m; //调用偏特化
Date<int*, double*> w; // 调用模板类