小编在学习模板进阶之后,觉得模板的内容很有用,所以今天带给大家的内容是模板进阶的所有内容,内容包括模板的使用,模板的特化,模板的全特化,模板的偏特化,模板链接时候会出现的链接错误及解决方法,还有模板的优缺点。好啦,该学习啦!~~~
一、模板的特化
1、函数模板的特化
在开始之前大家先看一段代码:
// 这两段代码作用相似 只有类型不同 那这样写就会很麻烦 很浪费时间
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
那如何来解决这样浪费时间的问题呢,这里就要用到函数模板,这里我直接说语法该如何使用模板函数(语法方式就如下图代码书写的一样,需要用到 template 来定义类型 T,然后用类型T替换上面代码中的类型即可,牢记 <> 不可以省略) 这样就可以做到省略很多相同的代码,把书写相同代码的过程交给编译器来帮我们实现。
// template<typename T>
template<class T> // 这里 class 换做 typename 也可以
T Add(T x, T y) // 这样就可以减少不同类型相同代码的书写
{
return x + y;
}
看到这块虽然这样模板可以实现这种功能,但是如果模板使用是来实现下面的代码运行就会和我们想要的效果不一样,请看下面的代码:
template<class T> // 如果用这个模板 传指针的话 就不能正确比较数值大小 比较的是指针的大小
bool my_less(T left, T right)
{
return left < right;
}
int main()
{
int a = 10;
int b = 20;
cout << my_less(&a, &b);
return 0;
}
20 < 10 应该是 false,运行结果应该为0,所以这样写并没有达到我们想要的比大小的结果,而只是比较了指针的大小,那应该如何去处理这种特殊情况,这里就要用到函数模板的实例化来解决这种特殊情况,那该如何实现模板的实例化呢,语法如下代码:
template<> // 这就是函数模板实例化的语法写法
bool my_less<int*>(int* left, int* right) // 遇到int*这种类型就不会走上面代码的模板
{ // 就会直接来到模板实例化的函数部分
return *left < *right;
}
记住这里<>不能省略,而且<>中没有内容,实例化的内容在函数名的后面,函数参数部分的 T 也被实例化的内容所替代,这样就达到了比较地址内容里面的内容的效果,而不是比较指针大小。
2、类模板和特化
类模板
还是先请大家看一段代码,才可以明白为什么要存在类模板,请看下面:
// 两个模板的类成员不同,但是需要写两份代码,和上面函数一样很麻烦
class Data
{
public:
private:
int _d1;
int _d2;
};
class Data
{
public:
private:
char _d1;
char _d2;
};
那该如何处理呢 ,没错,是类模板来解决,类模板的语法我放在下面啦,大家请食用(带注释):
// 语法和函数模板类似,尖括号不能省略
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
3、类模板的全特化和偏特化(放在一起讲)
出现类模板的全特化和偏特化都是为了来实现特定参数类型不同的功能,解释一下,就是当参数是全特化或者特化类型的时候就会执行程序员自己写的特化的代码,不会走模板的内容。
大家先看看全特化和偏特化的语法,该如何书写全特化和偏特化的代码,我放在下面啦,请大家食用:(和函数部分类似,这次实在类名后加<>,顺便进行偏特化和全特化的操作)
// 全特化语法
template<>
// 在类名后面加上全特化的类型
class Data<int, char>
{
public:
Data() { cout << "Data<int, char>" << endl; }
private:
int _d1;
char _d2;
};
// 偏特化
// 对应类型 就直接调用偏特化所形成的模板
// 语法如下
template<class T>
class Data<int,T>
{
public:
Data() { cout << "Data<int, T>" << endl; }
private:
int _d1;
T _d2;
};
当类所调用的是特定的参数类型,就不会走模板类型,直接会走特定的全特化和偏特化的代码部分。和上面类模板放到一块大家看下执行的结果就会明白:
在这里忘了说了,指针类型和引用类型还得咱们自己来实现这样的模板,编译器不能帮我们生成对应功能的代码,就像函数的模板特化一样。
这里还有一个特别的内容,关于 typename 我觉滴还是很重要的,大家可以记下来,我把代码和注释放到下方这里不做太多的解释,大家请食用学习:
template<class T>
void PrintVector(const vector<T>& v)
{
// 类模板没实例化时,编译的时候 不去里面查细节东西,无法确认时类型还是静态变量
// 加typename明确告诉是类型
typename vector<T>::const_iterator it = v.begin(); // 这里 vector<T> 没有实例化 编译器不知道 onst_iterator 是成员变量还是类型
//auto it = v.begin(); // 如果是成员变量 就不能 :: 所以加上 typename 来告诉编译器这是类型
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
二、模板与定义分开写时出现的链接错误及解决方案
请大家先看看下面不同文件中的代码及注释了解一下链接错误和链接如何产生滴:
// 函数声明 // .h 文件中
template<class T> // 模板函数定义和声明分开的话 会出现链接错误
T Add(T x, T y);
// 编译器对多个.cpp 文件是分开单独编译的
// 函数定义 // func.cpp 文件中
template<class T> // 函数定义的地方不知道实例化成什么
T Add(T x,T y) // 定义的地方不会被编译 并且不会生成指令 因为没有实例化
{ // 没有指令 那就会出现链接错误
return x + y;
}
// test.c 文件中
// .h 文件会被编译
// 调用的地方知道函数实例化成什么
// 但是调用的话只有函数声明 没有定义
// 自然函数也就没有被编译成指令 不能被调用
cout << Add(1, 2) << endl; // 要强制调用也可以 就是给定义的模板进行实例化 就可以找到
函数模板与定义分开出现链接错误的详细分析:(请大家仔细阅读)
三段代码都在不同的文件中,特别是 .h 文件中的函数声明模板,代码在编译阶段,由于在test.c文件中调用了函数Add,调用的是 int 类型的函数模板,编译器就会在 .h 文件中找到对应的函数模板,但是在 .h 文件中只有函数的声明,定义知道函数实例化之后是 int 类型的,但是只有函数的声明,没有函数的定义,所以就不能生成对应的指令去执行Add函数,还有在func.cpp文件中可以找到对应的函数定义,但是函数定义的类型是不明确的(没有实例化),因为 .cpp 文件是分开编译的,所以就找不到需要的Add函数,就会发生链接错误。
解决方案有两种:
// 只需要和函数的定义写在一个.cpp文件中就可以
// 如果就想分开声明定义 并且还想使用的话 就必须显示实例化
// 但这样的话反而会很麻烦 因为这样就需要每个使用类型都得实例化一边
// 下面是实例化的语法
template
int Add(int x, int y);
三、模板的优缺点