跳到主要内容
C++ 模板初阶:函数与类模板基础 | 极客日志
C++ 算法
C++ 模板初阶:函数与类模板基础 C++ 模板实现泛型编程,允许编写与类型无关的代码。通过函数模板和类模板,编译器在编译时实例化生成具体类型代码,避免运行时开销。函数模板支持隐式和显式实例化,匹配非模板函数时有特定优先级规则。类模板需显式指定类型参数才能实例化为具体类。掌握模板可减少重复代码,提升复用性。
leon 发布于 2026/3/29 更新于 2026/4/23 2 浏览前言
C++ 模板是 C++ 中实现泛型编程的核心工具,允许程序员编写与类型无关的代码,从而提高代码的复用性和灵活性。模板在编译时进行实例化,根据实际使用的类型生成具体的代码,因此不会带来运行时开销。
一、模板基础
1.1 为什么需要模板?
在编写函数或类时,如果希望它们能处理多种数据类型(如 int、double、string),传统方法是使用函数重载,但这样会产生大量重复代码或失去类型信息。
模板允许将类型作为参数,编译器根据调用时传入的具体类型生成对应的代码。
场景:需要编写一个求两个数最大值的函数,支持 int、double 和 string(按字典序)。
① 传统方法:函数重载
#include <iostream>
#include <string>
using namespace std;
int max (int a, int b) {
cout << "int version\n" ;
return a > b ? a : b;
}
double max (double a, double b) {
cout << "double version\n" ;
return a > b ? a : b;
}
string max (const string& a, const string& b) {
cout << "string version\n" ;
return a > b ? a : b;
}
int main () {
cout << max (3 , 5 ) << ;
cout << ( , ) << ;
cout << ( , ) << ;
}
"\n"
max
3.14
2.7
"\n"
max
"hello"
"world"
"\n"
缺点:
代码重复:每个类型都要写一遍几乎相同的函数体。
扩展性差:若要支持新类型(如 char、float),必须添加新重载。
维护困难:修改算法(如改为 a < b 返回较小值)需要改动所有重载函数。
#include <iostream>
#include <string>
using namespace std;
template <typename T>
T max (T a, T b) {
cout << "template version for " << typeid (T).name () << "\n" ;
return a > b ? a : b;
}
int main () {
cout << max (3 , 5 ) << "\n" ;
cout << max (3.14 , 2.7 ) << "\n" ;
cout << max ("hello" , "world" ) << "\n" ;
cout << max ('a' , 'b' ) << "\n" ;
}
优点:
一份代码适用于所有类型,只要该类型支持 operator>。
无需为每种类型单独编写,维护成本低。
编译器根据实际调用生成对应版本的函数,效率与手写重载相同。
1.2 泛型编程思想 模板是 C++ 支持泛型编程的基础,其核心思想是'参数化类型'——将类型也视为一种参数,让算法和数据结构独立于具体类,从而编写与类型无关的通用代码,使之能够进行代码复用。
1.3 模板的分类
二、函数模板
2.1 函数模板的概念 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2.2 函数模板的定义 函数模板定义以 template 关键字开头,后跟模板参数列表,然后是函数声明。
模板函数的语法:
关键字 template <class 模板名称> 或 template<typename 模板名称>
例如:template<class T> 或 template<typename T>
备注:这里的模板名称为 T(可自定义为任意名称)
温馨提示:typename 是用来定义模板参数关键字,也可以使用 class(切记:不能使用 struct 代替 class)
通过函数模板定义函数
template <class T >
返回值类型函数名 (参数列表)
{
}
通过模板定义多个模板,实现接受不同类型的参数
例如:template<class T1, class T2>
温馨提示:
A. 当只有一个模板参数时,一般须传入相同类型的实参。
B. 当有两个模板参数时,可以传不同类型的实参
代码示例 1:通过函数模板编写一个交换两个数的函数。
#include <iostream>
using namespace std;
template <class T>
void Swap (T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main () {
int m = 10 , n = 20 ;
double a = 1.2 , b = 2.5 ;
Swap (m, n);
Swap (a, b);
return 0 ;
}
代码示例 2:通过函数模板编写一个求两个数最大值的函数。
#include <iostream>
using namespace std;
template <typename T>
T max (T a, T b) {
return a > b ? a : b;
}
int main () {
int i = max (3 , 5 );
double d = max (3.14 , 2.7 );
return 0 ;
}
代码示例 3:通过模板定义多个模板,以实现接受不同类型的参数
#include <iostream>
using namespace std;
template <class T1, class T2>
void func2 (const T1& x, const T2& y) {
cout << x << " " << y << endl;
}
int main () {
int a = 10 ;
double b = 3.14 ;
func2 (a, b);
return 0 ;
}
2.3 函数模板的原理 函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应 类型的函数以供调用。
例如:当用 double 类型使用函数模板时,编译器通过对实参类型的推演,将 T 确定为 double 类型,然后产生一份专门处理 double 类型的代码,对于 char 类型也是如此。
2.4 函数模板的实例化 不同类型的参数使用函数模板时,称为函数模板的实例化,模板参数实例化分为:隐式实例化 和 显式实例化。
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
#include <iostream>
using namespace std;
template <class T>
T Add (const T& left, const T& right) {
return left + right;
}
int main () {
int a1 = 10 , a2 = 20 ;
double d1 = 10.0 , d2 = 20.0 ;
Add (a1, a2);
Add (d1, d2);
return 0 ;
}
温馨提示:如果只有一个模板参数时,传入两个不同类型,该语句不能通过编译
#include <iostream>
using namespace std;
template <class T>
T Add (const T& left, const T& right) {
return left + right;
}
int main () {
int a1 = 10 ;
double d1 = 3.14 ;
Add (a1, d1);
return 0 ;
}
报错原因:
因为在编译期间,当编译器看到该实例化时,需要推演其实参类型:
通过实参 a1 将 T 推演为 int,通过实参 d1 将 T 推演为 double 类型,但模板参数列表中只有一个 T,编译器无法确定此处到底该将 T 确定为 int 或者 double 类型而报错。
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。
#include <iostream>
using namespace std;
template <class T>
T Add (const T& left, const T& right) {
return left + right;
}
int main () {
int a1 = 10 ;
double d1 = 3.14 ;
Add (a1, (int )d1);
return 0 ;
}
#include <iostream>
using namespace std;
template <class T>
T Add (const T& left, const T& right) {
return left + right;
}
int main () {
int a1 = 10 ;
double d1 = 3.14 ;
Add <int >(a1, d1);
return 0 ;
}
详情解释:显式指定模板参数
例如:Add<int>(a1, d1) 或 Add<double>(a1, d1)
这意味着你在调用时明确告诉编译器:'模板参数 T 就是 int(或 double),不要再推导了',此时编译器会跳过类型推导过程,直接使用你指定的类型来实例化函数模板。
编译器会检查这个调用是否合法:
对于 Add<int>(a1, d1),生成的函数签名是 int Add(const int&, const int&)。
实参 a1 是 int,可以直接绑定到 const int&。
实参 d1 是 double,但 double 可以隐式转换为 int,因此编译器会生成一个临时 int 对象(值为 d1 截断后的整数)传递给函数。
这种隐式转换是允许的,因此调用成功。
同理,Add<double>(a1, d1) 会将 a1 从 int 隐式转换为 double,也可以正常调用。
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
#include <iostream>
using namespace std;
template <class T>
T Add (const T& left, const T& right) {
return left + right;
}
int main () {
int a = 10 ;
double b = 20.0 ;
Add <int >(a, b);
return 0 ;
}
2.5 模板参数的匹配原则 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
int Add (int left, int right) {
return left + right;
}
template <class T>
T Add (T left, T right) {
return left + right;
}
void Test () {
Add (1 , 2 );
Add <int >(1 , 2 );
}
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。
如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
int Add (int left, int right) {
return left + right;
}
template <class T1, class T2>
T1 Add (T1 left, T2 right) {
return left + right;
}
void Test () {
Add (1 , 2 );
Add (1 , 2.0 );
}
三、类模板
3.1 类模板的概念 类模板不是具体的类,而是一个'类的蓝图'。它使用模板参数(通常是类型参数)来描述类中成员的类型,这些成员包括数据成员、成员函数的参数和返回值等。
3.2 类模板的定义 类模板的定义必须以 template 关键字开头,后面紧跟模板参数列表(用尖括号 <> 包围)。这是类模板语法的核心标志,告诉编译器接下来的类是一个模板,可以参数化类型或值。
基本语法定义如下所示:
template<class T1, class T2, ..., class Tn> 或 template<typename T1, typename T2, ..., typename Tn>
class 类模板名
{
// 类内成员定义
};
温馨提示:
template <typename T> 和 template <class T> 中的 T 是一个占位符类型名,在使用时会被实际类型替换。
类定义内可以使用 T 声明成员变量、成员函数参数和返回类型。
成员函数如果在类外定义,必须再次声明模板参数,并使用完整的类名 <T> 限定。
template <typename T>
class Stack {
public :
void push (const T& x) ;
T pop () ;
private :
T data[100 ];
int top;
};
成员函数如果在类外定义,必须再次声明模板参数,并使用完整的类名 <T> 限定:
template <typename T>
class Stack {
public :
void push (const T& x) ;
T pop () ;
private :
T data[100 ];
int top;
};
template <typename T>
void Stack<T>::push (const T& x) {
data[++top] = x;
}
template <typename T>
T Stack<T>::pop () {
return data[top--];
}
3.3 类模板的实例化 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
简单来说:对于模板类而言,类名不能代表类型,类名需要绑定模板参数才能成为类型。
#include <iostream>
#include <cstring>
using namespace std;
template <class T >
class Stack {
public :
Stack (int n = 4 ) : _array(new T[n]), _capacity(n), _size(0 ) {}
void Push (const T& x) ;
~Stack () { delete [] _array; }
private :
T* _array;
size_t _capacity;
size_t _size;
};
template <class T >
void Stack<T>::Push (const T& x) {
if (_size == _capacity) {
int newcapacity = _capacity * 2 ;
T* tmp = new T[newcapacity];
memcpy (tmp, _array, _capacity * sizeof (T));
delete [] _array;
_array = tmp;
_capacity *= 2 ;
}
_array[_size++] = x;
}
int main () {
Stack<int > st1;
st1. Push (1 );
st1. Push (2 );
st1. Push (3 );
Stack<double > st2;
st2. Push (1.1 );
st2. Push (1.2 );
st2. Push (1.3 );
Stack<double >* pst = new Stack <double >();
return 0 ;
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online