函数模版实例化

目录

一、前言

二、 什么是C++模板

💦泛型编程的思想 

 💦C++模板的分类

 三、函数模板

 💦函数模板概念

 💦函数模板格式

💦函数模板的原理

 💦函数模板的实例化

🍎隐式实例化

 🍉显式实例化

💦模板支持多个模板参数

 💦模板参数的匹配原则

四、类模板 

 💦类模板的概念

 💦类模板格式

 💦类模板的实例化

 💦类模板的分离编译

五、总结

六、共勉 


一、前言

        在我们学习C++时,常会用到函数重载。而函数重载,通常会需要我们编写较为重复的代码,这就显得臃肿,且效率低下。重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数。此外,代码的可维护性比较低,一个出错可能会导致所有的重载均出错。

       那么,模板的出现,就让这些问题有了解决方案,所以本次博客将为大家详细的讲解C++的模板!!

二、 什么是C++模板

        程序设计中经常会用到一些程序实体它们的实现和所完成的功能基本相同,不同的仅 仅是所涉及的数据类型不同。而模板正是一种专门处理不同数据类型的机制。


       模板------是泛型程序设计的基础(泛型generic type——通用类型之意)。
 

        函数、类以及类继承为程序的代码复用提供了基本手段,还有一种代码复用途径——类属类型(泛型),利用它可以给一段代码设置一些取值为类型的参数(注意:这些参数 的值是类型,而不是某类型的数据),通过给这些参数提供一些类型来得到针对不同类 型的代码。

💦泛型编程的思想 

         首先我们来看一下下面这三个函数,如果学习过了C++函数重载 和 C++引用 的话,就可以知道下面这三个函数是可以共存的,而且传值会很方便

  1. void Swap(int& left, int& right)
  2. {
  3. int temp = left;
  4. left = right;
  5. right = temp;
  6. }
  7. void Swap(double& left, double& right)
  8. {
  9. double temp = left;
  10. left = right;
  11. right = temp;
  12. }
  13. void Swap(char& left, char& right)
  14. {
  15. char temp = left;
  16. left = right;
  17. right = temp;
  18. }

        但是真的很方便吗?这里只有三种类型的数据需要交换,若是我们需要增加交换的数据呢?再CV然后写一个函数吗?
        这肯定是不现实的,所以很明显函数重载虽然可以实现,但是有一下几个不好的地方:

  • 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  • 代码的可维护性比较低,一个出错可能所有的重载均出错

       

        那是否能做到这么一点,告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码

⭐总结:
        所以,总结上面的这么一个技术,C++的祖师爷呢就想到了【模版】这个东西,告诉编译器一个模子,然后其余的工作交给它来完成,根据不同的需求生成不同的代码

这就是👉泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

 💦C++模板的分类

1️⃣: 函数模板(function tempalte):使用泛型参数的函数(function with generic parameters)

2️⃣:类模板(class template):使用泛型参数的类(class with generic parameters)

 三、函数模板

 知晓了模版的基本概念后,首先我们要来看的就是【函数模版】

 💦函数模板概念

        函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
        通过函数模板,可以编写一种通用的函数定义,使其能够适用于多种数据类型,从而提高代码的复用性和灵活性。

 💦函数模板格式

  1. template<typename T1, typename T2,......,typename Tn>
  2. 返回值类型 函数名(参数列表)
  3. {
  4. //……
  5. }
  6. 注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

因此,交换函数就可以这样套用模板:

  1. template<typename T>
  2. void Swap(T& left, T& right)
  3. {
  4. T temp = left;
  5. left = right;
  6. right = temp;
  7. }
  8. int main()
  9. {
  10. int x = 2, y = 3;
  11. cout << x << " " << y << endl << endl;
  12. Swap(x, y);
  13. cout << x << " " << y << endl << endl;
  14. cout << "-------------------------------" << endl;
  15. char a = 'a', b = 'b';
  16. cout << a << " " << b << endl << endl;
  17. Swap(a, b);
  18. cout << a << " " << b << endl << endl;
  19. cout << "-------------------------------" << endl;
  20. double c = 2.1, d = 3.1;
  21. cout << c << " " << d << endl << endl;
  22. Swap(c, d);
  23. cout << c << " " << d << endl << endl;
  24. cout << "-------------------------------" << endl;
  25. return 0;
  26. }

 

        我们通过这个函数模版,分别传入不同数据类型的参数,通过结果的观察可以发现这个函数模版可以根据不同的类型去做一个自动推导,继而去起到一个交换的功能。

💦函数模板的原理

 💬 那我现在想问一个问题,请问它们调用的真的是同一个函数吗?
        当然不是,这里我们三次Swap不是调用同一个函数,其实Swap的时候根据不同的类型通过模板定制出专属你的类型的函数,然后再调用,这里可以通过反汇编观察到:

 

        可以发现,在进行汇编代码查看的时候,被调用的函数模版生成了两个不同的函数,它们有着不同的函数地址,因此可以回答上一小节所提出的问题了,两次所调用的函数是不一样的,是根据函数模版所生成的


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

  


        在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
  

  • 补充:

 其实库里面有一个swap函数,因此我们也不需要自己写模板了:


直接套用swap即可:

  1. int main()
  2. {
  3. int x = 2, y = 3;
  4. cout << x << " " << y << endl << endl;
  5. swap(x, y);
  6. cout << x << " " << y << endl << endl;
  7. cout << "-------------------------------" << endl;
  8. char a = 'a', b = 'b';
  9. cout << a << " " << b << endl << endl;
  10. swap(a, b);
  11. cout << a << " " << b << endl << endl;
  12. cout << "-------------------------------" << endl;
  13. double c = 2.1, d = 3.1;
  14. cout << c << " " << d << endl << endl;
  15. swap(c, d);
  16. cout << c << " " << d << endl << endl;
  17. cout << "-------------------------------" << endl;
  18. return 0;
  19. }

 💦函数模板的实例化

        用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:
                         1️⃣: 隐式实例化                                             2️⃣: 显式实例化

🍎隐式实例化

 隐式实例化 让编译器根据实参推演模板参数的实际类型

  1. template<class T>
  2. T Add(const T& left, const T& right)
  3. {
  4. return left + right;
  5. }
  6. int main()
  7. {
  8. int a1 = 10, a2 = 20;
  9. double d1 = 10.0, d2 = 20.0;
  10. Add(a1, a2); //编译器推出T是int
  11. Add(d1, d2); //编译器推出T是double
  12. return 0;
  13. }

⚠ 注意:但是我调用的时候如若这样就会出错:

  1. int main()
  2. {
  3. int a1 = 10, a2 = 20;
  4. double d1 = 10.0, d2 = 20.0;
  5. Add(a1, d1); //err 编译器推不出来
  6. /*
  7. 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
  8. 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有
  9. 一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错
  10. 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
  11. */
  12. }

此时我们发现在模板函数里面,形参不匹配,就会出现报错


编译器无法确定这里的T到底是int还是double。此时有两种处理方式:

法一:用户自己来强制转化

  1. int main()
  2. {
  3. int a1 = 10, a2 = 20;
  4. double d1 = 10.0, d2 = 20.0;
  5. Add(a1, (int)d1); //强制类型转换。或者Add((double)a1, d1);
  6. }

法二:使用显式实例化
接下来进行讲解。

 🍉显式实例化

 显式实例化:在函数名后的<>中指定模板参数的实际类型

继刚才的例子,法二:使用显式实例化

  1. template<class T>
  2. T Add(const T& left, const T& right)
  3. {
  4. return left + right;
  5. }
  6. int main()
  7. {
  8. int a1 = 10, a2 = 20;
  9. double d1 = 10.0, d2 = 20.0;
  10. //显示实例化
  11. Add<int>(a1, d1); //double隐式类型转换成int
  12. Add<double>(a1, d2);
  13. return 0;
  14. }

💦模板支持多个模板参数

  1. template<class K, class V> //两个模板参数
  2. void Func(const K& key, const V& value)
  3. {
  4. cout << key << ":" << value << endl;
  5. }
  6. int main()
  7. {
  8. Func(1, 1); //K和V均int
  9. Func(1, 1.1);//K是int,V是double
  10. Func<int, char>(1, 'A'); //多个模板参数也可指定显示实例化不同类型
  11. }


 

 💦模板参数的匹配原则

  • 原则1: 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
  1. //专门处理int的加法函数
  2. int Add(int left, int right)
  3. {
  4. return left + right;
  5. }
  6. //通用加法函数
  7. template<class T>
  8. T Add(T left, T right)
  9. {
  10. return left + right;
  11. }
  12. int main()
  13. {
  14. Add(1, 2); //会调用哪个Add函数?
  15. }

首先,这俩Add可以同时存在,关键是我调用Add时调的是模板函数Add,还是专门的Add?

        通过反汇编得知,调用的是专属Add函数。得出结论:编译器在调用时,有现成的就调用现成的,没有就套用模板。当然,我们也有办法强制让编译器走模板函数,如下:

  1. void Test()
  2. {
  3. Add(1, 2); // 与非模板函数匹配,编译器不需要特化
  4. Add<int>(1, 2); // 调用编译器特化的Add版本
  5. }
  • 原则2:对于非模板函数同名函数模板如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  1. // 专门处理int的加法函数
  2. int Add(int left, int right)
  3. {
  4. return left + right;
  5. }
  6. // 通用加法函数
  7. template<class T1, class T2>
  8. T1 Add(T1 left, T2 right)
  9. {
  10. return left + right;
  11. }
  12. void Test()
  13. {
  14. Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
  15. Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
  16. }

四、类模板 

 💦类模板的概念

        类模板是对成员数据类型不同的类的抽象,它说明了类的定义规则,一个类模板可以生成多种具体的类。与函数模板的定义形式类似 类模板也是使用template关键字和尖括号“<>”中的模板形参进行说明,类的定义形式与普通类相同。

 💦类模板格式

  • 首先来看到的就是其定义格式,函数模版加在函数上,那对于类模版的话就是加在类上
  1. template<class T1, class T2, ..., class Tn>
  2. class 类模板名
  3. {
  4. // 类内成员定义
  5. };

我们以下面这个Stack类为例来进行讲解

  • 如果你学习了模版的相关知识后,一定会觉得这个类的限制性太大了只能初始化一个具有整型数据的栈,如果此时我想要放一些浮点型的数据进来的话也做不到,代码如下:
  1. // C++ 正常情况下,如果需要不同类型的类(int char double 等)
  2. typedef int DataType;
  3. class Stack
  4. {
  5. public:
  6. // 构造函数
  7. Stack(int capacity = 3) //初始化列表
  8. :_array(new DataType[capacity]) // 开辟一个DateType的动态数组,并进行初始化
  9. , _capacity(capacity)
  10. ,_size(0)
  11. {}
  12. void Push(DataType data)
  13. {
  14. // CheckCapacity();
  15. _array[_size] = data;
  16. _size++;
  17. }
  18. // 其他方法...
  19. ~Stack()
  20. {
  21. delete[]_array;
  22. _array = nullptr;
  23. _size = _capacity = 0;
  24. }
  25. private:
  26. DataType* _array;
  27. int _capacity;
  28. int _size;
  29. };
  30. int main()
  31. {
  32. Stack s1;
  33. return 0;
  34. }

💬 如果没有模版技术的话你会如何去解决这个问题呢?很简单那就是定义多个类
这是我们同学最擅长的事,CV一下两个栈就有了,StackInt存放整型数据,StackDouble存放浮点型数据

  1. class StackInt
  2. class StackDouble

但是本文我们重点要讲解的就是【模版技术】,技术界有一句话说得好 “不要重复造轮子”

  • 下面就是使用模版去定义的一个类,简称【模板类】,不限制死数据类型,将所有的DataType都改为【T】,代码如下:
  1. template<class T1>
  2. class Stack
  3. {
  4. public:
  5. // 构造函数
  6. Stack(int capacity = 4)
  7. :_a(new T1[capacity])
  8. ,_capacity(capacity)
  9. ,_size(0)
  10. {}
  11. void Push(T1 data)
  12. {
  13. _a[_size] = data;
  14. _size++;
  15. }
  16. // ...其他方法
  17. // 析构函数
  18. ~Stack()
  19. {
  20. delete[]_a;
  21. _a = nullptr;
  22. _capacity = _size = 0;
  23. }
  24. private:
  25. T1* _a;
  26. int _capacity;
  27. int _size;
  28. };
  29. int main()
  30. {
  31. Stack<int> s1;
  32. Stack<double> s2;
  33. return 0;
  34. }


 

 💦类模板的实例化

 👉 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

  • 可以看到因为我们将这个类定义为了类模版,此时便可以去初始化不同数据类型的栈了,上面说到过Stack是类名,但是像Stack<int>Stack<double>这些都是它的类型
  1. int main()
  2. {
  3. Stack<int> s1; // int
  4. Stack<double> s2; // double
  5. Stack<char> s3; // char
  6. return 0;
  7. }

 💦类模板的分离编译

        上面这样写的栈代码,其实并不是最规范的写法,还记得我们在学习C++类和对象讲到过一个类要声明和定义分离,那对于模板类也同样适用,我们马上来看看

首先进行栈 类的模板声明:

  1. #include <iostream>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. using namespace std;
  5. template<class T1>
  6. class Stack
  7. {
  8. public:
  9. // 构造函数
  10. Stack(int capacity = 4);
  11. //插入函数
  12. void Push(T1 data);
  13. // 其他方法....
  14. // 析构函数
  15. ~Stack();
  16. private:
  17. T1* _a;
  18. int _capacity;
  19. int _size;
  20. };

其次进行栈 类的模板定义:先看一下构造函数的定义

  1. // 构造函数
  2. Stack::Stack(int capacity)
  3. :_a(new T1[capacity])
  4. ,_capacity(capacity)
  5. ,_size(0)
  6. {}

  • 不过呢可以看到直接像我们之前那样去进行类外定义似乎行不通,说缺少类模版“Stack”的参数列表因为这个成员函数内部也使用到了模版参数T,那么这个函数也要变为函数模版才行
  • 这里要强调一点的是对于普通类来说类名和类型是一样的, 像构造函数,它的函数名就是类名;可是对于模板类来说是不一样,类名和类型不一样,这里Stack只是这个模版类的类名罢了,但我们现在需要的是类型,此处就想到了我们在上面所学的【显式实例化】,这个模板类的类型即为Stack<T>

所以正确的写法为:

  1. #include "Stack.h"
  2. template<class T1>
  3. // 构造函数
  4. Stack<T1>::Stack(int capacity)
  5. :_a(new T1[capacity])
  6. ,_capacity(capacity)
  7. ,_size(0)
  8. {}
  9. template<class T1>
  10. // 插入函数
  11. void Stack<T1>::Push(T1 data)
  12. {
  13. _a[_size] = data;
  14. _size++;
  15. }
  16. // 析构函数
  17. template<class T1>
  18. Stack<T1>::~Stack()
  19. {
  20. delete[]_a;
  21. _a = nullptr;
  22. _capacity = _size = 0;
  23. }

⚠ 注意:类模板,是不支持,声明,定义,测试分开写的,会出现链接编译错误,如下:



解决方法:将测试和定义写在一起

这样就不会报错啦!

五、总结

  •  首先呢我们介绍了什么是【函数模版】,新学习了一个关键字叫做template,用它再配合模版参数就可以去定义出一个函数模版,有了它,我们在写一些相同类型函数的时候就无需去进行重复的CV操作了,在通过汇编观察函数模版的原理后,清楚了我们只需要传入不同的类型,此时模版参数就会去进行一个自动类型推导,从而产生不同的函数。函数模版定义好后还要对其实例化才能继续使用,但此时要注意的一点是如果传递进去的类型个数与模版参数的个数不匹配的话,其就无法完成自动类型推导,因为这会产生一个歧义。所以想要真正学好模版,这点是一定要搞清楚的!!!
  • 接下去呢我们又学习了【类模版】,没想到吧,类也可以变成一个模版,以Stack类为例,对于类模版而言,其类名和类型与普通类是不一样的,这点要注意了,尤其体现在类的成员函数放在类外进行定义的时候,也要将其定义为函数模版,函数名前面指明其类型,这才不会出问题。有了类模版之后,我们去显式实例化不同的数据类型后也可以让模版参数去做一个自动类型推导从而得到不同数据类型的栈

六、共勉 

     以下就是我对C++ 模板的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对C++STL库的理解,请持续关注我哦!!!   

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览 60422 人正在系统学习中

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

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

相关文章

揭秘链动3+1商业模式:打造未来商业新风潮

大家好&#xff0c;我是微三云周丽&#xff0c;今天给大家分析当下市场比较火爆的商业模式&#xff01; 小编今天跟大伙们分享什么是链动31模式&#xff1f; 在当今商业世界中&#xff0c;随着科技的飞速发展和消费者需求的不断升级&#xff0c;新的商业模式不断涌现。其中&a…

如何搭建一个vue2组件库(king-ui-pro)

文章引用图片无法查看&#xff0c;直接查看原文 感兴趣的可以关注或订阅下这个系列&#xff0c;后续会陆续将相关的组件与公共方法进行分享 目前已经完成了的组件有 多行省略pro版&#xff0c;不是简单的多行省略效果 公共方法&#xff1a; 1、图片预览&#xff0c;知乎的图…

leetcode(hot100)——贪心算法

55. 跳跃游戏 本题不用纠结于可以跳几步&#xff0c;可以聚焦于覆盖范围&#xff0c;即 当前位置当前跳数 能够覆盖的范围&#xff0c;若这个范围足以到达最后一个位置&#xff0c;则返回true&#xff1b;若for循环结束&#xff0c;则还没返回true&#xff0c;则返回false。 下…

苹果手机远程打卡教程

关于苹果手机远程打卡教程之——有电脑零成本版 &#x1f4ce;个人主页&#xff1a;我的主页 &#x1f4ce;小白一枚&#xff0c;欢迎指教&#x1f44f; 嗨嗨嗨&#xff0c;今天来出一期iPhone实现远程打卡的教程&#xff0c;让我们可以随时随地的打卡wherever&#xff5e; 准…

A4云打印仅需5分/页?云打印多少钱?云打印怎么收费?

随着互联网技术的发展&#xff0c;越来越多的同学在打印资料的时候都开始转向线上&#xff0c;转向云打印服务了。云打印服务不仅可以给我们节省很多时间&#xff0c;价格方面较打印店也更有优势。那么云打印多少钱&#xff1f;云打印怎么收费&#xff1f;今天小易就带大家来了…

了解IPS和IDS:这5个差异将改变你的安全观念!

IPS 代表 入侵防御系统&#xff08;Intrusion Prevention System&#xff09;&#xff0c;它是 IDS 的进一步发展&#xff0c;不仅具备检测攻击的能力&#xff0c;还能在检测到攻击后主动采取措施阻止攻击。IPS 通常部署在防火墙和网络设备之间&#xff0c;能够深度感知并检测流…

《自动机理论、语言和计算导论》阅读笔记:p225-p260

《自动机理论、语言和计算导论》学习第 9 天&#xff0c;p225-p260总结&#xff0c;总计 26 页。 一、技术总结 1.pushdown automation(PDA&#xff0c;下推自动机) 2.DPDA Deterministic PDA(确定性下推自动机)。 二、英语总结 1.instantaneous (1)instant: adj. happi…

基于python实现web漏洞挖掘技术的研究(django)

基于python实现web漏洞挖掘技术的研究(django) 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;网络爬虫&#xff0c;SQL注入&#xff0c;XSS漏洞工具&#xff1a;pycharm、Navicat、Maven 系统的实现与漏洞挖掘 系统的首页面 此次的系统首页面是登录的页…

2024年内外贸一体化融合发展(长沙)交易会

2024年内外贸一体化融合发展&#xff08;长沙&#xff09;交易会 一、总体思路 充分发挥湖南作为全国内外贸一体化试点地区作用&#xff0c;坚持“政府主导、市场驱动、企业为主”的原则&#xff0c;以“助力双循环&#xff0c;拓展新市场&#xff0c;促进新消费”为主题&…

线程池 ThreadPoolExecutor 参数详解

一、引言 提到 Java 线程池&#xff0c;就不得不说 ThreadPoolExecutor&#xff0c;它是 Java 并发包 java.util.concurrent 中的一个类&#xff0c;提供一个高效、稳定、灵活的线程池实现&#xff0c;用于实现多线程并发执行任务&#xff0c;提高应用程序的执行效率。 在《任…

4款百里挑一的国产软件,功能强大且免费,埋没了让人心酸

闲话少说&#xff0c;直上狠货&#xff01; 1、Billfish 这款素材管理工具——Billfish&#xff0c;深受用户好评&#xff0c;个人版完全免费使用。无论是文档、图片、音频&#xff0c;还是复杂的工程文件AEP&#xff0c;它都能轻松实现本地与云端的同步管理。快速预览功能让…

Godot3D学习笔记1——界面布局简介

创建完成项目之后可以看到如下界面&#xff1a; Godot引擎也是场景式编程&#xff0c;这里的一个场景相当于一个关卡。 这里我们点击左侧“3D场景”按钮创建一个3D场景&#xff0c;现在在中间的画面中会出现一个球。在左侧节点视图中选中“Node3D”&#xff0c;右键创建子节点…