C++的进阶泛型编程学习(1):函数模板的基本概念和机制

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、模板
    • 1.1 模板的概念
      • 1.1.1 形象的解释:模板就是通用的模具,目的是提高通用性
      • 1.1.1 模板的特点:
      • 1.1.2 综述模板的作用
    • 1.2 模板的使用机制
      • 1.2.1 函数模板
  • 二、函数模板的深入学习及注意机制
    • 2.1 函数模板的自动类型推导
      • 2.1.1 ①自动类型推导,必须使得推导出的数据类型T是一致的
    • 2.2 typename为什么可以替换为class?
    • 2.3 函数模板的功能及使用时机
  • 三、函数模板具体案例——数组排序
    • 3.1 目的:测试char数组和int数组的排序
    • 3.2 思路:
    • 3.3 全部代码
  • 四、普通函数与函数模板的区别
    • (1)定义方式:
    • (2)参数类型:
    • (3)重载:
    • (4)代码生成:
    • (5)使用方式:
  • 五、普通函数和函数模板的调用规则
    • 5.1 如果函数模板和普通函数都可以实现,优先调用普通函数
    • 5.2 可以通过空模板参数列表来强制调用函数模板
    • 5.3 函数模板也可以发生重载
    • 5.4 如果函数模板可以产生更好的匹配优先调用函数模板
  • 六、函数模板的局限性:不是万能的
    • 6.1 问题提出:函数模板无法解决数组、结构体这类特殊传参的比较
    • 6.2 解决办法
  • 七、学习模板并不是为了写模板,而是在STL(标准模板库)能够运用系统提供的模板


前言

回顾一下C++的面向对象特性有哪些?
在这里插入图片描述
在这里插入图片描述
我用这两个表来简要的总结了一下,接下来要接触的,是C++中更加复杂,更加进阶的内容

一、模板

1.1 模板的概念

C++的模板是一种通用编程工具,它允许在编写代码时定义通用的数据类型或函数,以便在不同的上下文中重复使用。模板可以用于类、函数和类成员函数。

1.1.1 形象的解释:模板就是通用的模具,目的是提高通用性

在这里插入图片描述
另外比如说PPT模板、实验报告模板等等。

1.1.1 模板的特点:

①模板不可以直接使用,他只是一个框架
②模板的通用不是万能的,只在一个或者几个方面发挥作用
所以C++里面的模板的作用也是大同小异

1.1.2 综述模板的作用

C++中的模板是一种通用编程工具,它允许在编写代码时定义通用的数据类型或函数,以便在不同的上下文中重复使用。模板的作用包括代码重用、泛型编程、类型安全、高性能代码生成以及提供容器和算法库等功能。通过使用模板,可以编写通用的代码,适应不同的数据类型,提高代码的灵活性和可重用性。模板在编译时进行类型检查,提供类型安全性,并通过编译时代码生成来提高性能。

1.2 模板的使用机制

C++提供两种模板机制:函数模板类模板

1.2.1 函数模板

函数模板是C++中的一种特殊函数,它可以用于定义通用的函数,可以在不同的数据类型上进行操作。函数模板通过使用模板参数来表示通用的数据类型,从而实现代码的重用和泛型编程。
即告诉编译器,我要声明一个暂时未知函数返回类型与未知形参的函数

函数模板的语法如下所示:

template <typename T>
返回类型 函数名(参数列表) {// 函数体
}

template:声明创建了一个模板
typename :表明其后面的符号是一种数据类型
T:一种通用的数据类型,通常为大写字母,可以替换

在上面的语法中,template 表示这是一个函数模板,并且T是一个模板参数,可以是任意类型。返回类型表示函数的返回类型,函数名是函数的名称,参数列表是函数的参数列表。

写一个实例:

template <typename T>
T maximum(T a, T b) {return (a > b) ? a : b;
}int main() {int num1 = 10, num2 = 20;int maxNum = maximum(num1, num2);cout << "Maximum number is: " << maxNum << endl;double num3 = 3.14, num4 = 2.71;double maxDouble = maximum(num3, num4);cout << "Maximum double is: " << maxDouble << endl;return 0;
}

二、函数模板的深入学习及注意机制

2.1 函数模板的自动类型推导

在C++中,函数模板可以使用自动类型推导来推断模板参数的类型。通过使用auto关键字作为函数模板的参数类型,编译器可以根据函数调用时的实参类型来推导出模板参数的类型

以下是一个使用自动类型推导的函数模板示例:

template <typename T>
void print(T value) {std::cout << value << std::endl;
}int main() {print(10);  // 推导为int类型print(3.14);  // 推导为double类型print("Hello");  // 推导为const char*类型return 0;
}

在上面的示例中,print函数模板使用了自动类型推导,它的模板参数类型使用了auto关键字。在main函数中,我们分别调用了print函数,并传递了不同类型的参数。编译器会根据实参的类型推导出模板参数的类型,并实例化相应的函数。

2.1.1 ①自动类型推导,必须使得推导出的数据类型T是一致的


需要注意的是,自动类型推导仅适用于函数模板的参数类型,而不适用于函数模板的返回类型。

2.2 typename为什么可以替换为class?

在C++中,typename和class在函数模板中都可以用来表示模板参数的类型。它们在函数模板中的使用是等效的,可以互相替换使用

使用typename关键字来表示模板参数的类型是C++标准的做法,特别是在模板的嵌套和依赖名称的情况下。例如,在模板内部使用嵌套类型时,需要使用typename来指示编译器该名称是一个类型。

使用class关键字来表示模板参数的类型是C++早期版本的做法,但在C++标准化过程中,为了更好地表达模板参数是类型的概念,引入了typename关键字。

因此,typename和class在函数模板中的使用是等效的,可以根据个人喜好和代码风格选择使用其中之一。但在一些特定的情况下,如模板的嵌套和依赖名称时,使用typename是必需的

2.3 函数模板的功能及使用时机

函数模板是一种通用的函数定义,可以用于处理多种不同类型的数据。它允许编写一次函数定义,然后根据需要在不同的上下文中使用不同的数据类型。

函数模板的主要功能是实现代码的重用和泛化。通过使用函数模板,可以编写一次通用的函数定义,然后在需要时根据具体的数据类型进行实例化。这样可以避免重复编写相似的代码,提高代码的可维护性和可读性

函数模板的使用时机包括但不限于以下情况:

处理不同类型的数据:当您需要编写一个函数来处理多种不同类型的数据时,可以使用函数模板。例如,您可以编写一个通用的排序函数,可以用于排序整数数组、浮点数数组或字符串数组。

泛化算法:当您需要编写一个通用的算法,可以适用于不同类型的数据结构时,可以使用函数模板。例如,您可以编写一个通用的搜索函数,可以在不同类型的容器中查找特定的元素。

类型安全的操作:函数模板可以提供类型安全的操作,因为它们在编译时进行类型检查。这意味着如果您在函数模板中使用了不兼容的数据类型,编译器将在编译时报错,而不是在运行时出现错误。

代码简化和减少重复:函数模板可以简化代码并减少重复。通过使用函数模板,您可以避免编写多个相似的函数来处理不同类型的数据,从而减少了代码量和维护成本。

三、函数模板具体案例——数组排序

3.1 目的:测试char数组和int数组的排序

如:定义char数组:“badcfe”,要求按照字母表的顺序进行从前往后的排序(即ASCII码数值大小),同时,定义int数组:2 5 8 7 4 1 6 3,要求从小到大排序。
使用函数模板进行泛化处理,提高重用性。

3.2 思路:

采用冒泡排序,每次比较相邻元素的大小,让较大的那个排后面,不断循环。
定义一个函数模板,传参的数组数据类型用模板代替。

template <typename T>void sort(T arr[], int len) {for (int i = 0; i < len - 1; i++) {for (int j = 0; j < len - i - 1; j++) {if (arr[j] > arr[j + 1]) {// 交换相邻元素int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}}

3.3 全部代码

#include <iostream>
#include<string>using namespace std;template <typename T>void sort(T arr[], int len) {for (int i = 0; i < len - 1; i++) {for (int j = 0; j < len - i - 1; j++) {if (arr[j] > arr[j + 1]) {// 交换相邻元素int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}}int main(void) {char chararr[] = { 'b','a','d','c','f','e'};int charlen = sizeof(chararr) / sizeof(chararr[0]);sort(chararr, charlen);std::cout << "按字母表顺序排序后的char数组:";for (int i = 0; i < charlen; i++) {std::cout << chararr[i] << " ";}std::cout << std::endl;int intarr[] = { 2, 5, 8, 7, 4, 1, 6, 3 };int intlen = sizeof(intarr) / sizeof(intarr[0]);sort(intarr, intlen);std::cout << "从小到大排序后的int数组:";for (int i = 0; i < intlen; i++) {std::cout << intarr[i] << " ";}std::cout << std::endl;system("pause");return 0;
}

四、普通函数与函数模板的区别

定义一个普通函数max,用于比较两个整数的大小并返回较大的数:

int max(int a, int b) {return (a > b) ? a : b;
}

接下来,我们定义一个函数模板templateMax,也用于比较两个数的大小并返回较大的数:

template<typename T>
T templateMax(T a, T b) {return (a > b) ? a : b;
}

现在,我们来比较普通函数和函数模板的区别。

(1)定义方式:

普通函数的定义是针对特定类型的,如上述的max函数是针对整数类型的定义。

函数模板的定义使用template来声明一个通用的模板函数,可以适用于多种类型。

(2)参数类型:

普通函数的参数类型是具体指定的,如上述的max函数的参数类型是int。

函数模板的参数类型是使用模板参数T来表示的,可以是通用的类型。

(3)重载:

普通函数可以通过函数重载来处理不同类型的参数,如可以定义一个max函数来处理浮点数类型的参数。

函数模板可以通过参数推导来自动匹配不同类型的参数,无需手动重载。例如,我们可以使用templateMax函数模板来比较浮点数、字符等不同类型的参数。

(4)代码生成:

普通函数在编译时会生成具体的函数代码,如上述的max函数在编译时会生成一个针对整数类型的函数。

函数模板在编译时不会生成具体的函数代码,只有在实例化时才会根据具体的类型生成对应的函数代码。例如,当我们使用templateMax函数模板比较整数时,编译器会根据实际情况生成一个针对整数类型的函数。

(5)使用方式:

普通函数可以直接调用,如max(3, 5)。

函数模板需要通过实例化来生成具体的函数,然后才能调用。例如,templateMax(3, 5)会生成一个针对整数类型的函数,并返回较大的数。

五、普通函数和函数模板的调用规则

调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配优先调用函数模板

接下来一个个解释:

5.1 如果函数模板和普通函数都可以实现,优先调用普通函数

如果函数模板和普通函数都可以实现某个功能,编译器会优先选择普通函数进行调用,而不是实例化函数模板。

这是因为普通函数的匹配更加具体,不需要进行模板参数的推导和实例化过程,所以在编译时会更加高效。而函数模板需要在实例化时根据具体的类型生成对应的函数代码,这个过程可能会增加编译时间和代码体积。

例如,假设我们有一个普通函数foo和一个函数模板templateFoo,它们都可以接受一个整数作为参数:

void foo(int x) {// 普通函数实现std::cout << "普通函数"<< endl;
}template<typename T>
void foo(T x) {// 函数模板实现std::cout << "函数模板"<< endl;
}int main(void) {foo(40);system("pause");return 0;
}

此时编译器会调用普通函数foo。
当我们调用templateFoo函数模板时,编译器会进行实例化,生成针对具体类型的函数代码:
当我们调用foo函数时,编译器会直接选择普通函数进行调用:

但是,如果没有对应的普通函数实现,或者我们明确指定要调用函数模板,编译器会选择函数模板进行实例化和调用。

那如何告诉编译器去调用函数模板呢?

5.2 可以通过空模板参数列表来强制调用函数模板

即:

foo<>(40);

添加这个符号,就是强制告诉编译器去调用函数模板。

5.3 函数模板也可以发生重载

函数模板也可以发生重载。函数模板的重载与普通函数的重载类似,可以根据参数类型和数量的不同来进行区分。

当存在多个函数模板时,编译器会根据函数调用的参数类型和数量来选择最匹配的函数模板进行实例化和调用。

例如,考虑以下两个函数模板:

template<typename T>
void foo(T x) {// 函数模板1
}template<typename T>
void foo(T x, T y) {// 函数模板2
}

当我们调用foo函数时,编译器会根据参数类型和数量来选择最匹配的函数模板进行实例化和调用。

foo(42);        // 调用函数模板1,参数类型为int
foo(3.14, 2.71); // 调用函数模板2,参数类型为double

在这个例子中,根据参数类型和数量的不同,编译器可以正确地选择调用不同的函数模板。

因此,函数模板也可以发生重载,编译器会根据参数类型和数量来选择最匹配的函数模板进行实例化和调用。

5.4 如果函数模板可以产生更好的匹配优先调用函数模板

不解释了,跟5.1是一个道理

六、函数模板的局限性:不是万能的

6.1 问题提出:函数模板无法解决数组、结构体这类特殊传参的比较

对于这段代码:

template<typename T>
void foo(T a,T ,b) {a=b;
}

或者:

template<typename T>
void foo(T a,T ,b) {if(a>b){//}
}

传递参数如果是字符型或者整形还好,但是如果a和b是数组的话就不行了,无法运行。或者说a和b是我们用结构体自定义的一种数据类型,那也不行。

那如何解决这类问题呢?

6.2 解决办法

函数模板可以处理数组和结构体这类特殊传参的比较,但需要使用适当的比较操作符或自定义比较函数来实现。

对于数组的比较,你可以使用逐个比较数组元素的方式来判断它们是否相等。例如,考虑以下函数模板:

template<typename T, int size>
bool isEqual(T arr1[size], T arr2[size]) {for (int i = 0; i < size; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;
}

在这个例子中,arr1和arr2是模板参数类型为T的数组,size是数组的大小。

当你调用isEqual函数模板时,它会逐个比较数组元素,并返回比较结果。

int myArray1[] = {1, 2, 3, 4, 5};
int myArray2[] = {1, 2, 3, 4, 5};
bool result = isEqual<int, 5>(myArray1, myArray2);

在这个例子中,myArray1和myArray2作为参数传递给isEqual函数模板时,它们会被自动转换为指向数组首元素的指针,并且编译器会推导出数组的类型和大小。

对于结构体的比较,你可以重载比较操作符或自定义比较函数来实现结构体的比较。例如,考虑以下结构体和函数模板:

struct Point {int x;int y;
};template<typename T>
bool isEqual(T obj1, T obj2) {return obj1 == obj2;
}

在这个例子中,Point是一个结构体,isEqual函数模板使用了比较操作符==来比较结构体的相等性。

当你调用isEqual函数模板时,它会使用重载的比较操作符或自定义的比较函数来判断结构体是否相等。

Point p1 = {1, 2};
Point p2 = {1, 2};
bool result = isEqual<Point>(p1, p2);

在这个例子中,p1和p2作为参数传递给isEqual函数模板时,它们会被按值传递给函数,并使用重载的比较操作符==来比较结构体的相等性。

总结起来,函数模板可以处理数组和结构体这类特殊传参的比较,但需要使用适当的比较操作符或自定义比较函数来实现。对于数组的比较,你可以逐个比较数组元素;对于结构体的比较,你可以重载比较操作符或自定义比较函数。

七、学习模板并不是为了写模板,而是在STL(标准模板库)能够运用系统提供的模板

当然,模板分为函数模板和类模板,所以下一节讲学习类模板。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/257263.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣精选算法100道——【模板】前缀和(一维)

【模板】前缀和_牛客题霸_牛客网 (nowcoder.com) 目录 &#x1f6a9;了解题意 &#x1f6a9;算法原理 &#x1f388;设定下标为1开始 &#x1f388;取值的范围 &#x1f6a9;实现代码 &#x1f6a9;了解题意 第一行的3和2&#xff0c;3代表行数&#xff0c;2代表q次查询(…

PyTorch深度学习快速入门教程 - 【小土堆学习笔记】

小土堆Pytorch视频教程链接 声明&#xff1a; 博主本人技术力不高&#xff0c;这篇博客可能会因为个人水平问题出现一些错误&#xff0c;但作为小白&#xff0c;还是希望能写下一些碰到的坑&#xff0c;尽力帮到其他小白 1 环境配置 1.1 pycharm pycharm建议使用2020的&…

算法沉淀——哈希算法(leetcode真题剖析)

算法沉淀——哈希算法 01.两数之和02.判定是否互为字符重排03.存在重复元素04.存在重复元素 II05.字母异位词分组 哈希算法&#xff08;Hash Algorithm&#xff09;是一种将任意长度的输入&#xff08;也称为消息&#xff09;映射为固定长度的输出的算法。这个输出通常称为哈希…

MATLAB 1:基础知识

MATLAB中的数据类型主要包括数值类型、逻辑类型、字符串、函数句柄、结构体和单元数组类型。这六种基本的数据类型都是按照数组形式存储和操作的。 MATLAB中还有两种用于高级交叉编程的数据类型&#xff0c;分别是用户自定义的面向对象的用户类类型和Java类类型。 1.1.1数值类…

SpringBoot 接入讯飞星火大模型实现对话

申请地址 https://xinghuo.xfyun.cn/sparkapi?scrprice 免费申请200万Token 开发文档 https://www.xfyun.cn/doc/spark/Web.html#_1-接口说明 页面最下面有相关demo可以参考 介绍 接口是以套接字的形式分段返回&#xff0c;而且非http请求&#xff0c;比较繁琐&#xff0c;官…

【Linux】yum软件包管理器

目录 Linux 软件包管理器 yum 什么是软件包 Linux安装软件 查看软件包 关于rzsz Linux卸载软件 查看yum源 扩展yum源下载 Linux开发工具 vim编辑器 上述vim三种模式之间的切换总结&#xff1a; 命令模式下&#xff0c;一些命令&#xff1a; vim配置 Linux 软件包管理…

微服务—ES数据同步

目录 数据同步 问题分析 方案1. 同步调用 方案2. 异步通知 方案3. 监听binlog​编辑 各方案对比 案例——利用MQ实现数据同步 步骤1. 导入hotel-admin项目 步骤2. 声明交换机、队列 步骤3. 发送MQ消息 步骤4. 接收MQ消息 步骤5. 测试同步功能 数据同步 elasticsea…

统一数据格式返回,统一异常处理

目录 1.统一数据格式返回 2.统一异常处理 3.接口返回String类型问题 1.统一数据格式返回 添加ControllerAdvice注解实现ResponseBodyAdvice接口重写supports方法&#xff0c;beforeBodyWrite方法 /*** 统一数据格式返回的保底类 对于一些非对象的数据的再统一 即非对象的封…

typescript中的Omit排除类型及Pick取想要的属性

Omit 的使用:排除类型 type OmitUser {name: string,age: number,sex:string } type newOmit Omit<OmitUser, sex>// 定义一个对象并将其类型设置为 newOmit const example: newOmit {name: "John",age: 30 };console.log( Omit 的使用:排除类型 , example…

AJAXJSON入门篇

AJAX&JSON 概念&#xff1a;AJAX(Asynchronous JavaScript And XML):异步的JavaScript和XML AJAX作用&#xff1a; 与服务器进行数据交换&#xff1a;通过AJAX可以给服务器发送请求&#xff0c;并获取服务器响应的数据 使用了AJAX和服务器进行通信&#xff0c;就可以使用H…

lv15 平台总线驱动开发——ID匹配 3

一、ID匹配之框架代码 id匹配&#xff08;可想象成八字匹配&#xff09;&#xff1a;一个驱动可以对应多个设备 ------优先级次低&#xff08;上一章名称匹配只能1对1&#xff09; 注意事项&#xff1a; device模块中&#xff0c;id的name成员必须与struct platform_device中…

MySQL主从环境,主库改端口后,从库如何操作?

主库&#xff1a;mysql-111 从库&#xff1a;mysql-112 主库由3306端口修改成3307后&#xff0c; 从库执行如下命令 mysql> stop slave; mysql> change master to master_port3307; mysql> CHANGE MASTER TO MASTER_HOST192.168.10.111,MASTER_USERbeifen,MASTER_PA…

npm install 安装依赖如何加速

在使用npm安装依赖时&#xff0c;有几种方法可以加速这一过程&#xff0c;尤其是在面临网络限制或npm官方源速度慢的情况下。以下是一些常用的加速技巧&#xff1a; 1. 使用国内镜像源 国内有几个镜像源可以提供更快的下载速度&#xff0c;例如淘宝npm镜像。你可以通过以下命…

如何用一根网线和51单片机做简单门禁[带破解器]

仓库:https://github.com/MartinxMax/Simple_Door 支持原创是您给我的最大动力… 原理 -基础设备代码程序- -Arduino爆破器程序 or 51爆破器程序- 任意选一个都可以用… —Arduino带TFT屏幕——— —51带LCD1602——— 基础设备的最大密码长度是0x7F&#xff0c;因为有一位…

Excel模板1:彩色甘特图

Excel模板1&#xff1a;彩色甘特图 分享地址 当前效果&#xff1a;只需要填写进度&#xff0c; 其余效果都是自动完成的 。 阿里网盘永久分享&#xff1a;https://www.alipan.com/s/cXhq1PNJfdm ​省心。能用公式的绝不使用手动输入。 ​​ 这个区域以及标题可以手动输入…

Python爬虫——解析库安装(1)

目录 1.lxml安装2.Beautiful Soup安装3.pyquery 的安装 我创建了一个社区&#xff0c;欢迎大家一起学习交流。社区名称&#xff1a;Spider学习交流 注&#xff1a;该系列教程已经默认用户安装了Pycharm和Anaconda&#xff0c;未安装的可以参考我之前的博客有将如何安装。同时默…

机器学习之局部最优和全局最优

(1)局部最优&#xff0c;就是在函数值空间的一个有限区域内寻找最小值;而全局最优&#xff0c;是在函数值空间整个区域寻找最小值问题。 (2)函数局部最小点是它的函数值小于或等于附近点的点&#xff0c;但是有可能大于较远距离的点。 (3)全局最小点是那种它的函数值小于或等于…

二叉树-------前,中,后序遍历 + 前,中,后序查找+删除节点 (java详解)

目录 提要&#xff1a; 创建一个简单的二叉树&#xff1a; 二叉树的前中后序遍历&#xff1a; 二叉树的前序遍历&#xff1a; 二叉树的中序遍历&#xff1a; 二叉树的后续遍历&#xff1a; 小结&#xff1a; 二叉树的前中后续查找&#xff1a; 二叉树的前序查找&#…

Python 数据可视化之山脊线图 Ridgeline Plots

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 JoyPy 是一个基于 matplotlib pandas 的单功能 Python 包&#xff0c;它的唯一目的是绘制山脊线图 Joyplots&#xff08;也称为 Ridgeline Plots&…

SpringMVC原理(设计原理+启动原理+工作原理)

文章目录 前言正文一、设计原理1.1 servlet生命周期简述1.2 设计原理小结 二、启动原理2.1 AbstractHandlerMethodMapping 初始化 --RequestMapping注解解析2.2 DispatcherServlet 的初始化2.3 DispatcherServlet#initHandlerMappings(...) 初始化示例说明 三、工作原理 前言 …