C++从入门到实战(十三)C++函数模板与类模板初阶讲解

C++从入门到实战(十三)C++函数模板与类模板初阶讲解

C++从入门到实战(十三)C++函数模板与类模板初阶讲解


前言

  • 在上一篇博客中,我们围绕 C/C++ 内存管理展开讨论,深入解析了内存分布模型、C 与 C++ 内存管理的核心差异,并初步认识了 C++ 中new与delete的基本用法,为理解 C++ 内存管理体系打下基础。
  • 从本文开始,我们将暂别内存管理主题,转而聚焦 C++ 模板编程的核心模块 ——函数模板初阶
我的个人主页,欢迎来阅读我的其他文章
https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.ZEEKLOG.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482

一、为什么需要模板

1. 函数重载的问题

在C++里,要是你想实现交换两个变量值的功能,对不同类型的变量(像intdoublechar),就得写不同的Swap函数

  • 就像下面这样:
voidSwap(int& left,int& right){int temp = left; left = right; right = temp;}voidSwap(double& left,double& right){double temp = left; left = right; right = temp;}voidSwap(char& left,char& right){char temp = left; left = right; right = temp;}

不过,这种函数重载的做法存在一些弊端:

  • 代码复用率低:这些重载函数只是处理的变量类型不同,代码的逻辑是一样的。要是以后有新的类型,就得手动再写对应的Swap函数。
  • 可维护性差:如果其中一个Swap函数出了问题,可能所有重载函数都得检查一遍,因为它们的代码逻辑本质相同,一处出错可能其他地方也有类似问题。

2. 泛型编程和模板的作用

为了解决上面的问题,C++引入了泛型编程的概念

  • 泛型编程的目标是编写和具体类型无关的通用代码,以此实现代码复用。而模板就是泛型编程的基础。
可以把模板想象成一个模具。当你需要不同类型的具体代码时,就往这个模具里填入不同的“材料”(也就是类型),这样就能得到不同“材料”的“铸件”(也就是具体类型的代码)。
在这里插入图片描述

比如,使用模板来实现Swap函数:

现在看一下,后面我们会详细讲解
template<typenameT>voidSwap(T& left, T& right){ T temp = left; left = right; right = temp;}

这里的template <typename T>就声明了一个模板,T是一个类型参数,它代表任意类型。在使用这个Swap函数时,编译器会根据传入的变量类型,自动用具体类型替换T,生成对应的代码。这样就不用为每种类型都写一个单独的函数,既提高了代码复用率,也让代码更易于维护。

在这里插入图片描述

二、函数模板

  • 函数模板就像是一个函数家族,它和具体的类型没关系。在使用的时候,会根据传入的实参类型,生成特定类型的函数版本。

2.1 函数模板格式

函数模板的定义格式是:template<typename T1, typename T2,......,typename Tn>,后面接着返回值类型、函数名和参数列表。

  • 比如Swap函数模板:
template<typenameT>voidSwap( T& left, T& right){ T temp = left; left = right; right = temp;}

这里的typename是用来定义模板参数的关键字,也能用class,但不能用struct代替class

#include<iostream>usingnamespace std;template<typenameT>voidSwap( T& left, T& right){ T temp = left; left = right; right = temp;}intmain(){int a =5, b =10; cout <<"Before swap: a = "<< a <<", b = "<< b << endl;Swap(a, b);// 调用模板函数,传入int类型的参数 cout <<"After swap: a = "<< a <<", b = "<< b << endl;return0;}
在这里插入图片描述

2.2 函数模板的原理

  • 函数模板就像一个蓝图,它本身不是真正的函数,而是让编译器根据使用方式生成特定类型函数的模具。
  • 这样就把原本我们要做的重复工作交给了编译器。
  • 例如用double类型使用模板时,编译器会把T确定为double类型,生成处理double类型的代码

在编译阶段,编译器会根据传入的实参类型,推演出对应的类型,然后生成专门处理该类型的代码。

在这里插入图片描述

2.3 函数模板的实例化

  • 用不同类型的参数使用函数模板,就叫做函数模板的实例化。
  • 分为隐式实例化和显式实例化。

(1)隐式实例化:

让编译器根据实参来推导出模板参数的实际类型。比如Add函数模板:
template<classT> T Add(const T& left,const T& right){return left + right;}
#include<iostream>usingnamespace std;template<classT> T Add(const T& left,const T& right){return left + right;}intmain(){int a1 =10, a2 =20;double d1 =10.0, d2 =20.0; cout <<Add(a1, a2)<< endl; cout <<Add(d1, d2)<< endl;}
在这里插入图片描述
  • 当我们写成这样时
int a1 =10, a2 =20;double d1 =10.0, d2 =20.0;Add(a1, d1);
  • 编译器会报错,因为编译器无法确定T是int还是double
  • 在模板里,编译器一般不会进行类型转换,怕出问题。

处理方式:需要自己强制转换

在这里插入图片描述

(2)显式实例化:

在函数名后的<>里指定模板参数的实际类型。
  • 比如:
intmain(void){int a =10;double b =20.0;// 显式实例化 Add<int>(a, b);return0;}

如果类型不匹配,编译器会尝试隐式类型转换,转换不成功就会报错

#include<iostream>usingnamespace std;template<classT> T Add(const T& left,const T& right){return left + right;}intmain(void){int a =10;double b =20.0;// 显式实例化 cout << Add<int>(a, b)<< endl;return0;}
在这里插入图片描述

2.4 模板参数的匹配原则

1. 非模板函数与同名函数模板共存

  • 在 C++ 里,一个非模板函数和同名的函数模板能够同时存在。
  • 而且函数模板可以实例化为与非模板函数相同功能的函数。
#include<iostream>// 专门处理 int 类型的加法非模板函数intAdd(int left,int right){return left + right;}// 通用加法函数模板template<classT> T Add(T left, T right){return left + right;}intmain(){// 调用 Add(1, 2),会和非模板函数匹配 std::cout <<"调用 Add(1, 2) 的结果: "<<Add(1,2)<< std::endl;// 调用 Add<int>(1, 2),会调用编译器特化的 Add 版本 std::cout <<"调用 Add<int>(1, 2) 的结果: "<< Add<int>(1,2)<< std::endl;return0;}
在这里插入图片描述

在上述代码中,定义了一个专门处理 int 类型的非模板 Add 函数,以及一个通用的 Add 函数模板。

  • 当调用 Add(1, 2) 时,由于实参类型是 int,编译器会优先选择非模板函数。而调用 Add<int>(1, 2) 时,明确指定了使用函数模板,编译器会对模板进行实例化,生成处理 int 类型的函数。

2. 调用时的优先选择规则

当非模板函数和同名函数模板其他条件相同时,调用时会优先选择非模板函数

  • 不过,要是模板能产生更匹配的函数,就会选择模板。下面通过代码来进一步说明:
#include<iostream>// 专门处理 int 类型的加法非模板函数intAdd(int left,int right){return left + right;}// 通用加法函数模板,可处理不同类型的参数template<classT1,classT2>autoAdd(T1 left, T2 right){return left + right;}intmain(){// 调用 Add(1, 2),和非模板函数完全匹配 std::cout <<"调用 Add(1, 2) 的结果: "<<Add(1,2)<< std::endl;// 调用 Add(1, 2.0),模板函数能生成更匹配的版本 std::cout <<"调用 Add(1, 2.0) 的结果: "<<Add(1,2.0)<< std::endl;return0;}

在这段代码中,调用 Add(1, 2) 时,实参类型都是 int,与非模板函数完全匹配,所以会调用非模板函数

  • 而调用 Add(1, 2.0) 时,实参类型分别是 intdouble,非模板函数无法直接匹配,此时模板函数能生成更匹配的版本,编译器就会选择模板函数进行实例化并调用。
在这里插入图片描述

3. 类型转换规则差异

模板函数不允许自动类型转换,而普通函数可以进行自动类型转换。

#include<iostream>// 普通加法函数intAdd(int left,int right){return left + right;}// 函数模板template<classT> T Add(T left, T right){return left + right;}intmain(){int a =1;double b =2.0;// 普通函数可以进行自动类型转换 std::cout <<"普通函数调用 Add(a, b) 的结果: "<<Add(a, b)<< std::endl;// 模板函数不允许自动类型转换,下面这行代码会报错// std::cout << "模板函数调用 Add(a, b) 的结果: " << Add(a, b) << std::endl;// 若要使用模板函数,需手动进行类型转换 std::cout <<"模板函数调用 Add(a, static_cast<int>(b)) 的结果: "<<Add(a,static_cast<int>(b))<< std::endl;return0;}

在上述代码中,调用普通函数 Add(a, b) 时,编译器会自动将 double 类型的 b 转换为 int 类型。

  • 而调用模板函数 Add(a, b) 时,由于模板函数不允许自动类型转换,会导致编译错误。若要使用模板函数,需要手动进行类型转换,如 Add(a, static_cast<int>(b))

三、类模板

3.1 类模板是什么?

  • 想象你要做一个 “栈”(Stack)类,用来存储数据。
  • 如果数据可能是整数、小数、字符等不同类型,难道要为每种类型写一个独立的Stack类吗
  • 类模板就是解决这个问题的 “通用模具”:它定义一个与类型无关的类,通过传入具体类型,生成针对该类型的专属类。

3.2 类模板的定义格式

template<classT1,classT2,...,classTn>// 模板参数列表,T是类型占位符class 类模板名 {// 类的成员(变量/函数)可以使用T1、T2等模板参数// 例如:成员变量类型是T,成员函数参数类型是T&};
#include<iostream>usingnamespace std;template<typenameT>// 等价于template <class T>,T代表任意类型classStack{public:// 构造函数:初始化数组和容量Stack(size_t capacity =4){ _array =new T[capacity];// 数组元素类型是T _capacity = capacity; _size =0;}// 声明成员函数(参数类型是T&)voidPush(const T& data);// 向栈中添加数据private: T* _array;// 存储数据的数组,类型是T* size_t _capacity;// 容量 size_t _size;// 已存储元素数量};// 类外定义成员函数时,需要再次指定模板参数template<classT>// 必须重复模板参数声明voidStack<T>::Push(const T& data){// 用Stack<T>表明这是T类型的成员函数if(_size < _capacity){ _array[_size]= data;// 存入数据,类型是T _size++;}}

3.3 类模板的实例化:从 “模具” 生成具体类

类模板本身不是真正的类,必须通过实例化指定具体类型,才能生成可用的类

  • 语法:类模板名<具体类型> 对象名;
  • 显式指定类型:必须在类名后用<>明确写出类型(不能像函数模板那样隐式推导)。
  • 生成专属类:每个不同的类型实例化,都会生成一个独立的类。
    例如上面代码中的
intmain(){ Stack<int> st1;// 实例化出存储int的栈类,生成Stack<int>类 Stack<double> st2;// 实例化出存储double的栈类,生成Stack<double>类 st1.Push(10);// 向int栈中添加数据 st2.Push(3.14);// 向double栈中添加数据return0;}
  • Stack是模板名,不是类型;Stack才是具体的类型(就像 “模具压出的产品”)。
  • 每个实例化的类(如Stack<int>、Stack<double>)都是独立的,互不干扰。

3.4 类模板的注意事项

1.模板参数声明不能省略

  • 在类外定义成员函数时,必须像template <class T>一样重复声明模板参数,否则编译器不知道T是什么。

2. 声明和定义不能分离到.h 和.cpp

  • 模板需要在编译阶段根据实例化的类型生成代码,如果声明在.h、定义在.cpp,编译器可能找不到定义,导致链接错误。

3. 支持多个模板参数

  • 可以定义多个类型参数,例如:
template<classT,classU>// 两个类型参数T和UclassPair{ T first; U second;}; Pair<int,double>p(10,3.14);// 实例化时传入两个类型

以上就是这篇博客的全部内容,下一篇我们将继续探索C++中STL更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343
我的C++知识文章专栏
欢迎来阅读指出不足
https://blog.ZEEKLOG.net/2402_83322742/category_12880513.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦
在这里插入图片描述

Read more

Visual C++ 6.0中文版安装包下载教程及win11安装教程

本文分享的是Visual C++ 6.0(简称VC++6.0)中文版安装包下载及安装教程,关于win11系统下安装和使用VC++6.0使用问题解答,大家在安装使用的过程中会遇到不同的问题,如遇到解决不了的问题请给我留言! 一、安装包的下载 vc6.0安装包下载连接: https://pan.quark.cn/s/710dc0efe636 二、安装vc++6.0 1.鼠标右键解压到“VC++ 6.0”安装包,解压后如图所示: 2.双击Steup.exe,进行安装; 3.点击下一步 4.更改路径,建议不要安装在C盘(默认盘符),可以选择其他的盘符,点击浏览进行更改盘符。 5.选择C盘(默认盘或系统盘)以外的盘符。

By Ne0inhk
蓝耘赋能通义万相 2.1:用 C++ 构建高效 AI 视频生成生态

蓝耘赋能通义万相 2.1:用 C++ 构建高效 AI 视频生成生态

目录 开篇:AI 视频生成新时代的号角 通义万相 2.1:AI 视频生成的领军者 核心技术揭秘 功能特点展示 与其他模型的全面对比 C++:高效编程的基石 C++ 的发展历程与特性 C++ 在 AI 领域的广泛应用 通义万相 2.1 与 C++ 的完美融合 融合的意义与价值 融合的实现途径 使用深度学习框架的 C++ 接口 调用外部库 自定义实现 代码详解:使用 C++ 调用通义万相 2.1 进行视频生成 环境搭建与准备 代码示例与详细解析 代码功能详细解析 代码实战:使用 C++ 调用通义万相 2.1 进行视频生成

By Ne0inhk
【C++】模板的两大特性

【C++】模板的两大特性

文章目录 * 前言 * 1. 关于 typename 的使用场景 * 2. 模板的分离编译问题 * 2.1 简述程序编译链接的过程 * 2.1.1 预处理 * 2.1.2 编译 * 2.1.3汇编 * 2.1.4 链接 * 2.2 模板分离编译为什么会链接报错 * 2.2.1 什么是分离编译 * 2.2.2 模板分离编译存在的问题 * 3. 解决办法 前言 本文探讨了C++模板编程中的两个关键问题。第一部分介绍了typename在模板中的特殊使用场景,指出当模板参数访问内嵌类型时必须使用typename关键字来消除编译器歧义。第二部分分析了模板分离编译导致链接错误的原因,通过对比普通函数和模板函数的编译链接过程,解释了模板定义必须放在头文件中才能被实例化的原理。文章结合代码示例和编译链接过程图解,帮助读者理解模板编译机制和常见错误的解决方法。 1.

By Ne0inhk
Elasticsearch + Kibana 实战指南:从安装部署到 C++ 客户端封装,解锁搜索引擎开发核心技能

Elasticsearch + Kibana 实战指南:从安装部署到 C++ 客户端封装,解锁搜索引擎开发核心技能

文章目录 * 本篇摘要 * 一.ES 介绍及简单使用 * 1·介绍 * 2.安装过程 * 检测是否安转成功 * 对应配置文件修改 * 3.ES核心知识概念 * **1. 索引(Index-->库)** * **2. 文档(Document)** * **3. 字段(Field)** * **4. 类型(Type-->类似表)**(7.x后已废弃) * **5. 映射(Mapping)** * 4.kibana介绍 * **Kibana 是什么?** * **Kibana 和 Elasticsearch 的关系** * 5.安装Kibana过程 * 6.kibana-es使用 * 7.es-client使用及封装使用接口 * es接口 * 1.

By Ne0inhk