模板再认识

news2024/12/23 17:55:38
在前面的文章中我写了关于模板的一些简单的认识,现在我们来再次认识模板

文章目录

  • 1.非类型模板参数
  • 2.模板特化
    • 1). 模板特化的写法
    • 2). 类模板特化
    • 3). 函数模板特化
    • 4). 模板全/偏特化
  • 3.模板分离编译

1.非类型模板参数

在模板中还有一种是非类型的模板参数。我们代码展示:

#define N 10

template<class T>
class stack {
private:
	T* _arr[N];
	size_t size;
	size_t capacity;
};

int main()
{
	stack<int> st1;


	return 0;
}

这是我们以前编写静态容器的方式,通过宏定义来实现,但是有一个缺陷,那就是我们假如还要建立一个大小为100的静态栈呢?把N改成一百吗?我那我们有想要一个大小为10的静态栈和大小为10000的静态栈呢?这时候就会非常浪费空间。所以这时候就有了非类型模板参数的出现。

template <class T, size_t N>
class stack {
private:
	T* _arr[N];
	size_t size;
	size_t capacity;
};

int main()
{
	stack<int, 10> st1;
	stack<int, 100> st2;


	return 0;
}

在这里插入图片描述
这样就可以解决上面的问题了。非类型模板参数目前只支持整形(char, short,int)
在这里插入图片描述
可以看到STL中有一个容器静态数组也用了非类型模板参数。

2.模板特化

关于模板我们有函数模板,类模板,模板中也有一种新的知识,叫做模板特化。

1). 模板特化的写法

1. 必须要先有一个基础的函数/类模板
2. 关键字template后面接一对空的尖括号<>
3. 函数/类名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。(针对函数模板)

2). 类模板特化

话不多说,上代码:

#include <iostream>

using namespace std;

namespace xxx{
	template<class T>
	class stack
	{
	public:
		stack()
		{
			cout << "stack():template<class T>" << endl;
		}
	};

	template<>
	class stack<int>
	{
	public:
		stack()
		{
			cout << "stack():template<>" << endl;
		}
	};
}

int main()
{
	xxx::stack<double> s1;
	xxx::stack<int> s2;

	return 0;
}

在这里插入图片描述
我们可以看到当两种实例化的对象他们所调用的构造函数是不同的,原因是当对象实例化的时候,他们会去找“最符合”的类来实例化自己。

下面那个类就是模板的特化。特化是为了处理一些类型错误的场景。下面举个例子:

class A
{
public:
	A(int aa, int a)
		:_aa(aa)
		, _a(a)
	{}

	bool operator>(const A& y) const
	{
		if (_aa > y._aa) return true;
		else if (_aa == y._aa)
		{
			if (_a > y._a) return true;
			else return false;
		}
		else return false;
	}
private:
	int _aa = 0;
	int _a = 0;
};


template <class T>
class Greater {
public:
	bool operator()(const T& x, const T& y)
	{
		return x > y;
	}
};

template <>
class Greater<A*> {
public:
	bool operator()(const A* x,  const A* y)
	{
		return *x > *y;
	}
};

int main()
{
	A a1(100, 10);
	A a2(120, 10);
	cout << Greater<A>()(a1, a2) << endl;


	A* pa1 = new A(100, 1);
	A* pa2 = new A(122, 1);
	cout << Greater<A*>()(pa1, pa2) << endl;


	return 0;
}

我们定义了这么一个类,他里面有两个int成员,比较大小的方法经过了重载。现在哦我们用一个仿函数,来比较他们的大小。
在这里插入图片描述

我们发现,我们相比较的是对象的内容,但是第二组比较显然是比较了他俩的地址大小(由于申请内存时申请的地方不确定,所以大小关系也就不确定),所以为了不出现这样的情况,我们对仿函数特化一下:

class A
{
public:
	A(int aa, int a)
		:_aa(aa)
		, _a(a)
	{}

	bool operator>(const A& y) const
	{
		if (_aa > y._aa) return true;
		else if (_aa == y._aa)
		{
			if (_a > y._a) return true;
			else return false;
		}
		else return false;
	}
private:
	int _aa = 0;
	int _a = 0;
};


template <class T>
class Greater {
public:
	bool operator()(T& x, T& y)
	{
		return x > y;
	}
};

template <>
class Greater<A*> {
public:
	bool operator()(A* x,  A* y)
	{
		return *x > *y;
	}
};

int main()
{
	A a1(100, 10);
	A a2(120, 10);
	cout << Greater<A>()(a1, a2) << endl;


	A* pa1 = new A(100, 1);
	A* pa2 = new A(122, 1);
	cout << Greater<A*>()(pa1, pa2) << endl;


	return 0;
}

在这里插入图片描述
可以发现这样就解决了。

3). 函数模板特化

同理照搬即可,只是写法上与类模板有点不一样

class A
{
public:
	A(int aa, int a)
		:_aa(aa)
		, _a(a)
	{}

	bool operator>(const A& y) const
	{
		if (_aa > y._aa) return true;
		else if (_aa == y._aa)
		{
			if (_a > y._a) return true;
			else return false;
		}
		else return false;
	}
private:
	int _aa = 0;
	int _a = 0;
};


template <class T>
bool Greater(const T& x, const T& y)
{
	return x > y;
}

template <>
bool Greater<A*>(const A* x, const A* y)
{
	return *x > *y;
}

int main()
{
	A a1(100, 10);
	A a2(120, 10);
	cout << Greater(a1, a2) << endl;


	A* pa1 = new A(100, 1);
	A* pa2 = new A(122, 1);
	cout << Greater(pa1, pa2) << endl;


	return 0;
}

如果把上述代码,运行的话,会发现有语法错误,错误出现在仿函数的特化中。只需要这么写就可以了:

template <class T>
bool Greater(const T& x, const T& y)
{
	return x > y;
}

template <>
bool Greater<A*>(A* const & x, A* const  & y)
{
	return *x > *y;
}

但是这么写有点恶心,但是我们换一种写法:

template <class T>
bool Greater(const T& x, const T& y)
{
	return x > y;
}

//template <>
//bool Greater<A*>(A* const & x, A* const  & y)
//{
//	return *x > *y;
//}

bool Greater(const A*  x, const A*  y)
{
	return *x > *y;
}

下面这个函数,当函数模板实例化出模板函数的时候会与那个函数构成重载。不需要模板特化就可以写的简单易懂,也能实现要求。所以类模板推荐使用特化,函数模板不推荐

4). 模板全/偏特化

这个很好理解,结合开始对模板特化的理解,类模板实例化的成模板类的时候会找“最匹配的”类来实例化:

template<class T1, class T2>
class A
{
public:
	A()
	{
		cout << "A:template<class T1, class T2>" << endl;
	}

};
//偏特化
template<class T1>
class A<T1, int>
{
public:
	A()
	{
		cout << "A:template<class T1>" << endl;
	}

};
//全特化
template<>
class A<int, int>
{
public:
	A()
	{
		cout << "A:template<>" << endl;
	}

};

int main()
{
	A<double, double> a1;
	A<double, int> a2;
	A<int, int> a3;
	return 0;
}

在这里插入图片描述

3.模板分离编译

我们先写三个文件:
test.cpp

#include "func.h"

int main()
{
	printf<int>();
	return 0;
}

func.cpp

#include "func.h"

template<class T>
void printf()
{
	cout << "void printf()" << endl;
}

func.h

#pragma once
#include <iostream>
using namespace std;

template<class T>
void printf();

我们写完后,发现写的没啥问题,但是一运行就出错了。出的是一个链接错误。这其中的原因是:
在形成可执行程序的过程中。首先要预处理,编译,汇编,链接过程最后才能形成可执行程序。
在编译过程检查语法的时候,test.cpp文件里因为有了print函数的声明,所以没有报错,而func.cpp中的print函数它是一个模板函数,模板是需要成为可执行程序后运行起来需要他时才会生成模板函数或模板类,所以编译器就没有实现这个函数。自然在链接过程就没有对应的函数地址来生成符号表。所以会报链接错误,所以我们应该实例化实现才可以:
func.cpp:

template<>
void printf<int>()
{
	cout << "void printf()" << endl;
}

类模板也会出现这样的情况。
test.cpp

#include "func.h"

int main()
{
	A<int> a;
	return 0;
}

func.cpp

#include "func.h"

template<class T>
A<T>::A()
{
	cout << "A()" << endl;
}

func.h

#pragma once
#include <iostream>
using namespace std;

template<class T>
class A {
public:
	A();
};

这样也会出现链接错误,也是得显式写明类型。

func.cpp

#include "func.h"

template<>
A<int>::A()
{
	cout << "A()" << endl;
}

所以,还是建议写模板的时候,不要声明定义分离了。

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

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

相关文章

【数组拷贝+二维数组遍历】

文章目录 前言数组的拷贝第一种方式&#xff1a;第二种方式:利用工具类拷贝 二维数组二维数组的三种定义打印二维数组的某个元素遍历二维数组 二维数组的每个元素是一维数组遍历二维数组&#xff08;优化&#xff09;打印出一个数组&#xff08;不是数组元素&#xff09;的方法…

linux性能分析(三)性能优化导轮

一 别再让Linux性能问题成为你的绊脚石 ① 学习历程 备注&#xff1a; 跟我的学习经历很相像刚工作时遇到的场景跟我现在一样,深深的无力感驱使我继续前行目标: 性能优化成为我的肌肉记忆,写代码的潜意识 ... ② 常见的问题 ③ 性能问题为什么这么难呢 思考&#xff1a…

顺序表ArrayList

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 顺序表 概念Arraylist构造方法相关方法遍历操作 自…

嵌入式实时操作系统的设计与开发(任意大小的内存管理)

任意大小的内存管理是根据用户需要为其分配内存&#xff0c;即用户需要多大内存就通过acoral_malloc2()为之分配多大内存&#xff0c;同时每块分配出去的内存前面都有一个控制块&#xff0c;控制块里记录了该块内存的大小。 同时未分配出去的内存也有一个控制块&#xff0c;寻…

守护进程深度分析

思考 代码中创建的会话&#xff0c;如何关联控制终端&#xff1f; 新会话关联控制终端的方法 会话首进程成功打开终端设备 (设备打开前处于空闲状态) 1、关闭标准输入输出和标准错误输出2、将 stdin 关联到终端设备&#xff1a;STDIN_FILENO > 03、将 stdout 关联到终端设…

FreeRTOS_队列

目录 1. 队列简介 1.1 数据存储 1.2 多任务访问 1.3 出队阻塞 1.4 入队阻塞 1.5 队列操作过程 1.5.1 创建队列 1.5.2 向队列发送第一个消息 1.5.3 向队列发送第二个消息 1.5.4 从队列中读取消息 2. 队列结构体 3. 队列创建 3.1 函数原型 3.1.1 函数 xQueueCreate…

网卡状态检测函数

内核中网卡的开启状态通过__LINK_STATE_START进行标识。网卡开启或关闭时&#xff0c;通过该标识会被置位。 内核专门提供了一个函数用于检测该标志位。函数定义如下&#xff1a; static inline bool netif_running(const struct net_device *dev) {return test_bit(__LINK_S…

YOLOv5源码中的参数超详细解析(1)— 项目目录结构及文件(包括源码+网络结构图)

前言:Hello大家好,我是小哥谈。作为初学者,为了后期能够熟练的使用YOLOv5进行模型的训练,首先必须做的就是了解YOLOv5项目目录结构中各文件以及参数的作用。本篇文章就向大家简单介绍一下每个文件的具体功能及作用,关于YOLOv5详细的代码解读后期会慢慢更新,欢迎大家收藏关…

YOLOv5改进实战 | GSConv + SlimNeck双剑合璧,进一步提升YOLO!

前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…

三十七、【进阶】SQL的explain

1、explain 2、基础使用 在使用explain关键字时&#xff0c;只需要在所执行语句前加上explain即可 mysql> explain select * from stu where id3; ---------------------------------------------------------------------------------------------------------- | id | s…

【MySQL】8.0新特性、窗口函数和公用表表达式

文章目录 1. 新增特性2. 移除旧特性2.1 优点2.2 缺点 3. 新特性1&#xff1a;窗口函数3.1 使用窗口函数前后对比3.2 窗口函数分类3.3 语法结构3.4 分类讲解3.4.1 序号函数3.4.1.1 ROW_NUMBER()函数3.4.1.2 RANK()函数3.4.1.3 DENSE_RANK()函数 3.4.2 分布函数3.4.2.1 PERCENT_R…

HiveServer2负载均衡

有多个HiveServer2服务时&#xff0c;可以借助Zookeeper服务实现访问HiveServer2的负载均衡&#xff0c;将HiveServer2的压力分担到多个节点上去。本文详细介绍HiveServer2负载均衡的配置及使用方法&#xff0c;请根据EMR集群&#xff08;普通集群和Kerberos集群&#xff09;的…

浏览器从输入url到渲染页面发生了什么?

浏览器从输入url到渲染页面发生了什么&#xff1f; 一、解析URL 首先浏览器做的第一步工作就是要对 URL 进行解析&#xff0c;浏览器会判断这个url的合法性 &#xff0c;以及是否有可用缓存&#xff08;如果有缓存即可以不用进行下一步的DNS域名解析&#xff09;&#xff0c;…

《代码随想录》刷题笔记——字符串篇【java实现】

反转字符串 https://leetcode.cn/problems/reverse-string/description/ 【双指针法&#xff1a;一前一后】 public void reverseString(char[] s) {for (int i 0; i < s.length / 2; i) {char temp s[i];s[i] s[s.length - 1 - i];s[s.length - 1 - i] temp;} }反转…

【软考中级】网络工程师:10.组网技术(理论篇)

交换机基础 交换机分类 > 根据交换方式分      - 存储转发式交换&#xff08;Store and Forward&#xff09;完整接收数据帧&#xff0c;缓存、验证、碎片过滤&#xff0c;然后转发。   优点&#xff1a;可以提供差错校验和非对称交换。   缺点&#xff1a;延迟大。…

【Qt控件之QTabWidget】介绍及使用

描述 QTabWidget类提供了一个带有选项卡的小部件堆栈。 选项卡小部件提供了一个选项卡栏&#xff08;参见QTabBar&#xff09;和一个“页面区域”&#xff0c;用于显示与每个选项卡相关联的页面。默认情况下&#xff0c;选项卡栏显示在页面区域的上方&#xff0c;但可以使用…

2023年拼多多双11百亿补贴新增单件立减玩法介绍

2023年拼多多双11百亿补贴新增单件立减玩法介绍 拼多多启动了11.11大促活动&#xff0c;主题为“天天11.11&#xff0c;天天真低价”。消费者享受多重优惠&#xff0c;包括满减、百亿补贴和单件立减等。百亿补贴新增玩法&#xff0c;有超过20000款品牌商品参与单件立减活动。 …

【内网穿透】五分钟搭建全球领先开源ERP:Odoo,并实现公网访问

目录 前言 1. 下载安装Odoo&#xff1a; 2. 实现公网访问Odoo本地系统&#xff1a; 3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着…

Vue3踩坑指南

vue.config.ts不起作用 关于项目 项目使用的还是vue-cli搭建的&#xff0c;底层还是webpack&#xff0c;没有使用新的vite搭建。 踩坑1&#xff1a;vue.config.ts不起作用 我本着既然是vue3 ts的项目&#xff0c;那么为了规范&#xff0c;项目中所有的js文件都得替换成ts文…

HanLP集成到Springboot及使用自定义词典

前言 HanLP集成到Springboot及使用自定义词典 文章目录 前言简介集成Springboot扩展使用自定义词典路径易错问题 简介 开源工具包&#xff0c;提供词法分析、句法分析、文本分析和情感分析等功能&#xff0c;具有功能完善、性能高效、架构清晰、语料时新、可自定义等特点。 官…