<C++> 模板-下

news2024/11/16 5:48:19

目录

前言

一、非类型模板参数

二、类模板的特化

1. 概念

2. 函数模板特化

3. 类模板特化

4. 全特化

5. 偏特化

5.1 特化部分参数

5.2 对某些类型的进一步限制

三、模板的分离编译

1. 概念

2. 分离编译

3. 解决方法

1. 显式实例化 

2.  在一个文件内写声明和定义

四、模板总结

1. 优点

2. 缺点

总结


前言

        一般情况下template<class / typename T> 中 classtypename 没有什么区别,但是有一些特殊情况,typename 与 class 是有区别的

        例如:我们泛型了Print函数,实现可以打印任意容器内容的功能

template<class Container>
void Print(const Container& v)
{
	Container::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	Print(v);

	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	Print(lt);
	return 0;
}

        但是编译不通过,编译器要求必须在 Container::const_iterator 之前加上 typename 作为前缀,即

	typename Container::const_iterator it = v.begin();

问题:为什么需要加上typename呢?

  • 这是因为编译器无法分辨 Container::const_iterator 究竟是什么,因为此时Container还没有实例化,所以 Container::const_iterator 它可能表示的是类的静态成员变量,也可能是类的内嵌类型(内部类或typedef的类型)
class A
{
public:
	int begin()
	{
		return 0;
	}
	static int const_iterator;
};

int main()
{

	A aa;
	A::const_iterator it = aa.begin();
    编译器报错,因为A::const_iteratot本身是变量,不是类型
    所以,如果没有typename,编译器是不知道它究竟是类型还是变量

	return 0;
}
  • Container可能是类型或对象,若为类型,语法可以通过;若为对象,语法错误

解决:

        在 Container::const_iterator 前加上typename,以此来告诉编译器后面的Container::const_iterator是一个类型,后面的行为是合乎语法的,等Container实例化之后,在确定后面是什么类型

总结:

  • Container是一个没有实例化的模板,那么都需要在Container::iterator 之前加上typename,即使是vector<T>类型
  • 只要我们使用没有实例化的类模板时,都需要加上typename声明,例如priority_queue模板参数less在使用未实例化的模板时就加上了typename

这里我们也可以使用auto关键字,

auto it = v.begin();

就不需要typename声明 。在Container之后 auto 自动推导类型


一、非类型模板参数

        有时候,我们可能需要非类型的模板参数,来实现所需的类

例如:当我们实现静态的栈时,需要提前知道数组需要开辟多大的空间,但是如果都统一的开同样大的空间,难免会遇到空间冗余或太小等情况。所以,当我们在实例化对象时,如果可以指定数组空间大小,那么就可以解决上面的问题,此时C++11就升级了模板,增加了非类型模板参数,以实现将一个常量作为模板参数

template<class T, size_t N>
class Stack
{

private:
	T _a[N];
	int _top;
};

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

	return 0;
}

对于非模板类型参数

        1. 只能为整型(char也可以),但例如double、string等类型都不可以

        2. 是常量        

因为非模板类型参数大部分都是用在T _a[N]情况,所以最初设计的是整型

在std中还有一个容器array,它就使用了size_t非模板类型参数

array<int, 10> a;

        它对比C语言的数组几乎没有任何优势,这就显得委员会更新的很鸡肋,array的优点几乎只有对越界情况检查,越界读写都能检查,而普通数组不能检查越界读,少部分越界写可以检查,但如果仅此优点,vector完全可以替代。

 

二、类模板的特化

1. 概念

        在类名或函数名后用<>指定特化的参数,特化后的类的成员变量根据自己的需求编写,因为它与原模版已经成为了不同的类,成为了分支。一般特化的类都是很小的类

        顾名思义,对模板类型进行特殊化处理,如果调用时,实参类型与特化的模板参数类型匹配,则优先调用特化的模板函数或类。

        没有特化也可以,但是有特化会更方便

        通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果。

2. 函数模板特化

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

int main()
{
	cout << Less(1, 2) << endl;

	int a = 1, b = 2;
	cout << Less(&a, &b) << endl;
	return 0;
}

        当我们实参传递地址时,比较的是地址的大小,这与我们的意愿相反,这时我们可以使用特化这一功能,针对 int* 类型特化。在调用时,有现成的函数的就用现成的;没现成的用模板实例化合适的函数:

template<class T>
bool Less(T left, T right)
{
	return left < right;
}

template<>
bool Less<int*>(int* left, int* right)
{
	cout << "bool Less<int*>(int* left, int* right)" << endl;
	return *left < *right;
}

        但是,如果只是为了比较int型指针指向的值的大小,我们可以不使用模板,直接重载函数即可


bool Less(int* left, int* right)
{
	cout << "bool Less(int* left, int* right)" << endl;

	return *left < *right;
}

        如果是多类型指针,那么还是需要使用模板

template<class T>
bool Less(T* left, T* right)
{
	cout << "bool Less(T* left, T* right)" << endl;
	return *left < *right;
}

3. 类模板特化

        上面是针对函数模板举例,对于函数模板的特化,可以使用重载替代,但是对于类模板的特化就不同了

template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
};

        又例如priority_queue中Less仿函数的特化,因为在Less仿函数编写时,已经讲解了如果实参是Date*类型,那么每次比较的是指针的大小,这是错误的,所以我们可以特化类型Date*

template<class T>
class Less
{
public:
    bool operator()(const T& x, const T& y)
    {
        return x < y;
    }
};
template<>
class Less<Date*>
{
public:
    这里去掉&符号,因为const修饰不到&,会优先修饰*
    bool operator()(const Date* x, const Date* y)
    {
        return *x < *y;
    }
};

        特化后,就可以在实例化时只传一个参数,编译器用第一个模板参数去匹配,若与特化版本类型相同,则直接使用特化版本

priority_queue<Date*> pq;

        还可以改为模板T*,适配所有指针类型

template<class T1, class T2>
class Less<T1*, T2*>
{
public:
    bool operator()(const T* x, const T* y)
    {
        return *x < *y;
    }
};

4. 全特化

如果将全部的模板类型都特化即为全特化,全特化后template<>内将没有内容

template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
private:
    //T1 _d1;
    //T2 _d2;
};

        这样的特化,会在实参类型为int、double时被调用

5. 偏特化

5.1 特化部分参数

        只将一部分模板类型特化即为偏特化

        适配此类型(T, double)时,优先调用

template<class T1>
class Data<T1, double>
{
public:
	Data()
	{
		cout << "Data<T1, double>" << endl;
	}
private:
	//T1 _d1;
};

若第二个不是double,则会调用T1, T2模板

5.2 对某些类型的进一步限制

        例如:限制参数类型全部都是指针类型时

template<class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1*, T2*>" << endl;
	}
private:
};
template<>
class Data<int, double>
{
public:
	Data()
	{
		cout << "Data<int, double>" << endl;
	}
private:
    //T1 _d1;
    //T2 _d2;
};

        也可以特化引用

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

三、模板的分离编译

1. 概念

        一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式
        模板不支持直接的分离编译

2. 分离编译

// a.h
template<class T>
T Add(const T& left, const T& right);

// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
     return left + right;
}

// main.cpp
#include"a.h"
int main()
{
     Add(1, 2);
     Add(1.0, 2.0);
 
     return 0;
}

C/C++程序运行要经历四个阶段

        预处理——>编译——>汇编——>链接

预处理 :替换头文件,将.h文件在cpp文件展开,生成 .i 文件

编译:对程序按照语言特性进行词法、语法、语义分析是否错误,并确认已定义的函数的地址,只有声明的函数没有地址,检查无误后生成 .s 汇编文件。因为有声明,这是一种承诺,函数的定义部分编译器会在链接步骤拿着修饰后的函数名去其他文件符号表寻找,所以编译检查可以通过。

汇编:生成 .o 文件,即二进制文件

链接:将文件链接起来,生成 a.out文件

        程序链接错误,Add找不到函数地址,这就是模板分离编译的坏处 

3. 解决方法

1. 显式实例化 
template<class T>
T Add(const T& left, const T& right)
{
     return left + right;
}

template
class Add<int>;

template
class Add<double>;
  •  这样就失去了模板的意义,对于每一种类型都需要手动显式实例化,所以不推荐这种解决方法
2.  在一个文件内写声明和定义
  • 类内部一般将代码量较短的直接写在类内部,成为内联函数,对于代码量较长的模板函数,可以在类内部声明,在类外部但在同一文件内定义

四、模板总结

1. 优点

  • 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库STL因此而产生
  • 增强了代码的灵活性,如适配器、仿函数

2. 缺点

  • 模板会导致代码膨胀问题,导致编译时间变长
  • 出现模板编译错误时,错误信息很乱,不容易定位错误

总结

        模板还是有很多细节需要掌握的,了解模板知识后再练习大多数问题就可以解决了

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

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

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

相关文章

UE5、CesiumForUnreal实现加载GeoJson绘制盒体(Box)功能(StaticMesh方式)

文章目录 1.实现目标2.实现过程2.1 实现原理2.2 具体代码2.3 应用测试2.3.1 材质2.3.2 蓝图测试3.参考资料1.实现目标 与之前基于StaticMesh创建Polygon和Wall类似,本文通过读取本地GeoJson数据,在UE中以staticMeshComponent的形式绘制出盒体Box,支持Editor和Runtime模式,在…

HR人才测评,提高员工和岗位的适配度

作为中小企业领导&#xff0c;除了操心企业的经营管理&#xff0c;还得经常为内部运行做考虑。某个岗位总是缺少匹配的员工&#xff0c;而公司的员工如何进行岗位优化&#xff0c;这都是人资管理中经常遇到的难题&#xff0c;归根结底&#xff0c;是对人才评价的难题。 人才&a…

【PHY6222】simpleBLEPeripheral剖析

源码剖析 main.c main函数中所有使用extern引用外部的函数均无源码&#xff0c;具体细节不可知。 可以看到源码函数有以下几个&#xff1a; hal_rfphy_init&#xff1a; 相关参数&#xff1a; hal_init&#xff1a; jump_table.c 因为rom的code是不开源的&#xff0c;rom代…

ubuntu 安装 gparted

前提环境&#xff1a; 阿里云的源。 sudo apt update sudo apt upgrade sudo apt install gparted 搜索&#xff1a;

新能源充电桩工业4G路由器应用,推动绿色出行,响应环保理念

在智慧城市环保事业发展领域&#xff0c;新能源技术应用成熟&#xff0c;物联网技术越来越广泛&#xff0c;充电桩物联网成为了智慧城市建设的热门应用。充电桩作为新能源汽车的重要配套设施&#xff0c;对于节能减排和推动环保理念可持续发展具有重要意义。而工业4G路由器作为…

OpenLayers入门,OpenLayers6的WebGLPointsLayer图层样式运算符详解

专栏目录: OpenLayers入门教程汇总目录 前言 本章讲解使用OpenLayers6的WebGL图层显示大量点情况下,列举出所有WebGLPointsLayer图层所支持的所有样式运算符大全。 二、基于的OpenLayers版本 "ol": "^6.15.1"使用npm安装依赖npm install ol@6.15.1使…

Transformer——encoder

本文参考了b站的Eve的科学频道中的深入浅出解释Transformer原理和DASOU讲AI中的Transformer从零详解。 入浅出解释Transformer原理 Transformer从零详解 前言&#xff1a; 在自然语言识别中&#xff0c;之前讲过lstm&#xff0c;但是lstm有明显的缺陷&#xff0c;就是当文本过…

GNSS技术在灾害监测与应急响应中的关键作用

全球导航卫星系统&#xff08;GNSS&#xff09;技术在灾害监测与应急响应领域发挥着重要作用&#xff0c;为预防、监测和应对自然灾害提供了关键数据支持。本文将深入探讨GNSS技术在灾害监测与应急响应中的作用&#xff0c;并分析其对提高应对灾害能力的重要性。 一、GNSS在灾害…

78基于matlab的BiLSTM分类算法,输出迭代曲线,测试集和训练集分类结果和混淆矩阵

基于matlab的BiLSTM分类算法&#xff0c;输出迭代曲线&#xff0c;测试集和训练集分类结果和混淆矩阵&#xff0c;程序有详细注释&#xff0c;数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。

大模型的实践应用7-阿里的多版本通义千问Qwen大模型的快速应用与部署

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用7-阿里的多版本通义千问Qwen大模型的快速应用与部署。阿里云开源了Qwen系列模型,即Qwen-7B和Qwen-14B,以及Qwen的聊天模型,即Qwen-7B-Chat和Qwen-14B-Chat。通义千问模型针对多达 3 万亿个 token 的多语言数据进行了…

轻量封装WebGPU渲染系统示例<37>- 多个局部点光源应用于非金属材质形成的效果(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/BasePbrMaterialMultiLights.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class BasePbrMaterial…

5.4 Windows驱动开发:内核通过PEB取进程参数

PEB结构(Process Envirorment Block Structure)其中文名是进程环境块信息&#xff0c;进程环境块内部包含了进程运行的详细参数信息&#xff0c;每一个进程在运行后都会存在一个特有的PEB结构&#xff0c;通过附加进程并遍历这段结构即可得到非常多的有用信息。 在应用层下&am…

SQL基础理论篇(九):存储过程

文章目录 简介存储过程的形式定义一个存储过程使用delimiter定义语句结束符存储过程中的三种参数类型流控制语句 存储过程的优缺点参考文献 简介 存储过程Stored Procedure&#xff0c;SQL中的另一个重要应用。 前面说的视图&#xff0c;只能勉强跟编程中的函数相似&#xff…

datagrip只导出表结构

话不多说&#xff0c;直接上教程。 datagrip版本&#xff1a;2022.3 第一步&#xff0c;连接数据库 第二步&#xff0c;右击数据库&#xff0c;复制即可

vivado产生报告阅读分析14-时序报告10

Vivado IDE 中的例外报告 “ Report Exceptions ”对话框 在 AMD Vivado ™ IDE 中 &#xff0c; 选择“ Reports ” → “ Timing ” → “ Report Exceptions ” &#xff08; 报告 > 时序 > 例外报告 &#xff09; 即可打开“Report Exceptions ”对话框。 从“…

做接口自动化遇到的20个难点,记录下我是如何解决的!

我是一名接口自动化测试工程师&#xff0c;在公司中负责接口自动化测试的设计和执行。在公司中&#xff0c;接口自动化测试非常重要&#xff0c;因为公司的业务场景非常复杂&#xff0c;需要保证接口的质量。在这篇文章中&#xff0c;我将分享我在公司中接口自动化测试遇到的20…

Java JSON字符串替换其中对应的值

代码&#xff1a; public static void main(String[] args) { // String theData crmScene.getData();String theData "[{\"type\":1,\"values\":[\"审批中\",\"未交付\"],\"name\":\"status\"}]"…

UE4 基础篇十四:自定义插件

文末有视频地址和git地址 一、概念 虚幻里插件都是用C++写的,C++包括.h文件和.cpp文件,.h头文件通常包含函数类型和函数声明,cpp文件包含这些类型和函数的实现, 你为项目编写的所有代码文件都必须位于模块中,模块就是硬盘里的一个文件夹,包含名为“Build.cs”的C#文件…

政府采购变数大,联想还值不值得代理渠道商们“跟”?

文&#xff5c;新熔财经 作者&#xff5c;余一 “事业单位更换纯国产电脑”、“联想被排除在大订单之外”等消息下&#xff0c;联想硬件终端产品面临的问题日益严峻。 早在今年年初&#xff0c;联想集团&#xff08;下称联想&#xff09;掌门人杨元庆坦承&#xff0c;“智能…

深入了解Java 8 新特性:Stream流的实践应用(一)

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概一万多字&#xff0c;预计阅读时间长需要10分钟&#xff08;不要害怕字数过多&#xff0c;其中有一大部分是示例代码&#xff0c;读起…