- 模板分为函数模板和类模板两种
文章目录
一、函数模板
#include <iostream>
using namespace std;
//函数模板的声明
template <class T>
//模板函数:函数模板实例化后生成的具体的函数
void Swap(T& x1, T& x2)
{
T x = x1;
x1 = x2;
x2 = x;
}
int main()
{
int a = 2, b = 9;
double da = 1.2, db = 9.8;
float f = 3, m = 10;
short x = 5, y = 50;
long l = 500, n = 800;
char c = 'r', d = 'u';
Swap(a, b);
Swap(da, db);
Swap(f, m);
Swap(x, y);
Swap(l, n);
Swap(c, d);
cout << "a = " << a << ", " << "b = " << b << endl;
cout << "da = " << da << ", " << "db = " << db << endl;
cout << "f = " << f << ", " << "m = " << m << endl;
cout << "x = " << x << ", " << "y = " << y << endl;
cout << "l = " << l << ", " << "n = " << n << endl;
cout << "c = " << c << ", " << "d = " << d << endl;
return 0;
}
1.函数模板的概念及格式
-
概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
-
如上述根据不同的类型,模板函数产生了不同的类型版本的函数,当a和b作为实参传入时,产生的函数就是int型的。
-
格式:template <typename T1, typename T2, typename T3, … , typename Tn>
返回值类型 函数名(参数列表) {} -
其中typename是用来定义模板参数的关键字,也可以用class,但不可以用struct代替。
//函数模板的声明
template <class T>
//模板函数:函数模板实例化后生成的具体的函数
void Swap(T& x1, T& x2)
{
T x = x1;
x1 = x2;
x2 = x;
}
2.函数模板的原理
- 编译器需要根据传入实参类型来推演生成对应类型的函数,以供调用。
- 比如上述的样例中,当传入实参a和b时,编译器通过对实参类型的推演,将T确定为int类型,然后生成一份专门处理int类型的代码。当传入实参da和db时,编译器通过对实参类型的推演,将T确定为double类型,然后生成一份专门处理double类型的代码。当传入实参f和m时,编译器通过对实参类型的推演,将T确定为float类型,然后生成一份专门处理float类型的代码。当传入实参x和y时,编译器通过对实参类型的推演,将T确定为short类型,然后生成一份专门处理short类型的代码。当传入实参l和n时,编译器通过对实参类型的推演,将T确定为long类型,然后生成一份专门处理long类型的代码。当传入实参c和d时,编译器通过对实参类型的推演,将T确定为char类型,然后生成一份专门处理char类型的代码。
3.函数模板的实例化
#include <iostream>
using namespace std;
//函数模板的声明
template <class T>
//模板函数:函数模板实例化后生成的具体的函数
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 88, b = 100;
double c = 22.2, d = 99.9;
//隐式实例化
cout << "a + b = " << Add(a, b) << endl;
cout << "c + d = " << Add(c, d) << endl;
//当两个类型不一样时
//显式实例化
cout << "a + c = " << Add<int>(a, c) << endl;
//隐式实例化
cout << "b + d = " << Add((double)b, d) << endl;
return 0;
}
- 概念:用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数的实例化分为隐式实例化和显式实例化。
- 隐式实例化:让编译器根据实参推演模板参数的实际类型。
int main()
{
int a = 88, b = 100;
double c = 22.2, d = 99.9;
//隐式实例化
cout << "a + b = " << Add(a, b) << endl;
cout << "c + d = " << Add(c, d) << endl;
//隐式实例化
cout << "b + d = " << Add((double)b, d) << endl;
return 0;
}
- 显式实例化:在函数名后的<>中指定模板参数的实际类型。
int main()
{
int a = 88, b = 100;
double c = 22.2, d = 99.9;
//显式实例化
cout << "a + c = " << Add<int>(a, c) << endl;
return 0;
}
4.函数模板的匹配
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
//函数模板的声明
template <class T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
//优先调用int Add(int x, int y) {return x + y;}
cout << Add(1, 3) << endl;
//调用 T Add(const T& x, const T& y) {return x + y;}
cout << Add<int>(1, 3) << endl;
return 0;
}
二、类模板
- 样例:
#include <iostream>
using namespace std;
template <class T>
class my_vector
{
public:
void Push_back(const T& x);
size_t getSize()
{
return _size;
}
~my_vector();//若函数在类中声明
private:
T* _a;
size_t _size;
size_t _capacity;
};
//函数在类外定义
template <class T>
my_vector<T>::~my_vector()
{
if(_a)
delete[] _a;
_size = _capacity = 0;
}
int main()
{
my_vector<int> mv;
mv.Push_back(55);
my_vector<double> md;
md.Push_back(44.5);
my_vector<char> mc;
mc.Push_back('a');
return 0;
}
1. 类模板的实例化
- 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
int main()
{
//my_vector是类名
//my_vector<int>和my_vector<double>才是类型。
my_vector<int> mv;
my_vector<double> md;
return 0;
}
三、模板参数的分类
- 模板参数分成类型形参和非类型形参。
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
1.类型形参
- 出现在模板参数列表中,跟在class或者typename之后的参数类型的名称。
2.非类型形参
- 就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将参数作为常量来使用。
四、模板特化
1.概念
- 在原模板类的基础上,针对特殊类型进行特殊化的实现方式。
- 模板特化中分为函数模板特化与类模板特化。
2.类模板特化
#include <iostream>
using namespace std;
template <class T1, class T2>
class Date
{
public:
Date()
{
cout << "Date<T1, T2>" << endl;
}
};
(1)全特化:将模板参数类表中所有的参数都确定化。
template<>
class Date<double, int>
{
public:
Date()
{
cout << "Date<double, int>" << endl;
}
};
(2)半特化(偏特化)第一种清况:对部分参数进行特化。
//对第二个参数进行特化
template<class T1>
class Date<T1, int>
{
public:
Date()
{
cout << "Date<T1, int>" << endl;
}
};
//对第一个参数进行特化
template<class T2>
class Date<char, T2>
{
public:
Date()
{
cout << "Date<char, T2>" << endl;
}
};
(3)半特化(偏特化)第二种情况:对部分参数进行条件限制。
template <class T1, class T2>
class Date<T1*, T2*> // class Date<T1&, T2&>
{
public:
Date()
{
cout << "Date<T1*, T2*>" << endl;
//cout << "Date<T1&, T2&>" << endl;
}
};
(4)总结:
int main()
{
Date<double, double> d1; // 普通模板
Date<double, int> d2; // 全特化
Date<int, int> d3; // 半特化第一种清况
Date<double*, double*> d4; // 半特化第二种情况
}
五、模板分离编译
1.分离编译概念
- 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有文件链接起来形成单一的可执行文件的过程称为分离编译模式。
2.模板的分离编译
- C/C++程序运行的步骤:(1)预处理(2)编译(3)汇编(4)链接
(1)预处理:展开头文件、宏替换、条件编译、去掉注释。
(2)编译:对程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码,主义头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。
(3)汇编:将汇编代码转成二进制机器码。生成可重定位目标文件。
(4)链接:将多个obj文件合并成一个.o可执行文件,并处理没有解决的地址问题。 - 模板不支持声明和定义(声明放到.h文件中,定义放在.cpp文件中)分离编译。
- 模板不支持分离编译的原因:链接不上。模板代码的实现体在一个文件里,实例化模板的测试代码在另一个文件里,编译器却不知道,在链接之前,两个文件不会交互,所以模板代码就没有进行实例化,没有生成相应的测试用的替换类型的代码,所以出现链接错误,只出现函数名,找不到模板。
- 解决方法:(1)显式实例化:模板定义位置显式实例化。这方法不使用。在定义文件中加显式实例化。
//.h文件中
//在定义前加下面代码进行显式实例化
template
int Add<int>(const int& x, const int& y);
template
double Add<double>(const double& x, const double& y);
template
long Add<long>(const long& x, const long& y);
//定义
template<class T>
T Add(const T& x, const T& y)
{
return x + y;
}
- 由于不同类型的实参都要进行一次显式实例化,会比较麻烦,所以该方法不实用。
(2)最好的方法:把模板的声明和定义不要分离(都放在.hpp文件中)。(放到一个文件中,该文件中既有声明也有定义,就不存在链接过程。)
六、模板总结
1.优点
- 模板复用了代码,节省资源,更快的迭代开发,C++标准模板库(STL)因此产生。
- 增强了代码的灵活性。
2.缺点
- 模板会导致代码膨胀问题,也会导致编译时间变长。
- 出现模板编译错误时,错误信息非常凌乱,不容易定位错误位置。