C++类与对象(下)
文章目录
- 1.非类型模板
- 2.模板特化
- 2.1.类模板特化
- 2.1.1.全特化
- 2.1.2.偏特化
- 2.2.函数模板特化
- 3.函数模板声明定义分离
之前我们学习的模板能达到泛型的原因是:使用了“泛型的类型”,但是如果经过后面的“造轮子”(后面会尝试实现一下
STL
的一些类模板),就会很明显发现泛型不仅仅是类型的问题,例如:“适配器”的使用(在后面双端队列里有体现),实际上就是一种泛型,对于泛型的理解我们不能仅限于类型。
1.非类型模板
模板除了类型模板,还有非类型模板。
-
类型模板:出现在模板的参数列表中,跟在
class
或者typname
后的参数类型名称 -
非类型模板:使用一个常量作为类的一个非类型模板参数,在模板类/模板函数中可以将该参数作为常量来使用,且不能修改。并且,这里非类型模板参数也可以使用缺省值
//没有非类型模板参数
#include <iostream>
using namespace std;
#define NUM 10template<class T>
class Data
{
public://...
private:T _arr[NUM];
};
int main()
{Data<int> a1;//无法修改初始化大小(注意是初始化的时候修改大小)//只能手动调整#define的值//和之前的typedef的问题类似Data<double> a2;
}
这个时候就可以使用非类型模板参数,这个参数是一个常量,更加准确来说是不可被修改的整形常量(包括布尔类型)。
//有非类型模板参数
#include <iostream>
using namespace std;
//#define NUM 10template<class T, size_t N = 50>
class Data
{
public://...
private:T _arr[N];
};
int main()
{Data<int> a1;//默认初始化申请50个空间Data<double, 20> a2;//初始化时申请20个空间
}
您可能会疑惑:为什么不可以初始化先使用new
开辟固定的空间,等到后续操作进行扩容操作呢?注意这里只是利用这个例子来简述语法特性,并不是实际的用途(在后续“位图”等知识中有很大的价值)。
补充:除了使用这个常量,还可以将这个常量作为一个类的标识数字来使用。
函数模板也可以使用这一特性。
#include <iostream>
using namespace std;template<class T, size_t N = 50>
class Data
{
public://...
public:T _arr[N];
};
template<class T, long NUM = 50>//演示了其他整形
void function(T& i)
{i = NUM;
}int main()
{Data<int, 10> a1;Data<int, 100> a2;int i = 0;function<int, 200>(i);//演示了函数修改非类型模板参数cout << i << endl;
}
C++ 11
搞的新容器:静态数组array
,其类模板就是使用了这个非类型模板参数。
#include <iostream>
#include <array>
using namespace std;
int main()
{array<int, 10>arr;for (auto &i : arr){i = 10;}for (auto i : arr){cout << i << " ";}cout << endl;return 0;
}
可惜静态数组不会进行初始化(吐槽:std::array
当参数传递仍然要把数组的长度传过去,挺好玩的…),也支持范围for
,并且越界检查比较严格(传统数组是抽查,但是静态数组是读写越界全面检查,避免代码崩溃)。
嘛…感觉优势不算很大(大不了使用vector
,这也可以查找越界,还可以使用列表初始化)所以推广并不高。这个容器有点为了强迫症而统一STL
风格的感觉。
类似deque
在list
和vector
的感觉(后面会讲),静态数组就是传统数组和vector
之间的方案。
2.模板特化
通常模板可以实现和类型无关的代码,但是有一些特殊的类型可能会得到一些错误的、不符合预期的结果,因此需要进行特殊处理,这就有了“模板特化”这个概念。
2.1.类模板特化
2.1.1.全特化
#include <iostream>
using namespace std;
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 TestVector()
{Data<int, int> d1;Data<int, char> d2;//这样就会直接调用全特化的模板,不会再去类模板构造
}
int main()
{TestVector();
}
2.1.2.偏特化
除了全特化,还可以进行偏特化。在下述代码中,我们可以看到偏特化不仅只是做了一些类型的指定,也可以对类型做进一步限制。
#include <iostream>
using namespace std;
template<class T1, class T2>
class Data
{
public:Data(const T1& d1, const T2& d2) : _d1(d1), _d2(d2){ cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//1.部分类模板参数特化
template <class T1>
class Data<T1, int>
{
public:Data(const T1& d1, const int& d2) : _d1(d1), _d2(d2){ cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};//2.1.对两个参数进行进一步限制,偏特化为指针类型
template <typename T1, typename T2>//这里也是必须写,和全特化有些不同
class Data <T1*, T2*>
{
public:Data(const T1& d1, const T2& d2) : _d1(d1), _d2(d2){ cout << "Data<T1*, T2*>" << endl; }
private:T1 _d1;//注意其成员不是指针,仍然是原类型T2 _d2;//注意其成员不是指针,仍然是原类型
};//2.2.对两个参数进行进一步限制,偏特化为引用类型
template <typename T1, typename T2>//这里也是必须写,和全特化有些不同
class Data <T1&, T2&>
{
public:Data(const T1& d1, const T2& d2) : _d1(d1), _d2(d2){cout << "Data<T1&, T2&>" << endl; }
private:T1 _d1;T2 _d2;
};void test()
{Data<int, double> d1(10, 20);//调用基础的类模板Data<int, int> d2(30, 40);//调用偏特化的类模板Data<int*, int*> d3(1, 2);//调用偏特化的指针版本Data<int&, int&> d4(3, 4);//调用偏特化的引用版本
}
int main()
{test();return 0;
}
补充:偏特化会使得特化更加强大,某些程度上来说比全特化更加常用。
因此可以总结类模板的特化语法就是:
//1.原类模板
template<class T1, class T2, /*...*/, class Tn>
class ClassName
{/*...*/};//2.特化类模板
template</*填入仍旧继续使用的泛型(如果都使用可以省略这里)*/>
class ClassName</*指定特定的类型,并且写入仍旧使用的泛型,注意顺序*/>
{/*...*/};
2.2.函数模板特化
#include <iostream>
using namespace std;
//类模板
class Data
{
public:Data(int d) : _d(d) {}bool operator<(const Data& x){ return _d < x._d; }
private:int _d;
};//函数模板
template<class T>
bool Less(T left, T right)
{return left < right;
}//特化函数模板
template<>
bool Less<Data*>(Data* left, Data* right)
{return (*left) < (*right);
}
/*
template<>
bool Less<Data*>(const Data* & left, const Data* & right)//这种写法很特殊,是没有办法通过的,原本是为了使用const修饰引用变量,避免引用变量被修改,但是由于指针和const修饰的特殊性,导致const修饰了*,因此只能改成:(Data* const& left, Data* const& right)这种写法虽然奇怪,但是却是正确的。
{return (*left) < (*right);
}
*/int main()
{cout << Less(1, 2) << endl;//调用了普通的函数模板Data d1(1);Data d2(2);cout << Less(d1, d2) << endl;//调用了普通的函数模板Data* p1 = &d1;Data* p2 = &d2;cout << Less(p1, p2) << endl;//调用特化后的函数模板,虽然这种调用看起来很奇怪return 0;
}
注意
1
:区分好“匹配”和“特化”和“实例化”。
- 匹配:是有相匹配的类型,可以使用对应的模板
- 实例化:是编译器自己做的,将匹配对应的模板进行实例化
- 特化:特化不是全新的模板,必须依赖模板,不可以单独存在
注意
2
:实际上特化更加适合类模板一些,实际上函数重载(重载)对比函数模板特化(匹配)更加简单。
3.函数模板声明定义分离
这一点凸显在函数的声明定义的分离上,假设有下面三个文件:
//function.h内声明
#pragma once
#include <iostream>
template<class T>
T Add(const T& left, const T& right);int NoTemplateAdd(const int& left, const int& right);
//function.cpp内定义
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}int NoTemplateAdd(const int& left, const int& right)
{return left + right;
}
//main.cpp内包含头文件并且调用
#include "function.h"
int main()
{std::cout << Add(1, 2);//链接错误std::cout << Add(1.0, 2.0);//链接错误std::cout << NoTemplateAdd(1, 2);//成功调用return 0;
}
可以发现函数模板没有办法声明和定义分离在两个文件中,会显示链接错误(但是普通的函数可以)。
让我们来分析一下这里面的原因:
C/C++
要运行程序,就需要经历“预处理-编译-汇编-链接”- 在编译阶段,会对多份源文件做各自的编译(进行词法、语法、语义分析、错误检查等)并且生成多份的汇编代码(注意头文件是不会参与编译的)这个时候在
function.obj
或者说function.o
中,由于编译器没有看到函数的实例化,因此没有生成具体的加法函数。 - 而在
main.obj
或者main.o
中,编译器看到有加法函数的调用,但是暂时不知道具体的实现,因此就暂时放进了符号表里等待后续链接 - 在链接阶段由于没有实例化,因此
function.obj
或者说function.o
中没有加法函数的定义,根本就无法提供加法函数的地址在符号表里供main.obj
或者main.o
链接
因此后续链接的时候就会报错,即“链接错误”。
如果一定要分离,有两种方法:
-
进行显示实例化(有缺陷)
//function.h #include <iostream> using namespace std;template <typename T> void MyFunction(T value);
//function.cpp #include "function.h"template <typename T> void MyFunction(T value) {cout << value << endl; } //显式实例化int类型的函数模板 template void MyFunction<int>(int value);
//main.cpp #include "function.h"int main() {//调用int版本的函数模板MyFunction(42);return 0; }
-
在一个翻译单元里分离,即:干脆直接将定义和声明都写在一个
.hpp
内,这样做是更加推荐的。
相关文章:
C++类与对象(下)
文章目录 1.非类型模板2.模板特化2.1.类模板特化2.1.1.全特化2.1.2.偏特化 2.2.函数模板特化 3.函数模板声明定义分离 之前我们学习的模板能达到泛型的原因是:使用了“泛型的类型”,但是如果经过后面的“造轮子”(后面会尝试实现一下 STL的一…...
SpringBoot——》引入Redis
推荐链接: 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...
C# newtonsoft序列化将long类型转化为字符串
/// <summary> /// 转化为json的时候long类型转为string /// </summary> public class LongJsonConverter: JsonConverter {public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){try{return r…...

黑马点评-02使用Redis代替session,Redis + token机制实现
Redis代替session session共享问题 每个Tomcat中都有一份属于自己的session,所以多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时可能会导致数据丢失 用户第一次访问1号tomcat并把自己的信息存放session域中, 如果第二次访问到了2号tomcat就无法获取到在1号…...

arm 点灯实验代码以及现象
.text .global _start _start: 1.设置GPIOE寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R0,0x50000A28 LDR R1,[R0] ORR R1,R1,#(0x1<<4) 第4位置1 STR R1,[R0] 1.设置GPIOF寄存器的时钟使能 RCC_MP_AHB4ENSETR[4]->1 0x50000a28 LDR R…...

选择适合普通公司的项目管理软件
不管是打工人还是学生党都适合使用Zoho Projects项目管理软件。利用项目概览功能,将整体项目尽收眼底,作为项目管理者,项目日程、进度都可见,Zoho Projects项目管理APP助推项目每一环节的进展,更便于管理者设计项目的下…...
E (1081) : DS堆栈--逆序输出(STL栈使用)
Description C中已经自带堆栈对象stack,无需编写堆栈操作的具体实现代码。 本题目主要帮助大家熟悉stack对象的使用,然后实现字符串的逆序输出 输入一个字符串,按字符按输入顺序压入堆栈,然后根据堆栈后进先出的特点࿰…...
访问者模式 行为型设计模式之九
1.定义 在不改变数据结构的前提下,增加作用于一组对象元素的新功能。 2.动机 访问者模式适用于数据结构相对稳定的系统它把数据结构和作用于数据结构之上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式的目的是要把处理从数据结构…...
JVM垃圾回收之JVM GC算法探究
JVM垃圾回收之JVM GC算法探究 在Java虚拟机(JVM)中,垃圾回收(Garbage Collection,GC)是自动管理内存的重要机制,它负责回收程序中不再使用的对象所占用的内存。GC算法是垃圾回收的核心…...

Django 前端模板显示换行符、日期格式
linebreaksbr 显示换行符 <td>{{ data.sku_list|default:"无"|linebreaksbr }}</td> date:"Y年m月d日 H:i" 设置日期格式 <td>{{ data.submit_time|date:"Y年m月d日 H:i" }}</td> 其他语法 forloop 获取循环的索引 …...

Aurora中的策略模式和模板模式
Aurora中的策略模式和模板模式 在aurora中为了方便以后的扩展使用了策略模式和模板模式实现图片上传和搜索功能,能够在配置类中设置使用Oss或者minio上传图片,es或者mysql文章搜索。后续有新的上传方式或者搜索方式只需要编写对应的实现类即可ÿ…...

Ubuntu 22.04 安装系统 手动分区 针对只有一块硬盘 lvm 单独分出/home
自动安装的信息 参考自动安装时产生的分区信息 rootyeqiang-MS-7B23:~# fdisk /dev/sdb -l Disk /dev/sdb:894.25 GiB,960197124096 字节,1875385008 个扇区 Disk model: INTEL SSDSC2KB96 单元:扇区 / 1 * 512 512 字节 扇区大…...

Android系统定制之监听USB键盘来判断是否弹出软键盘
一.项目背景 在设备上弹出软键盘,会将一大部分UI遮挡起来,造成很多图标无法看到和点击,使用起来不方便,因此通过插入usb键盘输入代替软键盘,但是点击输入框默认会弹出软键盘,因此想要插入USB键盘时,默认关闭软键盘,拔出键盘时再弹出,方便用户使用 二.设计思路 2.1…...

LeakyReLU激活函数
nn.LeakyReLU 是PyTorch中的Leaky Rectified Linear Unit(ReLU)激活函数的实现。Leaky ReLU是一种修正线性单元,它在非负数部分保持线性,而在负数部分引入一个小的斜率(通常是一个小的正数),以防…...

Qt单一应用实例判断
原本项目中使用QSharedMemory的方法来判断当前是否已存在运行的实例,但在MacOS上,当程序异常崩溃后,QSharedMemory没有被正常销毁,导致应用程序无法再次被打开。 对此,Qt assistant中有相关说明: 摘抄 qt-s…...

企业AI工程化之路:如何实现高效、低成本、高质量的落地?
MLOps工程实践 概述面临挑战目的内容简介读者对象专家推荐目录 写在末尾: 主页传送门:📀 传送 概述 作为计算机科学的一个重要领域,机器学习也是目前人工智能领域非常活跃的分支之一。机器学习通过分析海量数据、总结规律&#x…...

最短路径专题8 交通枢纽 (Floyd求最短路 )
题目: 样例: 输入 4 5 2 0 1 1 0 2 5 0 3 3 1 2 2 2 3 4 0 2 输出 0 7 思路: 由题意,绘制了该城市的地图之后,由给出的 k 个编号作为起点,求该点到各个点之间的最短距离之和最小的点是哪个,并…...

文件扫描模块
文章目录 前言文件扫描模块设计初级扫描方案一实现单线程扫描整合扫描步骤 设计初级扫描方案二周期性扫描 总结 前言 我们这个模块考虑的是数据库里面的内容从哪里获取。 获取完成后,这时候,我们就需要把目录里面文件/子文件都获取出来,并存入数据库。 文件扫描模…...

MySQL之主从复制
概述: 将主库的数据 变更同步到从库,从而保证主库和从库数据一致。 它的作用是 数据备份,失败迁移,读写分离,降低单库读写压力 原理: 主服务器上面的任何修改都会保存在二进制日志( Bin-log日志…...
[leetcode 单调栈] 901. 股票价格跨度 M
设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。 当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。 例如,如果未来 7 天股票的价格是 [100…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...

python基础语法Ⅰ
python基础语法Ⅰ 常量和表达式变量是什么变量的语法1.定义变量使用变量 变量的类型1.整数2.浮点数(小数)3.字符串4.布尔5.其他 动态类型特征注释注释是什么注释的语法1.行注释2.文档字符串 注释的规范 常量和表达式 我们可以把python当作一个计算器,来进行一些算术…...

CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)
漏洞概述 漏洞名称:Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号:CVE-2023-25194 CVSS评分:8.8 影响版本:Apache Kafka 2.3.0 - 3.3.2 修复版本:≥ 3.4.0 漏洞类型:反序列化导致的远程代…...
GB/T 43887-2024 核级柔性石墨板材检测
核级柔性石墨板材是指以可膨胀石墨为原料、未经改性和增强、用于核工业的核级柔性石墨板材。 GB/T 43887-2024核级柔性石墨板材检测检测指标: 测试项目 测试标准 外观 GB/T 43887 尺寸偏差 GB/T 43887 化学成分 GB/T 43887 密度偏差 GB/T 43887 拉伸强度…...