C++小工修炼之路XVII

什么是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<存放的元素类型>>
	注意:优先级队列中存放的必须是内置类型的话优先级队列可以直接使用
	加入存放的是自定义的数据类型则必须要对>或者<进行重载,但是有的时候重载之后还是不行,那么可能就需要通过仿函数来进行完成了

C++适配器

模板参数分为:非类型模板参数,类型模板参数

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;
}

反正就是函数特化,有时候容易出状况,就算你把特化版本写出来有时也不会调,所以说函数模板对于哪一种类型有问题,就直接把哪一种类型的具体函数给出来就可以了,特化容易出错。

2void 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;
1func(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; // 调用模板类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值