【C++】模板进阶

📝前言:

之前我们讲过模板,这篇文章我们来讲讲模板进阶

🎬个人简介:努力学习ing

📋个人专栏:C++学习笔记

🎀CSDN主页 愚润求学

🌄其他专栏:C语言入门基础python入门基础python刷题专栏


文章目录

一,非参数类型模板

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

  • 类型形参:跟在class或者typename之后的参数类型名称。
  • 非类型形参 :用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常
    量来使用

注意:

  1. 浮点数 (C++20后开始支持)、类对象 以及字符串是不允许作为非类型模板参数的
  2. 非类型的模板参数必须在编译期就能确认结果

1 类型形参模板

cpp 复制代码
template<class T>
T Add(T a, T b)
{
	return a + b;
}

在这里,模板的参数列表为类型

cpp 复制代码
int main()
{
	cout << Add(2, 3) << endl;
	cout << Add(2.1, 3.2) << endl;
	return 0;
}

运行结果:

2 非类型形参模板

函数模板例子:

cpp 复制代码
int main()
{
	cout << Add<int, 5>(2) << endl;
	cout << Add(2.1) << endl;
	return 0;
}

说明:

  • Add<int, 5>(2):显示指定模板参数
  • Add(2.1)没有显示传递,则由编译器推导:2.1 ------ 浮点数,于是 T 就为浮点型,a用缺省值 3

运行结果:

类模板例子(模拟实现一个array):

array底层基于数组实现

cpp 复制代码
namespace tr
{

	template<class T, size_t N = 10>
	class array
	{
	public:
		array(){}
		T& operator[](size_t index) { return _array[index]; }
		const T& operator[](size_t index) const { return _array[index]; }
		size_t size() { return _size; }
		bool empty() { return _size == 0; }

	private:
		T _array[N]; // 由模板非类型形参的大小来初始化底层数组大小
		size_t _size;
	};

}
cpp 复制代码
int main()
{
	tr::array<int,5> a1;
	tr::array<int> a2;
	return 0;
}

运行结果(调试):

array和vector以及原生数组的区别

  • array的内存分配在栈上
  • vector的内存分配在堆上

栈上分配内存效率更高,vector和array的at()对数组越界进行检查

二,模板特化

1 什么是模板特化

模板特化是指:在原模板类的基础上,针对特定的模板参数类型或值,提供专门的实现。分为函数模板特化与类模板特化。

2 全特化

全特化:将模板列表里面的所有参数都确定化。

函数模板

函数模板的特化步骤(类的也类似):

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<><>里面放置需要用到(不特化的)的通用模板参数
  3. 函数名(类名)后跟一对尖括号,尖括号中指定需要特化的类型,没有特化的就用通用模板参数
  4. 模板函数特化版本的函数形参表类型必须和通用模板函数(实例化)对应位置的基础参数类型完全相同。

示例:

cpp 复制代码
// 通用模板函数
template <typename T>
T add(T a, T b) {
	return a + b;
}

// 正确的针对 int 类型的特化版本
template <> // 因为全特化是所有参数确定化,所以<>中为空
int add<int>(int a, int b) {
	std::cout << "<int, int>" << std::endl;
	return a + b;
}

// 错误的特化版本(形参类型与通用模板实例化后不一致)
/*
template <>
int add<int>(double a, double b) {
    std::cout << "Wrong specialization" << std::endl;
    return static_cast<int>(a + b);
}
// 这里要用int实例化通用模板,实例化后,模板参数列表应该为两个int,但是double和通用模板实例化后的参数列表不一致
*/

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该

函数直接给出。函数模板不建议特化。

即:

cpp 复制代码
int add(int a, int b)
{
	cout << "<int, int>" << endl;
	return a + b;
}

类模板

示例:

cpp 复制代码
// 普通模板(通用模板)
template<class T1, class T2>
class Print
{
public:
	Print() { cout << "Print<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

// 全特化
template<>
class Print<int, double>
{
public:
	Print() { cout << "Print<int, double>" << endl; } // 调默认构造初始化的时候顺便打印
private:
	int _d1;
	double _d2;
};
cpp 复制代码
int main()
{
	Print<int, int> p1; // 用连个int实例化,走通用模板
	Print<int, double> p2; // 符合全特化的模板的参数,走全特化
	return 0;
}

运行结果:

3 偏特化

偏特化有两种表现:

  1. 参数部分偏特化,即确定某些参数
  2. 参数更进一步的限制

参数部分偏特化

部分参数确定化,没有特化的参数用原来通用模板的。

偏特化

类模板偏特化示例:

cpp 复制代码
// 类模板偏特化
template<class T1, class T2>
class Print
{
public:
	Print() { cout << "Print<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

// 类模板偏特化
template<class T2>
class Print<int, T2>
{
public:
	Print() { cout << "Print<int, T2>" << endl; } // 调默认构造初始化的时候顺便打印
private:
	int _d1;
	double _d2;
};

template<class T1>
class Print<T1, double>
{
public:
	Print() { cout << "Print<T1, double>" << endl; } // 调默认构造初始化的时候顺便打印
private:
	int _d1;
	double _d2;
};
cpp 复制代码
int main()
{
	Print<int, int> p1;  // 调用偏特化中第一个参数确定为int的
	Print<double, double> p2; // 调用偏特化中第二个参数确定为double的
	cout << endl;
	Print<Print<int, int>, Print<int, int>> p3; // 两个匿名对象都调用偏特化,然后对于p3,找不到对应的偏特化,只能调用普通模板来实例化
	return 0;
}

运行结果:

参数更进一步限制

示例:

cpp 复制代码
// 参数更进一步限制
template<class T1, class T2>
class Print
{
public:
	Print() { cout << "Print<T1, T2>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

// 参数偏特化成指针类型
template<class T1, class T2>
class Print<T1*, T2*>
{
public:
	Print() { cout << "Print<T1*, T2*>" << endl; }
private:
	T1 _d1; // 在这里T1不是T1*,就是普通的T1类型,这样我们即可以使用T1,又可以使用T1*
	T2 _d2; 
};

// 参数偏特化成引用类型
template<class T1, class T2>
class Print<T1&, T2&>
{
public:
	Print() { cout << "Print<T1&, T2&>" << endl; }
private:
	T1 _d1; // 在这里T1不是T1*,就是普通的T1类型,这样我们即可以使用T1,又可以使用T1*
	T2 _d2;
};
cpp 复制代码
int main()
{
	Print<int, int> p1; 
	cout << endl;
	Print<int*, int*> p2;
	Print<int&, int&> p3;
	return 0;
}

运行结果:

编译器调用模板的顺序(当都可以匹配时):全特化→偏特化→通用模板

三,模板分离编译

当模板的声明和定义分离:即声明在.h文件,但是定义在.cpp文件的时候,往往会产生链接错误。因为链接时,需要通过重定位,将所遇到的标识符(如某个函数名)的地址链接上。

那为什么声明和定义分离就链接不上呢?

假设main.cpp文件包含了模板add声明所在的.h文件,add模板的定义在另一个.cpp文件

知识理解:

  1. 编译器在使用模板的地方看到模板的定义才能完成实例化
  2. 模板没有实例化时,就不会生成对应的代码

过程分析:

  1. main.c在预处理时,把.h头文件展开,但是文件里只有声明,没有定义。编译器在main.c文件里面知道模板实例化需要的类型,知道声明,但是没有定义,就无法完成实例化,产生不了对应的代码
  2. add定义实现的.cpp文件中,不知道具体的类型,也不会实例化
  3. 到了链接部分,在main.o里面遇到对模板add的使用,但是在所有文件里面都找不到add实例化后的对应代码,于是发生链接错误

解决方法

  1. 在定义的位置显示实例化,让编译器知道具体的类型

如:

cpp 复制代码
// add.h
template <typename T>
T add(T a, T b);

// add.cpp
#include "add.h"
template <typename T>
T add(T a, T b) {
    return a + b;
}
// 显式实例化
template int add<int>(int a, int b);

add.cpp中显式实例化了add<int>,这样知道具体类型就能实例化,链接器在链接时就能找到对应的代码

  1. 将声明和定义放在头文件里面(更推荐使用这种方法)

🌈我的分享也就到此结束啦🌈

要是我的分享也能对你的学习起到帮助,那简直是太酷啦!

若有不足,还请大家多多指正,我们一起学习交流!

📢公主,王子:点赞👍→收藏⭐→关注🔍

感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

相关推荐
Susea&4 分钟前
数据结构初阶:队列
c语言·开发语言·数据结构
慕容静漪6 分钟前
如何本地安装Python Flask并结合内网穿透实现远程开发
开发语言·后端·golang
ErizJ8 分钟前
Golang|锁相关
开发语言·后端·golang
GOTXX12 分钟前
【Qt】Qt Creator开发基础:项目创建、界面解析与核心概念入门
开发语言·数据库·c++·qt·图形渲染·图形化界面·qt新手入门
搬砖工程师Cola21 分钟前
<C#>在 .NET 开发中,依赖注入, 注册一个接口的多个实现
开发语言·c#·.net
天下琴川27 分钟前
Dify智能体平台源码二次开发笔记(5) - 多租户的SAAS版实现(2)
人工智能·笔记
巨龙之路29 分钟前
Lua中的元表
java·开发语言·lua
徐行11043 分钟前
C++核心机制-this 指针传递与内存布局分析
开发语言·c++
序属秋秋秋1 小时前
算法基础_数据结构【单链表 + 双链表 + 栈 + 队列 + 单调栈 + 单调队列】
c语言·数据结构·c++·算法
划水哥~1 小时前
Kotlin作用域函数
开发语言·kotlin