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…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
