关于模板的大致认识【C++】

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

函数模板

函数模板的原理

template <typename T> //模板参数 ——类型 
void Swap(T& x1, T& x2)
{
	T tmp = x1;
	x1 = x2;
	x2 = tmp;
}
int main()
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;

	swap(a, b);
	swap(c, d);
	int* p1 = &a;
	int* p2 = &b;
	swap(p1, p2);
	return 0;
}

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
关于模板的大致认识【C++】,c++,开发语言

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

1、隐式实例化:让编译器根据实参推演模板参数的实际类型

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

	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 
		cout << Add(a1, a2) << endl;
		cout << Add(d1, d2) << endl;
	//实参传递的类型, 推演T的类型  
		cout << Add( a1, (int)d1  ) << endl;
		cout << Add( (double)a1, d1) << endl;
	//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 
		cout << Add<int> (a1, d1) << endl;
		cout << Add<double>(a1, d1) << endl;

	return 0;
}

2、显式实例化:在函数名后的<>中指定模板参数的实际类型

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

	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	//函数模板根据调用,自动推导模板参数的类型 ,实例化对应的参数 
		cout << Add(a1, a2) << endl;
		cout << Add(d1, d2) << endl;
	//实参传递的类型, 推演T的类型  
		cout << Add( a1, (int)d1  ) << endl;
		cout << Add( (double)a1, d1) << endl;
	//显示实例化 ,用指定的类型实例化 ,相当于隐式类型转换 
		cout << Add<int> (a1, d1) << endl;
		cout << Add<double>(a1, d1) << endl;

	return 0;
}
template<class T >
 T * Alloc(int n )
{
	 return new T[n];
}

 int main()
 {
	 //有些函数无法自动推导函数模板的类型,实例化对应的参数,只能显式实例化
	 double *p1 = Alloc <double>(10);
	 return 0;
 }

模板参数的匹配原则

类模板

类模板 ,无法推演实例化,所以类模板都是显式实例化

class Stack
{
public :
	Stack(int capacity = 3)
	{
		_array= new  T[capacity];
			_size = 0;
		_capacity = 0; 
	}
	void Push(const T &  data)
	{
		_array[_size++] = data;
	}
	~Stack()
	{
		free(_array);
		_size = _capacity = 0;
	}
private :
	T * _array;
	int _size;
	int _capacity;
};
int main()
{
	Stack <int> s1(); // int 
	Stack <double> s2();//double 
	Stack <char> s3();//char
	//Stack <int ,doule> s2();


	return 0; 
}

类模板的定义格式

函数类模板的声明和定义分离

template<class T>


class Stack
{
public:
	//声明 
	Stack(int capacity );
	void Push(const T& data)
	{
		_array[_size++] = data;
	}
	~Stack()
	{
		free(_array);
		_size = _capacity = 0;
	}
private:
	T* _array;
	int _size;
	int _capacity;
};
//定义 
template<class T>
 Stack<T>::Stack(int capacity )
{
	_array = new  T[capacity];
	_size = 0;
	_capacity = 0;
}
int main()
{
	Stack <int> s1(); // int 
	Stack <double> s2();//double 
	Stack <char> s3();//char
	//Stack <int ,doule> s2();


	return 0;
}

对于普通类,类名和类型是一样的,但是对于类模板 ,类名和类型是不一样的 上面的代码中Stack是类名 ,但是Stack < T >是类型

类模板的实例化

非类型模板参数

模板参数可分为类型形参非类型形参

类型形参: 出现在模板参数列表中,跟在class或typename关键字之后的参数类型名称。

非类型形参: 用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

如果此时有一个需求,实现一个静态数组的类,就需要用到非类型模板参数

template<class T, size_t N> //N:非类型模板参数
// N是常量 ,且N必须是整形
class StaticArray
{
public:
	size_t arraysize()
	{
		return N;
	}
private:
	T _array[N]; //利用非类型模板参数指定静态数组的大小
};
int main()
{
	StaticArray<int, 10> a1; //定义一个大小为10的静态数组
	cout << a1.arraysize() << endl; //10
	StaticArray<int, 100> a2; //定义一个大小为100的静态数组
	cout << a2.arraysize() << endl; //100
	return 0;
}

注意:

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。

typename 与class

一般来说,typename 和class 没有什么区别,但是在有一种情景下是有区别的

template<class Container>
void Print(  const Container& v  )
{
	//Container::const_iterator it = v.begin();是不行的
	// 因为编译不确定Container::const_iterator是类型还是对象
	// typename的作用就是明确告诉编译器这里是类型,等模板实例化再去找
	typename 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> lt1;
	lt1.push_back(1);
	lt1.push_back(2);
	lt1.push_back(3);
	lt1.push_back(4);
	Print(lt1);
	return 0;
}

以上情景需要使用typename

模板的特化

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

模板的特化 即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。
模板特化中分为函数模板特化 和 类模板特化。

函数模板特化

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

template<class T>
bool Less(T left ,T right)
{
	return left < right;
}
//函数模板的特化
template<>
bool Less<int * >(int * left, int* right)
{
	return *left < *right;
}
int main()
{
	int a = 1, b = 2;
	cout << Less(1, 2);
	cout << endl;
	cout << Less(&a, &b);
	return 0;
}

类模板特化

不仅函数模板可以进行特化,类模板也可以针对特殊类型进行特殊化实现,并且类模板的特化又可分为全特化和偏特化(半特化)。

全特化

全特化即是将模板参数列表中所有的参数都确定化。

例如,对于以下类模板:

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

偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。

偏特化有以下两种表现方式:
1、部分特化 ,将模板参数类表中的一部分参数特化

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

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() 
	{
	cout<<"Data<T1, int>" <<endl;
	}
private:
	T1 _d1;
	int _d2;
};
int main()
{
	Data<double, int> d1;//偏特化
	Data<int, double> d2;//调用基础的模板
	return 0;
}

2、参数更进一步的限制

template<class T1, class T2>
class Data
{
public:
	Data()
	 {
	 cout<<"Data<T1, T2>" <<endl;
	 }
private:
	T1 _d1;
	T2 _d2;
};
//偏特化:对类型的进一步限制
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
};

int main()
{
	Data<int, int > d1;
	Data<int, double > d2;
	Data<int*, double > d3;
	Data<int*, double* > d4;
	Data<void*, void* > d5;
	return 0;
}

模板的分离编译

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

在分离编译模式下,我们一般创建三个文件

一个头文件用于进行函数声明
一个源文件用于对头文件中声明的函数进行定义
最后一个源文件用于调用头文件当中的函数

举个例子:如果对一个加法函数模板进行分离编译

关于模板的大致认识【C++】,c++,开发语言

如果这三个文件生成可执行文件时,会在链接阶段产生报错
这是为什么呢?

C / C++程序要运行起来一般要经历以下四个步骤:
预处理、编译、汇编、链接

如果需要详细的了解这四个步骤,请点击这里

关于模板的大致认识【C++】,c++,开发语言

这三个文件经过预处理后就只剩下两个文件了

Visual Studio平台:

预处理后就进入编译阶段,
虽然在 main.i 当中有调用Add函数的代码,但是在 main.i 里面也有Add函数模板的声明,在编译阶段并不会发现任何语法错误,在编译阶段将 Add.i 和 main.i 翻译成了汇编语言,即将 Add.i 和 main.i 变成了Add.s 和 main.s

进入汇编阶段,利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,
即将 Add.s 和 main.s变成了Add.o和 main.o

最后将Add.o和 main.o进行链接操作生成a.out,
但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义
原因是函数模板T还没有实例化,可以将模板定义的位置显式实例化。(这种方法不实用,不推荐使用)。

总结:
编译阶段看有没有声明,声明是一种承诺
在编译阶段,检查声明,查看函数名、参数、返回值是否对上,如果对上,则编译阶段通过
进入链接阶段,编译器会拿着修饰后的函数去其他文件符号表查找,如果查到,则链接阶段通过

模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

解决方法

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。
  2. 模板定义的位置显式实例化。(这种方法不实用,不推荐使用)。

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注你们的每一次支持都将转化为我前进的动力!!!文章来源地址https://www.toymoban.com/news/detail-673666.html

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

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

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

相关文章

  • 【高级程序设计语言C++】初识模板

    概念: 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。 具体格式: templatetypename T1, typename T2,…,typename Tn 返回值类型 函数名(参数列表){} 输出结果: typename是用来定义模板参数,也可以使用class(切记

    2024年02月15日
    浏览(45)
  • [C++开发 03 _ 模板(167)]

    1.1模板的概念  模板的特点: 模板不可以直接使用,它只是一个框架 模板的通用并不是万能的 1.2函数模板 1.2.1函数模板语法 函数模板的引入: 函数模板的举例:  总结: 函数模板利用template 使用函数模板有两种方式:1、自动类型推导     2、显示指定类型 模板的

    2024年01月20日
    浏览(39)
  • 带你了解关于FastAPI快速开发Web API项目中的模板和Jinja

    摘要: FastAPI 实际上是为构建 API 和微服务而设计的。它可用于构建使用 Jinja 提供 HTML 服务的 Web 应用程序。 本文分享自华为云社区《FastAPI 快速开发 Web API 项目: 模板和 Jinja 介绍》,作者:宇宙之一粟。 模板是全栈 Web 开发的重要组成部分。使用 Jinja,您可以构建丰富的模

    2023年04月25日
    浏览(35)
  • 【区块链技术开发】 关于Windows10平台Solidity语言开发环境配置

    在 Windows 上配置 Solidity 语言开发环境需要进行以下步骤:

    2023年04月20日
    浏览(67)
  • Web前端开发技术课程大作业: 关于美食的HTML网页设计——HTML+CSS+JavaScript在线美食订餐网站html模板源码30个页面:

    👨‍🎓静态网站的编写主要是用HTML DIV+CSS JS等来完成页面的排版设计👩‍🎓,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等,用的最多的还是DW,当然不同软件写出的前端Html5代码都是一致的,本网页适合修改成为各种类型的产品展示网页,比

    2024年02月12日
    浏览(95)
  • 关于一个C++项目:高并发内存池的开发过程(一)

    原项目地址: 高并发内存池项目: 高并发内存池项目的课堂板书+代码 (gitee.com) 本打算利用五一假期的时间将这个项目一口气开发完成,但由于本人的懈怠,这个项目最终只完成了80%。于是利用长假后的一天假期,将这个项目的框架搭建完成。本以为这个项目就此结束,但是

    2024年02月05日
    浏览(39)
  • 关于一个C++项目:高并发内存池的开发过程(二)

    上篇文章梳理了内存申请操作的流程,大概测试了一下,没有发现什么问题。这篇文章将梳理内存释放操作的流程,若申请操作中,有些细节没有把控好,那么释放操作将bug不断。有些bug我至今还在调试…所以,这篇文章的梳理,侧重点依然是逻辑结构。代码的细节可能存在

    2024年02月05日
    浏览(71)
  • 玄子Share-自然语言编程(NLP)_Java开发小白向 ChatGPT 提问的最佳模板

    以下内容均为 ChatGPT 回答 玄子: 我向你提问时,问题描述精确的重要性 ChatGPT 3.5 问题描述的精确性非常重要,因为它可以让回答者更好地理解您的问题,并且更容易提供准确和有用的解决方案。如果问题描述不够清晰或不够详细,回答者可能会误解您的问题或者理解不到位

    2023年04月09日
    浏览(50)
  • 关于DNS的一些认识

    目录 什么是DNS? 一台具有单个DNS的机器可以拥有多个地址吗? 一台计算机可以有多个属于不同顶级域的DNS名字吗? 什么是DNS?         DNS是域名系统(Domain Name System)的缩写,它是互联网中用于将域名转换为IP地址的一种系统。在互联网上,每个设备都被分配一个唯一

    2024年02月09日
    浏览(44)
  • Unity 关于生命周期函数的一些认识

    Unity 生命周期函数主要有以下一些: Awake() : 在脚本被加载时调用。用于初始化对象的状态和引用。 OnEnable() : 在脚本组件被启用时调用。在脚本组件被激活时执行一次,以及在脚本组件被重新激活时执行。 Reset() : 在脚本组件被重置时调用。用于重置脚本的初始状态。 Start

    2024年01月21日
    浏览(53)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包