C++杂记002:template详解

阅读量: 鲁文奎 2021-04-22 19:14:36
Categories: Tags:

[toc]

前言

模板(template)是C实现代码重用机制的重要工具,是泛型技术(即与数据类型无关的通用程序设计技术)的基础。
模板是C
中相对较新的语言机制,它实现了与具体数据类型无关的通用算法程序设计,能够提高软件开发的效率,是程序代码复用的强有力工具。
本章主要介绍了函数模板和类模板两类,以及STL库中的几个常用模板数据类型。

0x0100 概念&分类

0x0101 模板概念

模板是对具有相同特性的函数或类的再抽象,模板是一种参数多态性的工具,可以为逻辑功能相同而类型不同的程序提供一种代码共享的机制。
一个模板并非一个实实在在的函数或类,仅仅是一个函数或类的描述,是参数化的函数和类。
约定: 下文提到的所有的函数模板或模板函数指的都是用template关键字进行声明的函数. 未使用template关键字进行声明的函数称之为普通函数

0x0102 模板分类

0x0103 函数模板

函数模板提供了一种通用的函数行为,该函数行为可以用多种不同的数据类型进行调用,编译器会据调用类型自动将它实例化为具体数据类型的函数代码,也就是说函数模板代表了一个函数族。
与普通函数相比,函数模板中某些函数元素的数据类型是未确定的,这些元素的类型将在使用时被参数化;与重载函数相比,函数模板不需要程序员重复编写函数代码,它可以自动生成许多功能相同但参数和返回值类型不同的函数。

0x0200 函数模板

0x0201 函数模板的定义

template <class T1, class T2,…>返回类型 函数名(参数表){
   	…… //函数模板定义体
}

template 是定义模板的关键字;写在一对<>中的T1,T2,…是模板参数,其中的class表示其后的参数可以是任意类型。
模板参数常称为类型参数或类属参数,在模板实例化(即调用模板函数时)时需要传递的实参是一种数据类型,如int或double之类。
函数模板的参数表中常常出现模板参数,如T1,T2

0x0202 使用函数模板的注意事项

  1. 在定义模板时,不允许template语句与函数模板定义之间有任何其他语句。
template <class T>
int x;                 //错误,不允许在此位置有任何语句
T min(T a,T b){…}
  1. 函数模板可以有多个类型参数,但每个类型参数都必须用关键字class或typename限定。 此外,模板参数中还可以出现确定类型参数,称为非类型参数。 例:
template <class T1,class T2,class T3,int T4>
T1 fx(T1 a, T 2 b, T3 c){…}

在传递实参时,非类型参数T4只能使用常量

  1. 不要把这里的class与类的声明关键字class混淆在一起,虽然它们由相同的字母组成,但含义是不同的。这里的class表示T是一个类型参数,可以是任何数据类型,如int、float、char等,或者用户定义的struct、enum或class等自定义数据类型。

  2. 为了区别类与模板参数中的类型关键字class,标准C++提出了用 typename 作为模板参数的类型关键字,同时也支持使用 class 。比如,把min定义的template 写成下面的形式是完全等价的:

template <typename T> 
T min(T a,T b){…}

0x0300 函数模板的实例化

0x0301 实例化发生的时机

模板实例化发生在调用模板函数时。当编译器遇到程序中对函数模板的调用时,它才会根据调用语句中实参的具体类型,确定模板参数的数据类型,并用此类型替换函数模板中的模板参数,生成能够处理该类型的函数代码,即模板函数。

int x=min(2,3);     
int y=min(3,9);
int z=min(8.5);

编译器只在第1次调用时生成模板函数,当之后遇到相同类型的参数调用时,不再生成其他模板函数,它将调用第1次实例化生成的模板函数。

0x0302 实例化的方式

  1. 隐式实例化
    编译器能够判断模板参数类型时,自动实例化函数模板为模板函数
template <typename T> T max (T, T);
…
int i = max (1, 2); 
float f = max (1.0, 2.0);
char ch = max (‘a’, ‘A’);
…

隐式实例化,表面上是在调用模板,实际上是调用其实例

  1. 显示实例化explicit instantiation
    若编译器不能判断模板参数类型或常量值, 需要使用特定数据类型实例化
    语法形式:
    模板名称<数据类型,…,常量值,…> (参数)
template <class T> T max (T, T);
…
int i = max (1, ‘2’);// error: data type can’t be deduced
int i = max<int> (1, ‘2’);
…

0x0400 函数模板的特化

0x0401 特化的原因

但在某些情况下,模板描述的通用算法不适合特定的场合(数据类型等)
比如:如max函数

char * cp = max ("abcd", "1234");
实例化为:char * max (char * a, char * b){return a > b ? a : b;}

这肯定是有问题的,因为字符串的比较为:

char * max (char * a, char * b)
{	return strcmp(a, b)>0 ? a : b;   }

0x0402 特化

所谓特化,就是针对模板不能处理的特殊数据类型,编写与模板同名的特殊函数专门处理这些数据类型
模板机制为C++提供了泛型编程的方式,在减少代码冗余的同时仍然可以提供类型安全。 特化必须在同一命名空间下进行,可以特化类模板也可以特化函数模板,但类模板可以偏特化和全特化,而函数模板只能全特化。 模板实例化时会优先匹配”模板参数”最相符的那个特化版本。

模板特化的定义形式:

template <> 返回类型 函数名<特化的数据类型>(参数表) {  
		//TODO 							  
} 
// 普通模板函数
template <class T>
int max(const T lhs, const T rhs)
{
    return lhs > rhs? lhs: rhs;
}

// 函数特化
template <>
int max<int>(const int lhs, const int rhs)
{
    return lhs > rhs? lhs: rhs;
}

// 普通类模板
template <class T1, class T2>
class A
{
    T1 data1;
    T2 data2;
};

// 全特化类模板
template <>
class A<int, double>{
    int data1;
    double data2;
};

// 半特化类模板
template <class T2>
class A<int, T2>
{
    int data1;
    T2 data2;
};

说明:

0x0403 调用顺序

当同一程序中具有模板与普通函数时,其匹配顺序如下:

  1. 完全匹配的非模板函数
  2. 完全匹配的模板函数
  3. 类型相容的非模板函数

0x0500 类模板

0x0501 类模板的概念

类模板可用来设计结构和成员函数完全相同,但所处理的数据类型不同的通用类。
如:
双精度栈:

class doubleStack{
    private:
    double data[size];
    ……
};

字符栈:

class charStack{
    private:
    char data[size];
    ……
};

这些栈除了数据类型之外,操作完全相同,就可用类模板实现。

0x0502 类模板的声明

template<class T1,class T2,…>
class 类名{
    	……// 类成员的声明与定义
}

其中T1、T2是类型参数
类模板中可以有多个模板参数,包括类型参数和非类型参数

0x0503 类模板的声明

在下面的模板参数表中,T1、T2是类型参数,T3是非类型参数。

template<class T1,class T2,int T3>

在实例化时,必须为T1、T2提供一种数据类型,为T3指定一个整常数(如10),该模板才能被正确地实例化。

0x0504 类模板的成员函数的定义

在类模板外定义,语法

template <模板参数列表>
返回值类型 类模板名<模板参数名表>::成员函数名 (参数列表)
{
   //TODO
};
template <class T>
void Stack<T>::push(T const& elem)
{
	elems.push_back(elem);
}

0x0504 类模板特化

解决例9-9的方法是特化。即用与该模板相同的名字为某种数据类型专门重写一个模板类。
类模板有两种特化方式:

特化成员函数的方法:

template <> 返回类型 类模板名<特化的数据类型>::特化成员函数名(参数表){
   //函数定义体
}
template<> 
void Array<char *>::Sort(){
    for(int i=0;i<Size-1;i++){
        int p=i;
        for(int j=i+1;j<Size;j++)
            if(strcmp(a[p],a[j])<0)
                p=j;
        char* t=a[p];
        a[p]=a[i];
        a[i]=t;
    }
}