C++模板详解(函数模板、类模板)

news/2024/12/5 5:03:59/

hello,这里是bangbang,今天来讲下模板

目录

1. 泛型编程

2. 函数模板

  2.1 函数模板概念

  2.2 函数模板格式

  2.3 函数模板的实例化

  2.4 模板参数的匹配原则

3. 类模板

  3.1 类模板定义格式

  3.2 类模板实例化

4. 非类型模板参数

5. 模板特化

  5.1 模板特化概念

  5.2 函数模板特化

  5.3 类模板特化

6. 模板分离编译

  6.1 什么是分离编译

  6.2 模板的分离编译

7. 总结


1. 泛型编程

如何实现一个通用的交换函数?

有的小伙伴或许会想到用函数重载,如下:

void Swap( int& left,  int& right)
{int temp = left;left = right;right = temp;
}
void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}

但是这就出现问题了,只要我类型不符合函数参数,那我就要再重载一份,这也太麻烦了!

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

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢? 

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

2. 函数模板

  2.1 函数模板概念

        函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
        函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

  2.2 函数模板格式

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

交换函数的函数模板(2项参数类型相同):

template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

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

  2.3 函数模板的实例化

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

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

函数模板(假设1个模板参数),如果出现类型不匹配,会发生什么呢?

我们都知道C++内置类型进行运算,会向上算术转换,比如int+double=double,那么函数模板在这块是如何处理的呢,也可以直接将结构转换成double?我们来看:

此时有两种处理方式:1.用户自己强制转换。2.使用显示实例化。

用户自己强制转换:

Add(a1,(int)d1);

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

Add<int>(a1,d1);//显示实例化

  2.4 模板参数的匹配原则

  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
在VS下,打断点按F11进行查看,发现只有Add<int>()进入函数模板
  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

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

3. 类模板

  3.1 类模板定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};

 注意:类模板中函数放在类外进行定义时,需要加模板参数列表

//类模板
template<class T>
class Vector
{}//类外定义
template<class T>
Vector<T>::函数定义

  3.2 类模板实例化

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

4. 非类型模板参数

模板参数分类: 类型形参 非类型形参
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
namespace lh
{// 定义一个模板类型的静态数组template<class T, size_t N = 10>class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。

在这里补充一下array,我们想想平时写代码,数组用的是C++的array,还是C的数组形式。为什么C++要整出一个array类呢?

 既然大佬整了出来,那肯定是和C有一定区别的!不知道小伙伴平时有没有发现C语言的数组形式对越界检查是非常不严格的!

 

 优势:

 C语言中越界检查采取抽查方式指针解引用),越界读不报错,越界写有限报错。

 C++11,array只要越界一定能查到(函数调用)。


5. 模板特化

  5.1 模板特化概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}
可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即: 在原模板类的基础上,针对特殊类型所进行特殊化的实现方式 。模板特 化中分为 函数模板特化 类模板特化

  5.2 函数模板特化

函数模板的特化步骤:
  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
void date_test()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
}
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是 将该函数直接给出
bool Less(Date* left, Date* right)
{return *left < *right;
}
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此 函数模板不建议特化

  5.3 类模板特化

全特化:将模板参数列表中所有的参数都确定化。
偏特化:部分特化(针对指针类型/针对引用类型/指针、引用复合类型)

typename还有告诉编译器这是类型还是变量的作用!

因为静态类型变量指定类域也可以访问,所以在使用类型时,我们前面加typename进行区分。(不加编译器区分不清楚是类型还是变量)


6. 模板分离编译

  6.1 什么是分离编译

        一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
        模板不支持分离编译!

  6.2 模板的分离编译

 链接问题:都是有声明找不到定义

7. 总结

优点:

        1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。(重复的工作交给编译器做)

        2.增强了代码的灵活性。

缺点:

        1.模板会导致代码膨胀问题,也会导致编译时间变长。

        2.出现模板编译错误时,错误信息非常凌乱,不易定位错误。



http://www.ppmy.cn/news/66569.html

相关文章

C#套接字通信之UDP组播

文章目录 组播的概念简单示例组播的优势 组播的概念 UDP信息传递的方式分三类&#xff0c;分别是 单播 Unicast&#xff1a;客户端与服务器之间的点到点连接多播MultiCast&#xff1a;又称组播&#xff0c;是“一对一组”的通讯模式&#xff0c;加入同一个组的主机可以接受到…

kubespray部署k8s 1.26集群安装指南

Kubespray 是一个自由开源的工具&#xff0c;它提供了 Ansible 剧本(playbook) 来部署和管理 Kubernetes 集群。它旨在简化跨多个节点的 Kubernetes 集群的安装过程&#xff0c;允许用户快速轻松地部署和管理生产就绪的 Kubernetes 集群。 它支持一系列操作系统&#xff0c;包…

力扣 139. 单词拆分

一、题目描述 给你一个字符串 s 和一个字符串列表 word_dict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&#xff1a; 输入: s "leetcode"…

Appium环境搭建及元素定位

Appium简介 Appium是一个开源测试自动化框架&#xff0c;可用于原生&#xff0c;混合和移动Web应用程序测试。它使用WebDriver 协议驱动iOS&#xff0c;Android和Windows应用程序。 01 环境搭建步骤 Appium环境安装&#xff1a; 第一步 安装 appium 桌面版客户端 Appium-1…

【动态代理】JDK动态代理与cglib动态代理源码解析

JDK动态代理 demo展示 UserService&#xff0c;接口类 public interface UserService {void addUser(); }UserServiceImpl&#xff0c;实现类 public class UserServiceImpl implements UserService {Overridepublic void addUser() {System.out.println("register al…

Liunx telnet 命令详解

文章目录 telnet补充说明语法选项参数实例 telnet 登录远程主机和管理(测试ip端口是否连通) 补充说明 telnet命令 用于登录远程主机&#xff0c;对远程主机进行管理。telnet因为采用明文传送报文&#xff0c;安全性不好&#xff0c;很多Linux服务器都不开放telnet服务&#…

【Java多线程编程】线程的六种状态

前言&#xff1a; 在我们进行多线程编程&#xff0c;脑海里会想到线程运行的状态到底是什么&#xff1f;因此我整理出这线程的状态这篇博文。线程的状态分为六种&#xff1a;新建状态&#xff08;NEW&#xff09;、就绪状态&#xff08;RUNNABLE&#xff09;、阻塞状态&#xf…

黑马头条(学习笔记)

​ 目录 一. 项目概述 二、项目初始化 移动端 REM 适配&#xff1a; 关于 PostCSS 配置文件&#xff1a; Autoprefixer 插件的配置 &#xff1a; postcss-pxtorem 插件的配置&#xff1a; 关于字体图标: 配置路由&#xff1a; 封装请求模块: 三&#xff1a;登录注册&…