C++ 模板进阶知识

目录

[一. 非类型模板参数](#一. 非类型模板参数)

与类型模板参数确认的区别

何时确认

确认方式

[二. 模板的特化](#二. 模板的特化)

[1. 概念](#1. 概念)

[2. 函数模板特化](#2. 函数模板特化)

[3. 类模板特化](#3. 类模板特化)

[3.1 全特化](#3.1 全特化)

[3.2 偏特化](#3.2 偏特化)

[(1). 部分特化](#(1). 部分特化)

[(2). 进一步限制](#(2). 进一步限制)

[4. 实际应用](#4. 实际应用)

[三. 模板分离编译](#三. 模板分离编译)

[1. 概念](#1. 概念)

[2. 模板的分离编译](#2. 模板的分离编译)

[3. 解决方法](#3. 解决方法)

[四. 优缺点总结](#四. 优缺点总结)

[1. 优点](#1. 优点)

[2. 缺点](#2. 缺点)


一. 非类型模板参数

模板参数分为 类型形参与非类型形参。

  1. 类型形参

即为出现在模板参数列表中,跟在class或者typename之后的参数类型名称

  1. 非类型形参

就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

如下代码

cpp 复制代码
#include<iostream>
#include<algorithm>

using namespace std;
namespace Pc
{
	template<class T,size_t N=20,int X=1111>
	class array
	{
	public:
		void printstatic()
		{
			cout << "N  " << N << endl;
			cout << "X  " << X << endl;
		}
		T& operator[](size_t key)
		{
			return _array[key];
		}
		const T& operator[](size_t key) const
		{
			return _array[key];
		}
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			return _size == 0;
		}
	private:
		T _array[N];
		size_t _size;
	};
}

int main()
{
	Pc::array<int,66,10> a1;
	a1.printstatic();
	Pc::array<int> a2;
	a2.printstatic();
	Pc::array<int, 88, 22> a3;
	a3.printstatic();
	return 0;
}

输出结果为

需要注意

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。

  2. 非类型的模板参数必须在编译期就可以确认结果

如以下代码

cpp 复制代码
	template<float X=1.11,array<int> A1,string S>
	void test()
	{

	}

编译结果如下

直接就通过编译发现问题了

与类型模板参数确认的区别
何时确认
  1. 类型模板参数在模板实例化后才确认,即根据提供的参数类型来确定模板中类型参数的具体方式。

  2. 非类型的模板参数必须在编译时就可以确认结果,因为非类型模板参数作为模板定义中的一个常量表达式,其值必须在编译时已知,便于编译器生成相应代码

确认方式
  1. 类型的模板参数: 函数模板是通过传参来推导,类模板是通过显式指定

  2. 非类型的模板参数: 通过一个显示指定一个编译时常量表达式来确定。这个常量表达式可以是整形字面量、,枚举常量、全局或静态const整形变量等。不能是浮点数、类对象、字符串等类型,因为这些类型无法在编译时确定其值或表示形式

二. 模板的特化

1. 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门来进行小于比较的函数模板

cpp 复制代码
#include<iostream>
#include<algorithm>

using namespace std;

template<class T>
bool numless(T num1,T num2)
{
	return num1 < num2;
}

int main()
{
	cout << numless(3, 6) << endl;
	cout << numless(1.3, 1.6) << endl;

	int a = 11,b=10;
	int* pa = &a;
	int* pb = &b;
	cout << numless(a, b) << endl;
	cout << numless(pa, pb) << endl;

	return 0;
}

输出结果如下

可以看到我们的numless绝大多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。

我们上方代码所演示的a与b的比较正确,但是pa与pb并没有比较pa与pb所指向的内容而是比较pa与pb指针的地址,所以无法达到我们的预期

这个时候就需要对模板进行特化。即:在原模板基础上,针对特殊类型所进行特殊化的实现方式。模板特化分为函数模板特化与类模板特化

2. 函数模板特化

函数模板特化的步骤

  1. 必须要现有一个基础的函数模板

  2. 关键字template后面接一对空的尖括号<>

  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型

  4. 函数形参表 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些 奇怪的错误。

如以下代码

cpp 复制代码
#include<iostream>
#include<algorithm>

using namespace std;

template<class T>
bool numless(T num1,T num2)
{
	return num1 < num2;
}
template<>
bool numless<int*>(int* pa, int* pb)
{
	return *pa < *pb;
}
int main()
{
	int a = 11,b=10;
	int* pa = &a;
	int* pb = &b;
	cout << numless(a, b) << endl;
	cout << numless(pa, pb) << endl;

	cout << endl;

	int c = 66, d = 77;
	int* pc = &c, * pd = &d;
	cout << numless(c, d)<<endl;
	cout << numless(pc, pd) << endl;

	return 0;
}

输出结果为

比较int* 指针时调用模板特化其余正常调用模板

但是我们一般情况下函数模板遇到不能处理或者是处理有误的类型,为了简单实现通常都是直接通过函数给出

cpp 复制代码
bool numless(int* pa, int* pb)
{
	return *pa < *pb;
}

这样简单明了,代码可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化

3. 类模板特化
3.1 全特化

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

如以下代码

cpp 复制代码
#include<iostream>
#include<algorithm>

using namespace std;
template<class T1, class T2>
class Test
{
public:
	Test()
	{
		cout << "Test<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
template<>
class Test<int, char>
{
public:
	Test() 
	{
		cout << "Test<int, char>" << endl;
	}
private:
	int _t1;
	char _t2;
};
void TestVector()
{
	Test<int, int> t1;
	Test<int, char> t2;
}

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

上述代码是:如果第一个参数是int第二个参数是char就走特化,其余都走模板生成

输出结果为

验证了我们的说法

3.2 偏特化

偏特化: 针对模板参数进一步进行条件限制设计的特化版本。比如对下方模板

cpp 复制代码
template<class T1, class T2>
class Test
{
public:
	Test()
	{
		cout << "Test<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

偏特化有两种表现方式:

(1). 部分特化

将模板参数列表中的一部分参数特化。

cpp 复制代码
template<class T1>
class Test<T1, int>
{
public:
	Test()
	{
		cout << "Test<T1, int>" << endl;
	}
private:
	T1 _d1;
	int _d2;
};
(2). 进一步限制

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

cpp 复制代码
template<class T1 ,class T2>
class Test<T1*, T2*>
{
public:
	Test()
	{
		cout << "Test<T1*, T2*>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
template<class T1, class T2>
class Test<T1&, T2&>
{
public:
	Test()
	{
		cout << "Test<T1&, T2&>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

分别为将两个参数偏特化为指针类型

和将两个参数偏特化为引用类型

4. 实际应用

假设我们实现了一个日期类使用仿函数来将其进行比较,但是依然对指针比较不一定保持正确

可以进行一下特化来处理问题

cpp 复制代码
template<class T>
struct Less
{
	bool operator()(const T& x1,const T& x2)
	{
		return x1 < x2;
	}
};
template<>
struct Less<int*>
{
	bool operator() (const int* x1, const int* x2)
	{
		return *x1 < *x2;
	}
};
//template<typename T>
//struct Less<T*>
//{
//	bool operator() (const T* x1, const T* x2)
//	{
//		return *x1 < *x2;
//	}
//};

三. 模板分离编译

1. 概念

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

2. 模板的分离编译

假如模板声明与定义分开了,头文件中进行声明Add.h

cpp 复制代码
//头文件
template<class T>
T Add(const T& left, const T& right);

Add.cpp中完成定义

cpp 复制代码
//定义
#include"add.h"
template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}

test.cpp中调用

cpp 复制代码
#include"add.h"

int main()
{
    Add(1, 2);
    Add(2.1, 1.2);
    return 0;
}

C\C++程序要运行,一般要经历以下步骤:

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

编译:对对程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码

链接:将多个obj文件合成一个,并处理没有解决的地址问题

obj文件

程序编译时 生成的中间代码文件。 目标文件,一般是程序编译后的二进制文件,再通过链接器 (LINK.EXE)和资源文件链接就成可执行文件了。

我们上面的写法,在源文件中编译器没有看到对Add模板函数的实例化,因此不会生成具体的加法函数(就没有地址)

在main.obj中调用的Add<int>与Add<double>,编译器在链接时才会找其地址,但是这个两个函数没有实例化没有生成具体代码,因此链接时报错。

3. 解决方法
  1. 将声明与定义放到一个文件"xxx.cpp"里 或者 直接在头文件里定义也可以

直接写定义到头文件

在.h头文件里就不是声明了,在编译时就会实例化成对应函数,就有对应的地址可以call过去

  1. 模板定义的位置显式实例化。这种方法不实用,不推荐使 用

在Add.cpp里

cpp 复制代码
//定义
#include"add.h"
template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}

template
int Add(const int&left , const int& right);

template
float Add(const float& left, const float& right);

要调用什么类型就怎么样显式的写,一种类型写一个,极其麻烦

四. 优缺点总结

1. 优点
  1. 模板复用了代码,节省资源,更快的迭代开发,C++标准模板库(STL)因此而生

  2. 增强了代码的灵活性

2. 缺点
  1. 模板会导致代码膨胀问题,也会导致编译时间变长

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


这篇就到这里啦

(づ ̄3 ̄)づ╭❤~

相关推荐
xiaoshiguang318 分钟前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡20 分钟前
【C语言】判断回文
c语言·学习·算法
别NULL22 分钟前
机试题——疯长的草
数据结构·c++·算法
TT哇26 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
飞飞-躺着更舒服1 小时前
【QT】实现电子飞行显示器(改进版)
开发语言·qt
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
CYBEREXP20081 小时前
MacOS M3源代码编译Qt6.8.1
c++·qt·macos
ZSYP-S2 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos2 小时前
c++------------------函数
开发语言·c++
yuanbenshidiaos2 小时前
C++----------函数的调用机制
java·c++·算法