目录
模板基础
非类型模板参数
在我们平时使用模板时, 非类型模板参数用的较少一些, 其实在模板参数可以分为两种 :
类型参数 : 在关键字class 或者 typename 之后的类型名称 例如 int , char, string , 也可以是自己实现的自定义类型
非类型参数 : 就是用一个常量作为模板(类模板和函数模板)的一个参数,在类(函数)模板中可将该参数当成常量来使用
非类型模板参数只能是只能是三种, const 整型, const 指针, const 引用, 其他类型都不行, 因为模板参数必须在编译期就能确认结果。来看个非类型模板的例子, 定义一个静态数组
#include<iostream>
using namespace std;
template<class T, size_t N = 10>//非类型模板参数
class Array {
T m_array[N];
size_t m_size;
public:
Array() :m_size(N) {}
T& operator[](size_t index) {
return m_array[index];
}
const T& operator[](size_t index)const {
return m_array[index];
}
size_t size()const {
return m_size;
}
bool empty()const {
return 0 == m_size;
}
};
class Test {
public:
template<class T, size_t N = 10>//非类型模板参数
void Solution(Array<T,N>& a) {
int size = a.size();
for (int i = 0; i < size; ++i) {
a[i] = i + 1;
}
for (int i = 0; i < size; ++i) {
cout << a[i] << " ";
}
cout << endl;
}
};
int main() {
Test test;
Array<int> a;
Array<int, 15> b;
const int N = 12;
Array<int, N> c;
test.Solution(a);//函数模板隐式实例化
test.Solution(b);
test.Solution(c);
system("pause");
return 0;
}
模板的特化
一般情况下, 使用模板可以实现一些不受制于类型的代码, 但对于一些不适合这个模板的类型的可能会得到一些错误的结果
下面分别从函数模板和类模版没有特化时出现的一些问题来看
函数模板特化
函数模板在没有特化出特定类型时可能出现以下为问题, 比如下面代码:
#include<iostream>
#include<string>
using namespace std;
class Date {
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
:m_year(year),
m_month(month),
m_day(day)
{
}
bool operator==(Date& t) {
if (&t == this) {
return true;
}
return m_year == t.m_year && m_month == t.m_month && m_day == t.m_day;
}
};
template<class T>
bool IsEqual(T a, T b) {
return a == b;
}
int main() {
string s1 = "abc";
string s2 = "abc";
string s3 = "abcd";
Date d1(2019, 10, 2);
Date d2(2019, 10, 2);
Date d3(2019, 10, 3);
int num1 = 10, num2 = 10;
IsEqual(s1, s2) ? cout << "s1==s2" << endl : cout << "s1!=s2" << endl;
IsEqual(s1, s3) ? cout << "s1==s3" << endl : cout << "s1!=s3" << endl;
IsEqual(d1, d2) ? cout << "d1==d2" << endl : cout << "d1!=d2" << endl;
IsEqual(d1, d3) ? cout << "d1==d3" << endl : cout << "d1!=d3" << endl;
IsEqual(num1,num2) ? cout << "num1==num2" << endl : cout << "num1!=num2" << endl;
cout << endl;
IsEqual(&s1, &s2) ? cout << "s1==s2" << endl : cout << "s1!=s2" << endl;
IsEqual(&d1, &d2) ? cout << "d1==d2" << endl : cout << "d1!=d2" << endl;
IsEqual(&num1, &num2) ? cout << "num1==num2" << endl : cout << "num1!=num2" << endl;
system("pause");
return 0;
}
我们发现只要传入的参数支持==比较(包括==运算符重载), 都是可以得到结果的, 因进行并不会出错, 但是当我们传入指针时, 发现其IsEqual()比较的是指针的值的大小, 从IsEual()函数实现上也不难看出, 这个函数是无法实现比较指针的, 那么这时就需要对这个函数模板的传入会出错的类型进行特化
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型。
4. 函数模板没有偏特化(只能将所有模板参数特化)
如下:
template<class T>
bool IsEqual(T a, T b) {
return a == b;
}
template<>
bool IsEqual<string*>(string* a, string* b) {
return *a == *b;
}
template<>
bool IsEqual<Date*>(Date* a, Date* b) {
return *a == *b;
}
template<>
bool IsEqual<int*>(int* a, int* b) {
return *a == *b;
}
如下图结果正确
讲句实在的, 函数模板特化这个功能其实是有些鸡肋的, 在函数模板遇到不能处理或者处理有误的类型时, 通常都是直接给出这个函数, 如下:
template<class T>
bool IsEqual(T a, T b) {
return a == b;
}
bool IsEqual(string* a, string* b) {
return *a == *b;
}
bool IsEqual(Date* a, Date* b) {
return *a == *b;
}
bool IsEqual(int* a, int* b) {
return *a == *b;
}
类模板特化
类模版的特化分为全特化和偏特化
全特化: 类模版的所有模板参数都特化
偏特化: 偏特化具体表现为 部分特化 和 模板参数进一步限定
部分特化 : 将模板参数中的一部分特化
模板参数进一步限定 : 对所有的模板参数进行限定
全特化
来看这个例子 :
#include<iostream>
using namespace std;
template<class T1,class T2>
class Test {
public:
void func() {
cout << "<T1, T2>" << endl;
}
};
template<>//全特化
class Test<int,double> {
public:
void func() {
cout << "<int, double>" << endl;
}
};
int main() {
Test<int, char> a;
Test<int, double> b;
a.func();
b.func();
system("pause");
return 0;
}
偏特化
部分特化:
#include<iostream>
using namespace std;
template<class T1,class T2>
class Test {
public:
void func() {
cout << "<T1, T2>" << endl;
}
};
template<class T1>//偏特化
class Test<T1,double> {
public:
void func() {
cout << "<T1, double>" << endl;
}
};
int main() {
Test<int, char> a;
Test<float, double> b;
a.func();
b.func();
system("pause");
return 0;
}
模板参数进一步限定
我们通常用于指针, 引用的特化, 如将T特化为T*, 也可以将T特化为 const T*, T&, const T&等,以下是以T*为例:
#include<iostream>
#include<string>
using namespace std;
class Date {
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
:m_year(year),
m_month(month),
m_day(day)
{
}
bool operator==(Date& t) {
if (&t == this) {
return true;
}
return m_year == t.m_year && m_month == t.m_month && m_day == t.m_day;
}
};
template<class T>
class Solution {
public:
bool SIsEqual(T a, T b) {
return a == b;
}
};
template<class T>
class Solution <T*>{
public:
bool SIsEqual(T* a, T* b) {
return *a == *b;
}
};
template<class T>
bool IsEqual(T a, T b) {
Solution<T> f;
return f.SIsEqual(a,b);
}
int main() {
string s1 = "abc";
string s2 = "abc";
string s3 = "abcd";
Date d1(2019, 10, 2);
Date d2(2019, 10, 2);
Date d3(2019, 10, 3);
int num1 = 10, num2 = 10;
IsEqual(s1, s2) ? cout << "s1==s2" << endl : cout << "s1!=s2" << endl;
IsEqual(s1, s3) ? cout << "s1==s3" << endl : cout << "s1!=s3" << endl;
IsEqual(d1, d2) ? cout << "d1==d2" << endl : cout << "d1!=d2" << endl;
IsEqual(d1, d3) ? cout << "d1==d3" << endl : cout << "d1!=d3" << endl;
IsEqual(num1,num2) ? cout << "num1==num2" << endl : cout << "num1!=num2" << endl;
cout << endl;
IsEqual(&s1, &s2) ? cout << "s1==s2" << endl : cout << "s1!=s2" << endl;
IsEqual(&d1, &d2) ? cout << "d1==d2" << endl : cout << "d1!=d2" << endl;
IsEqual(&num1, &num2) ? cout << "num1==num2" << endl : cout << "num1!=num2" << endl;
system("pause");
return 0;
}
模板的分离编译
分离编译 : 一个程序(项目)由若干个源文件共同实现, 再生成可执行程序前, 每个源文件单独预处理, 编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
预处理 --> 编译 --> 汇编 ->> 链接
在刚学习模板时我们可能会把模板申明在头文件.h中, 模板定义.cpp文件中进行分离编译, 这时就会出现错误, 错误提示我们在链接时找不到实例化出的模板函数或模板类, 这是为什么呢? 我们知道, 函数模板或类模板只有在编译时, 才会生成一个真正的实体(模板函数或模板类), 模板 ==> 实体 这个过程在编译时完成, 而分离编译时多文件各自编译各自的, 由于各自编译, 所以在.cpp中的模板定义在编译时并不知道到底要实例化出一个怎样的模板函数或模板类, 所以就不会实例化. 当在链接阶段, 需要链接这个模板函数或模板类时就会报错
解决方法: 不用分离编译......哈哈, 将声明和定义都放在头文件中, 其他一些方法不实用也不方便