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…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...