C++模板

模板是泛型编程的基础,泛型编程即以一个独立于任何特定类型的方式编写代码。
模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,
所有模板就是将本来应该我们做的重复的事情交给了编译器
下面,我们先看一个例子,了解一下什么是模板?

一.函数模板

1.格式:
template <typename T1 , typename T2, … typename Tn>
返回值类型 函数名(参数列表) { }

#include<iostream>

template<typename T>
void Swap(T& left , T& right)
{
	T temp = left;
	left = right;
	right = temp;
}
int main()
{
	int a = 1, b = 2;
	char a1 = 10,b1 = 20;
	swap(a,b);
	swap(a1,b1);
	return 0;
}
二.函数模板的实例化

用不同类型的参数使用函数模板的时候,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显示实例化
1.隐式实例化:让编译器根据实参推演模板参数的实际类型

template<class T>
T Add(const T& left , const T& right)
{
	return left + right;
}
int main()
{
	int a = 10,b = 20;
	double c = 10.0,d = 20.0;
	Add(a,b);
	Add(c,d);

	//Add(a,d);
	// 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
	// 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	// 编译器无法确定此处到底该将T确定为int 或者 double类型而报错
	// 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅

	Add(a,(int)d) ;
	//此时有两种处理方式:
	//1.用户自己来进行强制转化
	//2.使用下面所说的显示实例化
	return 0;
}

2.显示实例化:在函数名的后面<>中制定模板参数的实际类型

int main()
{
	int a = 10;
	double b = 20.0;

	Add<int> (a,b);
	return 0;
}
//如果类型还不匹配,编译器就会进行隐式类型的转化,如果无法转化成功编译器就会进行报错。
三.模板参数的匹配原则

1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数还可以被实例化为这个非模板函数。

int Add(int left ,int right)//专门处理int的加法函数
{
	return left + right;
}

template<typename T>//通用加法函数
T Add(T left,T right)
{
	return left + right;
}

int main()
{
	Add(1,2);		//与非模板函数匹配,编译器不需要进行特化
	Add<int>(1,2);  //调用编译器特化的Add版本
}

2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非函数模板而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

//专门处理int的加法函数
int Add(int left,int right)
{
	return left + right;
}

//通用加法函数
template<class T1, class T2>
T1 Add(T1 left , T2 right)
{
	return left + right;
}

int main()
{
	Add(1,2);//与非函数模板类型完全匹配,不需要函数模板进行实例化
	Add(1,2.0);//模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数。
}

面试题:
1.在c++template中很多地方都用到了typename与class这两个关键字,而且好像可以替换,是不是这两个关键字完全一样呢?

  • 相信学习C++的人对class这个关键字都非常明白,class用于定义类,在模板引入c++后,最初定义模板的方法为: template…
    在这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字,它的作用同class一样表明后面的符号为一个类型,这样在定义模板的时候就可以使用下面的方式了: template…
    在模板定义语法中关键字class与typename的作用完全一样。

2.typename难道仅仅在模板定义中起作用吗?
其实不是这样,typename另外一个作用为:使用嵌套依赖类型(nested depended name),如下所示:

class MyArray 
{ 
public:
typedef int LengthType;
.....
}

template<class T>
void MyMethod( T myarr ) 
{ 
typedef typename T::LengthType LengthType; 
LengthType length = myarr.GetLength; 
}

这个时候typename的作用就是告诉c++编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有typename,编译器没有任何办法知道T::LengthType是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
3.模板为什么不支持分离编译?

  • 分离编译:一个项目由若干个源文件共同实现,而每个源文件(.cpp)单独编译成目标文件(.obj),最后将所有目标文件连接起来形成单一的可执行文件(.exe)的过程。
---------------test.h-------------------

void f();//这里声明一个函数f

---------------test.cpp--------------

#include”test.h”
void f()
{//do something
}  //这里实现出test.h中声明的f函数

---------------main.cpp--------------

#include”test.h”
int main()
{
		f(); //调用f,f具有外部连接类型
}

上述程序在编译器的内部过程为?

  • 在编译mian.cpp的时候,编译器并不知道f的实现,所以当碰到对f的调用时只是给出一个指示,指示连接器为它寻找f的实现体,所以main.obj中没有关于f实现的二进制代码。
  • 在编译test.cpp的时候,编译器找到了f的实现,所以在test.obj里有f实现的二进制代码。
  • 连接时,连接器在test.obj中找到f实现的二进制地址,然后将main.obj中未解决的f地址替换成该二进制地址。
//-------------test.h----------------//
 
template<class T>
class A
{
public: 
		void fun(); // 这里只是个声明
};
 
//---------------test.cpp-------------//
 
#include”test.h"

template<class T>
void A<T>::fun() // 模板的实现
{//do something
}
 
//---------------main.cpp---------------//
 
#include”test.h”
int main()
{ 
		fun();
}
  • 解释:编译器在main()函数中使用fun()处并不知道A::fun的定义,因为它不在test.h文件里面,于是编译器只能寄希望于连接器,希望它能够在其他.obj里面找到A::fun的实例,在本题中就是test.obj。

  • 然而,后者中并没有A::fun的二进制代码。因为C++标准 明确表示,当一个模板不被用到的时候它就不会被实例化出来,而tset.cpp中并没有用到A::fun,所以实际上test.cpp编译出来的test.obj文件中关于A::fun一行代码也都没有,于是连接器只能给一个链接错误。

  • 但是,如果在test.cpp中写一个函数,其中调用A::fun,则编译器就会将其实例化出来,因为在这个点上(test.cpp中),编译器知道模板的定义,所以能够实例化。于是,test.obj的符号导出表中就有了A::fun这个符号的地址,于是连接器就能够完成任务。

  • 关键是:在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,因此就会出现链接错误。

  • 解决方法
    1.在模版头文件xxx.h文件里面显示实例化,模版类的定义后面添加
    template class A< int>,但是一般不推荐使用这种方法,一是因为一些编译器不支持,而是因为每次使用一次模版,都需要把实例化声明在下边。
    2.将函数声明和函数定义放在一个源文件(xxx.hpp)中。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值