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.mzph.cn/news/683471.shtml

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

相关文章

揭秘Angular世界的奥秘:全面提升你的前端开发技能!

介绍&#xff1a;Angular是一个由Google维护的开源JavaScript框架&#xff0c;专为构建Web应用程序而设计&#xff0c;特别适合开发大型单页应用&#xff08;SPA&#xff09;。以下是对Angular的详细介绍&#xff1a; 技术栈&#xff1a;Angular使用HTML作为模板语言&#xff0…

作业2.15

1.head head 文件名 默认回显文件的前十行 head -n 文件名 回显文件的前n行 2.tail tail 文件名 默认回显文件的后10行 tail -n 文件名 回显文件的后n行 3. | 管道符 指令1 | 指令2 | 指令3 | 管道符左侧指令的输出用作管道符右侧指令的输入 4.file 查看文件的信息 file 文件…

怎么查看python的安装路径

要查看Python的安装路径&#xff0c;你可以使用以下几种方法&#xff1a; 方法1&#xff1a;使用命令行&#xff08;适用于所有操作系统&#xff09; 打开命令行界面&#xff08;在Windows上是命令提示符或PowerShell&#xff0c;在macOS和Linux上是终端&#xff09;。输入以…

耳机壳UV树脂制作私模定制耳塞需要注意什么问题?

制作私模定制耳塞需要注意以下问题&#xff1a; 耳模制作&#xff1a;获取准确的耳模是制作私模定制耳塞的关键步骤。需要使用合适的材料和方法&#xff0c;确保耳模的准确性和稳定性。材料选择&#xff1a;选择合适的UV树脂和其它相关材料&#xff0c;确保它们的质量和性能符…

2024.2.15每日一题

LeetCode 二叉树的层序遍历 II 107. 二叉树的层序遍历 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历&am…

滑动窗口(三)

Leetcode30. 串联所有单词的子串 题目 Leetcode30. 串联所有单词的子串 解法(滑动窗口) 利用substr函数截取出来的s中截取出一段一段的单词&#xff0c;然后和words中比较是否相等。 hash1<string, int>用来存储words中单词出现的次数left right指针每次移动的步数为wo…

c++stoi函数解释

别问我为什么不继续发恶魔轮盘了&#xff0c;我的代码被我的堂弟给删了&#xff0c;全部变成小猪佩奇动画片了 std::stoi是C标准库中的一个函数&#xff0c;用于将字符串转换为整数类型。它的全名是std::string to int&#xff0c;所以stoi就是string to int的缩写。 它的函数…

【图论经典题目讲解】CF715B - Complete The Graph

C F 715 B − C o m p l e t e T h e G r a p h \mathrm{CF715B - Complete\ The\ Graph} CF715B−Complete The Graph D e s c r i p t i o n \mathrm{Description} Description 给定一张 n n n 个点&#xff0c; m m m 条边的无向图&#xff0c;点的编号为 0 ∼ n − 1 0\…

SpringCloud-Hystrix:服务熔断与服务降级

8. Hystrix&#xff1a;服务熔断 分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系&#xff0c;每个依赖关系在某些时候将不可避免失败&#xff01; 8.1 服务雪崩 多个微服务之间调用的时候&#xff0c;假设微服务A调用微服务B和微服务C&#xff0c;微服…

深度学习疆界:探索基本原理与算法,揭秘应用力量,展望未来发展与智能交互的新纪元

目录 什么是深度学习 深度学习的基本原理和算法 深度学习的应用实例 深度学习的挑战和未来发展方向 挑战 未来发展方向 深度学习与机器学习的关系 深度学习与人类的智能交互 什么是深度学习 深度学习是一种基于神经网络的机器学习方法&#xff0c;旨在模仿人类大脑分析…

2024.02.14作业

1. 请编程实现二维数组的杨辉三角 #include <stdio.h> #include <stdlib.h> #include <string.h>int main() {int n;scanf("%d", &n);int a[n][n];memset(a, 0, sizeof(a));a[0][0] 1;for (int i 1; i < n; i){for (int j 0; j < i …

云原生之容器编排-Docker Swarm

1. 前言 上一篇我们讲到Docker Compose可以定义和运行多容器应用程序&#xff0c;用一个YAML配置文件来声明式管理服务&#xff0c;在一台安装了Docker engine的Linux系统上可以很好的工作&#xff0c;但是现实中不可能只有一台Linux系统&#xff0c;一台Linux系统不可能有足够…

单片机学习笔记---LCD1602功能函数代码

目录 LCD1602.c 模拟写指令的时序 模拟写数据的时序 初始化 显示字符 显示字符串 显示数字 显示有符号的数字 显示16进制数字 显示二进制数 LCD1602.h main.c 上一篇讲了LCD1602的工作原理&#xff0c;这一节开始代码演示&#xff01; 新创建工程&#xff1a;LCD1…

黑马程序员——移动Web——day02

目录 空间转换 空间转换简介平移视距旋转左手法则rotate3d-了解立体呈现案例-3d导航缩放动画 动画实现步骤animation复合属性animation拆分写法案例-走马灯精灵动画多组动画综合案例-全名出游 背景云彩位置和动画文字动画 1.空间转换 空间转换简介 空间&#xff1a;是从坐标…

P1000 超级玛丽游戏(洛谷)

题目背景 本题是洛谷的试机题目&#xff0c;可以帮助了解洛谷的使用。 建议完成本题目后继续尝试 P1001、P1008。 另外强烈推荐新用户必读贴 题目描述 超级玛丽是一个非常经典的游戏。请你用字符画的形式输出超级玛丽中的一个场景。 ********************####....#.#..###…

AcWing 112. 雷达设备(区间贪心)

[题目概述] 假设海岸是一条无限长的直线&#xff0c;陆地位于海岸的一侧&#xff0c;海洋位于另外一侧。 每个小岛都位于海洋一侧的某个点上。 雷达装置均位于海岸线上&#xff0c;且雷达的监测范围为 d&#xff0c;当小岛与某雷达的距离不超过 d 时&#xff0c;该小岛可以被雷…

实验5-4 使用函数计算两点间的距离

本题要求实现一个函数&#xff0c;对给定平面任意两点坐标(x1​,y1​)和(x2​,y2​)&#xff0c;求这两点之间的距离。 函数接口定义&#xff1a; double dist( double x1, double y1, double x2, double y2 );其中用户传入的参数为平面上两个点的坐标(x1, y1)和(x2, y2)&…

[Angular 基础] - 自定义事件 自定义属性

[Angular 基础] - 自定义事件 & 自定义属性 之前的笔记&#xff1a; [Angular 基础] - Angular 渲染过程 & 组件的创建 [Angular 基础] - 数据绑定(databinding) [Angular 基础] - 指令(directives) 以上是能够实现渲染静态页面的基础 之前的内容主要学习了怎么通过…

科技魔法!阿里通义千问让你跟随音乐摇摆起来!

2024年&#xff0c;一个名叫《科目三》的舞蹈在众多社交平台上火爆开来。它的火爆程度&#xff0c;甚至让一向以科技惊人闻名的亿万富翁马斯克也不得不对其前来“致敬”。然而&#xff0c;学习这种舞蹈却是一项颇具挑战的任务&#xff0c;尤其是对于四肢并非十分协调的人来说。…

第13章 网络 Page727~728 asio定时器例子:后创建的定时器先产生到点事件

代码&#xff1a; 35行&#xff0c;42行&#xff0c;51行&#xff0c;分别构造三个对象&#xff0c; 36行&#xff0c;43行&#xff0c;52行&#xff0c;设置了三个任务peng1、peng2、peng3&#xff0c;并将任务交给io_service对象&#xff08;不需要ios的run()方法启动起来&a…