模板(c++)part2

目录

1.非类型模板参数

2.特化 

2.1函数模板特化

2.2类模板特化

 2.2.1全特化

2.2.2偏特化

3.模板分离编译


1.非类型模板参数

注意,假如

#define N 10
template<class T>
class A
{
private:
	T a[N];
};
这样的一个类模板,a数组的大小是定死的
而为了解决这个问题,引入了一个新的定义
template<class T,size_t N>
class A
{
private:
	T a[N];
};
这样在类模板构造类的时候,可以通过自己传的常量来控制一些数据大小等

注意,只能是整型常量(看编译器,越新,可能支持的常量类型越多)

2.特化 

针对一些特殊情况我们需要对类模板或函数模板进行特化,比如我们构造了一个用来比较大小的函数模板,但是如果传参数的时候,传了指针,这样就不会出现正确的结果了(跟自定义和内置无关,自定义类型比较的时候是依靠的相应类里面的运算符重载),因为这时候比较的是地址大小了

template<class T>
bool Less(T left, T right)
{
 return left < right;
}

2.1函数模板特化

特化步骤:

1.有基础的模板

2.特化的模板,关键字template后面<>里面是空的

3.函数名后面跟一个<特化需要的类型名>

4.注意,基础模板和特化模板,函数的形参要保持一致,类型可以不一样,其他方面尽量一致,否则编译器会给我们很多惊喜

template<class T>
bool Less(T left, T right)
{
 return left < right;
}

template<>
bool Less<Date*>(Date* left, Date* right)
{
     return *left < *right;
}
这就是对上面函数模板的一个特化,就是一种特殊处理
针对特殊情况特殊处理
但是这里其实意义不大,因为这种特化是针对已知自定义类型或内置类型
对未知类型,还是要自己手动写一个函数
平时更多的还不如直接写一个
bool Less<Date*>(Date* left, Date* right)
{
     return *left < *right;
}
直接走函数匹配,不用走模板

但是有一种场景我们需要注意,比如优先队列中,我们是priority_queue<Date*>,这样的话,我们就需要考虑自己来写了(按理来说是两种自己写的方式,但是我们平时都是用库里的容器,这样less也都是库里的,我们不能特化库里的,如果是自己写的比较模板,可以自己特化,具体方式参考下面的类模板特化。排除这个只剩下一种了)自己写一个新的比较模板,再传入优先队列的模板里

template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

struct kp {
	bool operator()(const mm<int, int>* a, const mm<int, int>* b)
	{
		return *a < *b;
	}
};
注意,我这里没写解引用的重载,直接放编译器会报错,我这里只是举例

int main()
{
	priority_queue<mm<int, int>*, vector<mm<int, int>*>, kp>a;
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
}

2.2类模板特化

 2.2.1全特化

即,将类模板的参数全部确定的特化方式

特化方式很简单,我们知道类模板是根据模板参数来生成类的,全特化就是针对其中的某种情况进行特化,比如上面当我们传入long long和double类型的,会走下面的特化,而不是依靠基础模板生成类,特化的类里面的内容也可以依据我们需要进行修改

2.2.2偏特化

第一种部分偏特化

 

#include<iostream>
#include<queue>
using namespace std;


template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

//全特化
template<>
class mm<long long, double>
{
public:
	mm() {
		cout << 222 << endl;
	}
};
//偏特化
template<class t1>
class mm<t1,double>
{
public:
	mm() {
		cout << 333;
	}
};


int main()
{
	mm<long long, double>a;
	mm<int, double>b;
偏特化就是限定了一部分类模板的一部分参数,比如这里,一般情况下遇到第二个参数是double的
都会走偏特化的部分,生成类的时候只有一部分是要生成的,剩下都是现成的

根据结果,我们可以发现,全特化是优先于偏特化的,毕竟有现成的成品,何必自己点份原料外卖再自己做呢

}

第二种,是对参数类型做了进一步的限制,比如之前我们一直讲的传指针的情况

#include<iostream>
#include<queue>
using namespace std;


template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

//全特化
template<>
class mm<long long, double>
{
public:
	mm() {
		cout << 222 << endl;
	}
};
//偏特化
template<class t1>
class mm<t1,double>
{
public:
	mm() {
		cout << 333 << endl;
	}
};
//进一步的偏特化
template<class t1,class t2>
class mm<t1, t2*>
{
public:
	mm() { cout << 444 << endl; }
};
template<class t1, class t2>
class mm<t1*, t2*>
{
public:
	mm() { cout << 555 << endl; }
};

int main()
{
	mm<long long, double>a;
	mm<int, double>b;
	mm<int*, double*>c;
	mm<int, double*>d;
显而易见,我们可以针对指针,引用,const等进行特化

}

这个时候,

​
template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};

struct kp {
	bool operator()(const mm<int, int>* a, const mm<int, int>* b)
	{
		return *a < *b;
	}
};
注意,我这里没写解引用的重载,直接放编译器会报错,我这里只是举例

int main()
{
	priority_queue<mm<int, int>*, vector<mm<int, int>*>, kp>a;
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
	a.push(new mm<int, int>);
}


​有新的解决方法
​
template<class t1,class t2>
class mm {
public:
	mm() {
		cout << 111 << endl;
	}
};
​template<class t>
class kp {
	bool operator()(const t& a, const t &b)
	{
		return a < b;
	}
};
template<class t>
class kp<t*> {
	bool operator()(const t*const& a, const t*const& b)
	{
		return *a < *b;
	}
注意,这里如果为了也想用引用,比如在引用前加入const修饰
因为我们如果是将一个非const类型传进去,会进行类型转换,权限缩小
类型转换之后会产生一个新的临时变量,再将临时变量传进去,而形参接受的时候,如果
加了引用,这时候引用的是临时变量,而临时变量是常量,如果要引用必须加const修饰
但这里其实意义不大,因为指针就算直接传值,消耗也不大的
};
这样针对原比较函数进行特化就方便许多(本来我们要指定类型,像上面写函数模板特化的时候,
我们还要指定哪种指针类型,而现在我们只需要记得把自己新写的类加上相应的解引用大于小于
等运算符重载即可),
虽然还是有问题,因为我们平时习惯用库里的了,不过问题不大,库里也是类似的设计。

3.模板分离编译

 在这之前,先复习下关于编译器的问题

我们知道,编译器在预处理阶段,.cpp文件是执行展开头文件操作的,.h文件只是用来展开,其他没什么用。然后.cpp->.i,然后再对.i文件进行语法检查等操作,.i->.s,再然后对.s文件进行汇编(变成机器能识别的语言)在这过程中,对调用函数会汇编成相应的语句,.s->.o,最后将整个工程里的所有.o文件链接起来。

我们可以注意到,在过程中,除了同流程的文件外,整个工程的文件只有在最后链接的时候才会有所关联。

这个时候,函数分离编译和模板分离编译就出现了差异,分离编译就是定义和声明分离在.cpp和.h文件中,而函数分离编译之所以可以成功,是因为函数定义出现之后,会产生相应的地址放在函数表里,这样另外一个文件如果调用了该函数,就可以依靠地址直接在内存中找到相应的函数,但是模板不同。

模板是只有在生成具体的函数或类的时候,才会在内存中留下地址,因此,关联上面的流程,当链接的时候调用的.o文件只有调用模板生成的函数或类(头文件展开的),这样就完全不知道模板在哪,更不知道怎么让模板生成相应的类或函数,更何况是实例化类、调用里面的函数了。而另一个只有定义的.cpp文件生成的.o文件,没有收到类型参数,就完全不知道要生成什么样的类或函数了,因此调用的时候,更不会在内存中留下地址供其他文件调用。

因此,为了解决这个问题,有2个方法

一个是将声明和定义放在同一个.h文件或.hpp文件里。

一个就是显示实例化(既然是在定义类或函数的.cpp里找不到类或函数,那干脆,就手动写一个)

.h文件

template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}
显示实例化
template
int Add<int>(const int&, const int&);
因为模板生成类或函数是在链接前的,所以我们只能手动在源文件里,
增加一个显示实例化,在链接前就让模板生成一个指定的类或函数
这样当另一个文件链接起来的时候,调用相应函数或实例化类,就不会
出现找不到地址的问题了

.cpp文件

#include<iostream>
using namespace std;
#include"s.h"
int main(){
    Add(1,2);
}

4. 总结模板

优:复用代码,节省资源(时间),加快迭代开发,增强代码灵活性

缺:容易代码膨胀,增加编译时间,模板编译错误时信息混乱,不易定位错误

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值