如何编写一个通用的函数?

简介: 如何编写一个通用的函数?

前言

本文主要讲解如何使用简单的模板,了解模板的原理以及基本知识.

一、函数模板

模板的作用:

C++中模板的作用是支持泛型编程。==泛型编程=是一种编程范式,它只考虑算法或数据结构的抽象,而不考虑具体的数据类型。通过使用模板,可以编写一种通用的算法或数据结构,而不需要为每种数据类型都编写一遍相关代码。模板可以用于函数、类、结构体等地方,以实现通用的算法和数据结构。使用模板可以提高代码的复用性和可读性,减少代码的重复编写。

示例:实现一个交换函数.

使用模板之前:

void swap(int& a, int& b)
{
  int tmp = a;
  a = b;
  b = tmp;
}
void swap(double& a, double& b)
{
  double tmp = a;
  a = b;
  b = tmp;
}
void swap(char& a, char& b)
{
  char tmp = a;
  a = b;
  b = tmp;
}
void test1()
{
  //交换整形
  int a = 2, b = 3;
  cout << "a=" << a << "  " << "b=" << b << endl;
  swap(a, b);
  cout << "a=" << a << "  " << "b=" << b << endl;
  //交换字符型
  char c1 = 'd', c2 = 'f';
  cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
  swap(c1, c2);
  cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
  //交换...
}

上述实现过程中使用函数重载实现.但是函数重载会有一些不合适的问题.

 1.函数重载只是重载的函数类型不同,代码复用率比较低,对于一个新的类型又要增加新的函数.

 2.由于功能基本一样,只是类型不同,导致代码的可维护性比较低,一个出错可能所有的重载均出错,均要修改.


这时,函数模板就派上用场了.

(1)函数模板的格式

template<typename T1, typename T2,......,typename Tn>

返回值类型+ 函数名 +(参数列表){}

其中,typename 可以使用class代替,不能使用struct代替.

示例:使用模板后的通用交换函数.

template <class T>//模板
void swap(T& a, T& b)//T会根据传参的对象进行推导为相应的类型
{
  T tmp = a;
  a = b;
  b = tmp;
}
void test1()
{
  //交换整形
  int a = 2, b = 3;
  cout << "a=" << a << "  " << "b=" << b << endl;
  swap(a, b);
  cout << "a=" << a << "  " << "b=" << b << endl;
  //交换字符型
  char c1 = 'd', c2 = 'f';
  cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
  swap(c1, c2);
  cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
  //交换...
}

(2)函数模板的原理(重点)

函数模板类似于一个模具,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做.

这就类似于古代的印刷术,如果每本书都需要手写,那效率是否太低了,还有各种情况可能会出错.但是印刷术的使用,就可以使用模具生成.

1c73280fc3ac4928a3d8441c7623d18b.png

函数模板的原理是通过将类型参数化,使函数能够在编译时根据实际参数的类型推断生成具体的函数实例。编译器会根据调用函数时的参数类型,实例化出适合该类型的函数版本。

比如:

当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码.当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码.如上图所示.

(3)模板参数的显示实例化

上面我们实现的交换函数,模板根据传参时不同的参数,自动推演出函数参数的实际类型.我们称这类通过编译器进行自动推导的实例化模板参数称为模板参数的隐式实例化.

那什么是显示实例化呢?

template <typename T>
T add(const T& a, const T& b)
{
  return a + b;
}
void test1()
{
  int a = 2, b = 3;
  double d1 = 2.5, d2 = 4.1;
  cout << add(a, b) << endl;
  cout << add(d1, d2) << endl;
  //下面这句会报错,因为一个模板参数无法在一个函数中实例化为2个不同类型的参数,一个int,一个double
  //cout << add(a, d2) << endl;
}

一个函数模板参数在同一个函数中,无法被识别为不同的两个实例类型参数,当编译器推导出a是int时,又推出d2是double类型,则编译器陷入两难.

就好比:

int:妈妈说今天不许出去玩!

double:爸爸说今天可以出去玩!

编译器:我听谁的.

解决方案:

直接将参数先强转为一样的,当模板函数接收到参数时,就只有一样的结果了.

  //解决方法1:传参时将其中不同的参数强转,使参数们相同
  cout << add(a, (int)d2) << endl;
  cout << add((double)a, d2) << endl;

模板参数的显示实例化:


让爸妈先商量好听谁的.

  //解决方法2:显示指定模板的参数
  cout << add<int>(a, d2) << endl;  //听妈妈的
  cout << add<double>(a, d2) << endl; //听爸爸的

我们应当是考虑如何在调用时采取不同的调用方式去满足我们的需求,千万不要想着去修改模板函数的返回值,参数使他们固定生成,那模板就不通用了,而且不是什么时候我们都可以去修改模板的.

错误示例:

template <typename T>
T add(const T& a, const int& b)//直接修改参数,进行固定
{
  return a + b;
}

(4)模板匹配

对于函数名相同的非模板函数和模板函数同时存在时,编译器会优先选择非模板函数.除非模板可以产生更好的匹配函数,才会选择模板.


编译器:有现成的为啥不用.

void swap(double& a, double& b)
{
  double tmp = a;
  a = b;
  b = tmp;
}
//template <class  T>
template <typename  T>//函数模板
void swap(T& a, T& b)
{
  T tmp = a;
  a = b;
  b = tmp;
}
void test1()
{
  //交换double
  double d1 = 2.5, d2 = 4.5;
  //非模板函数和模板函数同时存在时,编译器优先选择非模板函数,有现成的为啥不用?非得自己去再模具刻一个?
  swap(d1, d2);
  cout << "d1=" << d1 << "  " << "d2=" << d2 << endl;
  //交换整形
  int a = 2, b = 3;
  //没有现成的非模板函数,则编译器调用模板函数去实例化一份
  swap(a, b);
  cout << "a=" << a << "  " << "b=" << b << endl;
}

交换double型数据时,会调用void swap(double& a, double& b)函数,因为有现成的可以调用.

交换int整形时,则会调用模板函数void swap(T& a, T& b),实例化生成int型的函数.

e9699130d1654e0a9216a059fb10b820.gif

小知识:

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

因为模板函数的参数是通过参数类型进行推导的.

二、类模板

类模板的格式

template <typename T>
class A
{
  //成员
}

类模板在后续学习STL时候会具体介绍,目前了解一下即可,使用方法与函数模板类似,这里就不过多介绍了.

template <typename T>
class A
{
public:
  A(size_t capacity = 10)
    : _data(new T[capacity])
    , _size(0)
    , _capacity(capacity)
  {}
  void push_back() {
    //...
  }
  ~A()
  {
    delete _data;
    _size = 0;
    _capacity = 0;
  }
private:
  T* _data;
  size_t _size;
  size_t _capacity;
};
void test3()
{
  A<int> a1;      //实例化为存储int数据的类
  A<double> a2;   //实例化为存储double数据的类
}

本文只是对模板的初步了解,后续会遇到更加复杂的模板,比如多参数的模板等,知识一点点的学,不求速成,坚持一点点的积累,一起加油吧!

今天就讲到这里了,拜拜.



529b5f4f3453477ca75cfd3042847171.gif

目录
相关文章
|
搜索推荐 前端开发 JavaScript
什么是百度优化?百度SEO优化解决方案
百度优化的解决方案不仅可以帮助企业提升网站在百度PC端的收录与关键词排名,也可以获得更好的移动端收录与关键词排名,从而达到品牌SEO推广及引流的目的。接下来小编为你详细分享什么是百度优化以及实用的解决方案,一起来看看吧。
1726 0
|
5月前
|
Kubernetes 网络协议 API
在k8s集群中解决master节点与node通信问题
整个排查和解决流程需要综合应用以上方法,以及根据具体情况调整排查顺序或应用其他技术细节。为保证解决方案的实用性和有效性,还需紧跟Kubernetes社区的最新动态和最佳实践。在实际操作过程中,应记录所采取的步骤和观察到的系统响应,以便在遇到类似问题时能够快速定位和解决。
441 8
|
前端开发 JavaScript 小程序
2022年前端学习路线(个人认为)
本文提供了一条前端工程师的学习路径,分为三个阶段。第一阶段(1-2个月)重点掌握HTML、CSS、JavaScript及H5+CSS3,目标是能够构建基本的静态网页。第二阶段(2-3个月)深入Node.js、Git、Webpack及相关工具和规范,提升项目管理和构建能力。第三阶段(4-6个月)专注于Vue、React、Angular等主流框架,以及uni-app和微信小程序的开发,旨在实现跨平台应用的开发能力。每个阶段都有详细的课程推荐,帮助初学者逐步成长为合格的前端工程师。
201 1
|
自然语言处理 运维 开发工具
深入探讨了 NeoVim 相较于传统 Vim 的优势,包括更好的扩展性、现代化的界面和用户体验、多语言编程支持、强大的异步处理能力、更好的协作支持、持续的更新和改进、活跃的社区以及与现代开发工具的集成
本文深入探讨了 NeoVim 相较于传统 Vim 的优势,包括更好的扩展性、现代化的界面和用户体验、多语言编程支持、强大的异步处理能力、更好的协作支持、持续的更新和改进、活跃的社区以及与现代开发工具的集成。通过命令对比,展示了两者在启动、配置、模式切换、移动编辑、搜索替换、插件管理、文件操作、窗口缓冲区管理和高级功能等方面的差异。总结部分强调了 NeoVim 在多个方面的显著优势,解释了为什么越来越多的运维人员选择 NeoVim。
992 3
|
安全 搜索推荐
基础入门 HTTP数据包&Postman构造&请求方法&请求头修改&状态码判断
基础入门 HTTP数据包&Postman构造&请求方法&请求头修改&状态码判断
240 0
|
人工智能 搜索推荐 物联网
移动应用开发的未来趋势:从跨平台到AI集成
在数字化浪潮的推动下,移动应用已成为我们日常生活不可或缺的一部分。本文将探讨移动应用开发领域的最新进展,特别是跨平台框架和人工智能技术的融合如何塑造这一行业。通过分析当前技术栈、工具和最佳实践,我们将揭示未来移动应用开发的趋势,并讨论这些变化对开发者、企业和最终用户的意义。
364 1
|
存储 缓存 Linux
linux 自动定时清理缓存
linux 自动定时清理缓存
583 0
|
C++ 容器
[Qt5] QT+VS读取dwg文件
[Qt5] QT+VS读取dwg文件
464 0
|
JavaScript 网络架构
Vue 动态添加路由及生成菜单
Vue 动态添加路由及生成菜单
450 0
|
消息中间件 数据安全/隐私保护 Windows
windows下RabbitMQ安装后,无法进入web管理页面问题
windows下RabbitMQ安装后,无法进入web管理页面问题
506 0