【C++进阶】仿函数 模板进阶

目录

前言

 1. 仿函数

  1.1 什么是仿函数

 1.2 仿函数的应用

 2. 模板

2.1 非类型模板参数

 非类型模板参数的应用

 2.2 模板特化

 概念

函数模板的特化

 类模板特化

  全特化

 偏特化

 3. 模板分离编译问题

 解决办法

4. 模板总结

 总结


前言

          我们已经基本学习完了C++的一些基础特性,后续也会继续深入学习C++,对于后续的内容,仿函数和模板是非常重要的基础,所以本文我将会向大家更深层面的介绍C++模板以及仿函数的应用;

在这里插入图片描述

 1. 仿函数

         前文我们提到在实现priority_queue时与STL库存在差异,想要实现priority_queue的完整封装需要对模板有更深入的了解,以及仿函数的应用;

  1.1 什么是仿函数

         仿函数(Functor)是一种行为类似函数的对象,可以像函数一样被调用。在C++中,仿函数是一个类或者结构体,重载了函数调用运算符operator(),使得对象可以像函数一样被调用;

        它本质上就是一个只重载 “ ( ) ” 的类,它的出现是为了代替C语言中的函数指针;C语言中的回调函数在使用时用函数指针非常难受,函数指针的使用导致代码可读性较差,并且使用函数指针有一些局限性,比如只能传递静态函数或全局函数,无法传递非静态成员函数等;

 1.2 仿函数的应用

 在priority_queue中就使用了仿函数如下图:

 

 通过传递模板参数来控制大堆小堆;

 举个简单的例子:

// 可以加模板,加模板后它可以比较int类型,也可以比较double类型
class Greater
{
public:bool operator()(const int& x, const int& y){return x > y;}
};int main()
{Greater com;//com是一个对象// 它可以像函数一样去使用cout << com(10, 100) << endl; // false->0return 0;
}

它就是一个仿函数;operator()的具体内我们可以根据自己的需求实现(比如比较两指针指向内容的大小);它也常用于STL的算法函数中;

vector<int> v = { 1,9,3,6,7,5,8,2 };Greater com;//默认情况了下是升序std::sort(v.begin(), v.end(), com);//降序//也可以传匿名对象

我们可以利用仿函数的特性,做到通过传模板参数达到priority_queue大堆小堆的调整:

//增加一个模板参数
template<class T, class container = vector<T>, class Compare = Less<T>>
class priority_queue
{
public:private:container _con;
};

 将向上调整向下调整中比较大小的地方修改即可

Compare com;//实例化一个对象
// 把_con[parent] < _con[child]修改为使用仿函数
com(_con[parent] , _con[child])

这里就不再展示完整的代码,完整封装后的代码我已上传我的代码仓库,详细可见:priority_queue模拟封装

 注意:

         函数传参和模板传参这里很容易混淆,sort是一个函数,调用时传的对象是形参:

std::sort(v.begin(), v.end(), com);

priority_queue 建小堆时传的是模板参数

priority_queue<int, vector<int>, Greater<int>> pq;
//Greater<int>是类型

 2. 模板

         前边已经对模板的基本使用做了一些介绍,本文将会更深入的介绍模板;

2.1 非类型模板参数

模板参数可分为两类:

  • 类型形参:在模板定义中使用的类型参数,可以用任何合法的类型来实例化模板(也就是之前的基础用法,跟在class或者typename之后的参数类型)
  • 非类型形参:在模板定义中使用的非类型参数,通常是一个常量表达式(template <int N>)

比如:

template <class T, size_t N = 10>
class A {T data[N];
};

T 是一个类型形参,N 是一个非类型形参

 注意:非类型模板参数只支持整形

 非类型模板参数的应用

        非类型模板参数有什么用,比如我们需要两个固定大小的栈,一个容量设为10,一个容量设为100;我们用类模板实现的栈可以存储任何类型的数据,但是如何创建出两个不同大小的栈?只用类型参数无法解决,这时就需要用到非类型模板参数;

template<class T, size_t N>
class Stack
{private:T _a[N];
};int main()
{Stack<int, 10> st1;     Stack<double, 100> st2;  //Stack<double, n> st2; // 这样不行,必须是常量return 0;
}

C++的容器也有使用,C++11添加了新的容器叫array,它是一个定长数组;

 

 使用如下:

array<int, 10> a1;

它是C++11新出的容器,目的就是为了代替C语言的静态数组,C语言的数组对越界没有严格的检查(越界不一定会报错),array的检查比较严格,一旦越界就会报错;

不用它的主要原因是因为array不会对数组进行初始化,大家也更倾向于使用vector;

 2.2 模板特化

 概念

        模板特化是指为特定类型或值参数提供定制化的模板实现。通常情况下,模板是通用的,可以适用于多种类型或值参数。但有时候我们需要针对特定的类型或值参数提供特定的实现这时就可以使用模板特化来实现。

 举个例子:

我们实现一个Less,它可以比较任何类型,但对于指针类型却没办法正确比较;

template<class T>
bool Less(T left, T right)
{return left < right;
}

这时就可以用模板的特化,这样写:

template<>
bool Less<int*>(int* left, int* right)
{return *left < *right;
}
函数模板的特化

上述的解决办法使用的就是函数模板的特化,函数模板特化有以下要求:

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误

         但是平时我们不会怎么使用函数特化,如果出现函数模板无法解决的情况,一般情况下都会用函数重载(函数重载更为简洁明了,代码的可读性高,容易书写)不建议使用函数模板特化;

 类模板特化

类模板特化的规则和函数模板特化的规则类似:

  • 必须要先有一个基础的类模板
  • 关键字template后面接一对空的尖括号<>
  • 类名后跟一对尖括号,尖括号中指定需要特化的类型
  全特化

 全特化是将模板参数列表中所有的参数都确定化

template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};void test()
{Data<int, int> d1;Data<int, char> d2;
}

 在调用模板实例化时会调用更加相符的模板;

 偏特化

 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本,比如:

template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};

这是一个简单的类模板,偏特化有以下两种表现方式:

  • 部分特化

将模板参数类表中的一部分参数特化:

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};
  • 参数更进一步的限制

偏特化并不仅仅是指特化部分参数,它还可以对模板对模板参数做更进一步的限制:

template <typename T1, typename T2>
class Data <T1*, T2*>
//还可以特化为class Data <T1&, T2&>
{
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:T1 _d1;T2 _d2;
};

 3. 模板分离编译问题

        在使用模板封装完一个类时我们会发现,当声明和定义分离时,编译会报错;主要报错是因为链接错误,为什么会这样?

比如:

// a.h
template<class T>
T Add(const T& left, const T& right);// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

我们将一个类声明和定义分离,C/C++程序运行主要分为以下阶段:

  • 预处理
  • 编译
  • 汇编
  • 链接

         在编译阶段,编译器对多个源文件分离开单独编译,头文件不会参与编译,问题就出在这里;a.cpp中的Add是函数模板,它在编译时没有明确的数据类型,所以Add不会生成汇编代码;

 到了链接阶段,由于Add没有生成汇编代码,链接时就会找不到Add(地址),进而造成了链接错误;

 解决办法

  1.  将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h
  2.  模板定义的位置显式实例化(比较不实用,类型多的时候需要实例化很多次,不推荐)

         最简单快捷的方式就是将声明和定义写在同一个文件中,编译时,文件已经展开,向上找就可以找到函数;

4. 模板总结

优点:

 使用模板可以更好的复用代码,更快的迭代开发,C++的STL库也因此而产生,增强代码的灵活性

缺点:

使用模板后,具体的工作都交给了编译器,编译器需要推导类型然后实例化,这会导致编译的时常增加,出现错误时,报错信息也比较凌乱,不易定位错误;


 总结

        后续的容器封装会用到很多模板相关的知识,模板对于后续的学习至关重要;好了,以上便是本文的全部内容,希望可以对你有所帮助,感谢阅读!

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

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

相关文章

轻量级模型,重量级性能,TinyLlama、LiteLlama小模型火起来了,针对特定领域较小的语言模型是否与较大的模型同样有效?

轻量级模型&#xff0c;重量级性能&#xff0c;TinyLlama、LiteLlama小模型火起来了&#xff0c;针对特定领域较小的语言模型是否与较大的模型同样有效? 当大家都在研究大模型&#xff08;LLM&#xff09;参数规模达到百亿甚至千亿级别的同时&#xff0c;小巧且兼具高性能的小…

babylonjs入门

基于babylonjs封装的一些功能和插件 &#xff0c;希望有更多的小伙伴一起玩babylonjs&#xff1b; 欢迎加群&#xff1a;464146715 官方文档 中文文档 Babylonjs案例分享 ​ import React, { FC, useCallback, useEffect, useRef, useState } from react; import TemplateBB…

C++笔记之执行一个可执行文件时指定动态库所存放的文件夹lib的路径

C++笔记之执行一个可执行文件时指定动态库所存放的文件夹lib的路径 参考博文: 1.C++笔记之执行一个可执行文件时指定动态库所存放的文件夹lib的路径 2.Linux笔记之LD_LIBRARY_PATH详解 3.qt-C++笔记之使用QProcess去执行一个可执行文件时指定动态库所存放的文件夹lib的路径 c…

一款.NET下 WPF UI框架介绍

WPF开源的UI框架有很多,如HandyControl、MahApps.Metro、Xceed Extended WPF Toolkit™、Modern UI for WPF (MUI)、Layui-WPF、MaterialDesignInXamlToolkit、等等,今天小编带大家认识一款比较常用的kaiyuanUI---WPF UI,这款ui框架美观现代化,用起来也超级方便, 界面展示…

matlab生成模拟的通信信号

matlab中rand函数生成均匀随机分布的随机数&#xff0c;randn生成正态分布的随机数&#xff1b; matlab来模拟一个通信信号&#xff1b; 通信信号通过信道时&#xff0c;研究时认为它会被叠加上服从正态分布的噪声&#xff1b; 先生成随机信号模拟要传输的信号&#xff0c;s…

【深入理解设计模式】装饰者设计模式

装饰者设计模式 装饰者设计模式&#xff08;Decorator Design Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向现有对象添加新功能而不改变其结构。这种模式通常用于需要动态地为对象添加功能或行为的情况&#xff0c;而且这些功能可以独立于对象本身来进行扩展…

Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules。Android studio纯java代码报错

我使用java代码 构建项目&#xff0c;初始代码运行就会报错。我使用的是Android Studio Giraffe&#xff08;Adroid-studio-2022.3.1.18-windows&#xff09;。我在网上找的解决办法是删除重复的类&#xff0c;但这操作起来真的太麻烦了。 这是全部报错代码&#xff1a; Dupli…

解决gogs勾选“使用选定的文件和模板初始化仓库”报错500,gogs邮件发送失败,gogs邮件配置不生效,gogs自定义模板等问题

解决gogs勾选“使用选定的文件和模板初始化仓库”报错500,gogs邮件发送失败,gogs邮件配置不生效,gogs自定义模板等问题 前几天出了教程本地部署gogs&#xff0c;在后期运行时发现两个问题&#xff1a; 第一&#xff1a;邮件明明配置了&#xff0c;后台显示未配置&#xff0c;…

QT之项目经验(windows下的sqlite,c++开发)

目录 一、需要时间去磨练gui的调整和优化 1. 借鉴网上开源项目学习 2. gui的布局及调整是磨人的一件事情 3. gui的布局也是可以用组件复刻的 4. 耗时的设备树 二、多线程异步弹窗 三、定时任务动态变更设定 1.确定按钮触发 2.此处监听定时任务时间的改变 3.此处对改变做出具…

html5盒子模型

1.边框的常用属性 border-color 属性 说明 示例 border-top-color 上边框颜色 border-top-color:#369; border-right-color 右边框颜色 border-right-color:#369; border-bottom-color 下边框颜色 border-bottom-color:#fae45b; border-left-color 左边框颜色…

【GPTs分享】每日GPTs分享之Image Generator Tool

今日GPTs分享&#xff1a;Image Generator Tool。Image Generator Tool是一种基于人工智能的创意辅助工具&#xff0c;专门设计用于根据文字描述生成图像。这款工具结合了专业性与友好性&#xff0c;鼓励用户发挥创造力&#xff0c;同时提供高效且富有成效的交互体验。 主要功能…

mac flutter 配置

下载Flutter Sdk 直接访问官网无法下载&#xff0c;需要访问中国镜像下载 Flutter SDK 归档列表 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter Start building Flutter Android apps on macOS - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 下载后解压…