CPP模板编程
C++模版编程
C++ 模板编程是 C++ 语言的一个强大特性,通过编写泛型代码,可以创建能够处理多种数据类型的函数或类,而不需要为每种类型重复编写相同的代码逻辑。
一、为什么使用模板
比如我们现在想要完成这样一个工作,比较两个的大小。在C++中,我们可以通过函数重载的方式来完成这个工作。
// 函数重载
int compare(int x, int y) {cout << "int compare(int x, int y)" << endl;if (x > y) return 1;else if (x < y) return -1;else return 0;
}
int compare(double x, double y) {cout << "int compare(double x, double y)" << endl;if (x > y) return 1;else if (x < y) return -1;else return 0;
}
这两个函数除了参数列表不同,其余的功能等都是一样的,根据比较的值类型不同而需要写多个函数,造成代码冗余。C++中可以通过模板编程来简化开发。
二、模板的概念
模板(Templates)允许编写通用的代码,这些代码可以适应不同的数据类型,而无需为每种数据类型重复编写相同的逻辑。
模板是C++中泛型编程的基础, 作为强类型语言,C++ 要求所有变量都具有具体类型,由程序员显式声明或编译器推导。
int a = 100; //必须明确声明变量a的数据类型
而模板允许程序员在定义类或函数时,编写与数据类型无关的通用代码。
比如:
template<class T> T compare(T x, T y);
或者
template<typename T> T compare(T x, T y);
区别
-
在模板参数列表中(
template<...>
里),class
和typename
完全等价,都是“类型参数”的意思。 -
在模板内部,如果要声明依赖于模板参数的类型(比如
T::value_type
),必须用typename
:template<typename T> void func() {typename T::value_type v; // 这里必须用 typename }
上面的代码描述了一个具有 T类型
的泛型函数的模板,其返回值和参数(x和 y)都具有此类型。那么T到底是什么类型?实际上,T只是一个占位符,具体是什么类型,此时并不知道,需要在具体调用的时候,指定参数的实际类型。
如下代码:此时T就是int类型。
int a = 10;
int b = 20;
int c = compare(a, b);
如下代码,此时T就是double类型。
double a = 5;
double b = 8;
double c = compare(a, b);
因此,对于T compare(T x, T y)
来说,它就是一类函数的万能公式。具有通用性,但是不能直接使用。就好像我们编写简历时用的模板一样,不能直接用,在具体编写简历的时候,指定其中的内容。
模板可以分为函数模板和类模板两大类。
三、函数模版
C++中的函数模板是一种编写通用函数的机制,这些函数能够操作多种数据类型,而无需为每种类型单独编写函数定义。
1、函数模版的定义
语法形式如下:多个参数用逗号分隔。
template <typename t1,typename t2,...... > 返回值类型 函数名(参数列表){ //函数体
}
template <class t1,class t2,......> 返回值类型 函数名(参数列表){ //函数体
}
template
关键字表示这是一个模板定义。typename T
声明了一个类型参数T
,表示函数可以接受任何类型作为此参数。- 模板定义中 <> 里的内容即模板参数列表,不能为空
示例:
template <typename T> T minmum(T t1, T t2)
{return t1 < t2 ? t1 : t2;
}
2、函数模板的使用
T的类型会根据实际调用minmum函数时其实参类型进行推导。
int main()
{int a = 100;int b = 200;int result = minmum(a, b);cout << result << endl; double num1 = 1;double num2 = 2;double min = minmum(num1, num2);cout << min << endl;
}
函数模板本身不是一个函数,函数模板像一个公式,它列好了函数返回值类型、 函数名、函数的参数列表的参数类型,编译器通过这个公式在函数被调用时自动推导参数的类型,然后把这个模板实例化成相应的函数。
3、模板的实例化
函数模板的泛型参数类型的实际类型是在编译期间确定的,这个过程被称为模板实例化。
当编译器遇到一个函数模板的调用时,它会根据传给模板函数的参数类型来推导出模板参数的具体类型,然后生成针对这些具体类型的函数代码。
以上面函数为例,当用double类型使用函数模板时,编译器通过对实参类型的推导,将T确定为double类型,然后产生一份专门处理double类型的代码。这个过程叫实例化函数模板。
4、隐式推导和显式推导
函数模板实例化过程中,其模板参数列表推导过程分为:隐式推导和显式推导。
(1)显示推导
明确指定参数类型。
int a = 100;
int b = 200;
int result = minmum<int>(a, b);
(2)隐式推导
隐式推导也叫自动类型推导
int a = 100;
int b = 200;
int result = minmum(a, b);
注意:隐式推导,不可以进行隐式类型转换。如下代码编译错误,char c不能隐式转换为int。
int a = 100;
//int b = 200;
char c = 'A';
int result = minmum(a, c);
cout << result << endl;
1️⃣ 模板类型推导阶段
- 当你写
template<typename T> T minmum(T x, T y);
调用minmum(a, c)
:- 编译器会尝试 推导 T。
a
是int
→ 推导 T = intc
是char
→ 推导 T = char- 冲突 → 编译错误。
- 重点:模板推导阶段不会做类型提升(char → int)或其他隐式转换。
所以说“隐式推导,不可以进行隐式类型转换”。
2️⃣ 普通函数调用
- 如果是普通函数:
int minmum(int x, int y);
minmum(a, c);
- 这里会允许 隐式类型转换:
char c
会自动提升为int
。- 编译成功,返回结果也是
int
。
✅ 总结:
- 模板函数:类型推导阶段禁止隐式类型转换,不一致就报错。
- 普通函数:调用参数类型可以做隐式转换,符合函数参数类型即可。
模板类型推导阶段不会做隐式转换,但显式指定模板参数后,就不是推导阶段,而是普通函数调用阶段,允许隐式转换。如:
double m = 3.5, n = 7.2;
cout << max<int>(m, n) << endl;
m 和 n 是 double
模板要求参数类型为 T,这里就是 int → T a, T b 实际就是 int a, int b
这里发生了普通的隐式类型转换:
double m → 转换成 int(小数部分丢失)
double n → 转换成 int
(3)错误案例
#include <iostream>
template <typename T,typename E> T maxNum(T t1,T t2) {return t1 > t2 ? t1 : t2;
}
int main(){int result =minNum<int>(20, 30); //会报错,因为上面定义E类型,但是使用时没有指定E的类型//解决方案:int result =minNum<int,int>(20, 30); 或者在template模板中把E类型去掉return 0;
}
四、类模板
类模板是面向类型的通用蓝图,让同一个类逻辑可以支持多种数据类型,提高复用性、类型安全性和开发效率。
- 与函数模板不同,编译器不能为类模板推断模板的参数类型,需要显示指定参数类型;同时类模板参数可以有默认值。
- 将类模板和函数模板的定义写在头文件中,通常不需要为它们单独创建对应的
.cpp
文件。这是因为模板的实例化发生在使用模板的地方,编译器需要看到模板的完整定义才能生成函数或类的代码。如果将模板的定义放在.cpp
文件中,编译器在处理那些包含模板声明的.h
文件时无法看到具体的实现,从而无法为每一个具体类型生成对应的模板实例。
1、语法
template <typename T1, typename T2,...,typename Tn> class 类模板名
{
// 类内成员定义
};
template <class T1, class T2,...,class Tn> class 类模板名
{
// 类内成员定义
};
2、类模板的定义及使用
template <class T> // 或 template<typename T>
class MyTemplateClass
{
public:MyTemplateClass(T data): data(data) {}void print() {std::cout << data << std::endl;}
private:T data;
};
T
就是模板参数,代表类中某个成员的数据类型。- 可以有多个模板参数:
template <typename T1, typename T2>
- 可以指定默认类型:
template <typename T=int>
#include "MyTemplateClass.h"int main() {MyTemplateClass<int> obj1(100); // T=intMyTemplateClass<double> obj2(3.14); // T=doubleMyTemplateClass<std::string> obj3("Hello"); // T=std::stringobj1.print(); // 输出:100obj2.print(); // 输出:3.14obj3.print(); // 输出:Helloreturn 0;
}
- 使用时 必须显示指定类型(与函数模板不同,函数模板可以让编译器推导类型)。
- 编译器在看到
MyTemplateClass<int>
时,会根据模板生成一个具体的int
类,生成MyTemplateClass<double>
时又生成一个对应的 double 类。
类模板(Class Template)就是 用类型参数化的类,相当于给类开了一个“通用接口”,允许用不同的数据类型生成对应的类。
- 核心思想:把类的数据类型抽象成模板参数,使用时再指定具体类型。
- 优点:
- 复用性强:同一个模板可以生成多种类型的类,而不需要重复写代码。
- 类型安全:编译器在实例化时会检查类型。
- 减少冗余:避免手动为每种类型写一份几乎一样的类。