文章目录
模板
为什么要模板,其实目的就是一个,
就是将本来应该我们做的重复的事情交给编译器
模板的基本用法
模板函数
用法用例
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
上面实现了一个两个类型交互的函数,利用模板函数,我们可以传入类型完成不同类型值的交换,例如
int a;
int b;
swap<int>(a,b);
float f1;
float f2;
swap<float>(f1,f2);
隐式实例化
编译器根据传入实参推演模板参数的实际类型
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a;
int b;
swap(a,b);
}
显式实例化
在函数名后的<>中指定模板参数的实际类型
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a;
int b;
swap<int>(a,b);
}
模板参数的匹配原则
(1)一个非模板函数可以和一个同名的模板函数同时存在,而且该函数的模板还可以被实例化为这个非模板函数
void swap(int& a,int& b)
{
int tmp = a;
a = b;
b = tmp;
}
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a=10;
int b=20;
swap(a,b);//编译器优先调用可以匹配的非模板参数
swap<int>(a,b);//主动让编译器生成swap的实例
}
(2)对于非模板函数的同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好的函数,那么将选择模板
void swap(int& a,int& b)
{
int tmp = a;
a = b;
b = tmp;
}
template<typename T>
void swap(T& a,T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a=10;
int b=20;
float f1;
float f2;
swap(a,b);//编译器优先调用可以匹配的非模板参数
swap(f1,f2);//编译器会生成一个更加匹配的版本,编译器根据实参生成更加匹配的add函数
}
(3)模板函数不允许自动类型转换,但普通函数可以进行自动类型转换.
模板定义的格式
类模板
template<class T1,class T2,..,clas Tn>
class 模板类名
{
//类内成员定义
}
类外的函数定义需要加上模板参数列表
template<class T>
class Vector
{
...
}
template <class T>
vector<T>::~vector()
{
...
}
模板的实例化
我们定义的模板函数其实不是真正调用的函数,我们真正使用的函数是通过模板函数实例化出来的代码
非类型模板参数
上面的模板是传类型,而事实上,模板还可以传非类型的参数,例如下面
示例代码
#include <iostream>
template<class T,size_t N = 10>
class Array
{
public:
T _array[N];
};
int main()
{
Array<int,10> arr1;
Array<int,20> arr2;
return 0;
}
注意
(1)浮点数,类对象以及字符串是不允许作为非类型模板参数的
(2)非类型的模板参数必须在编译期就能确认结果。就和数组长度的定义一样
模板的特化
模板可以实现与类型无关的代码,但是也会有一些地方需要特殊处理,例如
template<class T>
bool IsEqual(T& left,T& right)
{
return left==right;
}
void Test()
{
char* p1 = "hello";
char* p2 = "world";
if(IsEqual(p1, p2))
cout<<p1<<endl;
else
cout<<p2<<endl;
}
对于字符串的比较就不能直接像上面这样比较,而需要特殊化处理
模板特化的步骤
(1)必须有一个基础的模板
(2)关键template后面接一对空的尖括号<>
(3)函数名后跟一对尖括号,尖括号指定需要特化的类型
(4) 函数形参表必须和函数模板的基础参数类型完全相同,如果不同就会报错
template<>
bool IsEqual<char*>(char*& left, char*& right)
{
if(strcmp(left, right) > 0)
return true;
return false;
}
注意
也可以直接实现该函数,直接匹配最佳的非模板参数,那样就不需要编译器再生成一个实例了
bool IsEqual(char* left, char* right)
{
if(strcmp(left, right) > 0)
return true;
return false;
}
利用非类型模板参数的特性解题
(1)求1+2+3+…n,要求不能使用乘法除法,for,while,if,else,switch,case等关键字及条件判断语句(A?B:C)**
解题思路
利用非类型参数做一个递归的实例化,利用特化做一个递归的收敛
代码
模板的偏特化
部分特化
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
对参数进行限制
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
模板特化的应用
类型萃取
为什么要类型萃取呢?看下面的代码
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
int main()
{
// 试试下面的代码
string strarr1[3] = {"11", "22", "33"};
string strarr2[3];
Copy(strarr2, strarr1, 3);
}
显然对字符串的拷贝,上面的代码并不通用 ,我们可以尝试用一个方法区别自定义类型和内置类型,比如
template<class T>
void Copy(T* dst, const T* src, size_t size, bool IsPODType)
{
if(IsPODType)
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
上面增加了一个 bool 来区别自定义类型和内置类型,但是这样就增加了使用上出错的概率,能否用一种方法让编译器自动识别呢,如下
//
// POD: plain old data 平凡类型(无关痛痒的类型)--基本类型
// 指在C++ 中与 C兼容的类型,可以按照 C 的方式处理。
//
// 此处只是举例,只列出个别类型
bool IsPODType(const char* strType)
{
const char* arrType[] = {"char", "short", "int", "long", "long long", "float",
"double", "long double"};
for(size_t i = 0; i < sizeof(array)/sizeof(array[0]); ++i)
{
if(0 == strcmp(strType, arrType[i]))
return true;
}
return false;
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
if(IsPODType(typeid(T).name()))
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
上面利用tipeid来确认拷贝对象的实际类型,在内置类型的集合中寻找是否匹配内置类型,但是这样就有缺陷了,每次都要比较字符串,效率很低
事实上我们可以借助模板的特化进行类型萃取,而且是编译的时候就已经完成了,所以这方法效率很高
/*
T为int:TypeTraits<int>已经特化过,程序运行时就会使用已经特化过的TypeTraits<int>, 该类中的
IsPODType刚好为类TrueType,而TrueType中Get函数返回true,内置类型使用memcpy方式拷贝
T为string:TypeTraits<string>没有特化过,程序运行时使用TypeTraits类模板, 该类模板中的IsPODType
刚
好为类FalseType,而FalseType中Get函数返回true,自定义类型使用赋值方式拷贝
*/
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
if(TypeTraits<T>::IsPODType::Get())
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
int main()
{
int a1[] = {1,2,3,4,5,6,7,8,9,0};
int a2[10];
Copy(a2, a1, 10);
string s1[] = {"1111", "2222", "3333", "4444"};
string s2[4];
Copy(s2, s1, 4);
return 0;
}
关于为什么模板不能分离编译?
1.为什么模板不能分离编译了,首先我们要这样想,模板是一个图纸一样的东西,假设我们的模板参数是一个int,那编译器就要借助这个模板生成一个为int类型服务的代码,也就是生成一个模板实例,而这个实例是一段代码。
2.模板生成实例的时候是在链接前,生成实例的前提是当前文件中调用了模板
3.C/C++程序要运行,一般要经历一下步骤
预编译—》编译—>汇编—> 链接
编译:对程序按照语言特性进行词法,语法,语义分析,错误检查无误后生成汇编代码,注意头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。
链接:将多个obj文件和合并成一个,并处理没有解决的地址问题。
下面代码引用自 https://blog.csdn.net/pongba/article/details/19130
//-------------test.h----------------//
template<class T>
class A
{
public:
void f(); // 这里只是个声明
};
//---------------test.cpp-------------//
#include”test.h”
template<class T>
void A<T>::f()// 模板的实现
{
…//do something
}
//---------------main.cpp---------------//
#include”test.h”
int main()
{
A<int> a;
f(); // #1
}
编译器在编译 #1的时候并不知道A::f的定义,因为它不在test.h里面,于是编译器只好寄希望于连接器,希望它能够在其他的.obj里面找到A::f的实例,在本例中就是test.obj,然而,实际上后者根本就没有A::f的二进制代码
因为C++标准明确表示,当一个模板不被用到的时候它就不该被实例化出来,test.cpp中用到了A::f了吗?没有!!所以实际上test.cpp编译出来的test.obj文件中关于A::f一行的二进制代码也没有,于是连接器就傻了,根本就没有要的二进制代码嘛,于是就报出了链接错误
如果在test.cpp中写一个函数,然后里面调用了A::f 编译器就会把它实例化出来,因为test.cpp使用了A::f,并且test.cpp也有A::f的模板,所以就会实例化出来,在这种情况下,我们再链接,就发现可以链接上了。
下面是在test.cpp中写一个函数,函数中调用A::f,这样就会生成A::f的实例,test.obj的符号导出表中就有了A::f这个符号的地址,于是连接器就能完成任务。
如下
//---------test.h------//
template<class T>
int Add(const T& d1,const T& d2);
//---------test.cpp-----//
#include "test.h"
template<class T>
int Add(const T& d1,const T& d2)
{
return d1+d2;
}
void fun()
{
Add<int>(1,2);
}
//-------main.cpp---//
#include <iostream>
#include "test.h"
using namespace std;
int main()
{
cout<<Add<int>(1,2)<<endl;
return 0;
}
//------makefile---//
main:main.cpp test.cpp
g++ -o $@ $^
加上void fun()
{
Add(1,2);
}后就可以编译成功了,同时要这样编译g++ -o main main.cpp test.cpp