C++模板

liitdar 2018-06-05 20:51:03 2789 收藏 23
分类专栏: C/C++语言 文章标签: C++ 模板

概述

C++是一门强类型语言,所以无法做到像一些动态语言(如 python 、 javascript )那样:编写出一段通用的逻辑,然后把任意类型的变量传入进行处理。不过,泛型编程弥补了C++的这个缺点,通过把通用逻辑设计为模板,摆脱了类型的限制,提供了继承机制以外的另一种抽象机制,极大地提升了代码的可重用性。
模板是泛型编程的基础,泛型编程是一种代码编写方式,通过使用泛型编程,我们可以编写出独立于任何特定类型的代码。
模板是创建泛型函数或类的蓝图(公式)。C++的STL容器,及其迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 vector ,我们可以定义许多包含不同类型元素的 vector ,比如: vector 或 vector
C++的模板包括函数模板和类模板。

函数模板

把处理不同类型的公共逻辑抽象成函数,就得到了函数模板。函数模板可以用来创建一个通用的函数,以支持多种不同类型的形参,避免重载函数的函数体重复设计。
函数模板的最大特点是把函数使用的数据类型作为参数。

函数模板的格式

template <typename type> 
ret-type func-name(parameter list)
{
   // 函数的主体
}

说明:
type 是函数所使用的数据类型的占位符名称,这个占位符名称可以在函数定义中使用;
关键字"typename"可替换为"class",两者作用一样;
ret-type 为函数的返回值;

函数模板示例

模板函数的示例代码(template_fun.cpp)如下:

#include 
using namespace std;

template <typename T>
T Max(T a, T b){ return (a > b ? a : b);}

int main()
{
    int i = 1;
    int j = 2;
    cout << "Max(i, j) is: " << Max(i, j) << endl;
    cout << "Max(i, j) is: " << Max<int>(i, j) << endl;

    float x = 1.1;
    float y = 2.2;
    cout << "Max(x, y) is: " << Max(x, y) << endl;
	cout << "Max(i, j) is: " << Max<int>(i, j) << endl;//显示指定的话,会先转化为int类型
    return 0;
}

编译并运行上述代码,结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MqJyD7h7-1636187480143)(media/image1.png)]{width=
在上述结果能够看到,通过使用函数模板,我们可以使函数Max支持多种类型( int 和 float )的参数比较,实现了泛型编程的效果。

函数模板的实参推断

在前面的函数模板的代码示例中,我们使用了两种方式为函数模板指定类型参数,如下:
cout << "Max(i, j) is: " << Max(i, j) << endl;
cout << "Max(i, j) is: " << Max(i, j) << endl;

正常情况下,我们使用"Max(i, j)“这种形式为函数模板指定类型参数,可以理解为显式指定类型参数。而为了使用方便,除了显式为函数模板指定类型参数之外,我们还可以让编译器从传递给函数的实参推断出类型参数,这一功能被称为"模板实参推断”。如使用"Max(i, j)"形式时,编译器会根据 i 和 j 的实参值、推断出类型参数为 int ,其效果就相当于显式指定了 int 类型。

类模板

使用类模板可以使用户为类声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取值为任意类型。
注意:模板的声明或定义只能在全局、命名空间或类范围内进行,不能在局部范围、函数内进行,比如不能在main函数中声明或定义一个模板。

类模板的格式

template <typename type>
class class-name {
    // 类的主体
}

说明:
type 是类所使用的数据类型的占位符名称,这个占位符名称可以在类的定义中使用;
关键字"typename"可替换为"class",两者作用一样;

类模板示例

类模板的类声明代码(template_class.h)如下:

#ifndef __TEMPLATE_CLASS_H__
#define __TEMPLATE_CLASS_H__
template <typename T>
class CTmpl
{
public:
    // 成员函数声明
    T FunA(T a, T b);
    CTmpl();
};
#endif

//类模板的类实现代码(template_class.cpp)如下:
#include 
#include "template_class.h"
using namespace std;
template <typename T>
CTmpl<T>::CTmpl(){}
// 成员函数的具体实现
template <typename T>
T CTmpl<T>::FunA(T a, T b){ return (a + b);}

int main()
{
    CTmpl<int> tmpl_int;
    cout << "tmpl_int.FunA(1, 2) is: " << tmpl_int.FunA(1, 2) << endl;
    CTmpl<float> tmpl_float;
    cout << "tmpl_float.FunA(1.1, 2.2) is: " << tmpl_float.FunA(1.1, 2.2) << endl;
    return 0;
}

编译并运行上述代码,结果如下:
在这里插入图片描述
在上述结果能够看到,通过使用类模板,我们可以使类CTmpl的数据成员和成员函数支持多种类型( int 和 float ),实现了泛型编程的效果。

类模板外部定义成员函数方法

在这里需要注意在类模板外部定义成员函数的方法,如下:

template <typename T>
ret-type class-name<typename T>::fun-name(parameter list)
{
    // 函数的主体
}

类模板的默认模板类型形参

可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。
类模板的类型形参默认值形式为:template class A{};为第二个模板类型形参T2提供int型的默认值。
类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如template class A{};就是错误的,因为T1给出了默认值,而T2没有设定。
在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。比如template class A{public: void h();}; 定义方法为template void A::h(){}。

模板类的继承

普通类继承模板类

普通类继承模板类比较简单,如:

template<class T>
class TBase{
   T data;
	……
};
class Derived:public TBase<int>{
	……
};

模板类继承了普通类(非常常见)

模板类继承普通类:

class TBase{
	……
};
template<class T>
class TDerived:public TBase{
	T data;
	……
};

类模板继承类模板

类模板继承类模板:

template<class T>
class TBase{
	T data1;
	……
};
template<class T1,class T2>
class TDerived:public TBase<T1>{
	T2 data2;
	……
};

模板类继承类模板,即继承模板参数给出的基类

模板类继承模板参数给出的基类------继承哪个基类由模板参数决定

#include
using namespace std;
class BaseA{
public:
    BaseA(){cout<<"BaseA founed"<<endl;}
};
class BaseB{
public:
    BaseB(){cout<<"BaseB founed"<<endl;}
};
template<typename T, int rows>
class BaseC{
private:
    T data;
public:
    BaseC():data(rows){
        cout<<"BaseC founed "<< data << endl;}
};

template<class T>
class Derived:public T{
public:
    Derived():T(){cout<<"Derived founed"<<endl;}
};


void main()
{
    Derived<BaseA> x;// BaseA作为基类
    Derived<BaseB> y;// BaseB作为基类
	Derived<BaseC<int, 3> > z; // BaseC作为基类
    
}

模板的非类型形参

非类型模板形参:模板的非类型形参也就是内置类型形参,如template class B{};其中int a就是非类型的模板形参。非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。

全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template class A{};如果有int b,这时A m;将出错,因为b不是常量,如果const int b,这时A m;就是正确的,因为这时b是常量。非类型形参一般不应用于函数模板中,比如有函数模板template void h(T b){},若使用h(2)调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h(2)这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。

非类型模板形参的形参和实参间所允许的转换:

(1) **允许从数组到指针,从函数到指针的转换。即数组到指针的转换。**如:

template <int *a> 
class A{};

int b[1];
A<b> m;

(2) **const修饰符的转换。即从int *到const int *的转换。**如:

template<const int *a> 
class A{};

int b;
A<&b> m;

(3) **提升转换。**即从short到int 的提升转换。如:

templatee<int a> 
class A{};

const short b=2;
A<b> m;

(4) **整值转换。**即从int 到unsigned int的转换。如:

template\<unsigned int a> 
class A{};

A<3> m;

(5) 常规转换

模板的实例化

总结一下,C++只有模板显式实例化(explicit instantiation),隐式实例化(implicit instantiation),特化(specialization,也译作具体化,偏特化)。首先考虑如下模板函数代码:

template <typename T>  
void swap(T &a, T &b){  
	...  
}  

隐式实例化

我们知道,模板函数不是真正的函数定义,他只是如其名提供一个模板,模板只有在运行时才会生成相应的实例,隐式实例化就是这种情况:

int main(){  
    ....  
    swap<int>(a,b);  
    ....  
} 

它会在运行到这里的时候才生成相应的实例,很显然的影响效率。
这里顺便提一下swap(a,b);中的是可选的,因为编译器可以根据函数参数类型自动进行判断,也就是说如果编译器不不能自动判断的时候这个就是必要的。

显式实例化

前面已经提到隐式实例化可能影响效率,所以需要提高效率的显式实例化,显式实例化在编译期间就会生成实例,方法如下:

template void swap<int>(int &a,int &b);  

这样就不会影响运行时的效率,但编译时间随之增加。

特化

这个swap可以处理一些基本类型如long int double,但是如果想处理用户自定义的类型就不行了,特化就是为了解决这个问题而出现的:

template <> void swap<job>(job a,job b){...}  

其中job是用户定义的类型。

模板的特化(具体化)和偏特化

类模板

测试代码如下:

#include 
using namespace std;
template<typename T1,typename T2>
class Test{
public:
    Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}
private:
    T1 a;
    T2 b;
};
template<>   //全特化,由于是全特化,参数都指定了,参数列表故为空。
class Test<int ,char>{
public:
    Test(int i,char j):a(i),b(j){cout<<"全特化"<<endl;}
private:
    int a;
    char b;
};
template<typename T2> //由于只指定了一部分参数,剩下的未指定的需在参数列表中,否则报错。
class Test<char,T2>{
public:
    Test(char i,T2 j):a(j),b(j){cout<<"个数偏特化"<<endl;}
private:
    char a;
    T2 b;
};
template<typename T1,typename T2> //这是范围上的偏特化
class Test<T1*,T2*>{
public:
    Test(T1* i,T2* j):a(i),b(j){cout<<"指针偏特化"<<endl;}
private:
    T1* a;
    T2* b;
};
template<typename T1,typename T2>//同理这也是范围上的偏特化
class Test<T1 const,T2 const>{
public:
    Test(T1 i,T2 j):a(i),b(j){cout<<"const偏特化"<<endl;}
private:
    T1 a;
    T2 b;
};
int main()
{
    int a;
    Test<double,double> t1(0.1,0.2);
    Test<int,char> t2(1,'A');
    Test<char,bool> t3('A',true);
    Test<int*,int*> t4(&a,&a);
    Test<const int,const int> t5(1,2);
    return 0;
}

运行结果截图:
C++模板_第1张图片

函数模板

而对于函数模板,却只有全特化,不能偏特化:

#include 
using namespace std;
//模板函数
template<typename T1,typename T2>
void fun(T1 a,T2 b){
    cout<<"模板函数"<<endl;
}
//全特化
template<>
void fun(int a,char b){
    cout<<"全特化"<<endl;
}
//函数不存在偏特化,以下代码是错误的
/*
template
void fun(char a,T2 b){
    cout<<"偏特化"<
int main()
{
    int a=0;
    char b='A';
    fun(a,a);
    fun(a,b);
    return 0;
}

运行截图如下:
C++模板_第2张图片

模板实例化问题

在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。
(1) 声明一个类模板的指针和引用,不会引起类模板的实例化,因为没有必要知道该类的定义。
(2) 定义一个类类型的对象时需要该类的定义,因此类模板会被实例化。
(3) 在使用sizeof()时,它是计算对象的大小,编译器必须根据类型将其实例化出来,所以类模板被实例化。
(4) new表达式要求类模板被实例化。
(5) 引用类模板的成员会导致类模板被编译器实例化。
(6) 需要注意的是,类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址的时候,才被实例化。用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型。

你可能感兴趣的:(C++,c++,开发语言,后端)