C++修炼:模板进阶

news2025/5/13 8:38:14

        Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》

欢迎点赞,关注!

        我们之前已经介绍并使用过模板了,现在我们说一下模板的更深层次的用法。当然了这一部分内容很简单,再加上我们不是第一次接触模板了,所以很快就能理解。

1、非类型模板参数

        这个很好理解,就是我们之前模板参数都是用class和typename=后面的参数类型,但是这个非类型形参就是我们可以直接用一个常量(注意得是常量)作为模板的参数。

#include<iostream>
using namespace std;
template<class T,size_t N=100>
class A
{
public:
	A()
		:_a = N;
	{}
private:
	T _a;
};

        需要注意的是,这个非类型模板参数不能是浮点数,类对象和字符串。非类型模板参数在编译期就能确定结果。

2、模板的特化

2.1函数模板特化

        其实就是模板的特殊化处理。比如说我们想写一个比较函数,我们给出以下这样的函数模板:

template<class T>
bool cmp(T x, T y)
{
	return x > y;
}

        但这个时候问题来了,我们传两个int类型没问题,两个double类型没问题,两个指针类型呢?他就会按照两个地址来比较,可是我们不想比地址啊,我们想比地址存储的值,所以说我们直接特殊化处理,当传入两个对象为指定类型时就调用我们特化的函数模板。

#include<iostream>
using namespace std;

template<class T>
bool cmp(T x, T y)
{
	return x > y;
}

template<>//这个地方不能填东西
bool cmp<int*>(int* a, int* b)
{
	return *a > *b;
}

int main()
{
	int a = 1;int b = 2;
	double c = 1.6;double d = 1.2;

	int* e = &a;
	int* f = &b;

	cout<<cmp(a, b)<<" ";
	cout << cmp(c, d) << " ";

	cout << cmp(e, f) << " ";//走特化
	return 0;
}

输出结果:

        当然其实这里我们直接把这个函数写出来就行(因为有现成先用现成),没必要弄个特化出来。

 2.2、类模板特化

         类模板特化分全特化和偏特化。全特化就是指定所有的模板参数,偏特化就是指定一部分模板参数,或者说进一步限制了参数

#include<iostream>
using namespace std;
template<class T,class V>
class A
{
public:
	A()
	{
		_a = 100;
		_b = 200;
	}
private:
	T _a;
	V _b;
};

//全特化
template<>
class A<int,double>
{
public:
	A()
	{
		_a = 100;
		_b = 200;
	}
private:
	int _a;
	double _b;
};

//偏特化
template<class T>
class A<T,int>
{
public:
	A()
	{
		_a = 100;
		_b = 200;
	}
private:
	T _a;
	int _b;
};

        当然他也符合有现成先用现成。 

        下面这种限制类型也属于偏特化:

template<class T,class V>
class A<T*, V*>
{
public:
	A()
	{
		_a = 100;
		_b = 200;
	}
private:
	T _a;
	V _b;
};

        如果我们实例化A<int*,char*>这样一个对象就会调用我们上面偏特化的代码。

        接下来我们介绍一个typename特有的应用场景。我们实现这样的一个类模板:

template<class Container>
void Print(const Container& con)
{
	typename Container::iterator if = con.begin();
	while ()
	{
		...
	}
	...
}

        我们想取适配器的迭代器,但是问题是我们的编译器不知道iterator是个什么,他可能是个变量也可能是个类(我们之前模拟实现list的时候iterator就是个类),那我们就可以在前面加上typename告诉编译器这是个类。 

3、模板的分离编译

         首先我们程序运行需要经过几个主要步骤:

        (1)预处理

        这个阶段主要是编译器来做的。编译器展开头文件,替换宏操作,去掉注释,输出一个纯文本文件(.i或.ii)

        (2)编译

        使用编译工具g++clang++等将预处理阶段输出的纯文本文件转汇编,并且检查语法。如果语法有问题就会编译报错。如果编译没问题就输出一个.s文件。

        (3)汇编

        使用汇编工具将我们.s文件转换为二进制机械码(让电脑能读懂的语言),然后输出一个.o或.obj文件。常用的windows系统输出的是.obj文件,大家可能也注意到过。

        (4)链接

        合并多个目标文件,并输出a.out或.exe文件(windows)。今天我们只要看的就是这个链接。

        首先我们说什么是分离编译。我们之前模拟实现stl中的容器是非常习惯写一个.h头文件放所有函数声明,然后一个.cpp文件写所有函数,然后再写一个.cpp文件放测试函数。如果我们把模板函数的声明和定义分开放到不同文件那就没办法正常运行。

test.h

#pragma once
#include<iostream>
//光声明没定义
template<class T>//模板函数
int add(T a, T b);

int mul(int a, int b);//普通函数

testt.cpp(存放函数定义)

#define _CRT_SECURE_NO_WARNINGS 1
#include"test.h"
//光定义没声明
template<class T>
int add(T a, T b)
{
	return a + b;
}

int mul(int a, int b)
{
	return a * b;
}

main.cpp(测试函数)

#include"test.h"
using namespace std;

int main()
{
	int x = add(1, 2);//编译报错:无法解析的外部符号
	int y = mul(1, 2);//普通函数支持分离编译

	cout << x << endl;
	cout << y << endl;
	return 0;
}

         为什么会编译报错呢?我们简单理解一下:在.h文件中只有声明没有定义。在写定义的cpp文件中有定义,但是我不知道实例化成什么,没有办法正常实例化。因为编译器不确定T到底是什么东西。那为什么我们直接把定义放到.h文件中是可以运行的呢?

        我们这样理解,模板函数的定义是既有定义,又有声明。但是我们如果再.h里面写了声明,把定义放到别的文件中,我们走的函数要先找声明,他找的是我们.h中的声明,而我们的定义自带的声明就失效了。

        那么怎么解决分离编译的问题呢?

        第一,我们显示实例化出一份来。

#define _CRT_SECURE_NO_WARNINGS 1
#include"test.h"
//光定义没声明
template<class T>
int add(T a, T b)
{
	return a + b;
}
template<>
int add(int a, int b)
{
	return a + b;
}

int mul(int a, int b)
{
	return a * b;
}

        程序可以运行,但是第一,太麻烦了。第二,我们如果传入的不是int,int呢?如果传int, double或者int char呢?我们需要特化出很多份来。大可不必。

        第二,就是你别分离编译(就是这么直接)。一份优秀的代码模板函数不会分离编译。

4、模板总结

使用模板的优点:

        1、模板复用了代码,节省资源,更快地迭代器开发,C++的标准模板库(STL)因此而产生。

        2、增强了代码的灵活性。

缺陷:

        1、会导致代码膨胀问题,导致编译时间变长。

        2、出现模板编译错误时,错误信息非常凌乱,不易定位错误。

        好了,今天的内容就分享到这,我们下期再见!

 

 

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

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

相关文章

android-ndk开发(10): use of undeclared identifier ‘pthread_getname_np‘

1. 报错描述 使用 pthread 获取线程名字&#xff0c; 用到 pthread_getname_np 函数。 交叉编译到 Android NDK 时链接报错 test_pthread.cpp:19:5: error: use of undeclared identifier pthread_getname_np19 | pthread_getname_np(thread_id, thread_name, sizeof(thr…

UI自动化测试框架:PO 模式+数据驱动

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. PO 设计模式简介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成…

Java笔记4

第一章 static关键字 2.1 概述 以前我们定义过如下类&#xff1a; public class Student {// 成员变量public String name;public char sex; // 男 女public int age;// 无参数构造方法public Student() {}// 有参数构造方法public Student(String a) {} }我们已经知道面向…

2025年渗透测试面试题总结-渗透测试红队面试八(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 渗透测试红队面试八 二百一十一、常见中间件解析漏洞利用方式 二百一十二、MySQL用户密码存储与加密 …

MiniMind:3块钱成本 + 2小时!训练自己的0.02B的大模型。minimind源码解读、MOE架构

大家好&#xff0c;我是此林。 目录 1. 前言 2. minimind模型源码解读 1. MiniMind Config部分 1.1. 基础参数 1.2. MOE配置 2. MiniMind Model 部分 2.1. MiniMindForCausalLM: 用于语言建模任务 2.2. 主干模型 MiniMindModel 2.3. MiniMindBlock: 模型的基本构建块…

如何进行前端性能测试?--性能标准

如何进行前端性能测试&#xff1f;–性能标准 前端性能测试指标&#xff1a; 首次加载阶段 场景&#xff1a;用户首次访问网页&#xff0c;在页面还未完全呈现各种内容和功能时的体验。重要指标及原因 首次内容绘制&#xff08;FCP - First Contentful Paint&#xff09;​…

通信网络编程——JAVA

1.计算机网络 IP 定义与作用 &#xff1a;IP 地址是在网络中用于标识设备的数字标签&#xff0c;它允许网络中的设备之间相互定位和通信。每一个设备在特定网络环境下都有一个唯一的 IP 地址&#xff0c;以此来确定其在网络中的位置。 分类 &#xff1a;常见的 IP 地址分为 I…

Off-Policy策略演员评论家算法SAC详解:python从零实现

引言 软演员评论家&#xff08;SAC&#xff09;是一种最先进的Off-Policy策略演员评论家算法&#xff0c;专为连续动作空间设计。它在 DDPG、TD3 的基础上进行了显著改进&#xff0c;并引入了最大熵强化学习的原则。其目标是学习一种策略&#xff0c;不仅最大化预期累积奖励&a…

热门CPS联盟小程序聚合平台与CPA推广系统开发搭建:助力流量变现与用户增长

一、行业趋势&#xff1a;CPS与CPA模式成流量变现核心 在移动互联网流量红利见顶的背景下&#xff0c;CPS&#xff08;按销售付费&#xff09;和CPA&#xff08;按行为付费&#xff09;模式因其精准的投放效果和可控的成本&#xff0c;成为企业拉新与用户增长的核心工具。 CPS…

Linux系统管理与编程15:vscode与Linux连接进行shell开发

兰生幽谷&#xff0c;不为莫服而不芳&#xff1b; 君子行义&#xff0c;不为莫知而止休。 【1】打开vscode 【2】点击左下角连接图标 【3】输入远程连接 选择合适的操作系统 输入密码&#xff0c;就进入Linux环境的shell编程了。 在vscode下面粘贴拷贝更方便。比如 然后在v…

RabbitMQ概念详解

什么是消息队列&#xff1f; 消息队列是一种在应用程序之间传递消息的技术。它提供了一种异步通信模式&#xff0c;允许应用程序在不同的时间处理消 息。消息队列通常用于解耦应用程序&#xff0c;以便它们可以独立地扩展和修改。在消息队列中&#xff0c;消息发送者将消息发送…

linux基础操作5------(shell)

一.前言 本文来介绍一下linux的shell&#xff0c;除了最后的那个快捷键&#xff0c;其他的还是做一个了解就行了。 Shell&#xff1a; 蛋壳的意思&#xff0c;是linux中比较重要的一个概念&#xff0c;所有的命令其实都称之为shell命令。 看图解&#xff1a;shell就是内核的一…

BUUCTF 大流量分析(三) 1

BUUCTF:https://buuoj.cn/challenges 文章目录 题目描述&#xff1a;密文&#xff1a;解题思路&#xff1a;flag&#xff1a; 相关阅读 CTF Wiki BUUCTF | 大流量分析 &#xff08;一&#xff09;&#xff08;二&#xff09;&#xff08;三&#xff09; 题目描述&#xff1a; …

vLLM中paged attention算子分析

简要分析vLLM中PA的代码架构和v1与v2的区别 vLLM版本&#xff1a;0.8.4 整体结构分析 首先从torch_bindings.cpp入手分析&#xff1a; 这里可以看到vLLM向pytorch中注册了两个PA算子&#xff1a;v1和v2 其中paged_attention_v1和paged_attention_v2分别实现在csrc/attentio…

多样本整合Banksy空间聚类分析(Visium HD, Xenium, CosMx)

在空间数据分析中&#xff0c;传统的单细胞聚类算法&#xff0c;例如Seurat和Scanpy中的lovain和leiden等聚类算法&#xff0c;通常在处理空间数据时忽略了空间信息。然而&#xff0c;由于细胞状态受其周围细胞的影响&#xff0c;将转录组数据与细胞的空间信息结合起来进行聚类…

使用 OAuth 2.0 保护 REST API

使用 OAuth 2.0 保护 REST API 使用 OAuth 2.0 保护 REST API1.1 不安全的api1.2 安全默认值安全默认值Spring Security 默认值 需要对所有请求进行身份验证Servlet、过滤器和调度程序安全优势 使用所有请求的安全标头进行响应缓存标头 严格传输安全标头内容类型选项需要对所有…

解决下拉框数据提交后回显名称不对

问题背景描述 页面组件使用 antd 的 Select 组件&#xff0c;下拉框的 options 数据是动态获取的&#xff0c;基本就是有value 和 label 属性的对象数组。 提交数据后&#xff0c;我们有一个保存草稿的操作&#xff0c;支持返回或者刷新页面&#xff0c;浏览其他页面之后通过其…

lenis滑动插件的笔记

官网 lenis - npm 方法一&#xff1a;基础判断&#xff08;推荐&#xff09; 通过 Lenis 自带的 scroll 和 limit 属性直接判断&#xff1a; const lenis new Lenis()// 滚动事件监听 lenis.on(scroll, ({ scroll, limit }) > {const distanceToBottom limit - scroll…

Android Framework

Android 分区 /boot&#xff1a;存放引导程序&#xff0c;包括内核和内存操作程序。/system&#xff1a;相当于电脑 C 盘&#xff0c;存放 Android 系统及系统应用。/recovery&#xff1a;恢复分区&#xff0c;可以进入该分区进行系统恢复。/data&#xff1a;用户数据区&#…

OpenMCU(六):STM32F103开发板功能介绍

概述 距上一篇关于STM32F103的FreeRTOS博客的发布已经过去很长时间没有更新了。在这段时间内&#xff0c;大家可以看到博主发表了一系列的关于使用qemu 模拟实现STM32F103的博客&#xff0c;博主本来想借助qemu开发stm32F103相关的一些软件功能&#xff0c;博主开发出来并成功运…