C++函数模板详解(结合代码)

目录

1. 模板概念

2. 函数模板语法

3. 函数模板注意事项

4. 函数模板案例

5. 普通函数与函数模板的区别

6. 普通函数与函数模板的调用规则

7. 模板的局限性


1. 模板概念

在C++中,模板是一种通用的程序设计工具,它允许我们处理多种数据类型而不是固定的一种。函数模板就是其中之一,它使得我们可以编写一个函数来处理不同类型的数据。

模板的特点:

  • 模板不可以直接使用,它只是一个框架,必须确定出T的类型(第3章有讲)

  • 模板的通用并不是万能的

2. 函数模板语法

C++提供两种模板机制:函数模板类模板 。为避免文章冗长,本文先介绍函数模板,下一篇文章介绍类模板。

函数模板作用:
建立一个通用函数,其函数返回值类型形参类型可以不具体制定,用一个虚拟的类型来代表。

使用语法:

template<typename T> 或者 template<class T>

template<typename T>表示声明一个模板,typename T是模板参数,
typename —— 表面其后面的符号是一种数据类型,可以用class代替。
个人习惯用template<typename T>代表函数模板,template<class T>代表类模板。
T —— 通用的数据类型,名称可以替换,通常为大写字母T。

示例:

//交换整型函数
void swapInt(int& a, int& b) {int temp = a;a = b;b = temp;
}//交换浮点型函数
void swapDouble(double& a, double& b) {double temp = a;a = b;b = temp;
}//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{T temp = a;a = b;b = temp;
}void test01()
{int a = 10;int b = 20;//swapInt(a, b);//利用模板实现交换//1、自动类型推导mySwap(a, b);//2、显示指定类型mySwap<int>(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;}int main() {test01();system("pause");return 0;
}
  • 函数模板利用关键字 template

  • 使用函数模板有两种方式:1、自动类型推导    2、显示指定类型

  • 模板的目的是为了提高复用性,将类型参数化

3. 函数模板注意事项

  • 自动类型推导的限制:当使用自动类型推导时,编译器需要根据函数参数类型推导出一致的数据类型 T 才能成功实例化模板函数。如果无法推导出一致的类型,将导致编译错误。
    // 示例代码
    template<typename T>
    void myFunction(T arg1, T arg2) {// 函数体
    }int main() {myFunction(10, 20);  // 正确:推导出一致的 int 类型myFunction(10, 20.5);  // 错误:无法推导出一致的类型return 0;
    }
    
  • 模板参数的确定:在模板函数调用时,必须要确定模板参数的数据类型,否则编译器无法生成对应的函数实例。
    // 2、模板必须要确定出T的数据类型,才可以使用
    template<class T>
    void func()
    {cout << "func 调用" << endl;
    }void test01()
    {//func(); //错误,模板不能独立使用,必须确定出T的类型func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
    }
    

fun<int>()代表对模板函数 fun 进行实例化,并指定模板参数 T 的具体类型为 int。在这种情况下,编译器会生成一个针对 T 为 int 类型的具体函数实现。其效果就好比是将模板中的 T 替换为 int,然后使用 int 类型的函数来处理相应的逻辑

4. 函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序

  • 排序规则从大到小,排序算法为选择排序

  • 分别利用char数组int数组进行测试

//交换的函数模板
template<typename T>
void mySwap(T &a, T&b)
{T temp = a;a = b;b = temp;
}template<typename T> // 也可以替换成class
//利用选择排序,进行对数组从大到小的排序
void mySort(T arr[], int len)
{for (int i = 0; i < len; i++){int max = i; //最大数的下标for (int j = i + 1; j < len; j++){if (arr[max] < arr[j]){max = j;}}if (max != i) //如果最大数的下标不是i,交换两者{mySwap(arr[max], arr[i]);}}
}
template<typename T>
void printArray(T arr[], int len) {for (int i = 0; i < len; i++) {cout << arr[i] << " ";}cout << endl;
}
void test01()
{//测试char数组char charArr[] = "bdcfeagh";int num = sizeof(charArr) / sizeof(char);mySort(charArr, num);printArray(charArr, num);
}void test02()
{//测试int数组int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };int num = sizeof(intArr) / sizeof(int);mySort(intArr, num);printArray(intArr, num);
}int main() {test01();test02();system("pause");return 0;
}

5. 普通函数与函数模板的区别

普通函数与函数模板区别:

  • 普通函数调用时可以发生自动类型转换隐式类型转换

  • 函数模板调用时,如果利用自动类型推导不会发生隐式类型转换

  • 如果利用显示指定类型的方式,可以发生隐式类型转换

示例:

// 普通函数和函数模板的区别// 1、普通函数调用可以发生隐式类型转换
// 2、函数模板用 自动类型推导时,不可以发生隐式类型转换
// 3、函数模板用 显示指定类型时,  可以发生隐式类型转换// 普通函数
int myAdd01(int a, int b)
{return a + b;
}// 模板函数
template<typename T>
T myAdd02(T a, T b)
{return a + b;
}void test01()
{int a = 10;int b = 20;char c = 'c';cout << "普通函数:int a + int b = " << myAdd01(a, b) << endl;// 普通函数中,隐式的将c转成了ASSIC码,c-99cout << "普通函数:int a + char c = " << myAdd01(a, c) << endl;// 自动类型推导// 会报错,模板函数自动类型推导时,不会发生隐式类型转换// cout << "模板函数:int a + char c = " << myAdd02(a, c) << endl;// 显示指定类型cout << "模板函数:int a + char c = " << myAdd02<int>(a, c) << endl;
}int main()
{test01();
}

6. 普通函数与函数模板的调用规则

调用规则如下:

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

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

  3. 函数模板也可以发生重载

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

//普通函数与函数模板调用规则
void myPrint(int a, int b)
{cout << "调用的普通函数" << endl;
}template<typename T>
void myPrint(T a, T b) 
{ cout << "调用的模板" << endl;
}template<typename T>
void myPrint(T a, T b, T c) 
{ cout << "调用重载的模板" << endl; 
}void test01()
{//1、如果函数模板和普通函数都可以实现,优先调用普通函数// 注意 如果告诉编译器  普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到int a = 10;int b = 20;myPrint(a, b); //调用普通函数//2、可以通过空模板参数列表来强制调用函数模板myPrint<>(a, b); //调用函数模板//3、函数模板也可以发生重载int c = 30;myPrint(a, b, c); //调用重载的函数模板//4、 如果函数模板可以产生更好的匹配,优先调用函数模板char c1 = 'a';char c2 = 'b';myPrint(c1, c2); //调用函数模板
}int main() 
{test01();system("pause");return 0;
}

既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性。

7. 模板的局限性

例如:

template<class T>void f(T a, T b){ a = b;}

如果数据类型是int型,此时的赋值操作没问题。如果传入的a和b是一个数组,就无法实现了。

再例如:

template<class T>void f(T a, T b){ if(a > b) { ... }}

如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行。

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板

#include<iostream>
using namespace std;#include <string>class Person
{
public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};//普通函数模板
template<class T>
bool myCompare(T& a, T& b)
{if (a == b){return true;}else{return false;}
}//具体化,显示具体化的原型template<>开头,并通过名称来指出类型
//具体化优先于常规模板
template<> bool myCompare(Person &p1, Person &p2)
{if ( p1.m_Name  == p2.m_Name && p1.m_Age == p2.m_Age){return true;}else{return false;}
}void test01()
{int a = 10;int b = 20;//内置数据类型可以直接使用通用的函数模板bool ret = myCompare(a, b);if (ret){cout << "a == b " << endl;}else{cout << "a != b " << endl;}
}void test02()
{Person p1("Tom", 10);Person p2("Tom", 10);//自定义数据类型,不会调用普通的函数模板//可以创建具体化的Person数据类型的模板,用于特殊处理这个类型bool ret = myCompare(p1, p2);if (ret){cout << "p1 == p2 " << endl;}else{cout << "p1 != p2 " << endl;}
}int main() 
{test01();test02();system("pause");return 0;
}

在这个特定的例子中,通用的函数模板 myCompare 用于比较两个对象是否相等,但是当对象类型是 Person 类型时,我们想要做一些特殊的比较,比如比较 Person 类的 m_Namem_Age 成员变量。为了实现这一目的,我们对通用模板进行了具体化。

具体化的语法是在 template<> 关键字后面指定特定的类型,这里是 Person,然后是模板的原型,即函数签名 bool myCompare(Person &p1, Person &p2)在函数体中,我们以特定方式实现了 Person 类型对象的比较逻辑,即比较它们的名字和年龄。

当在代码中调用 myCompare 函数,并传递 Person 类型的参数时,编译器会优先选择这个具体化版本,而不是通用的模板版本。

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化

  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板

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

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

相关文章

Symbol 类型必知必会

一. Symbol的概念 Symbol 是 JavaScript 中的一种新的基本数据类型&#xff0c;引入自 ECMAScript 6&#xff08;ES6&#xff09;标准。它是一种不可变且唯一的数据类型&#xff0c;可以用来创建独一无二的键&#xff08;key&#xff09;。 Symbol 的创建方式是通过调用全局的…

测试开发工程师(QA)职业到底需要干些什么?part4:安全QA

安全测试开发QA工作的主要目标是确保软件、应用程序或系统在安全方面的健壮性和可靠性。以下是安全测试开发QA工作中的一些常见任务和职责&#xff1a; 安全测试计划和策略&#xff1a;安全测试开发QA团队负责制定全面的安全测试计划和策略。他们会评估应用程序或系统的安全需求…

【优选算法】专题1 -- 双指针 -- 复写0

前言&#xff1a; 补充一下前文没有写到的双指针入门知识&#xff1a;专题1 -- 双指针 -- 移动零 目录 基础入门知识&#xff1a; 1. 复写零&#xff08;easy&#xff09; 1. 题⽬链接&#xff1a;1089.复习0 - 力扣&#xff08;LeetCode&#xff09; 2. 题⽬描述&#xff…

数据容器-dict以及总结-Python

师从黑马程序员 字典的定义 同样使用{},不过存储的元素是以个个的&#xff1a;键值对&#xff0c;如下语法&#xff1a; #定义字典 my_dict1{"王力宏":99,"周杰伦":88,"林俊杰":77} #定义空字典 my_dict2{} my_dict3dict() print(f"字典1…

springboot 将manage关闭。

这是SpringBoot自带的接口&#xff0c;会将所有的接口暴露在外面。所以我们上生产环境&#xff0c;需要将这个接口给关闭。 默认是 management.endpoints.web.exposure.include* 只需将配置文件改成下面&#xff0c;Springboot自带的接口就会关闭。 management.endpoints.…

【物联网开源平台】tingsboard二次开发

别看这篇了&#xff0c;这篇就当我的一个记录&#xff0c;我有空我再写过一篇&#xff0c;编译的时候出现了一个错误&#xff0c;然后我针对那一个错误执行了一个命令&#xff0c;出现了绿色的succes,我就以为整个tingsboard项目编译成功了&#xff0c;后面发现的时候&#xff…

记录微信小程序云开发的增删改查

目录 一、准备工作 1、创建集合添加数据 2、设置数据权限 3、小程序连接数据库 二、增删改查 1.查 1、查询单集合所有数据 2、条件查询 1、直接:相当于等于 2、调用指令 3、查询单条&#xff08;根据id查询&#xff09; 2.增 3.改 4.删 一、准备工作 1、创建集合添…

力扣---最长公共子序列---二维动态规划

思想&#xff1a; 定义g[i][j]&#xff1a;text1的前i位和text2的前j位的最长公共子序列长度。递推公式&#xff1a;如果text[i]text[j]&#xff0c;那么只需要看g[i-1][j-1]即可&#xff0c;此时g[i][j]g[i-1][j-1]1。如果text[i]!text[j]&#xff0c;那么g[i][j]max(g[i-1][j…

【研发管理】研发管理规范

研发管理规范 目的定义工作职责产品经理项目经理运维负责人研发负责人研发工程师 基本原则研发过程描述需求分析分析设计研发实现测试验收发布上线线上监控 目的 软件研发相关管理&#xff0c;有效控制技术风险&#xff0c;提高研发和运行质量 定义 包括需求分析、分析设计…

【C】盛最多水的容器(双指针)

盛最多水的容器 原题目链接:点击跳转 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和(i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说…

小学生古诗文大会往届真题测一测(来自主办方)和非常详细的解析

新学期开学一眨眼已经过了一个多月了&#xff0c;有家长朋友开始关心2024年上海市小学生古诗文大会什么时候开始&#xff1f;如何准备小学生古诗文大会&#xff1f;如何激发孩子学习古诗词的兴趣&#xff1f;如何提高小学古诗词和古诗文大会的学习成绩&#xff1f;... 最近&…

基于SpringBoot+Vue共享汽车管理系统(源码+部署说明+演示视频+源码介绍)

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…

3.25C++

定义自己的命名空间&#xff0c;其中有string类型的变量&#xff0c;再定义两个函数&#xff0c;一个函数完成字符串的输入&#xff0c;一个函数完成求字符串长度&#xff0c;再定义一个全局函数完成对该字符串的反转 #include <iostream> #include <cstring> usi…

网络基础二——序列化与反序列化

1、应用层 我们程序员写的一个个解决我们实际问题&#xff0c;满足我们日常需求的网络程序&#xff0c;都是在应用层。 2、再谈“协议” 协议是一种 " 约定 "。 socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的 . 如果我们…

c++模拟银行家算法

在复习408时,使用代码模拟能够加深对知识点的理解 1.银行家算法介绍 银行家算法是迪杰斯特拉发明的死锁避免的算法。找到合理的资源分配序列保证各进程的正常推进是银行家算法的核心。 2.自定义类以及成员变量和成员函数说明 进程类 //进程类 class Process; //资源最大需求…

FPGA之状态机学习

作为一名逻辑工程师&#xff0c;掌握和应用状态机设计是必不可少的。能够灵活的应用状态机是对逻辑工程师最基本的要求&#xff0c;状态机设计的好坏能够直接影响到设计系统的稳定性&#xff0c;所以学会状态机是非常的重要。 1.状态机的概念 状态机通过不同的状态迁移来完成特…

【C语言】Infiniband驱动__mlx4_init_one函数

一、注释 Linux内核驱动程序中的部分&#xff0c;属于Mellanox网卡驱动mlx4的初始化过程。 // Mellanox 以太网驱动主程序代码 static int __mlx4_init_one(struct pci_dev *pdev, int pci_dev_data,struct mlx4_priv *priv) {int err; // 错误码变量int nvfs[MLX4_MAX_PORTS…

【SSH配置公钥私钥免密登录】

SSH配置公钥私钥免密登录 SSH的安全机制一、修改远程主机ssh设置二、在windows客户端生成公钥私钥文件三、将客户端公钥追加到远程主机 .ssh/authorized_keys中参考链接 SSH的安全机制 SSH之所以能够保证安全&#xff0c;原因在于它采用了非对称加密技术(RSA)加密了所有传输的…

机载激光雷达标定板提高测量精度

机载激光雷达系统标定板&#xff08;LiDAR&#xff09;是一种高精度的遥感技术&#xff0c;它通过发射激光脉冲并接收反射回来的光波来测量目标物体的距离。这种技术在测量地形、建筑物、植被等方面有着广泛的应用。为了确保机载激光雷达系统的数据准确性和可靠性&#xff0c;需…

An Experimental Study of State-of-the-Art Entity Alignment Approaches论文阅读

最先进的实体对齐方法的实验研究综述 Title: An Experimental Study of State-of-the-Art Entity Alignment Approaches 日期: 2022 发表单位: IEEE github: https://github.com/DexterZeng/EAE 原文地址: https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber9174835 概括…