目录
泛型编程
函数模板
函数模板概念
函数模板的定义和语法
函数模板的工作原理
函数模板的实例化
隐式实例化
显示实例化
函数模板的匹配原则
类模板
类模板的定义格式
类模板的实例化
泛型编程
什么是泛型编程?
泛型编程(Generic Programming)是一种编程范式,它使得算法和数据结构能够独立于特定类型而进行设计和实现。通过泛型编程,程序员可以编写出更通用、更复用的代码,从而提高代码的灵活性和可维护性。泛型编程的核心思想是通过参数化类型(type parameters)使代码能够处理不同的数据类型,而无需重复编写相同逻辑的代码。
举一个例子
如果让你编写一个函数,用于两个数的交换。在C语言中,我们会用如下方法:
// 交换两个整型
void Swapi(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 交换两个双精度浮点型
void Swapd(double* p1, double* p2)
{
double tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
因为C语言不支持函数重载,所以用于交换不同类型变量的函数的函数名是不能相同的,并且传参形式必须是址传递,不能是值传递。
而在学习了C++的函数重载和引用后,我们又会用如下方法实现两个数的交换:
// 交换两个整型
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
// 交换两个双精度浮点型
void Swap(double& x, double& y)
{
double tmp = x;
x = y;
y = tmp;
}
C++的函数重载使得用于交换不同类型变量的函数可以拥有相同的函数名,并且传参使用引用传参,使得代码看起来不那么晦涩难懂。
但是,这种代码仍然存在它的不足之处:
1、重载的多个函数仅仅只是类型不同,代码的复用率比较低,只要出现新的类型需要交换,就需要新增对应的重载函数。
2、代码的可维护性比较低,其中一个重载函数出现错误可能意味着所有的重载函数都出现了错误。
那我们能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成相应的代码呢?
就像做月饼的模子一样,我们放入不同颜色的材料,就能得到形状相同但颜色不同的月饼。
如果在C++中,也能够存在这样一个模具,通过给这个模具填充不同颜色的材料(类型),从而得到形状相同但颜色不同的月饼(生成具体类型的代码),那将会大大减少代码的冗余。巧的是前人早已将树栽好,我们只需在此乘凉。
函数模板
函数模板概念
函数模板是一个通用的函数框架,它在定义时并不指定具体的数据类型,而是通过模板参数(template parameters)将类型泛化。这样,同一个函数模板可以处理不同类型的参数,而不需要为每种类型分别编写函数定义。函数模板的主要目的是实现代码的泛型编程。
函数模板的定义和语法
函数模板的定义包括模板声明和模板函数体。模板声明使用关键字 template
,后跟一个尖括号(<>
)中的模板参数列表。模板参数列表中可以包含一个或多个类型参数,通常使用 typename
或 class
关键字来声明类型参数。函数模板的基本语法如下:
template<typename T1,typename T2,…,typename Tn>
返回类型 函数名(参数列表)
{
//函数体
}
示例
template<typename T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
函数模板的工作原理
函数模板的工作原理是通过模板参数将类型泛化,在函数调用时根据传入的具体类型生成对应类型的函数版本。这个过程称为模板实例化(template instantiation)。当编译器遇到函数模板的调用时,它会根据实参的类型生成一个特定类型的函数版本。
函数模板是一个蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如,当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于double类型也是如此。
函数模板的实例化
函数模板实例化是指编译器根据传入的实参类型生成特定类型的函数实现。实例化过程发生在编译时。
在C++中,函数模板的实例化可以分为显式实例化(explicit instantiation)和隐式实例化(implicit instantiation)。理解这两种实例化方式有助于更好地控制模板代码的生成和使用。下面将详细介绍这两种实例化方式及其应用。
隐式实例化
隐式实例化是指在使用函数模板时,编译器根据函数调用的实参类型自动生成相应的模板函数实例。这种实例化方式是最常见的,编译器会在遇到模板函数调用时自动完成实例化过程。
示例
考虑以下函数模板:
template <typename T>
T add(T a, T b) {
return a + b;
}
这段代码定义了一个函数模板 add
。函数模板的返回类型由模板参数 T
确定。也就是说,add
函数的返回类型取决于调用时传递的参数类型。
当我们调用 add
函数模板时,编译器会根据传入的实参类型自动实例化模板函数:
int main() {
int result = add(3, 4); // 隐式实例化为 int add(int, int)
double result2 = add(3.5, 4.5); // 隐式实例化为 double add(double, double)
return 0;
}
在这个例子中,编译器会在编译过程中根据 add(3, 4)
和 add(3.5, 4.5)
生成 int add(int, int)
和 double add(double, double)
的具体实现。这些实例化过程是自动完成的,不需要程序员显式指明。
显示实例化
显式实例化是指程序员在代码中明确指定模板实例化的具体类型,从而提前生成相应的模板函数实例。显式实例化可以提高编译效率,减少代码膨胀,或者解决模板代码分离编译时的链接问题。
语法
显式实例化的语法如下:
template 返回类型 函数名<具体类型>(参数列表);
示例
考虑以下函数模板:
template <typename T>
T multiply(T a, T b) {
return a * b;
}
我们可以显式实例化 multiply
函数模板,指定具体类型:
// 显式实例化
template int multiply<int>(int, int);
template double multiply<double>(double, double);
int main() {
int result = multiply<int>(3, 4); // 使用显式实例化的 int multiply(int, int)
double result2 = multiply<double>(3.5, 4.5); // 使用显式实例化的 double multiply(double, double)
return 0;
}
在这个例子中,我们明确告诉编译器为 int
和 double
类型实例化 multiply
函数模板。编译器会提前生成 int multiply(int, int)
和 double multiply(double, double)
的具体实现。
函数模板的匹配原则
一、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //调用非模板函数,编译器不需要实例化
int d = Add<int>(a, b); //调用编译器实例化的Add函数
return 0;
}
二、对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化
int b = Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数
return 0;
}
三、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
return 0;
}
因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。
类模板
类模板的定义格式
类模板的定义格式如下:
template<class T1,class T2,…,class Tn>
class 类模板名
{
//类内成员声明
};
示例:类模板的定义和使用
定义一个简单的类模板
template<class T>
class Score
{
public:
void Print()
{
cout << "数学:" << _Math << endl;
cout << "语文:" << _Chinese << endl;
cout << "英语:" << _English << endl;
}
private:
T _Math;
T _Chinese;
T _English;
};
注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。
template<class T>
class Score
{
public:
void Print();
private:
T _Math;
T _Chinese;
T _English;
};
//类模板中的成员函数在类外定义,需要加模板参数列表
template<class T>
void Score<T>::Print()
{
cout << "数学:" << _Math << endl;
cout << "语文:" << _Chinese << endl;
cout << "英语:" << _English << endl;
}
除此之外,类模板不支持分离编译,即声明在xxx.h文件中,而定义却在xxx.cpp文件中。
类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后面根<>,然后将实例化的类型放在<>中即可。
//Score不是真正的类,Score<int>和Score<double>才是真正的类
Score<int> s1;
Score<double> s2;
注意:类模板名字不是真正的类,而实例化的结果才是真正的类。