目录
前言
非类型模板参数
模板特化
函数模板特化
类模板特化
全特化
偏特化
部分特化
对参数的限制
分离编译问题
解决方法
以前,在 C++ 入门的时候我们曾说过模板的基础操作。简单讲了函数模板与类模板,今天我们来进一步学习模板的其他操作。
一个类中,我们定义了一个数组,数组的长度事先由一个常量决定,便可以简单封装一个限定大小的数组。
#define N 10
template
class arry
{
public:
T arr[N];
};
int main()
{
arry a1;
return 0;
}
但是,若我们想以这个模板进行实例化,且一个数组长度为 10,另一个数组长度为 20 该怎么办呢?
显然,此时一个 N 已经无法满足我们的使用需求了,但定义多个常量又显得代码十分冗余。
于是我们引入非类型模板参数来解决这个问题。
我们在模板的参数列表后再增加一个参数,以整型的类型进行定义,这种参数实例化出来的实际数据为常量,因此可以用于对数组的定义。
template
class arry
{
public:
T arr[N];
};
int main()
{
arry a1;
arry a2;
return 0;
}
值得注意的是, 非类型模板参数只能使用整型,浮点型是无法使用的。这个模板操作更多用于位图的开辟或是开辟静态数据结构。
由于实例化出来的值为常量,因而无法被修改,
在泛型编程中,我们当前实现的函数可能并不符合某种类型,例如比较函数的实现中,大部分类型都能够使用,但是当类型为指针时,我们想要比较的是指向的内容,而实际上的结果为地址的比较。于是我们便可以使用模板特化进行特殊处理。
[注意]:使用模板特化的前提条件是要有一个原模板,模板特化不能单独使用。
原来实现的函数是这样的,借助模板实现比较函数,但遇到指针的时候就变成了地址的比较。
template
bool Less(T a1, T a2)
{
return a1 < a2;
}
int main()
{
int b = 4;
int a = 3;
cout << Less(a, b) << endl;
cout << Less(&a, &b) << endl;
}
此时我们便可以用全特化的方式,对一种类型进行特殊处理。
template<>
bool Less(int* a1, int* a2)
{
return *a1 < *a2;
}
其实,模板特化在函数模板中的作用并不明显,不如直接进行函数重载,而更多的优点体现在类模板之中。
类模板特化还分作全特化和偏特化,我们可以根据模板参数直接观察出二者的区别。
当模板参数都确定化即为全特化,当模板参数为特化的指定类型时,就使用特化的模板,否则使用原模板。
template
class A
{
public:
A() { cout << "A(T1,T2)" << endl; }
T1 t1;
int t2;
};
//全特化
template<>
class A
{
public:
A() { cout << "A(int,char)" << endl; }
int t1;
char t2;
};
int main()
{
A a1;
A a2;
return 0;
}
如当前创建了实例化了两个类,其中第一个使用的就是全特化模板,而第二个使用的为原模板。
之前我们就说过,模板的本质其实是将重复的代码实现交由编译器完成,但此时已经有一个准备好的代码,因此编译器就没必要自己实现一个,直接调用即可。
偏特化又分作两种情况,分别是部分特化于对参数进一步的限制。
部分特化会将模板参数列表的一部分参数特化。
例如,当我们要对第二个模板参数为 int 的类进行特殊化处理时,模板参数列表中只写不特化处理的参数,接着在类名旁边标注完整的模板参数类型。
//部分特化,只有第二个模板参数为int才会使用这个模板
template
class A
{
public:
A() { cout << "A(T1,int)" << endl; }
T1 t1;
int t2;
};
此时,a1 的第二个模板参数为 int,因此便使用偏特化的模板进行实例化。
对参数的限制这里,我们模板列表照写,在类名后限定参数类型,例如这里分别限定了指针和引用,由于这里有两个模板参数,因此传入的指针可以是一同一种也可以是两种不同的指针。
//偏特化,限定类型为指针
template
class A
{
public:
A() { cout << "A(T1*,T2*)" << endl; }
T1 t1;
T2 t2;
};
//偏特化,限定类型为引用
template
class A
{
public:
A() { cout << "A(T1&,T2&)" << endl; }
T1 t1;
T2 t2;
};
模板是无法分离编译的,若将模板声明放在一个 .h 文件实现放在另一个文件,则会出现链接错误。
这里,我们在头文件中定义了两个函数,第一个是模板函数,第二个是普通的函数。
//.h
template
bool Less(T t1, T t2);
bool Grate(int t1, int t2);
//.cpp
template
bool Less(T t1, T t2)
{
return t1 < t2;
}
bool Grate(int t1, int t2)
{
return t1 > t2;
}
虽然此时编译器没有报错,但运行代码时,便会直接出错,指明找不到该函数。
这里刚好就涉及到我们上一篇文章中所讲的动态库的链接原理,编译器之所以有语法错误的检测是因为它在后台会不断地编译代码,此时文件内部的函数名实则是用该函数在文件中的位置结合代替的,当链接的时候才会将二者关联起来,因此在编译阶段才不会有错误提示。
还有一件事,上面也讲到了,模板的本质是编译器会自动帮我们生成函数,若是编译器也没有帮我们生成,那不就等于不进行操作了。同理,一直到链接前库文件都没有接收到实例化的请求,于是编译器便没有帮我们生成函数,于是在连接阶段,调用的函数文件拿着地址去库文件中找,便找不到对应的函数,因而出现链接失败。
而另一个函数却是实实在在存在的,链接阶段主函数一下就找到了对应的文件,并发生函数跳转因而不会出现问题。
解决方法有两种:
模板特化出来的函数是已经存在的,在链接时编译器就能够直接找到该函数完成链接。
但这种方法直接破坏了模板的泛型效果,因此不推荐使用。
template
bool Less(T t1, T t2)
{
return t1 < t2;
}
template<>
bool Less(int t1,int t2)
{
return t1 < t2;
}
我们将模板的声明与定义都放在同一个头文件中,函数实现时要记得加上模板,因此在预处理的时,头文件在原代码上方展开,主函数发出需要实例化的信息时,编译器便能帮助我们实例化出代码,因此程序可以成功运行。
template
bool Less(T t1, T t2);
bool Grate(int t1, int t2);
template
bool Less(T t1, T t2)
{
return t1 < t2;
}
好了,今天 【C++】模板进阶 的相关内容到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。