C++模板进阶

个人主页:小则又沐风

个人专栏:<数据结构>

<竞赛专栏>

<C语言>
<C++>

<Linux>

座右铭

路虽远,行则将至;事虽难,做则必成

目录

前言

非类型模板参数

模板的特化

函数模板特化

类模板特化

全特化

偏特化

模板分离编译

头文件

模板实现文件

主函数文件

解决方法

头文件

主函数

总结


前言

在之前的文章中我们初步了解了C++模板的使用

知道了函数模板 类模板今天我们来进一步了解一下这个模板的使用

非类型模板参数

在之前的模板中我们的模板参数都是一种用来代替类型的参数

今天我们在认识一下模板参数的其他的用法

  1. 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
  2. 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常 量来使用。
cpp 复制代码
#include<iostream>
using namespace std;
template<class T ,size_t N=10>
class date
{
private:
	T _val;
	int a[N];
};
int main()
{
	date<int>d1;

	return 0;
}

我们可以看到这个类模板的第二个模板参数不是一个类型的参数而是一个整形的变量

这是我们用的默认的参数值构造出的对象

cpp 复制代码
#include<iostream>
using namespace std;
template<class T ,size_t N=10>
class date
{
private:
	T _val;
	int a[N];
};
int main()
{
	date<int>d1;
	date<int, 20>d2;
	return 0;
}

很显然我们的第二个模板的参数就像一个带着缺省值的函数的参数一样

所以我们在需要的时候我们就可以在模板的参数中添加上一个类似的参数

注意:

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

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

第一条的注意我们很容易理解

那么什么叫做必须在编译期间就能确认结果呢?

cpp 复制代码
#include<iostream>
using namespace std;
template<class T ,size_t N>
class date
{
private:
	T _val;
	int a[N];
};
int main()
{
	int x = 10;
	date<int,x>d1;
	date<int, 20>d2;
	return 0;
}

当我们的第二个参数是一个变量的时候我们的代码就会报错了

cpp 复制代码
#include<iostream>
using namespace std;
template<class T ,size_t N>
class date
{
private:
	T _val;
	int a[N];
};
int main()
{
	const int x = 10;
	date<int,x>d1;
	date<int, 20>d2;
	return 0;
}

但是如果是一个const的变量的话就会正常运行

总而言之:非类型模板参数必须是:常量、字面量、const 常量、枚举,不能是变量!

模板的特化

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

cpp 复制代码
template<class T>
bool Less(const T& x, const T& y)
{
	return x < y;
}

那么接下来我们先来验证一下这个模板函数是否对内置类型是否是适用的

cpp 复制代码
int a = 10;
int b = 15; 
if (Less(a, b))
{
	cout << "a<b" << endl;
}

是可以的

那么自定义的类型呢?

就以我们最熟悉的Date的类

cpp 复制代码
#include<iostream>
using namespace std;
//template<class T ,size_t N>
//class date
//{
//private:
//	T _val;
//	int a[N];
//};
class Date
{
public:
	Date(int year=2026, int month=5, int day=10)
	{
		_year = year;
		_day = day;
		_month = month;
	}
	bool operator<(const Date& x) const
	{
		if (_year < x._year)
			return true;
		else if (_year == x._year && _month < x._month)
			return true;
		else if (_year == x._year && _month == x._month && _day < x._day)
			return true;
		return false;
	}
private:
	int _year;
	int _month;
	int _day;
};
template<class T>
bool Less(const T& x, const T& y)
{
	return x < y;
}
int main()
{
	Date d1(2000, 10, 12);
	Date d2;
	int a = 10;
	int b = 15; 
	if (Less(a, b))
	{
		cout << "a<b" << endl;
	}
	if (Less(d1, d2))
	{
		cout << "d1<d2" << endl;
	}
	/*const int x = 10;
	date<int,x>d1;
	date<int, 20>d2;*/
	return 0;
}

需要注意的是我们的函数模板只是给出了比较,但是对于自定义的类型并没有给出比较的方法,所以我们需要重载一下比较运算符

这样看起来我们的函数的模板是没有任何问题的

但是如果是这样的情况呢?

cpp 复制代码
int main()
{
	Date d1(2000, 10, 12);
	Date d2;
	int b = 15; 
	int a = 10;
	int* pa = &a;
	int* pb = &b;
	if (Less(pa, pb))
	{
		cout << "a<b" << endl;
	}
	else
	{
		cout << "b<a" << endl;
	}
	if (Less(d1, d2))
	{
		cout << "d1<d2" << endl;
	}
	/*const int x = 10;
	date<int,x>d1;
	date<int, 20>d2;*/
	return 0;
}

我们可以看到这段代码把两个整形的比较变成了两个指针的比较

这样我们得到的答案就不是我们想要的结果了,那么对于这一种特殊的情况的话我们就需要对指针的类型特殊的写一份代码,这个就叫做模板的特化

函数模板特化

函数模板的特化步骤:

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

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

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

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

cpp 复制代码
template<class T>
bool Less( T x,  T y)
{
	return x < y;
}
template<>
bool Less<int*>(int* x, int* y)
{
	return *x < *y;
}

那这就是针对这个int*的特化的模板了

但是需要说的是特化是真的不好用

我们来看

cpp 复制代码
template<class T>
bool Less( T& x,  T& y)
{
	return x < y;
}
template<>
bool Less<int*>(int* x, int* y)
{
	return *x < *y;
}

但我们把基础的模板的参数改成一个引用的话我们的特化就使用不了

这是因为我们的特化版本的参数和这个基础的不一样,就找不到了

所以模板的特化是需要严格对应的

但是这个特化还仅仅是只针对一个类型的指针

那为什么我们不在写一个函数模板针对指针呢?

当然我们还有一个选择就是函数的重载啊

在我的眼中这函数模板的特化是一个下下策(可以用来炫技)

类模板特化

全特化

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

cpp 复制代码
#include<iostream>
using namespace std;
template<class T1,class T2>
class date
{
public:
	date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	T1 x;
	T2 y;
};
template<>
class date<int, char>
{
public:
	date()
	{
		cout << "date<int, char>" << endl;
	}
private:
	int x;
	char y;
};
int main()
{
	date<int, int> d1;
	date<int, char> d2;
	return 0;
}

在这里的第二个类对象就是全特化的实例

偏特化

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

cpp 复制代码
#include<iostream>
using namespace std;
template<class T1,class T2>
class date
{
public:
	date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	T1 x;
	T2 y;
};
template<>
class date<int, char>
{
public:
	date()
	{
		cout << "date<int, char>" << endl;
	}
private:
	int x;
	char y;
};
template<class T2>
class date<double, T2>
{
public:
	date()
	{
		cout << "class date<double, T2>" << endl;
	}
private:
	double x;
	T2 y;
};
int main()
{
	date<int, int> d1;
	date<int, char> d2;
	date<double, int>d3;
	return 0;
}

那么如果全特化和偏特化同时满足是选择哪一个?

cpp 复制代码
#include<iostream>
using namespace std;
template<class T1,class T2>
class date
{
public:
	date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	T1 x;
	T2 y;
};
template<>
class date<int, char>
{
public:
	date()
	{
		cout << "date<int, char>" << endl;
	}
private:
	int x;
	char y;
};
template<class T2>
class date<double, T2>
{
public:
	date()
	{
		cout << "class date<double, T2>" << endl;
	}
private:
	double x;
	T2 y;
};
template<>
class date<double, int>
{
public:
	date()
	{
		cout << "class date<double, int>" << endl;
	}
private:
	double x;
	int y;
};
int main()
{
	date<int, int> d1;
	date<int, char> d2;
	date<double, int>d3;
	return 0;
}

可以看到编译器会优先选择特化参数多的哪一个

当然偏特化还有另一种的用法

就是把参数偏特化成另一种类型形态修饰型偏特化

这样看起来有点难以理解

在之前的偏特化中我们把一个参数是确定的,另一个是模板参数

现在我们将要了解的偏特化是把模板参数限制到一个范围

cpp 复制代码
template<class T1, class T2>
class date<T1*, T2*>
{
public:
	date()
	{
		cout << "class date<T1*, T2*>" << endl;
	}
public:
	T1* x;
	T2* y;
};

比如上面的代码我们把这个类型限制到指针的类型

这也是偏特化的范畴

这也是函数模板没有的特性

模板分离编译

之前我们说尽量把模板的定义和模板的声明放在一起

现在我们来彻底的解决这个问题

头文件

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;
template<class T>
bool Less(T x, T y);

模板实现文件

cpp 复制代码
#include"a.h"
template<class T>
bool Less(T x, T y)
{
	return x < y;
}

主函数文件

cpp 复制代码
#include"a.h"
int main()
{
	int x = 10;
	int y = 12;
	Less(x, y);
	return 0;
}

那么这就是一个经典的定义和声明分离的模板

那么会发生什么

我去这是什么报错啊

这么复杂

我来解释一下

我们知道的程序的运行是要经过预处理,编译,汇编,链接的

那么预处理的结果就是把我们的头文件展开什么的

然后我们的实现的文件和主函数的文件都有了模板的声明了

我们知道每一个的.cpp文件会生成一个.obj文件在生成的时候每一个.cpp文件是互相不干扰的

所以我们的函数模板实现的.cpp在生成.obj文件的时候并不知道把函数模板实例化成什么样子

所以并没有什么操作.

这就导致了在最后的来链接的环节我们的模板还是一个模板,所以我们的模板失去了效果

解决方法

只需要把定义和声明放在同一个文件中

头文件

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;
template<class T>
bool Less(T x, T y)
{
	return x < y;
}

主函数

cpp 复制代码
#include"a.h"
int main()
{
	int x = 10;
	int y = 12;
	if (Less(x, y))
	{
		cout << "x<y" << endl;
	}
	else
	{
		cout << "y<x" << endl;
	}
	return 0;
}

这样就可以正常运行了

总结

本文围绕 C++ 模板核心内容展开,涵盖非类型模板参数、模板特化与模板分离编译三大板块。非类型模板参数允许模板接收编译期常量,拓展了模板的使用场景。模板特化分为函数模板特化与类模板特化,其中类模板特化包含全特化与偏特化,前者针对特定类型,后者可适配指针、引用等泛化形态或部分参数固定的场景,而函数模板特化在工程中多被重载替代。模板分离编译因.cpp 文件独立编译的特性,会导致模板无法在调用点实例化,最终引发链接错误,主流解决方案是将模板声明与实现均置于头文件中。

谢谢大家的观看!!!

相关推荐
段ヤシ.1 小时前
回顾Java知识点,面试题汇总Day3(持续更新)
java·开发语言·windows
计算机安禾1 小时前
【c++面向对象编程】第3篇:类与对象(二):构造函数与析构函数
开发语言·c++·算法
小年糕是糕手1 小时前
【C++】vector 不踩坑指南:用法、底层实现与迭代器失效解析
c++·算法
不会写DN1 小时前
PyScript-GitHubRepo:构建高性能GitHub仓库批量下载工具的技术实践
开发语言·前端·python
Setsuna_F_Seiei1 小时前
AI 提效之 MCP - Agent 与执行工具的链接协议
前端·javascript·ai编程
woai33642 小时前
项目-轻客管家1-环境准备
java
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_37:(深入掌握 CustomEvent 自定义事件接口)
前端·javascript·ui·html·音视频
码海扬帆:前端探索之旅9 小时前
深度定制 uni-combox:新增功能详解与实战指南
前端·vue.js·uni-app
xqqxqxxq9 小时前
Java AI智能P图工具技术笔记
java·人工智能·笔记