C++模板相关概念汇总

news2025/1/6 20:32:22

文章目录

      • 一、模板的概念与作用
      • 二、函数模板
        • 模板的非类型参数
        • 调用顺序
      • 三、类模板
      • 四、模板的编译模型

一、模板的概念与作用

C++模板是一种强大的代码复用机制,它允许程序员编写通用的代码,能够处理不同类型的数据,而无需为每种类型都重复编写相似的代码。通过模板,可以在编译时根据具体的类型参数生成对应的代码,极大地提高了代码的灵活性和可复用性。

例如,不用模板的情况下,如果要编写一个交换两个整数的函数和交换两个浮点数的函数,代码可能如下:

// 交换两个整数的函数
void swapInt(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 交换两个浮点数的函数
void swapFloat(float& a, float& b) {
    float temp = a;
    a = b;
    b = temp;
}

而使用模板,就可以编写一个通用的交换函数,能适用于多种类型:

// 模板函数
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

二、函数模板

  1. 定义
    函数模板以 template 关键字开头,后面跟着用尖括号括起来的模板参数列表,通常是类型参数(用 typenameclass 关键字来声明,表示此处可以是任意类型)。例如上面的 swap 函数模板,template <typename T> 就是定义部分,T 就是类型参数,之后定义的函数体中使用了这个 T 类型来处理参数,使得该函数可以针对不同类型进行实例化。

函数模板不进行编译,在调用点必须要能看见模板定义,否则将产生链接错误,模板调用点根据类型会实例化一份函数进行编译

  1. 实例化
    函数模板本身并不会生成实际的代码,只有在被调用时,编译器根据传入的实际参数类型来实例化模板,生成对应的具体函数版本。例如:
int num1 = 10, num2 = 20;
swap(num1, num2);  // 此时编译器会根据int类型实例化出一个专门用于交换两个整数的swap函数版本

float num3 = 3.14f, num4 = 2.71f;
swap(num3, num4);  // 同样,会根据float类型实例化出交换两个浮点数的swap函数版本

编译器会在后台为每一次不同类型的调用生成相应的函数代码,这个过程对程序员来说基本是透明的,但需要注意的是,如果实例化过程中出现类型相关的错误(比如类型不支持某些操作等),编译器会在编译阶段报错。

#include<iostream>
using namespace std;

//函数模板   T E 模板的类型参数
template<typename  T , typename E>
void compare(T a, E b)
{
	cout << a << endl;
}
//显示实例化
template void compare<int, double>(int ,double);
int main()
{
	//隐式实例化
	compare(1,1);//模板的实参推演
	return 0;
}

模板参数不是一个简单的宏替换,而是重定义的过程 ,不是# define 而是typedef 的过程

老的编译器只检查函数头部是否符合语法规则,不检查函数体是否符合语法,但是新的编译器开始全部检查

函数模板不允许部分特例化

模板的非类型参数
#include<iostream>
using namespace std;

//函数模板    SIZE:模板的非类型参数,是常量
template<typename  T , int SIZE>
int  findval(T *arr, T val)
{
	for (int i = 0; i < SIZE; ++i)
	{
		if (val == arr[i])
		{
			cout << "find it" << endl;
			return i;
		}
	}
	return -1;
}
int main()
{
	int arr[] = {1,2,3,4};
	findval<int, 4>(arr, 2);

	//
	int lenth = sizeof(arr) / sizeof(arr[0]);
	findval<int, lenth>(arr, 2);//报错,模板的非类型参数必须是常量
	return 0;
}
//以上代码改成 const  int lenth = sizeof(arr) / sizeof(arr[0]);即可

  1. 模板参数推断
    在很多情况下,编译器可以根据函数调用时传入的实际参数自动推断出模板参数的类型,不需要显式指定。例如上面的 swap 函数调用,传入 int 类型参数时,编译器就能推断出 Tint 类型。但也有一些情况需要显式指定模板参数类型,比如:
template <typename T>
T add(T a, T b) {
    return a + b;
}

int result = add(10, 20);  // 编译器可推断出T为int类型
double result2 = add<double>(10.0, 20.0);  // 这里显式指定模板参数为double类型,因为编译器可能无法准确从整数10和20推断出要用于double类型的加法运算
  1. 重载与特化
    函数模板可以重载,就像普通函数一样,只要函数签名(包括模板参数等)不同即可。例如:
template <typename T>
void print(T value) {
    std::cout << value << std=""};

template <typename T>
void print(T* value) {
    std::cout << "Pointer: " << value << std::endl;
}

上述代码定义了两个 print 函数模板,一个用于打印普通类型的值,一个用于打印指针类型的值,根据传入参数的类型不同,编译器会选择合适的重载版本进行实例化。

此外,还可以对函数模板进行特化,即针对特定的类型提供专门的实现,当使用该特定类型调用模板函数时,就会采用特化的版本。例如:

template <typename T>
void compare(T a, T b) {
    std::cout << (a == b? "Equal" : "Not equal") << std::endl;
}

// 针对char*类型的特化
template <>
void compare<char*>(char* a, char* b) {
    std::cout << (strcmp(a, b) == 0? "Equal" : "Not equal") << std::endl;
}

当调用 compare 函数模板时,如果传入的参数是 char* 类型,就会使用特化后的版本进行比较,而不是通用的版本。

调用顺序

先调用普通函数
在调用特例化
从函数模板实例化一个

三、类模板

  1. 定义
    类模板的定义方式与函数模板类似,也是以 template 关键字开头,后跟模板参数列表。例如:
template <typename T>
class Stack {
private:
    std::vector<T> elements;
public:
    void push(T element) {
        elements.push_back(element);
    }
    T pop() {
        T last = elements.back();
        elements.pop_back();
        return last;
    }
};

上述代码定义了一个简单的 Stack 类模板,它可以用来创建存储不同类型元素的栈结构,其中 T 就是类型参数,类中的成员变量和成员函数都基于这个 T 类型进行定义。

  1. 实例化
    类模板同样需要实例化才能生成实际的类对象。实例化可以通过在使用类模板时指定类型参数来实现,例如:
Stack<int> intStack;  // 实例化出一个存储int类型元素的栈
Stack<double> doubleStack;  // 实例化出一个存储double类型元素的栈

intStack.push(10);
doubleStack.push(3.14);

和函数模板一样,编译器会根据指定的类型参数为每个实例化生成对应的类代码,包括类的成员变量的内存布局以及成员函数的具体实现等都会根据具体类型来确定。

  1. 模板参数
    类模板的模板参数除了常见的类型参数(用 typenameclass 声明)外,还可以有非类型参数,非类型参数通常是一些常量表达式,比如整数、枚举值、指针类型等。例如:
template <typename T, int size>
class Array {
private:
    T elements[size];
public:
    T& operator[](int index) {
        return elements[index];
    }
};

在上述 Array 类模板中,T 是类型参数,用于指定数组中元素的类型,而 int size 是一个非类型参数,用于指定数组的大小,这样就可以创建不同大小、不同元素类型的数组类对象,例如:

Array<int, 5> intArray;  // 创建一个存储5个int元素的数组类对象
Array<double, 10> doubleArray;  // 创建一个存储10个double元素的数组类对象
  1. 成员函数的定义
    类模板的成员函数可以在类内定义,也可以在类外定义。如果在类外定义,需要使用模板声明,并且要带上模板参数,例如:
template <typename T>
class MyClass {
public:
    void func();
};

template <typename T>
void MyClass<T>::func() {
    // 函数体具体实现
}

注意,类模板的成员函数只有在被调用且对应的类模板实例化后才会被实例化生成实际的代码。

  1. 继承与模板
    在涉及继承关系时,模板类可以作为基类或者派生类。例如:
template <typename T>
class Base {
    // 基类的定义
};

template <typename T>
class Derived : public Base<T> {
    // 派生类的定义,继承自Base<T>,保证类型的一致性
};

派生类在继承模板类时,需要正确指定模板参数,使得继承关系在类型上是匹配的,并且派生类可以根据自身需求扩展或修改基类的功能。

四、模板的编译模型

C++模板采用的是分离编译模型,也就是模板的定义和使用通常可以在不同的源文件中进行。但这也带来了一些问题,因为模板在编译时需要根据具体类型进行实例化,所以编译器在处理模板时,需要能看到模板的完整定义才能正确进行实例化生成代码。

例如,在一个源文件中定义了函数模板:

// file1.cpp
template <typename T>
void myTemplateFunction(T a) {
    // 函数体内容
}

在另一个源文件中使用这个模板函数:

// file2.cpp
#include <iostream>

void func() {
    int num = 10;
    myTemplateFunction(num);  // 此处使用模板函数,但编译器可能无法正确实例化,因为看不到模板函数的完整定义
}

为了解决这个问题,常见的做法有将模板的定义和声明都放在头文件中(尽管违背了通常头文件放声明、源文件放定义的原则),或者使用 export 关键字(不过在现代C++中这个关键字已经很少使用且很多编译器支持有限)来告诉编译器模板的定义可以在其他地方找到,以便正确进行实例化。

总之,C++模板是一种非常强大且灵活的编程工具,通过合理运用它,可以写出高效、复用性强的代码,但同时也需要深入理解其语法、实例化机制以及编译相关的特点等内容,才能更好地驾驭它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2271108.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP框架+gatewayworker实现在线1对1聊天--发送消息(6)

文章目录 发送消息原理说明发送功能实现html部分javascript代码PHP代码 发送消息原理说明 接下来我们发送聊天的文本信息。点击发送按钮的时候&#xff0c;会自动将文本框里的内容发送出去。过程是我们将信息发送到服务器&#xff0c;服务器再转发给对方。文本框的id为msgcont…

DuckDB:密钥管理器及其应用

密钥管理器(Secrets Manager)为所有使用密钥的后端提供了统一的用户界面。密钥信息可以被限定范围&#xff0c;因此不同的存储前缀可以有不同的密钥信息&#xff0c;例如允许在单个查询中连接跨组织的数据。密钥也可以持久化&#xff0c;这样就不需要在每次启动DuckDB时都指定它…

告别Kibana:Elasticsearch 桌面客户端的新变革

告别Kibana&#xff1a;Elasticsearch 桌面客户端的新变革 在大数据处理与分析领域&#xff0c;Elasticsearch 及其相关技术的应用日益广泛。长期以来&#xff0c;Kibana 在数据可视化与查询管理方面占据重要地位&#xff0c;但随着技术的不断发展&#xff0c;用户对于更高效、…

模块化通讯管理机在物联网系统中的应用

安科瑞刘鸿鹏 摘要 随着能源结构转型和智能化电网的推进&#xff0c;电力物联网逐渐成为智能电网的重要组成部分。本文以安科瑞ANet系列智能通信管理机为例&#xff0c;探讨其在电力物联网中的应用&#xff0c;包括数据采集、规约转换、边缘计算、远程控制等技术实践&#…

AAAI 2025论文分享┆一种接近全监督的无训练文档信息抽取方法:SAIL(文中附代码链接)

本推文详细介绍了一篇上海交通大学乐心怡老师课题组被人工智能顶级会议AAAI 2025录用的的最新论文《SAIL: Sample-Centric In-Context Learning for Document Information Extraction》。论文的第一作者为张金钰。该论文提出了一种无需训练的、以样本为中心的、基于上下文学习的…

SAP物料主数据界面增加客制化字段、客制化页签的方式

文章目录 前言一、不增加页签&#xff0c;只增加客制化字段二、增加物料主数据页签 前言 【SAP系统MM模块研究】 #SAP #MM #物料 #客制化 #物料主数据 项目上难免会遇到客户要在物料主数据的界面上&#xff0c;增加新字段的需求。 实现方式有&#xff1a; &#xff08;1&…

ROS2软件架构全面解析-学习如何设计通信中间件框架

前言 ROS&#xff08;Robot Operating System&#xff09; 2 是一个用于开发机器人应用的软件平台&#xff0c;也称为机器人软件开发工具包 (SDK)。 ROS2是ROS1的迭代升级版本 &#xff0c;最主要的升级点是引入DDS&#xff08;Data Distribution Service&#xff09;为基础的…

接口自动化测试流程、工具及其实践

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、接口自动化测试简介 接口自动化测试是指通过编写脚本或使用自动化工具&#xff0c;对软件系统的接口进行测试的过程。接口测试是软件测试中的一种重要测试类…

香橙派5plus单独编译并安装linux内核无法启动的原因分析与解决记录

1 说明 我依照官方手册编译单独编译linux内核&#xff0c;安装后重启出现内核启动失败的问题,编译和安装步骤如下&#xff1a;# 1. 克隆源码 git clone --depth1 -b orange-pi-6.1-rk35xx https://github.com/orangepi-xunlong/linux-orangepi# 2 配置源码 make rockchip_linu…

数据库知识汇总1

一. 数据库系统概述 信息需要媒体&#xff08;文本、图像视频等&#xff09;表现出来才能被人类所获取&#xff0c;媒体可以转换成比特或者符号&#xff0c;这些称为数据&#xff1b; 数据/信息的特点&#xff1a;爆炸式增长、无限复制、派生&#xff1b; 数据库是指长期长期…

Win32汇编学习笔记03.RadAsm和补丁

Win32汇编学习笔记03.RadAsm和补丁-C/C基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net 扫雷游戏啊下补丁 在扫雷游戏中,点关闭弹出一个确认框,确认之后再关闭,取消就不关闭 首先第一步就是确认关闭按钮响应的位置,一般都是 WM_CLOSE 的消息 ,消息响应一般都在过…

OSPF特殊区域(open shortest path first LSA Type7)

一、区域介绍 1、Stub区域 Stub区域是一种可选的配置属性。通常来说&#xff0c;Stub区域位于自治系统的边界&#xff0c;例如&#xff0c;只有一 个ABR的非骨干区域。在这些区域中&#xff0c;设备的路由表规模以及路由信息传递的数量都会大量减少。 kill 4 5类type 传递1 …

论文解读之Generative Dense Retrieval: Memory Can Be a Burden

本次论文解读&#xff0c;博主带来生成式稠密检索&#xff1a;记忆可能成为一种负担的论文分享 一、简介 生成式检索根据给定的查询&#xff0c;自回归地检索相关的文档标识符&#xff0c;在小规模的文档库中表现不错&#xff0c;通过使用模型参数记忆文档库&#xff0c;生成…

vue,使用unplugin-auto-import避免反复import,按需自动引入

项目库&#xff1a;https://github.com/unplugin/unplugin-auto-import 参考&#xff1a; https://juejin.cn/post/7012446423367024676 https://cloud.tencent.com/developer/article/2236166 背景&#xff1a; vue3项目中&#xff0c;基本所有页面都会引入vue3框架的api&…

[深度学习] 大模型学习1-大语言模型基础知识

大语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;是一类基于Transformer架构的深度学习模型&#xff0c;主要用于处理与自然语言相关的各种任务。简单来说&#xff0c;当用户输入文本时&#xff0c;模型会生成相应的回复或结果。它能够完成许多任务&…

OCR图片中文字识别(Tess4j)

文章目录 Tess4J下载 tessdataJava 使用Tess4j 的 demo Tess4J Tess4J 是 Tesseract OCR 引擎的 Java 封装库&#xff0c;它让 Java 项目更轻松地实现 OCR&#xff08;光学字符识别&#xff09;功能。 下载 tessdata 下载地址&#xff1a;https://github.com/tesseract-ocr/…

Vue2/Vue3使用DataV

Vue2 注意vue2与3安装DataV命令命令是不同的Vue3 DataV - Vue3 官网地址 注意vue2与3安装DataV命令命令是不同的 vue3vite 与 Vue3webpack 对应安装也不同vue3vite npm install kjgl77/datav-vue3全局引入 // main.ts中全局引入 import { createApp } from vue import Da…

【JVM】总结篇-字节码篇

字节码篇 Java虚拟机的生命周期 JVM的组成 Java虚拟机的体系结构 什么是Java虚拟机 虚拟机&#xff1a;指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统 &#xff0c;是物理机的软件实现。常用的虚拟机有VMWare&#xff0c;Visual Box&…

国内Ubuntu环境Docker部署Stable Diffusion入坑记录

国内Ubuntu环境Docker部署Stable Diffusion入坑记录 本文旨在记录使用dockerpython进行部署 stable-diffusion-webui 项目时遇到的一些问题&#xff0c;以及解决方案&#xff0c;原项目地址: https://github.com/AUTOMATIC1111/stable-diffusion-webui 问题一览&#xff1a; …

音频进阶学习九——离散时间傅里叶变换DTFT

文章目录 前言一、DTFT的解释1.DTFT公式2.DTFT右边释义1&#xff09; 复指数 e − j ω n e^{-j\omega n} e−jωn2&#xff09;序列与复指数相乘 x [ n ] ∗ e − j ω n x[n]*e^{-j\omega n} x[n]∗e−jωn复指数序列复数的共轭正交正交集 3&#xff09;复指数序列求和 3.DTF…