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

C++泛型编程

一、什么是泛型编程

泛型编程 是一种编程范式,它通过编写可以处理多种数据类型的代码来实现代码的灵活复用。泛型编程主要通过模板来实现。

比如我们日常使用的容器类型vector就应用了模板来实现其通用性,我们在使用时可以通过传入型别创建对应的动态数组,如传入int定义vector<int> 整形数组,也可以传入char创建 vector<char> 字符数组等。

二、模板简介

1、简介

泛型编程主要使用模板来实现。模板就是允许你编写与类型无关的代码,即对所有传入的数据类型编写通用的实现代码。

比如,我想返回传入数据的占内存大小。对这个问题的解决,可以不依赖数据类型,我们都可以通过相同的操作返回结果。

template <typename T>
size_t getSize(const T& data) {return sizeof(data);
}

2、模板分类

模板主要分为函数模板和类模板。

函数模板是使用泛型参数的函数。例如如下函数

template<typename T>
T add_func(T a, T b){return a + b;
}

类模板即使用泛型参数的类。例如如下类,成员参数或成员函数中可以动态指定参数类型

template<typename T>
class MClass{
public:MClass(){}MClass(T t):__data(t){}T getData(){return __data;}private:T __data;
};

3、模板实例化

模板的声明知识给出一个函数或类提供一个语法框架,其实并没有完成成为一个函数或类。当你定义一个模板时,编译器并不立即生成代码。

当你在代码中使用模板时,编译器会根据传入的类型生成对应的代码。这被称为模板实例化。每当使用新的类型实例化一个模板时,编译器会生成一份新的代码副本。

编译阶段,编译器在模板实例化时会进行类型检查和其他错误检查,确保传入的类型符合模板的要求。如果有错误,编译器会报告这些错误。

运行阶段,模板已经被实例化并编译成代码,则运行时行为与普通函数相同。模板本身的特性不会影响运行时的性能,生成的代码是静态的。

如上述的函数模板,我们就可以通过传入参数类型int、char、double等构建出add_func<int>、add_func<char>、add_func<double>等不同的函数实例。

模板实例化有2种方式:

显式实例化:直接在代码中明确指定传入型别,如下方式的调用就是显式实例化

add_func<double>(5.09, 10.26);

隐式实例化:让编译器通过传入的参数进行型别推导完成的实例化。例如下面的调用,编译器会根据传入的数据,推导为add_func<int>的函数

add_func(2, 8);

 关于隐式实例化,一定要注意要能让编译器推导出型别。如下代码,只有一个模板参数的情况下,却传入了两种类型的值。类似这种情况编译器无法完成型别推导,就会报错。这时,可以

1)将数据类型强转为相同类型; 2)显式实例化模板 即可编译通过。

int main(){add_func(5.09, 28); //ERROR:无法推导出T的型别add_func(5.09, static_cast<double>(28)); //OK 将28强转为double后,只存在一种数据类型了,编译器可以推导出T=doubleadd_func<int>(5.09, 28); //OK 显式得指定T为int,不用编译器推导,在调用时5.09会被强转为int型使用(会丢失精度)
}

三、函数模板

以函数形式定义的模板,它可以定义一族函数。函数模板可以指定一个或者多个类型参数,这些类型参数在具体调用时被具体数据类型取代。

函数模板的定义

函数模板的定义如下代码:

注意:每个函数模板都要在其之前使用template<>声明,不可复用。

// 指定一个参数类型的函数模板
template<typename T>
void func(T t){}// 指定多个参数类型的函数模板
template<typename T1, typename T2>
T2 func(T1 t){  return T2();}//指定可变参数函数模板
template<typename ...Args>
void func(Args ...args){}

模板定义的语法不用多说,但是模板参数有很多知识点需要掌握。

函数模板参数

从上面看,我们知道模板参数可以是一个,可以是多个,也可以是可变参数。其中可变参数需要详细介绍,所以博主又写了一篇文章,大家可以参考

可变参数函数、可变参数模板和折叠表达式_可变参数模板函数-CSDN博客

下面介绍下关于模板参数的其他知识点

默认模板参数

模板的参数列表也可以设置默认传入类型。和函数参数默认值一样,默认类型必须在右侧。如下代码所示

template <typename T1, typename T2 = int>
void printData(T1 a, T2 b = 0)
{cout << a << " " << b << endl;
}int main(){printData<string>("print data:", 97); //默认是int类型,会打印97printData<string, char>("print data:", 97); //指定是char类型,会打印'a'return 0;
}

输出:

非类型模板参数

 除了类型模板参数,在模板中还可以使用非类型模板参数。如下代码所示,参数N是一个size_t常量参数,而不是个类型参数。

并且非类型参数也可以有默认值。

template<typename T, size_t N = 10>
T cal_func(T a, T b){return (a + b) * N;
}int main(){cout << cal_func(2,3) <<endl;cout << cal_func<int, 20>(2,3) <<endl;
}

输出

并不是所有参数类型都可以作为模板的非类型参数。非类型模板参数仅支持整形、枚举类型、字符常量几种。具体我放在类模板单元介绍,因为它们主要使用在类模板中,函数模板虽然在语法上也支持,但是用的不常见。

函数模板的重载

函数模板也可以像普通函数一样被重载,也可以重载普通函数,编译器可以通过推导来决定调用哪个函数

// 1、普通函数
void func(int a){cout << "func 1 called!!! value=" << a << endl;
}
// 2、具有一个模板参数的函数模板
template<typename T>
void func(T a){cout << "func 2 called!!! value=" << a << endl;
}
// 3、具有2个模板参数的函数模板
template<typename T>
void func(T a, T b){cout << "func 3 called!!! value=" << a << endl;
}
// 4、具有可变参数的函数模板
template<typename...Args>
void func(Args...args){cout << "func 4 called!!!" << endl;
}int main(){func(1);func<int>(2);func(3, 4);func(5.6);func(7, 8.9);
}

输出

如上述代码:函数func有3个重载版本,其中第一个是普通函数。

从调用结果看:

1)当直接传入一个int型参数时,调用的是普通函数func 1;

2)当显式调用一个模板函数时,即func<int>时,无论参数是什么,都会调用函数模板;

3)当传入一个非int型参数时,会调用具有一个模板参数的func 2;

4)当传入两个相同类型参数时,会隐式调用具有两个相同类型参数的模板函数func 3;

5)当传入两个不同类型参数时,编译器调用了可变参数的模板函数 func 4.

从上面结果可以推断出,编译器会优先调用更明确的函数,而不是选择推导;如果没有更明确的选择,必须要使用模板,编译器会优先选择推导更少的模板参数。即如果同时存在普通函数和模板函数可以调用时,编译器会优先调用普通函数;如果同时存在多个模板函数可以选择,编译器会选择调用具有更少“推导工作量”的模板函数。

函数模板的特化

模板的特化是指为特定类型或特定参数数量提供自定义实现。这允许开发者为某些类型提供更高效或更适合的实现,而不必改变模板的整体设计。

有两种主要类型的特化:

  1. 全特化:为特定类型提供完全不同的实现。
  2. 偏特化:为某些特定类型组合提供不同的实现,但保留模板的一部分灵活性。

但是函数模板只允许全特化

如下,我们实现一个判断是否相等的函数模板,但是因为浮点数不能使用“==”判断是否相等,所以进行了全特化

template<typename T>
bool Equal(T a, T b){return a == b;
}
// 全特化
template<>
bool Equal<double>(double a, double b){return abs(a-b) < 0.0001;
}int main(){cout << Equal(1, 2) << endl;cout << Equal(1.1, 1.1000001) << endl;
}

输出:

关于特化的剩余知识在类模板介绍。

四、类模板

类模板允许创建类的模板定义,类中的成员变量和成员函数都可以使用传入的数据类型确定。

类模板定义

类模板的定义形式和函数模板没有太大区别。只不过类模板不仅可以将成员函数的形参、返回值泛化,也可以将成员变量进行泛化。我把上面的类模板定义代码拷贝下来,大家可以再复习下

template<typename T>
class MClass{
public:MClass(){}MClass(T t):__data(t){}T getData(){return __data;}private:T __data;
};

在类模板的定义中,还有些函数模板涉及不到的注意事项:

1)如果在类模板中涉及到对类对象本身的使用,需要完整写出类模板名称,类似MClass<T>而不是只简单些MClass;

2)如果需要再类声明之外进行初始化(例如类静态成员变量)的成员,以及进行定义的成员(例如类成员函数),都需要完整地声明出模板参数;

如下代码,是相对比较复杂的类模板定义,简单实现了一个数组的功能

template <typename T, unsigned N>
class Array {
private:T data[N]; // 使用 N 定义数组大小
public:Array<T, N>& operator =(const Array<T, N>& other){}void setData(int i, T dat);T getData(int i){return data[i];}
};template <typename T, unsigned N>
void Array<T, N>::setData(int i, T dat){if (i >=0 && i < N){data[i] = dat;}
}int main(){Array<int, 10> arr;arr.setData(5, 999);cout << arr.getData(5) << endl;
}

输出

类模板参数

类模板的参数和函数模板类似,可以是一个,可以是多个,也可以是可变参数,也可以传入默认参数类型,这里就不再赘述。

下面重点介绍下在函数模板中没有说完的非类型模板参数的使用。

非类型模板参数

非类型模板参数支持整形、枚举类型、字符常量下面逐个介绍。

1、整形变量

类型:如size_t、int、unsigned int等

用途:

  • 经常用于定义数组大小、循环次数、容量等
  • 可以为函数或类提供编译时确认的常量

例如,

template <typename T, std::size_t N>
class Array {
public:T data[N]; // 使用 N 定义数组大小
};int main(){Array<int, 10> arr;cout << sizeof(arr.data)/sizeof(int) << endl;
}

输出

2、枚举类型

类型:enum

用途

  • 提供一种方式来选择特定的行为或配置
  • 可以用作模板参数来控制模板的行为
enum class Color { Red, Green, Blue };template <Color C>
class ColorBox {
public:void display() {if constexpr (C == Color::Red) {std::cout << "Red Box\n";} else if constexpr (C == Color::Green) {std::cout << "Green Box\n";} else {std::cout << "Blue Box\n";}}
};int main(){ColorBox<Color::Green> cbox;cbox.display();
}

输出

3、字符常量

类型:char

用途:

  • 可以用于定义固定的字符串或字符常量
  • 常用于模板元编程中,例如处理字符串或字符集

使用举例

template <char C>
class CharPrinter {
public:void print() {std::cout << C << std::endl;}
};int main(){CharPrinter<'A'> cp;cp.print();
}

输出

非类型模板参数的作用
  • 灵活性:非类型模板参数允许你编写更灵活和高效的代码。
  • 性能:由于在编译时已确定值,能提升性能。
  • 错误检查:使用非类型参数时,可以在编译阶段捕获错误,减少运行时错误。

类模板特化

类模板可以全特化也可以偏特化(也叫部分特化)。特化后的具体实现可以和泛式的实现不一样

类模板全特化

类模板全特化的格式和函数模板全特化基本相同

// 类模板
template<typename T, typename U>
class MClass{}// 全特化版本
template<>
class MClass<int, string>{};

上面的全特化版本用于特殊处理类型参数是<int, string>的问题。

类模板偏特化

如下类模板声明

// 类模板
template<typename T, typename U>
class MClass{};// 部分特化-特化第二个类型参数为string
template<typename T>
class MClass<T, string>{};// 部分特化-特化第一个类型参数为int
template<typename T>
class MClass<int, T>{};

下面,我写出完整地类模板、全特化和偏特化的几个类声明。及它们的使用。

示例代码

// 类模板
template<typename T, typename U>
class MClass{
public:MClass(){cout << "MClass T U called" << endl;}
};
// 全特化版本
template<>
class MClass<int, string>{
public:MClass(){cout << "MClass int str called" << endl;}
};
// 部分特化-特化第二个类型参数为string
template<typename T>
class MClass<T, string>{
public:MClass(){cout << "MClass T str called" << endl;}
};
// 部分特化-特化第一个类型参数为int
template<typename T>
class MClass<int, T>{
public:MClass(){cout << "MClass int T called" << endl;}
};int main(){MClass<int, string> c1;MClass<int, double> c2;MClass<double, string> c3;MClass<char, int> c4;return 0;
}

输出

从上面的代码中,我们可以看到,有些调用可以匹配多个类模板。比如如下这句调用,它可以使用MClass<int, string>的全特化版本,也可以使用MClass<int, T>或MClass<T, string>的偏特化版本,更可以使用MClass<T, U>的无特化一般版本。但为什么只调用了MClass<int, string>的全特化版本呢?

MClass<int, string> c1;

这是因为,编译器在查找类模板时,会优先匹配全特化版本,其次是偏特化版本,最后才是一般模板。

所以,上面的输出结果那般。

相关文章:

C++泛型编程

一、什么是泛型编程 泛型编程 是一种编程范式&#xff0c;它通过编写可以处理多种数据类型的代码来实现代码的灵活复用。泛型编程主要通过模板来实现。 比如我们日常使用的容器类型vector就应用了模板来实现其通用性&#xff0c;我们在使用时可以通过传入型别创建对应的动态数…...

【论文分享】利用大量街景图片研究街道空间质量与建筑环境属性之间的关联

本研究通过有序逻辑回归模型&#xff0c;结合街景图片和街道数据&#xff0c;分析了街道空间质量与建筑环境属性的关系。通过Kappa分析和相关性分析&#xff0c;确定了影响街道空间质量的因素&#xff0c;并绘制了质量分布图。这些因素与街道质量的不同维度相关联&#xff0c;对…...

【Linux第七课--基础IO】内存级文件、重定向、缓冲区、文件系统、动态库静态库

目录 引入内存级文件重新使用C文件接口 -- 对比重定向写文件读文件文件流 认识文件操作的系统接口open参数 -- flagflag的内容宏的传参方式 open关闭文件写文件读文件结论 引入文件描述符fd、对文件的理解理解一切皆文件方法集文件fd的分配规则 重定向代码的重定向输入重定向输…...

对比C/C++语言,Rust语言有什么优势?

Rust语言相较于C/C语言有以下几个主要优势&#xff1a; 1. 内存安全&#xff1a;Rust通过其所有权系统和借用规则在编译时捕获许多常见的内存安全错误&#xff0c;如空指针引用和数据竞争&#xff0c;避免了许多常见的安全漏洞。这与C/C不同&#xff0c;后者通常需要手动管理内…...

Rust语言有哪些数据类型?

Rust语言的数据类型主要包括以下几种&#xff1a; 一、基本数据类型 1. 整数类型 i8, i16, i32, i64, i128: 有符号整数 u8, u16, u32, u64, u128: 无符号整数 isize, usize: 根据平台选择大小的整数&#xff08;通常用于指针和索引&#xff09; 2. 浮点数类型 f32: 32位浮…...

【论文笔记】Attention Prompting on Image for Large Vision-Language Models

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Attention Prompting on I…...

VScode设置系统界面字体

现象&#xff1a; 系统界面字体太大&#xff0c;导致菜单栏字体显示不全&#xff0c;每次使用都要先点然后才能打开终端和帮助 缩小字体应该就可以实现全部都看到的效果 步骤 Window: Zoom Level 调整所有窗口的默认缩放级别。大于“0”的每个增量&#xff08;例如“1”&…...

Java中常见的异常类型

1、Exception和Error有什么区别&#xff1f; 首先Exception和Error都是继承于Throwable类&#xff0c;在Java中只有Throwable类型的实例才可以被抛出&#xff08;throw&#xff09;或者捕获&#xff08;catch&#xff09;&#xff0c;它是异常处理机制的基本组成类型。 Except…...

Java学习Day58:相声二人组!(项目统计数据Excel图表导出)

<!DOCTYPE html> <html xmlns"http://www.w3.org/1999/html"><head><!-- 页面meta --><meta charset"utf-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><title>瑞通健康</tit…...

springboot 自动装配和bean注入原理及实现

装配&#xff1a;创建bean&#xff0c;并加入IOC容器。 注入&#xff1a;创建bean之间的依赖关系。 1、类自动装配 SpringBoot 自动装配使得开发人员可以轻松地搭建、配置和运行应用程序&#xff0c;而无需手动管理大部分的 bean 和配置。 Spring Boot 的自动装配机制与模块…...

解决Redis缓存穿透(缓存空对象、布隆过滤器)

文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库 常见的解决方案有两种&#xff0c;分别…...

初探Flink的序列化

Flink中的序列化应用场景 程序通常使用(至少)两种不同的数据表示形式[2]&#xff1a; 1. 在内存中&#xff0c;数据保存在对象、结构体、列表、数组、哈希表和树等结构中。 2. 将数据写入文件或通过网络发送时&#xff0c;必须将其序列化为字节序列。 从内存中的表示到字节序列…...

QT 机器视觉 (3. 虚拟相机SDK、测试工具)

本专栏从实际需求场景出发详细还原、分别介绍大型工业化场景、专业实验室场景、自动化生产线场景、各种视觉检测物体场景介绍本专栏应用场景 更适合涉及到视觉相关工作者、包括但不限于一线操作人员、现场实施人员、项目相关维护人员&#xff0c;希望了解2D、3D相机视觉相关操作…...

1分钟解决Excel打开CSV文件出现乱码问题

一、编码问题 1、不同编码格式 CSV 文件有多种编码格式&#xff0c;如 UTF - 8、UTF - 16、ANSI 等。如果 CSV 文件是 UTF - 8 编码&#xff0c;而 Excel 默认使用的是 ANSI 编码打开&#xff0c;就可能出现乱码。例如&#xff0c;许多从网络应用程序或非 Windows 系统生成的 …...

基于SpringBoot+Vue的仓库管理系统【前后端分离】

基于SpringBootVue的仓库管理系统设计与实现 摘要 仓库管理系统在现代企业物流中具有重要作用&#xff0c;能够有效提高库存管理效率&#xff0c;优化资源配置。本系统采用Spring Boot作为后端框架&#xff0c;Vue作为前端框架&#xff0c;通过前后端分离的开发模式构建一个现代…...

vue和django接口联调

vue访问服务端接口 配置跨域 前端跨域 打开vite.config.js&#xff0c;在和resolve同级的地方添加配置。 proxy代表代理的意思 "/api"是以/api开头的路径走这个配置 target代表目标 changeOrigin: true,是开启跨域请求 rewrite是编辑路径。 (path) > pa…...

2-141 怎么实现ROI-CS压缩感知核磁成像

怎么实现ROI-CS压缩感知核磁成像&#xff0c;这个案例告诉你。基于matlab的ROI-CS压缩感知核磁成像。ROI指在图像中预先定义的特定区域或区域集合&#xff0c;选择感兴趣的区域&#xff0c;通过减少信号重建所需的数据来缩短信号采样时间&#xff0c;减少计算量&#xff0c;并在…...

开源库 FloatingActionButton

开源库FloatingActionButton Github:https://github.com/Clans/FloatingActionButton 这个库是在前面这个库android-floating-action-button的基础上修改的&#xff0c;增加了一些更强大和实用的特性。 特性&#xff1a; Android 5.0 以上点击会有水波纹效果 可以选择自定义…...

技术选型不当对项目的影响与补救措施

在项目管理中&#xff0c;初期技术选型与项目需求不匹配的情况并不罕见&#xff0c;这可能导致项目延误、成本增加和最终成果的不理想。补救的关键措施包括&#xff1a;重新评估技术选型、加强团队沟通、实施有效的需求管理以及建立持续的反馈机制。其中&#xff0c;重新评估技…...

Spring的核心类: BeanFactory, ApplicationContext 笔记241103

Spring的核心类: BeanFactory, ApplicationContext, ConfigurableApplicationContext, WebApplicationContext, WebServerApplicationContext, ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, XmlWebApplicationContext, AnnotationConfigServletWebServer…...

UE5移动端主要对象生命周期及监听

1、GameInstance 1、首先加载GameInstance,全局唯一,切换Map也是唯一的,用于做一些全局操作,比如监听Map加载,监听App进入前台、退出后台 // Fill out your copyright notice in the Description page of Project Settings.#include "Core/Base/MyGameInstance.h&q…...

LLM | 论文精读 | CVPR | SelTDA:将大型视觉语言模型应用于数据匮乏的视觉问答任务

论文标题&#xff1a;How to Specialize Large Vision-Language Models to Data-Scarce VQA Tasks? Self-Train on Unlabeled Images! 作者&#xff1a;Zaid Khan, Vijay Kumar BG, Samuel Schulter, Xiang Yu, Yun Fu, Manmohan Chandraker 期刊&#xff1a;CVPR 2023 DOI…...

kafka里的consumer 是推还是拉?

大家好&#xff0c;我是锋哥。今天分享关于【kafka里的consumer 是推还是拉&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka里的consumer 是推还是拉&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消费者&…...

针对物联网边缘设备基于EIT的手部手势识别的1D CNN效率增强的组合模型压缩方法

论文标题&#xff1a;Combinative Model Compression Approach for Enhancing 1D CNN Efficiency for EIT-based Hand Gesture Recognition on IoT Edge Devices 中文标题&#xff1a;针对物联网边缘设备基于EIT的手部手势识别的1D CNN效率增强的组合模型压缩方法 作者信息&a…...

商品满减、限时活动、折扣活动的计算最划算 golang

可以对商品的不同活动&#xff08;如满减、限时价和折扣&#xff09;进行分组&#xff0c;并在购物车中显示各个活动标签下的最优价格组合。以下代码将商品按活动类别进行分组计算&#xff0c;并输出在购物车中的显示信息。 package mainimport ("fmt""math&qu…...

vue3 + ts + element-plus 二次封装 el-table

一、实现效果&#xff1a; &#xff08;1&#xff09;数据为空时&#xff1a; &#xff08;2&#xff09;有数据时&#xff1a;存在数据合并&#xff1b;可自定义表头和列的内容 &#xff08;3&#xff09;新增行&#xff1a; &#xff08;4&#xff09;删除行&#xff1a; &a…...

python传递json参数给php

python传递json参数给php 在Python中&#xff0c;你可以使用requests库来发送JSON数据给一个PHP脚本。以下是一个简单的例子&#xff1a; 首先&#xff0c;安装requests库&#xff08;如果你还没有安装的话&#xff09;&#xff1a; pip install requests 然后&#xff0c;…...

2.若依vue表格数据根据不同状态显示不同颜色style

例如国标显示蓝色&#xff0c;超标是红色 使用是蓝色&#xff0c;未使用是绿色 <el-table-column label"外卖配送是否完成评价" align"center" prop"isOverFlag"> <template slot-scope"scope"> …...

JZ2440开发板——LCD

以下内容源于韦东山嵌入式课程的学习与整理&#xff0c;如有侵权请告知删除。 之前在博文中学习过LCD&#xff08;SoC是S5PV210&#xff09;&#xff0c;作为对比&#xff0c;本文学习S3C2440这款SoC的LCD方面的内容。主要涉及以下三个内容&#xff1a; 一、LCD的硬件原理 1.…...

YOLOv6-4.0部分代码阅读笔记-yolo_lite.py

yolo_lite.py yolov6\models\yolo_lite.py 所需的库和模块 #!/usr/bin/env python3 # -*- coding:utf-8 -*- import math import torch import torch.nn as nn import torch.nn.functional as F from yolov6.layers.common import * from yolov6.utils.torch_utils import i…...