什么是泛型编程?

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

函数模板

函数模板代表了一个函数家族,在使用时被参数化,根据实参类型产生函数的特定类型版本,函数模板不是函数

格式:

template<typename T1, typename T2,......,typename Tn>
//返回值类型 函数名(参数列表){}
//typename 也可以用 class 代替
template<typename T>
void Swap( T& left, T& right) {
	T temp = left;
	left = right;
	right = temp;
}

函数模板原理:
模板本身并不是一个函数,但是编译器会根据模板生成一个具体的函数,所以模板就是将本来程序猿做的事情交给了编译器来完成
在编译期间,编译器会根据传入的实参类型来推演形参类型,生成具体的函数,比如上述代码中如果传入两个 int 型变量,编译器会推演出变量类型生成 int 对应的处理函数,专门用来处理 int 型变量

模板函数实例化:
隐式实例化:让编译器根据实参推演模板参数的实际类型

template<class T>
T Add(const T& left, const T& right) {
	return left + right;
}
int main() {
	int a1 = 10;
	int a2 = 20;
	double d1 = 10.0
	double d2 = 20.0;
	Add(a1, a2);
	Add(d1, d2);
 
	/*
	Add(a1, d1);
	该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其形参类型
	通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	编译器无法确定此处到底该将T确定为int 或者 double类型而报错
	注意:在模板中,编译器一般不会进行类型转换操作
	*/
 
	// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
	Add(a, (int)d);
	return 0;
}

显示实例化:在函数名后的<>中指定模板参数的实际类型

int main(void) {
	int a = 10;
	double b = 20.0;
	
	// 显式实例化,如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错
	Add<int>(a, b);
	return 0;
}

模板参数的匹配原则:

一个模板可以和一个同名的普通函数共同存在,并且模板还可以实例化为这个函数
对于模板函数和非模板函数,优先调用非模板函数而不会生成模板函数,但是如果模板函数能够生成一个更加好的版本,则调用模板函数
模板函数不支持自动类型转换,普通函数支持

类模板

格式:

template<class T1, class T2, ..., class Tn>
class 类模板名 {
 // 类内成员定义
};

类模板的实例化:
类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中,类模板并不是类,实例化结果才是真正的类

vector<int> v;
//vector 不是类型,vector<int>才是类型

非类型模板参数

模板参数分为 类型形参和非类型形参
类型形参:就是在模板参数列表中,跟在 class 和 typename 后面的参数类型名称
非类型形参:就是用一个常量代替模板的一个参数,这个常量在模板中可以直接使用

template<class T, size_t N = 10>
class Array {
	public:
	private:
		T _array[N];
		size_t _size;
}

浮点数、类对象以及字符串是不允许作为非类型模板参数的

模板的特化

在原模板类的基础上,针对特殊类型所进行特殊化的实现方式,模板特化中分为函数模板特化与类模板特化

函数模板特化:
(1) 必须要先有一个基础的函数模板
(2) 关键字 template 后面接一对空的尖括号 <>
(3) 函数名后跟一对尖括号,尖括号中指定需要特化的类型
(4) 函数形参表: 必须要和模板函数的基础参数类型完全相同

void Swap(int& left, int& right) {
	int tmp = left;    
	left = right;                          
	right = tmp;                           
} 

类模板特化:
全特化:将模板参数列表中所有参数都确定化

template<class T1, class T2>
class Data {
	public:
		Data() {cout<<"Data<T1, T2>" <<endl;}
	private:
		T1 _d1;
		T2 _d2;
};
template<>
class Data<int, char> {
	public:
		Data() {cout<<"Data<int, char>" <<endl;}
	private:
		T1 _d1;
		T2 _d2;
};
void TestVector() {
	Data<int, int> d1;	//调用模板
	Data<int, char> d2; //调用偏特化版本
}

偏特化:任何对模板参数进行进一步条件限制生成的特化版本

template<class T1, class T2>
class Data {
	public:
		Data() {cout<<"Data<T1, T2>" <<endl;}
	private:
		T1 _d1;
		T2 _d2;
};

//1. 部分特化
//		将模板参数的一部分特化
template <class T1>
class Data<T1, int> {
	public:
		Data() {cout<<"Data<T1, int>" <<endl;}
	private:
		T1 _d1;
		int _d2;
};

//2. 对模板参数的进一步限制
//		两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*> { 
	public:
		Data() {cout<<"Data<T1*, T2*>" <<endl;}
	private:
		T1 _d1;
		T2 _d2;
};
//		两个参数特化为引用类型
template <typename T1, typename T2>
	class Data <T1&, T2&> {
	public:
		Data(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
		{
			cout<<"Data<T1&, T2&>" <<endl;
		}
	private:
		const T1 & _d1;
		const T2 & _d2; 
};
void test2 () {
	Data<double , int> d1; 	// 调用特化的int版本
	Data<int , double> d2; 	// 调用基础的模板 
	Data<int *, int*> d3; 	// 调用特化的指针版本
	Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}

类模板特化应用之类型萃取

类型萃取就是利用类模板的特化,将内置类型和自定义类型区分开

/*==============================================================
*    Copyright . All rights reserved.
*    Filename:  traits.cpp
*    Author:    shen
*    Last modified: 2019-08-28 16:20
*    Description: 
==============================================================*/
#include <cstdio>

// 代表内置类型
struct TypeTrue {
    static bool Get() {
        return true;
    }
};
// 代表自定义类型
struct TypeFalse {
    static bool Get() {
        return false;
    }
};
//给出以下类模板,用户可以按照任意类型实例化该类模板。
template<class T>
struct TypeTraits {
    typedef TypeFalse IsPODType;
};
//对上述的类模板进行实例化
template<>
struct TypeTraits<int> {
    typedef TypeTrue IsPODType;
};

template<>
struct TypeTraits<double> {
    typedef TypeTrue IsPODType;
};

template<>
struct TypeTraits<long> {
    typedef TypeTrue IsPODType;
};

template<>
struct TypeTraits<char> {
    typedef TypeTrue IsPODType;
};
// ... 所有内置类型都特化一下

//自定义类型
class Data {

};

int main() {
    if (TypeTraits<long>::IsPODType::Get()) {
        printf("is true\n");
    }
    else {
        printf("is false\n");
    }

    if (TypeTraits<Data>::IsPODType::Get()) {
        printf("is true\n");
    }
    else {
        printf("is false\n");
    }

    return 0;
}

模板分离编译

什么是分离编译:
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译

模板不支持分离编译:
如果将模板的声明和定义分别放在 .h 文件和 .cpp 文件中,在编译期间(不会对.h文件进行编译)由于在 .h 文件中只存在声明并不存在模板的定义,因此即使在 main 函数中调用了模板也不会对模板进行实例化,最终链接的时候会报错(找不到函数定义)

解决方法:
将模板的声明和定义放在同一个文件中

更多推荐

模板详解