news 2026/10/16 2:23:55

【C++修炼】模板初阶:泛型编程、函数模板、类模板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++修炼】模板初阶:泛型编程、函数模板、类模板

目录

模板初阶

一、泛型编程

二、函数模板

2.1 函数模板概念

2.2 函数模板格式

2.3 函数模板的原理

2.4 函数模板的实例化

2.5 模板参数的匹配原则

三、类模板

3.1 类模板的定义格式

3.2 类模板的实例化


模板初阶

一、泛型编程

代码示例 1.1

// C语言实现多种参数类型的同一交换函数Swap // 函数名不能相同,传地址需要创建指针变量 void SwapI(int* left, int* right) { int temp = *left; *left = *right; *right = temp; } void SwapD(double* left, double* right) { double temp = *left; *left = *right; *right = temp; } void SwapC(char* left, char* right) { char temp = *left; *left = *right; *right = temp; } // C++实现多种参数类型的同一交换函数Swap // 函数同名可以重载,可以使用引用无需再创建变量 void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } void Swap(char& left, char& right) { char temp = left; left = right; right = temp; }

代码示例1.1中是我们在C语言中会碰到的经典问题,在C++中得到了一定的改善,无需为函数名烦恼,也无需为了传地址额外创建指针变量,引用即是对象本身,不额外开辟空间。

但使用函数重载仍然显得冗余,还有几个不好的地方:

  • 1. 重载的函数仅仅是类型不同,函数体基本一致,代码复用率低,只要有新类型出现,用户就得手动增加对应的函数。
  • 2. 代码的可维护性低,一个出错可能所有的重载均出错。

能否告诉编译器一个模子,让编译器根据不同的类型,利用该模子来生成代码?

像钢水浇筑工艺,一个通用的模板(函数体逻辑),浇筑不同的材料(参数类型),生成不同材料的铸件(具体类型的代码)。

在C++中,将其称为泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

二、函数模板

2.1 函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,使用时根据实参类型确定参数的具体类型,生成具体函数。需要强调,有的地方也会称呼函数模板为模板函数,但需要严格区分,函数模板是模板,而不是函数。

2.2 函数模板格式

代码示例 2.2.1

// 1、在函数头上加 template<> 表明这是一个函数模板 // 2、在尖括号<>内可以声明一些通用类型给函数使用 // 3、typename和class都是定义模板参数类型的关键字(不可使用struct) // 4、class是早期使用的关键字,typename是后面加的,两者没有区别 // 5、模板参数类型的名称随意,Type的缩写是T所以用T,T1、T2等,也有用Key的缩写K的 // 6、T1、T2、Tn、K等模板参数类型,不是int、char等具体类型,使用时根据情况替换 template<typename T1, class T2, ......, typename Tn, typename K> 返回值类型 函数名( 函数参数列表 ) { // ... } // 例如Swap交换函数模板 // 1、template<>表明下方定义的是函数模板 // 2、尖括号<>内是通用类型声明,不是具体类型,使用时具体类型代替通用类型 // 3、若实际调用Swap时,实参是int类型,则int类型替换T类型 template<typename T> void Swap(T& left, T& right) { T temp = left; left = right; right = temp; } // 或是Add加法函数模板 // 可以定义多个模板参数类型T1、T2 template<typename T1, typename T2> T1 Add(T1& left, T2& right) { return left + right; }

2.3 函数模板的原理

函数模板是蓝图,它本身不是函数,是编译器在实际使用时,根据实参类型产生相应有具体类型的函数代码的模板。所以模板就是将本应我们做的重复工作交给了编译器。

在编译阶段,对于函数模板,编译器会根据传入的实参进行推演,生成对应类型的函数代码以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的函数代码,再调用该函数完成运算,对于字符等其他类型也是如此。

2.4 函数模板的实例化

用不同类型的参数使用函数模板生成具体函数代码,称为函数模板的实例化。模板参数类型的推演过程,称为模板参数实例化,模板参数可以隐式实例化,也可以显式实例化。

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

代码示例 2.4.1

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; // a1、a2都为int,推演出T为int Add(a1, a2); // d1、d2都为double,推演出T为double Add(d1, d2); // a1为int,d1为double,只有一个模板参数类型T // Add(a1, d1); // 1、手动进行强制类型转换 // Add(a, (int)d); // Add((double)a, d); return 0; }

Add(a1, d1);不能通过编译。因为在编译期间,函数模板实例化时,编译器需要推演实参类型,通过实参a1推演T为int,通过实参d1推演T为double,但模板参数列表中只有一个T,无法确定T是int还是double,存在歧义而报错。在模板中,编译器不会擅自进行类型转换操作,无法自主选择将a1类型转换成double,或是将d1类型转换成int。

除开定义多个模板参数类型,此时有两种处理方式:

  1. 用户自己进行强制类型转化,如上面的代码示例2.4.1;
  2. 显式实例化模板参数,如下方的代码示例2.4.2。

2、显式实例化:调用函数时,在函数名后使用尖括号<>指定模板参数的实际类型

代码示例 2.4.2

template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a = 10; double b = 20.0; // 显式实例化,函数名后使用尖括号<>指定模板参数T的类型为int Add<int>(a, b); return 0; } // 若函数模板的参数列表有多个参数,如: // template<typename T1, typename T2, typename T3> // 显式实例化时,格式如下: // FunctionName<int, double, char>(a, b, c); // 依次指定参数类型即可

指定了模板参数类型,如果再发生类型不匹配,编译器就有依据对实参进行隐式类型转换,但如果实参无法类型转换成指定的模板参数类型,编译器将会报错。

必须显式实例化的情况

代码示例 2.4.3

#include<iostream> using namespace std; // 存在部分必须显式实例化的情况 // 例如func函数:n为元素个数,创建n个元素的连续空间,并将其地址作为返回值 template<class T> T* func(int n) { // T为元素的类型 return new T[n]; } int main() { // 需要存储5个int元素,可以通过func动态申请一块空间,通过指针接收空间地址 // 此处必须显式实例化,因为不可以从实参推断模板参数T的具体类型 int* ptr = func<int>(5); for (int i = 0; i < 5; i++) ptr[i] = i + 1; for (int i = 0; i < 5; i++) { cout << ptr[i] << endl; } return 0; }

运行结果:

2.5 模板参数的匹配原则

  • 1.有具体类型的函数可以和它的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

代码示例 2.5.1

#include<iostream> using namespace std; // 专门处理int的Add加法函数,非模板函数 int Add(int left, int right) { cout << "int Add(int left, int right)" << endl; return left + right; } // Add加法函数模板 template<class T> T Add(T left, T right) { cout << "T Add(T left, T right)" << endl; return left + right; } // 特化:函数模板实例化为有具体类型的函数,称为特化 int main() { // 编译器处理函数与模板特化的原则是:有现成的用现成的,有更匹配的用更匹配的 // 与非模板函数匹配,编译器不需要特化 cout << Add(1, 2) << endl; // 通过显式实例化调用编译器特化的Add版本 cout << Add<int>(3, 4) << endl; return 0; }

运行结果:

  • 2. 对于非模板函数和函数模板的特化,如果其他条件都相同,优先调用非模板函数,而非模板的特化。如果模板可以产生一个更匹配的函数,那么将选择模板的特化。

代码示例 2.5.2

#include<iostream> using namespace std; // 专门处理int的Add加法函数,非模板函数 int Add(int left, int right) { cout << "int Add(int left, int right)" << endl; return left + right; } // Add加法函数模板 template<typename T1, typename T2> T1 Add(T1 left, T2 right) { cout << "T1 Add(T1 left, T2 right)" << endl; return left + right; } int main() { // 与非模板函数完美匹配,编译器无需特化 cout << Add(1, 2) << endl; // 函数模板可以生成更匹配的版本,编译器根据实参生成更加匹配的Add函数 // int Add(int left, double right) cout << Add(2, 3.1) << endl; return 0; }

一句话总结:有现成的用现成的,有更匹配的用更匹配的。

如果现成的就能匹配上,编译器就不会对函数模板进行特化,只会调用现成的。如果现成的不是特别匹配,但函数模板可以生成更匹配的实例,编译器就会对函数模板进行特化,调用生成的这个更匹配的函数。

  • 3. 函数模板在使用时不允许自动类型转换,但普通函数可以进行自动类型转换。

代码示例 2.5.3

#include<iostream> using namespace std; //// Add加法函数模板 //template<typename T1, typename T2> //T1 Add(T1 left, T2 right) //{ // cout << "T1 Add(T1 left, T2 right)" << endl; // return left + right; //} // 专门处理int的Add加法函数,非模板函数 int Add(int left, int right) { cout << "int Add(int left, int right)" << endl; cout << "left:" << left << " right:" << right << endl; return left + right; } int main() { // 函数模板使用时,类型不匹配,只能1、手动类型强转或2、显式实例化 // 1、Add(a, (int)d); 2、Add<int>(a, b); // 普通函数调用时,编译器无需判断参数类型,可以自动进行类型转换 // 函数形参left、right都是int,则double类型的实参3.1自动转换为int类型 cout << Add(2, 3.1) << endl; return 0; }

运行结果:

三、类模板

类模板代表一个类家族,该类模板与类型无关,使用时编译器根据需要生成有具体类型的类的代码。由于类是类型,和函数不同,不需要传参,无从推演模板参数的类型,因此使用时模板参数必须显式实例化。需要强调,有的地方也会称呼类模板为模板类,但需要严格区分,类模板是模板,而不是类。

3.1 类模板的定义格式

代码示例 3.1.1

// 1、在头上写template<>,表明下方是类模板的定义 // 2、typename和class两个关键字都可以定义模板参数类型 // 3、类的定义格式照常,除了不能用具体类型,而用模板参数的通用类型代替 // 4、和函数模板一样,T1、T2、Tn只是名称,只是代号,命名是随意的,但用T命名更大众化 template<typename T1, class T2, ..., typename Tn> class 类模板名称 { // 类内成员定义 };

代码示例 3.1.2

#include<iostream> using namespace std; // 类模版:是模板,不是类 // 模板参数T为栈中存储的数据的类型 template<typename T> class Stack { public: // 默认构造 Stack(size_t capacity = 4) :_arr(new T[capacity]) ,_capacity(capacity) ,_top(0) {} // 析构 ~Stack() { delete[] _arr; _arr = nullptr; _top = _capacity = 0; } // 类模板中成员函数的声明与定义分离:压栈方法的声明 void Push(const T& data); void Print() { for (size_t i = 0; i < _top; i++) { cout << _arr[i] << " "; } cout << endl; } private: T* _arr; // 栈的存储空间 size_t _capacity; // 空间大小 size_t _top; // 栈顶 }; // 使用模版就不建议成员函数的声明与定义分离到两个文件.h头文件和.cpp源文件,会出现链接错误 // 类模板中成员函数的声明与定义分离:压栈方法函数模板的定义 template<class T> void Stack<T>::Push(const T& data) { // 扩容 if (_top == _capacity) { T* tmp = new T[2 * _capacity]; memcpy(tmp, _arr, sizeof(T) * _top); delete[] _arr; _arr = tmp; _capacity *= 2; } // 数据入栈,同时更新栈顶 _arr[_top++] = data; } int main() { Stack<int> st1; // int st1.Push(1); st1.Push(2); st1.Push(3); st1.Push(4); st1.Print(); Stack<double> st2; // double st2.Push(1.1); st2.Push(2.1); st2.Push(3.1); st2.Push(4.1); st2.Print(); return 0; }

运行结果:

3.2 类模板的实例化

类模板的实例化与函数模板的实例化不同。类模板实例化必须显式实例化,在类模板名字后跟尖括号<>,括号内指定模板参数类型。单纯类模板的名字(如Stack)不是真正的类,只有有具体类型,完成实例化的结果(如Stack<int>、Stack<double>)才是真正的类。

// Stack是类模板名,是模板的名称 // Stack<int>、Stack<double>才是类,类的名称,类类型的名称 Stack<int> st1; // int Stack<double> st2; // double
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 2:21:33

磁盘垃圾文件清理器实战:安全清理与误删防护的平衡之道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 2:21:29

LangChain入门---基于DeepSeek-V4-Pro模型

一.LangChain基础概念1.什么是LLM大语言模型&#xff08;LLM&#xff09;是基于海量文本训练的深度学习模型&#xff0c;能够理解和生成自然语言。其核心是通过Transformer架构捕捉上下文依赖关系&#xff0c;实现文本生成、问答、翻译等任务。典型代表如GPT-4、Claude、DeepSe…

作者头像 李华
网站建设 2026/10/16 2:20:47

从零手写BP神经网络:NumPy实现MNIST手写数字识别

简介&#xff1a;面向深度学习初学者的BP神经网络手写数字识别工程&#xff0c;完整覆盖了从数据加载、模型构建到训练与预测的流程&#xff0c;可帮助读者吃透反向传播算法中梯度下降、链式法则等细节。资源包共包含2000个文件&#xff0c;其中以图片样本为主&#xff0c;辅以…

作者头像 李华