【C++学习】模板进阶——非类型模板参数 | 模板的特化 | 分离编译
🐱作者:一只大喵咪1201
🐱专栏:《C++学习》
🔥格言:你只管努力,剩下的交给时间!
模板我们之前一直都在使用,尤其是在模拟STL容器的时候,可以说,模板给类增加了更多的可能性,是C++最重要的部分之一。下面本喵来更深入的讲解一下模板。
模板进阶
- 📕非类型模板参数
- 📗std::array
- 📕模板的特化
- 📗函数模板的特化
- 📗类模板的特化
- 全特化
- 偏特化
- 类模板特化应用示例
- 📕分离编译
- 📕总结
📕非类型模板参数
#define N 10
namespace wxf
{template<class T>class array{public:T& operator[](size_t index){assert(index < _size);return _array[index];}const T& operator[](size_t index) const{assert(index < _size);return _array[index];}private:T _array[N];size_t _size;};
}
在上面代码中,创建了一个数组的模板类,它相比于C语言中的数组,可以自动进行越界检查。
wxf::array<int> a1;
wxf::array<double> a2;
我们可以创建不同类型的对象,数组中的元素可以是int,也可以是double类型。但是,它们的大小都是一样的,都被define定义的N限制了。
如果此时我想让int类型的数组大小是10个元素,而double类型的数组大小是100个元素,该怎么办?
此时就用到了非类型模板参数,我们之前使用的模板,它的参数都是类型模板参数,也就是它的参数都是类型,可以是内嵌类型,也可以是自定义类型。
- 非类型模板参数:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
在模板中,加入第二个参数,不使用class或者typename关键字来修饰,表面它是一个非类型模板参数。并且将这个参数应用在定义数组的大小上,如上图所示。
- 模板参数可以使用缺省值。
wxf::array<int, 10> a3;
wxf::array<double, 100> a4;
此时再创建类对象,就可以在实例化类型的同时,指定数组的大小。指定数组大小的这个模板参数就是非类型模板参数。
- 非类型模板参数必须是整形家族的常量,比如char类型,int类型等等,不能是double以及float类型。
如上图所示,将非类型模板参数的类型改成float以后就会报错,现在不支持这样,以后不一定。
- 在实例化时,传模板参数必须传常量,不能传变量。
如上图所示,当实例化传给非类型模板参数的值是一个变量的时候,就会报错。
📗std::array
在C++11的STL中,官方同样给我们提供了array容器。
它就使用了非类型模板参数,下面看看它有什么接口。
这些接口和我们之前学习string,vector,list以及stack和queue中的接口非常相似,但是,array中没有push_back。
- STL中的array是一个静态的,它的大小在创建时候就指定了,并不会发生扩容。
- 它对标的是C语言中的数组,只是比C语言中数组多了越界检查。
C语言数组越界:
int a1[10];
cout << a1[11] << endl;
越界访问并没有报错。
int a1[10];
a1[11] = 5;
越界写也没有报错。
无论是写还是读,越界的时候都不会报错。不同的编译器对越界的处理不同,有的是采取抽查的策略。总得的来说,C语言的数组在越界检查上面存在缺陷。
std::array越界:
std::array<int, 10> a1;
cout << a1[11] << endl;
a1[11] = 10;
使用STL中的array容器就会进行越界检查。
📕模板的特化
📗函数模板的特化
namespace wxf
{template<class T>bool less(T left, T right){return left < right;}
}
定义一个函数模板,进行比较大小,当左值小于右值时返回真。
比较结果符合我们的预期。
当传递的参数是两个变量的地址时,比较的结果就和刚刚的截然相反。
- 模板函数推断出来的类型是int*,按照函数模板的实现逻辑,比较的是两个地址的大小。
- 但是我们希望的是,即使传入的是地址,但是比较的仍然是两个数的大小。
此时我们就可以使用模板特化。
- 模板参数给空。
- 函数名后<>内放入要特化的类型,函数形参同样使用特化的类型。
- 改变特化后函数内部的逻辑。
可以看到此时就符合我们的预期了,即使传入的是地址,但是比较的仍然是两个数的大小。
- 模板特化必须在原模版的基础上进行特化。
- 其他类型仍然会实例化原模版,需要特别处理的类型就会走特化后的模板。
但是:
一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
就像上面的比较函数,在处理指针类型的变量时,完全可以写一个具体的函数而不用函数模板。
bool less(int* left, int* right)
{return *left < *right;
}
该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
📗类模板的特化
全特化
template<class T1,class T2>
class Data
{
public:Data(){cout << "Data<T1, T2>" << endl;}
private:T1 _a1;T2 _a2;
};
定义个这样的数据类,类型模板参数有两个。
此时无论是用什么类型进行实例化,编译器都会按照上面的类模板去推演,去实例化。
- 如果此时我想让int和char类型对象进行特殊化处理,不按照原理的类进行创建呢?
此时就可以将原本的类模板进行特化处理,对类型模板参数是int和char类型进行特殊化处理。
可以看到,用的类模板都不一样了,因为构造函数中打印的内容不一样。
全特化即是将模板参数列表中所有的参数都确定化。
偏特化
还是使用最初的Data数据类模板进行特化。偏特化有有两种:
- 部分特化
当两个模板参数中的第二个是double类型的时候,进行特殊处理。
无论第一个参数是什么类型,只要第二个是double类型的,就会按照这个部分特化后的模板进行实例化。
- 参数更进一步的限制
无论是什么类型,只有是指针类型,就进行特化处理。
此时特化后的模板就限制在了只处理指针类型。
还可以对引用类型进行特化。但是引用类型必须进行初始化。
此时特化后的模板就只限制在了引用类型。
从模板特化中也可以看出来编译器在实例化时推演的规则:
- 有现成的就用现成的,不进行推演。
- 有全特化的就用全特化的。
- 有偏特化的就用偏特化的。
- 实在没办法才全部进行推演。
总得来说,编译器也是懒狗,能少干事就少干,能不推演就不推演,能少推演就少推演。
类模板特化应用示例
template<class T>
struct less
{bool operator()(const T& x, const T& y) const{return x < y;}
};
写一个两个数相比较的仿函数,左数小于右数的时候返回真。
将vector中的数据按照升序进行排列。
- 使用的是算符库中的sort函数,需要使用到我们定义的仿函数。
同样,如果vector中放入的不是数据,而是数据的地址,但是我们又想按照升序排列。
可以看到,此时排序后的结果和排序前是一样的。
- 数组中的地址是连续的,而且后面元素的地址比前面元素的地址大。
- 此时使用仿函数less还是按照之前逻辑,比较的是地址值,而不是地址指向数据的值。
所以我们需要将less仿函数进行特化处理:
//特化处理
template<class T>
struct less<T*>
{bool operator()(T* const& x, T* const& y){return *x < *y;}
};
此时传的是地址,但是仍然按照升序重新排列好了。
说明:
看红色框中的参数类型,来复习一下const的用法。
- 通常情况下const T& x,目的是防止x被修改。
- 此时是T*类型的变量,仍然需要防止变量被修改。
- T* const& x,也是防止x被修改。
- const T*& x,防止的是指针x指向的内容被修改。
📕分离编译
通常我们会将定义的类,函数声明等等放在一个头文件中,具体的实现放在源文件中,采用声明和定义分离的方式。
同样,模板类也可以这样。以vector举例。
头文件中代码:
#include <iostream>namespace wxf
{template<class T>class vector{public:vector(int capacity = 10);int size();private:T* _arr;int _size;};
}
源文件中代码:
#include "myvector.h"namespace wxf
{template<class T>vector<T>::vector(int capacity):_arr(nullptr),_size(capacity){std::cout << "成功创建" << std::endl;}template<class T>int vector<T>::size(){return _size;}
}
此时对于我们实现的这个vector模板类,就是采用的声明和定义分离的方式。
但是在使用的时候,报的是链接错误。
原因分析
首先回忆一下编译链接的过程,它有如下几步:
- 预处理
myvector.h myvector.cpp test.cpp
- 将头文件在两个源文件中展开
- 去注释
- 宏替换
- 条件编译
- 编译
myvector.i test.i
- 两个.i文件分别进行编译,互相独立。
- 进行词法分析,语法分析,生成中间代码,优化(生成汇编语言)。
- 形成符号表,错误表等。
- 汇编
myvector.s test.s
- 两个.s文件分别进行汇编,相互独立。
- 生成二进制机器码。
- 链接
myvector.o test.o
- 多个.o文件进行链接。
- 合并符号表。
- 生成可执行文件。
两个源文件进行编译链接的过程是独立的。
对于myvctor.cpp:
- 在编译的时候会形成符号表。
- 由于此时只有模板类的声明和定义,并没有实例化,所以不能形参一个完整的类。
- 所以在最后生成的.o中,没有生成符号表。
对于test.cpp:
- 同样在编译的时候会形成符号表。
- 由于此时只有模板类的实例化和声明,但是没有定义。
- 所以生成暂时的符号表,等链接时候去寻找定义。
链接过程:
- 当test.o根据自己的符号表去myvector.o的符号表中寻找相同符号的具体定义时。
- 发现myvector.o中没有生成符号表,所以无法找到定义,也就是无法完成链接。
- 所以报链接错误。
解决方案一:
我们知道在myvector.cpp中确实模板类的实例化,所以给它加上实例化:
此时创建成功。
解决方案二:
我们知道,在test.cpp中确实模板类的定义,所以我们给它加上定义:
同样创建成功。
虽然两种方案都可以解决,但是第一种使用的非常别扭,所以我们常常使用第二种方案的改进版本。
- 在预处理的时候,myvector.h被复制展开到了test.cpp中,所以有了模板类的声明。
- 在test.cpp中增加模板类的定义后,相比于最开始的test.cpp,既有了声明又有了定义。
- 所以我们将声明定义都放在myvector.h中,在预处理后,test.cpp中自然就又有了声明,也有了定义。
- 若将模板类的定义放在实例化之前,那么就可以不要模板类的声明。
- 因为编译器是从下往上找,所以从实例化处向上寻找之间可以找到定义,声明和没有必要了。
通常将同时有模板类的声明和定义的文件,取后缀为.hpp。
此时同样创建成功,而且和我们之前写类模板的方式一样,用起来和看起来都很舒服。
强烈不建议使用声明和定义分离的方式,要使用.hpp的方式。
📕总结
优点:
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
- 增强了代码的灵活性。
缺点:
- 模板会导致代码膨胀问题,也会导致编译时间变长。
- 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
以上便是模板进阶的全部内容,未涉及到的细节,在后面使用到的时候再详细介绍。
相关文章:

【C++学习】模板进阶——非类型模板参数 | 模板的特化 | 分离编译
🐱作者:一只大喵咪1201 🐱专栏:《C学习》 🔥格言:你只管努力,剩下的交给时间! 模板我们之前一直都在使用,尤其是在模拟STL容器的时候,可以说,模板…...

【C++】C++11新特性——可变参数模板|function|bind
文章目录一、可变参数模板1.1 可变参数的函数模板1.2 递归函数方式展开参数包1.3 逗号表达式展开参数包1.4 empalce相关接口函数二、包装器function2.1 function用法2.2 例题:逆波兰表达式求值2.3 验证三、绑定函数bind3.1 调整参数顺序3.2 固定绑定参数一、可变参数…...

ssm框架之spring:浅聊事务--JdbcTemplate
简介 JdbcTemplate 是 Spring 对 JDBC 的封装,目的是使JDBC更加易于使用,JdbcTemplate是Spring的一部分。JdbcTemplate 处理了资源的建立和释放,它帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流&…...

盘点Python那些简单实用的第三方库
文章目录前言关于本文使用 pip 命令下载第三方库1、phone 库(获取手机号码信息)2、geoip2 库(IP 检测功能)3、freegames 库(免费小游戏)4、jionlp 库(解析地址信息)5、pyqrcode 库&a…...

leetCode热题21-26 解题代码,调试代码和思路
前言 本文属于特定的六道题目题解和调试代码。 1 ✔ [160]相交链表 Easy 2023-03-17 171 2 ✔ [54]螺旋矩阵 Medium 2023-03-17 169 3 ✔ [23]合并K个排序链表 Hard 2022-12-08 158 4 ✔ [92]反转链表 II Medium 2023-03-01 155 5 ✔ [415]字符串相加 Easy 2023-03-14 150 6 …...

ChatGPT推出第四代GPT-4!不仅能聊天,还可以图片创作!
3月15日凌晨,OpenAI震撼发布了多模态预训练大模型 GPT-4。 根据官网发布的通告可以知道,GPT-4 实现了以下几个方面的飞跃式提升:强大的AI创作识图能力;文字输入限制提升至 2.5 万字;回答准确性显著提高;能够…...

二叉搜索树:AVL平衡
文章目录一、 二叉搜索树1.1 概念1.2 操作1.3 代码实现二、二叉搜索树的应用K模型和KV模型三、二叉搜索树的性能分析四、AVL树4.1 AVL树的概念4.2 AVL树的实现原理4.3 旋转4.4 AVL树最终代码一、 二叉搜索树 1.1 概念 二叉搜索树( Binary Search Tree,…...

数据结构和算法(1):数组
目录概述动态数组二维数组局部性原理越界检查概述 定义 在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a col…...

python+django+vue全家桶鲜花商城售卖系统
重点: (1) 网上花店网站中各模块功能之间的的串联。 (2) 网上花店网站前台与后台的连接与同步。 (3) 鲜花信息管理模块中鲜花的发布、更新与删除。 (4) 订单…...

一文带你领略 WPA3-SAE 的 “安全感”
引入 WPA3-SAE也是针对四次握手的协议。 四次握手是 AP (authenticator) 和 (supplicant)进行四次信息交互,生成一个用于加密无线数据的秘钥。 这个过程发生在 WIFI 连接 的 过程。 为了更好的阐述 WPA3-SAE 的作用 …...
Python解题 - CSDN周赛第38期
又来拯救公主了。。。本期四道题还是都考过,而且后面两道问哥在以前写的题解里给出了详细的代码(当然是python版),直接复制粘贴就可以过了——尽管这样显得有失公允,考虑到以后还会出现重复的考题,所以现在…...

Android绘制——自定义view之onLayout
简介 在自定义view的时候,其实很简单,只需要知道3步骤: 测量——onMeasure():决定View的大小,关于此请阅读《Android自定义控件之onMeasure》布局——onLayout():决定View在ViewGroup中的位置绘制——onD…...

用Qt画一个温度计
示例1 以下是用Qt绘制一个简单的温度计的示例代码: #include <QPainter> #include <QWidget> #include <QApplication> class Thermometer : public QWidget { public:Thermometer(QWidget *parent 0); protected:void paintEvent(QPaintEvent …...

Java设计模式 04-建造者模式
建造者模式 一、 盖房项目需求 1)需要建房子:这一过程为打桩、砌墙、封顶 2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的. 3)请编写程序,完成需求. …...

安语未公告于2023年3月20日发布
因一些特殊原因,凡事都是有开始,高潮和结束三大过程,做出以下决定: 所有对 安语未文章 为之热爱、鞭策、奉献,和支持过的开发者: 注:所有资源以及资料都会正常下载和查看 如需联系࿱…...

进销存是什么?如何选择进销存系统?
什么是进销存?进销存软件概念起源于上世纪80年代,由于电算化的普及,计算机管理的推广,不少企业对于仓库货品的进货,存货,出货管理,有了强烈的需求,进销存软件的发展从此开始。 进入…...

基于BP神经网络的图像跟踪,基于BP神经网络的细胞追踪识别
目录 摘要 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络激活函数及公式 基于BP神经网络的细胞识别追踪 matab编程代码 效果 结果分析 展望 摘要 智能驾驶,智能出行是现代社会发展的趋势之一,其中,客量预测对智能出行至关重要,…...

Java面试总结篇
引用介绍 1.线程安全不安全的概念 线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏…...

100天精通Python(可视化篇)——第80天:matplotlib绘制不同种类炫酷柱状图代码实战(簇状、堆积、横向、百分比、3D柱状图)
文章目录0. 专栏导读1. 普通柱状图2. 簇状柱形图3. 堆积柱形图4. 横向柱状图5. 横向双向柱状图6. 百分比堆积柱形图7. 3D柱形图8. 3D堆积柱形图9. 3D百分比堆积柱形图0. 专栏导读 🏆🏆作者介绍:Python领域优质创作者、CSDN/华为云/阿里云/掘金…...

【Java】UDP网络编程
文章目录前言DatagramSocketDatagramPacket注意事项与区别代码演示前言 UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。 UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对…...

Springboot源代码总结
前言 编写微服务,巩固知识 文章目录 前言springboot原理springboot启动流程SpringBoot自动配置底层源码解析自动配置到底配了什么?自动配置类条件注解Starter机制@ConditionalOnMissingBeanSpringBoot启动过程源码解析构造SpringApplication对象SpringBoot完整的配置优先级s…...

JVM监控搭建
文章目录JVM监控搭建整体架构JolokiaTelegrafInfluxdbGrafanaJVM监控搭建 整体架构 JVM 的各种内存信息,会通过 JMX 接口进行暴露。 Jolokia 组件负责把 JMX 信息翻译成容易读取的 HTTP 请求。Telegraf 组件作为一个通用的监控 agent,和 JVM 进程部署在…...

java中如何优化大量的if...else...
目录 策略模式(Strategy Pattern) 工厂模式(Factory Pattern) 映射表(Map) 数据驱动设计(Data-Driven Design) 策略模式(Strategy Pattern) 将每个条件分…...

24. linux系统基础
两个进程间想通讯,必须要通过内核,今天讲的信号其实本质也是讲进程间通讯的意思,那么你为什么可以在shell环境下,可以和一个进程发kill-9啊? shell是不是相当于一个进程?你自己运行的那个进程是不是也相当于…...

【C++】面试101,二叉搜索树的最近公共祖先,在二叉树中找到两个节点的最近公共祖先,序列化二叉树,重建二叉树,输出二叉树的右视图,组队竞赛,删除公共字符
目录 1.二叉搜索树的最近公共祖先 2.在二叉树中找到两个节点的最近公共祖先 3.序列化二叉树 4.重建二叉树 5.输出二叉树的右视图 6.组队竞赛 7.删除公共字符 1.二叉搜索树的最近公共祖先 这是一个简单的问题,因为是二叉搜索树(有序)&am…...

Java常见面试题及解答
Java常见面试题及解答1 面向对象的三个特征2 this,super关键字3 基础数据类型4 public、protected、default、private5 接口6 抽象类6.1 抽象类和接口的区别7 重载(overload)、重写(override)8 final、finalize、final…...

【Docker】镜像的原理定制化镜像
文章目录镜像是什么UnionFS(联合文件系统)Docker镜像加载原理制作本地镜像 docker commit -m"提交的描述信息" -a"作者" 容器ID 要创建的目标镜像名:[标签名]案例演示ubuntu安装vim本地镜像发布到阿里云本地镜像发布到阿里云流程将本…...

国内版的ChatGPT弯道超车的机会在哪里?
前言 从去年11月最后一天ChatGPT诞生,截至目前,ChatGPT的热度可谓是爆了。众所周知,ChatGPT是美国“开放人工智能研究中心”研发的聊天机器人程序,它是一个人工智能技术驱动的自然语言处理工具,它能够通过学习和理解人…...

【字符串】
string1.char str[]类型fgets(s,10000,stdin) cin.getline(cin,10000) strlen(str)sizeof 求静态数组长度2.string类型getline(cin,a) cin.getline(cin,10000) str.lenth()str.size()cin 遇到空格就停止3.gets 函数char str[20];gets(str);4.puts 函数puts(str) 相当于 cout<…...

加载驱动之后无法在/dev/下生成vedio0
前言 环境介绍: 1.编译环境 Ubuntu 18.04.5 LTS 2.SDK orangepi Linux 5.4 SDK 3.uboot v2020.04 4.gcc gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 5.单板 orangepi pc plus 一、问题 继上一篇成功加载gc2035.ko文件之后,理论上…...