【C++】从零开始认识泛型编程 — 模版

这篇具有很好参考价值的文章主要介绍了【C++】从零开始认识泛型编程 — 模版。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++】从零开始认识泛型编程 — 模版,从零开始的C++生活,c++,java,jvm,算法,开发语言,visualstudio

送给大家一句话:
尽管眼下十分艰难,可日后这段经历说不定就会开花结果。总有一天我们都会成为别人的回忆,所以尽力让它美好吧。 – 岩井俊二

\\\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 ////
\\\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 ////
\\\ ⱶ˝୧(๑ ⁼̴̀ᐜ⁼̴́๑)૭兯 ////


1 前言

泛型编程是C++中十分关键的一环,泛型编程是C++编程中的一项强大功能,它通过模板提供了类型无关的代码,使得C++程序可以更加灵活和高效,极大的简便了我们编写代码的工作量。

泛型编程作为一种编程范式的主要优点包括:

  • 代码复用:同一个算法或数据结构可以用于不同的数据类型,提高了代码的复用性。
  • 性能:由于在编译时就已经知道具体的数据类型,因此编译器可以生成针对该类型的优化代码。
  • 类型安全:泛型编程仍然可以进行类型检查,从而减少运行时错误。

泛型编程它允许开发者编写独立于数据类型的算法和函数。在C++中,泛型编程主要通过模板(Templates)来实现。模板允许编写代码时使用抽象的数据类型,这些数据类型在编译时会被具体的类型所替换。

我们来看一个简单的例子:假如我想要编写一个求和函数,那么传统的写法是:

//光是简单的这三种常见类型的自身我们都需要写许多代码!
int sum(int a, int b)
{
	return a + b;
}
float sum(float a, float b)
{
	return a + b;
}
double sum(double a, double b)
{
	return a + b;
}

而通过使用模版就可以极大的简便我们的过程:

template<class A >
A sum(A a, A b)
{
	return a + b;
}

使用一个函数就可以实现多种类型的求和,极大的提高了代码的复用率!下面我们就来学习模版!!!

C++中的模板分为两类:函数模板(Function Templates)和 类模板(Class Templates);

2 函数模板

什么是函数模版

函数模板(Function Templates):允许定义一个函数,它可以接受任何类型的参数。编译器会根据传递给函数的实际参数类型来实例化函数的特定版本。
上面的函数就是使用的函数模版。

template<class A >
A sum(A a, A b)
{
  return a + b;
}

在这个例子中,sum 函数可以接受任何类型的参数(包括自定义类型),只要该类型支持比较操作。

如何使用函数模版

函数模版的格式是:

//需要几个模版就使用几个
template<typename T1, typename T2,......,typename Tn>
//写入对应函数即可

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

函数模板是C++中的一项强大特性,它本质上并非一个具体的函数实体,而更像是编译器生成具体类型函数的蓝图。当我们定义一个函数模板时,我们实际上是在描述一个能够处理多种数据类型的算法框架。编译器会根据这个框架,在程序中使用模板的具体实例时,自动生成对应的具体类型函数。只有使用了才会生成实例化函数哦!!!!

这样的设计理念,使得模板成为了一种将重复性的工作抽象化、自动化的工具,从而极大地提高了代码的复用性和开发效率。简而言之,函数模板让编译器承担了生成多样化函数实例的职责,让程序员能够专注于逻辑和结构,而不是繁琐的细节。

ps: 函数模版就像是让编译器干苦力,从而减去我们的工作量。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将A确定为double类型,然
后产生一份专门处理double类型的代码:
【C++】从零开始认识泛型编程 — 模版,从零开始的C++生活,c++,java,jvm,算法,开发语言,visualstudio
就这样编译器生成一个个函数,将模版实例化,这是一种隐式实例化。
我们在使用过程中可以通过显示实例化与隐式实例化来进行实例化

  1. 显示实例化:在函数名后的<>中指定模板参数的实际类型sum<int>(a,b) ,直接表明想要进行什么数据类型的函数即可。
  2. 隐式实例化:让编译器根据实参推演模板参数的实际类型,也就是正常使用函数,让编译器去处理类型(可能会发生类型转换,存在隐患)。

调用规则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

2 类模板

什么是类模版

类模板(Class Templates):允许定义一个类,其成员函数和方法可以操作任何类型的数据。与函数模板类似,编译器会根据使用时指定的类型来实例化类的特定版本。我们之前实现的vector等各种容器都使用到了类模版,通过类模版我们可以适配各种数据类型,省去重复造轮子的过程。

template <typename T>
class Stack {
public:
    void push(T value);
    T pop();
    bool isEmpty();
private:
    std::vector<T> elems;
};

在这个例子中,Stack 类可以被用来创建任何类型数据的堆栈。

如何使用类模版

与函数模版类似,我们在类声明的前面加上:

//需要几个模版就使用几个
template<typename T1, typename T2,......,typename Tn>
class ClassName
{

};

然后在类声明里面就可以直接使用我们的模版类型。

对于类模版的实例化是很关键的:

vector<int> num;
stack<string> st;
queue<char> q;
//在迭代器中更是好用
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;

我们加入对应的模版参数即可!!!

C++标准模板库(Standard Template Library,STL)是泛型编程在C++中的一个典型应用,它提供了一系列模板化的数据结构和算法,如向量(vector)、列表(list)、队列(queue)、栈(stack)、排序算法等,这些都可以用于任何符合特定要求的类型。

4 特别注意

通过上述的介绍,就可以进行使用模版来进行代码的编写了。但是仍然有一些注意事项!!!

4.1 非类型模板参数

模板参数分类类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

就比如STL 中有一个这样的容器array(很鸡肋,一般不使用,而且由于是静态数组,直接开在栈区,容易造成栈溢出),如果我们想要一个静态数组,就可以通过它来创建:

// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public:
	T& operator[](size_t index) { return _array[index]; }
	const T& operator[](size_t index)const { return _array[index]; }
	size_t size()const { return _size; }
	bool empty()const { return 0 == _size; }
private:
	T _array[N];//可以当做常量来使用
	size_t _size;
};

int main()
{
	array<int, 10> arr1;
	array<int, 1000> arr2;
	return 0;
}

通过传入的参数,编译器会生成两个不同的类,类的模版参数就是给定参数。
在C++20之前,只支持整型作为非类型模版参数(char , short , int , long long… )

4.2 模版缺省值

像函数参数一样,模版参数也支持使用缺省值!!!
使用缺省值就可以方便我们传入参数了:

//这里就是使用了缺省值
template<class T, size_t N = 10>
class array
{

};

另外再优先队列里也有很重要的使用:

//    默认底层容器是vector<T> , 默认用来比较的仿函数是 less<T>
template<class T , class Container = vector<T> , class compare = less<T> >
class priority_queue
{
};

4.3 编译细节

注意看下面的代码,我们在[ ] 重载中加入了一个size(1),明显不和语法规范,但是我们来看编译会出现什么现象:

template<class T, size_t N = 10>
class array
{
public:
	T& operator[](size_t index) { 
		assert(index < N);
		//明显错误
		size(1);
		
		return _array[index]; 
	}
	const T& operator[](size_t index)const { return _array[index]; }
	size_t size()const { return _size; }
	bool empty()const { return 0 == _size; }
private:
	T _array[N];//可以当做常量来使用
	size_t _size;
};

int main()
{
	array<int, 10> arr1;
	return 0;
}

来看:
【C++】从零开始认识泛型编程 — 模版,从零开始的C++生活,c++,java,jvm,算法,开发语言,visualstudio
我们的代码居然可以正常运行!!!这是怎么回事儿???

因为编译器在遇到模版时会进行下面操作:

  1. 根据模版的实例化形成模版半成品
  2. 实例化成具体的类/函数
  3. 进行语法编译

但是这里又增加了一个新的概念:按需实例化!!!没有实例化之前只会进行简单的框架检查。
也就是只有使用对应函数才会进行函数的实例化,才会进行语法编译,才会报错!!!
没有调用operator[ ],所以operator[ ] 有调用参数不匹配,就没有检查出来。
【C++】从零开始认识泛型编程 — 模版,从零开始的C++生活,c++,java,jvm,算法,开发语言,visualstudio
所以只有我们使用[ ] 重载函数时,才会进行检查!!!

4.4 模版特化

模版特化就是指把模版的参数进行确定,就进行了特殊化:
来看一段代码:

#include<iostream>

using namespace std;


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

template<>
class Data<int, char>
{
public:
	Data() { cout << "特化:Data<int, char>" << endl; }
private:
	int _d1;
	char _d2;
};

void TestVector()
{
	Data<int, int> d1;
	Data<int, char> d2;
}

int main()
{
	TestVector();
	return 0;
}

我们运行看看:
【C++】从零开始认识泛型编程 — 模版,从零开始的C++生活,c++,java,jvm,算法,开发语言,visualstudio

得到的是这样的结果。因为如果类的模版参数与模版特化一致,那么就会进行特化的模版来进行实例化。

比较复杂一点点的用法是指针特化

class Data
{
public:
	Data(int a, char b) 
	{  
		_d1 = a;
		_d2 = b;
	}
	bool operator<(Data d)
	{
		return _d1 < d._d1;
	}
private:
	int _d1;
	char _d2;
};

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

}

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

void TestVector()
{
	Data* d1 = new Data(1,'c');
	Data* d2 = new Data(2,'c');
	Less(d1,  d2);
}

int main()
{
	TestVector();
	return 0;
}

这样就是对指针进行特化
【C++】从零开始认识泛型编程 — 模版,从零开始的C++生活,c++,java,jvm,算法,开发语言,visualstudio
如果加上这个:


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

那么就会优先执行这个指针模版。
总的来说,函数模版真不如直接使用函数重载!!!

特化分为:全特化与偏特化

  1. 全特化即是将模板参数列表中所有的参数都确定化
  2. 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
    • 部分特化将模板参数类表中的一部分参数特化,如上面的例子。
    • 参数更进一步的限制,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。如上面的例子

类特化的是使用场景主要是在仿函数中进行使用,比如我们之前实现优先队列,在里面我们直接使用:

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

可以适配更多的类型指针。

4.5 模版的分离编译

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

模版不支持分离编译,如果声明与定义写到两个文件里,就会报错。

// 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;
}

这样就会发生报错!!!链接错误
链接错误:是在语法没问题情况下,链接的时候,一个函数声明去其他文件寻找函数定义,找不到就会发生链接错误。

那为什么寻找不到呢???明明我们写了函数定义。
因为 a.cpp下的函数定义没有实例化,调用函数时仅仅是声明知道了使用什么模版类型,而函数定义不知道使用什么模版参数,那自然无法实例化!!!

解决方法很简单:文章来源地址https://www.toymoban.com/news/detail-860535.html

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h就很好的。推荐使用这种。因为.h文件预处理展开后,实例化模版时,既有声明又有定义,直接就实例化了,就有函数地址了。不需要链接时再去找。
  2. 模板定义的位置显式实例化。这种方法真不实用,真不推荐使用。这样模版还有什么意义!?
// a.cpp

//template<class T>
//T Add(const T& left, const T& right)
//{
//	return left + right;
//}
template<>
int Add(const int& left, const int& right)
{
	return left + right;
}
template<>
double Add(const double& left, const double& right)
{
	return left + right;
}

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

到了这里,关于【C++】从零开始认识泛型编程 — 模版的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++模版简单认识与使用

    目录 前言: 1.泛型编程 2.函数模版 3.类模版 为什么要有类模版?使用typedef不行吗? 类模版只能显示实例化: 注意类名与类型的区别: 注意类模版最好不要声明和定义分离: 总结: 1.泛型编程 编写与类型无关的通用代码,是代码复用的一种手段,模版是泛型编程的基础。

    2024年04月17日
    浏览(24)
  • 从零开始的嵌入式Linux生活(一) 背景介绍

    近年来(截至2023年3月),随着各种各样的因素:实体经济、米国制裁、芯片热、智能汽车等, 嵌入式软件开发(Embedded Software)越来越火热,众多的芯片公司、应用方案公司、甚至是代理商公司如雨后春笋般成立; 各大招聘网站上“嵌入式开发”“驱动开发”等岗位也成为

    2023年04月09日
    浏览(34)
  • 【Linux】从零开始认识进程 — 前篇

    我从来不相信什么懒洋洋的自由。我向往的自由是通过勤奋和努力实现的更广阔的人生。。——山本耀司 学习进程,我们需要对计算机操作系统 有一个初步的了解,也就是经典的冯诺依曼体系: 计算机的逻辑结构。冯·诺依曼从逻辑入手,他的逻辑设计具有以下特点: (

    2024年03月18日
    浏览(46)
  • 【Linux】从零开始认识进程 — 中篇

    今天我们继续学习进程,首先送给大家一句话: 如果痛恨所处的黑暗,请你成为你想要的光。 —— 顾城 进程的路径是可以改变的, 每个进程在启动的时候,会记录自己当前在哪个路径下启动。 我们可以使用 fopen (\\\"log.txt\\\",“w”) 来进行使用,该函数会在路径下创建一个新文

    2024年03月22日
    浏览(25)
  • 从零开始面向对象编程 Java & Day 5,6 (美颜相机 Day 1)

    源代码如下: 使用了数组来存储获取鼠标的位置,并在合适的时间对已获取的点进行重置(很简单,应该都能看懂) 总的来说还是比较简单的,变化不多,只需要稍微注意一下怎么处理多边形的点、边和闭合情况即可(同样的,看不懂可以私信噢!) 因为每天要做的事情稍

    2024年02月22日
    浏览(29)
  • 从零开始学习 Java:简单易懂的入门指南之网络编程(三十七)

    1.1 网络编程概述 计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。 网络编程 在网络通信协议下,不同计算机上运

    2024年02月08日
    浏览(33)
  • 从零开始学运算放大器笔记一 | 认识运算放大器

    1. 运算放大器简介  2. 运算放大器的分析前提 3. 运算放大器的重要参数和测量方法(一)          运算放大器(又称”运放“,英文全拼为Operation Amplifier,缩写为OP AMP)是一种模拟电路模块,它采用差分电压输入,产生单端电压输出。它可以对输入信号进行放大以及加、

    2024年02月14日
    浏览(33)
  • 从零开始学ZYNQ(FPGA)笔记二 | 认识学习内容

    目录 1. 认识FPGA 什么是FPGA FPGA的编程过程  2. 认识ARM 什么是ARM ARM与FPGA的区别 ARM与Linux 3. 认识ZYNQ ZYNQ与FPGA的区别 ZYNQ的\\\"ARM\\\"和\\\"FPGA\\\" 关于PL 关于PS 4. 学习用板载资源 5. 总结         FPGA是一种集成电路,它可以在制造后由客户或设计者根据需要配置电路功能 。FPGA的内部由可

    2024年02月08日
    浏览(40)
  • 【从零开始学习Redis | 第八篇】认识Redis底层数据结构(下)

    目录 前言:   ZipList: Ziplist的特性: QucikList: QuicList特征: SkipList: 跳表特征: RedisObijct:  小心得: 总结:           在现代软件开发中,数据存储和处理是至关重要的一环。为了高效地管理数据,并实现快速的读写操作,各种数据库技术应运而生。其中,Redis作为一种

    2024年04月12日
    浏览(39)
  • Java语言-----泛型的认识

    目录 一.什么是泛型 二.泛型类的使用 2.1泛型类的定义  2.2泛型类的数组使用 三.泛型的上界  四.泛型的方法 五.泛型与集合 😽个人主页: tq02的博客_CSDN博客-C语言,Java领域博主  🌈梦的目标:努力学习,向Java进发,拼搏一切,让自己的未来不会有遗憾。  🎁欢迎各位→点

    2023年04月23日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包