当前位置: 首页 > news >正文

C++类与对象(下)

文章目录

  • 1.非类型模板
  • 2.模板特化
    • 2.1.类模板特化
      • 2.1.1.全特化
      • 2.1.2.偏特化
    • 2.2.函数模板特化
  • 3.函数模板声明定义分离

之前我们学习的模板能达到泛型的原因是:使用了“泛型的类型”,但是如果经过后面的“造轮子”(后面会尝试实现一下 STL的一些类模板),就会很明显发现泛型不仅仅是类型的问题,例如:“适配器”的使用(在后面双端队列里有体现),实际上就是一种泛型,对于泛型的理解我们不能仅限于类型。

1.非类型模板

模板除了类型模板,还有非类型模板。

  1. 类型模板:出现在模板的参数列表中,跟在class或者typname后的参数类型名称

  2. 非类型模板:使用一个常量作为类的一个非类型模板参数,在模板类/模板函数中可以将该参数作为常量来使用,且不能修改。并且,这里非类型模板参数也可以使用缺省值

//没有非类型模板参数
#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风格的感觉。

类似dequelistvector的感觉(后面会讲),静态数组就是传统数组和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:区分好“匹配”和“特化”和“实例化”。

  1. 匹配:是有相匹配的类型,可以使用对应的模板
  2. 实例化:是编译器自己做的,将匹配对应的模板进行实例化
  3. 特化:特化不是全新的模板,必须依赖模板,不可以单独存在

注意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;
}

可以发现函数模板没有办法声明和定义分离在两个文件中,会显示链接错误(但是普通的函数可以)。

让我们来分析一下这里面的原因:

  1. C/C++要运行程序,就需要经历“预处理-编译-汇编-链接”
  2. 在编译阶段,会对多份源文件做各自的编译(进行词法、语法、语义分析、错误检查等)并且生成多份的汇编代码(注意头文件是不会参与编译的)这个时候在function.obj或者说function.o中,由于编译器没有看到函数的实例化,因此没有生成具体的加法函数。
  3. 而在main.obj或者main.o中,编译器看到有加法函数的调用,但是暂时不知道具体的实现,因此就暂时放进了符号表里等待后续链接
  4. 在链接阶段由于没有实例化,因此function.obj或者说function.o中没有加法函数的定义,根本就无法提供加法函数的地址在符号表里供main.obj或者main.o链接

因此后续链接的时候就会报错,即“链接错误”。

如果一定要分离,有两种方法:

  1. 进行显示实例化(有缺陷)

    //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;
    }
    
  2. 在一个翻译单元里分离,即:干脆直接将定义和声明都写在一个.hpp内,这样做是更加推荐的。

相关文章:

C++类与对象(下)

文章目录 1.非类型模板2.模板特化2.1.类模板特化2.1.1.全特化2.1.2.偏特化 2.2.函数模板特化 3.函数模板声明定义分离 之前我们学习的模板能达到泛型的原因是&#xff1a;使用了“泛型的类型”&#xff0c;但是如果经过后面的“造轮子”&#xff08;后面会尝试实现一下 STL的一…...

SpringBoot——》引入Redis

推荐链接&#xff1a; 总结——》【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项目管理软件。利用项目概览功能&#xff0c;将整体项目尽收眼底&#xff0c;作为项目管理者&#xff0c;项目日程、进度都可见&#xff0c;Zoho Projects项目管理APP助推项目每一环节的进展&#xff0c;更便于管理者设计项目的下…...

E (1081) : DS堆栈--逆序输出(STL栈使用)

Description C中已经自带堆栈对象stack&#xff0c;无需编写堆栈操作的具体实现代码。 本题目主要帮助大家熟悉stack对象的使用&#xff0c;然后实现字符串的逆序输出 输入一个字符串&#xff0c;按字符按输入顺序压入堆栈&#xff0c;然后根据堆栈后进先出的特点&#xff0…...

访问者模式 行为型设计模式之九

1.定义 在不改变数据结构的前提下&#xff0c;增加作用于一组对象元素的新功能。 2.动机 访问者模式适用于数据结构相对稳定的系统它把数据结构和作用于数据结构之上的操作之间的耦合解脱开&#xff0c;使得操作集合可以相对自由的演化。访问者模式的目的是要把处理从数据结构…...

JVM垃圾回收之JVM GC算法探究

JVM垃圾回收之JVM GC算法探究 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;是自动管理内存的重要机制&#xff0c;它负责回收程序中不再使用的对象所占用的内存。GC算法是垃圾回收的核心&#xf…...

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中为了方便以后的扩展使用了策略模式和模板模式实现图片上传和搜索功能&#xff0c;能够在配置类中设置使用Oss或者minio上传图片&#xff0c;es或者mysql文章搜索。后续有新的上传方式或者搜索方式只需要编写对应的实现类即可&#xff…...

Ubuntu 22.04 安装系统 手动分区 针对只有一块硬盘 lvm 单独分出/home

自动安装的信息 参考自动安装时产生的分区信息 rootyeqiang-MS-7B23:~# fdisk /dev/sdb -l Disk /dev/sdb&#xff1a;894.25 GiB&#xff0c;960197124096 字节&#xff0c;1875385008 个扇区 Disk model: INTEL SSDSC2KB96 单元&#xff1a;扇区 / 1 * 512 512 字节 扇区大…...

Android系统定制之监听USB键盘来判断是否弹出软键盘

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

LeakyReLU激活函数

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

Qt单一应用实例判断

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

企业AI工程化之路:如何实现高效、低成本、高质量的落地?

MLOps工程实践 概述面临挑战目的内容简介读者对象专家推荐目录 写在末尾&#xff1a; 主页传送门&#xff1a;&#x1f4c0; 传送 概述 作为计算机科学的一个重要领域&#xff0c;机器学习也是目前人工智能领域非常活跃的分支之一。机器学习通过分析海量数据、总结规律&#x…...

最短路径专题8 交通枢纽 (Floyd求最短路 )

题目&#xff1a; 样例&#xff1a; 输入 4 5 2 0 1 1 0 2 5 0 3 3 1 2 2 2 3 4 0 2 输出 0 7 思路&#xff1a; 由题意&#xff0c;绘制了该城市的地图之后&#xff0c;由给出的 k 个编号作为起点&#xff0c;求该点到各个点之间的最短距离之和最小的点是哪个&#xff0c;并…...

文件扫描模块

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

MySQL之主从复制

概述&#xff1a; 将主库的数据 变更同步到从库&#xff0c;从而保证主库和从库数据一致。 它的作用是 数据备份&#xff0c;失败迁移&#xff0c;读写分离&#xff0c;降低单库读写压力 原理&#xff1a; 主服务器上面的任何修改都会保存在二进制日志&#xff08; Bin-log日志…...

[leetcode 单调栈] 901. 股票价格跨度 M

设计一个算法收集某些股票的每日报价&#xff0c;并返回该股票当日价格的 跨度 。 当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数&#xff08;从今天开始往回数&#xff0c;包括今天&#xff09;。 例如&#xff0c;如果未来 7 天股票的价格是 [100…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)

题目 做法 启动靶机&#xff0c;点进去 点进去 查看URL&#xff0c;有 ?fileflag.php说明存在文件包含&#xff0c;原理是php://filter 协议 当它与包含函数结合时&#xff0c;php://filter流会被当作php文件执行。 用php://filter加编码&#xff0c;能让PHP把文件内容…...