C++ 模板进阶

C++ 模板进阶

  • 一.非类型模板参数
    • 1.概念
    • 2.实例
    • 3.注意事项
  • 二.模板的特化
    • 1.引出
    • 2.函数模板的特化
      • 1.语法和使用
      • 2.建议
    • 3.类模板的特化
      • 1.全特化
      • 2.偏特化
        • 1.部分特化
        • 2.对参数进行进一步的限制
    • 4.匹配顺序
  • 三.模板的分离编译
    • 1.什么是分离编译
    • 2.模板的分离编译
    • 3.解决方法
      • 1.显式实例化(不推荐)
      • 2.分离编译放在头文件当中
  • 四.模板的总结

一.非类型模板参数

1.概念

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

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

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

2.实例

比方说我想实现一个静态的栈,栈的大小是10

#define N 10template<class T>
class Stack
{
private:T _arr[N];
};int main()
{Stack<int> st1;Stack<string> st2;return 0;
}

在这里插入图片描述
可是我需求变了,st1我想要10个大小,st2我想要200个大小,st3我想要1000个大小,st4我想要2个大小…
怎么办?

此时就需要用到非类型模板参数了

template<class T,size_t N>
class Stack
{
private:T _arr[N];
};int main()
{Stack<int,10> st1;Stack<string,100> st2;Stack<char, 1000> st3;Stack<int*, 2> st4;return 0;
}

在这里插入图片描述
此时你st1想要10个大小,st2想要100个大小,st3想要1000个…
都可以,完美的解决了这个问题

3.注意事项

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的
  2. 非类型的模板参数必须在编译期就能确认结果
    (因为模板是在编译阶段就要实例化的,而模板必须要知道实例化成的具体类型之后才能实例化,
    因此非类型模板参数和类型模板参数一样,必须在编译阶段就能够确认实例化成的具体类型才可以,
    否则编译阶段无法完成实例化,链接阶段就找不到实例化出的具体的类,进而发生链接错误)

二.模板的特化

1.引出

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

我们写一个非常简易的日期类,用这个日期类开始下面的介绍
这里给出了头文件,至于源文件就不给大家了,毕竟我们这篇博客的目的也不是实现日期类
关于日期类的完善大家可以看我的这篇博客:
C++类和对象中:运算符重载+const成员函数+日期类的完善
在这里插入图片描述
在这里插入图片描述
对于第三个比较,我们想要比较的其实是p1和p2,可是实际比较的是p1和p2这两个指针
不符合我们的期望,怎么办呢?

2.函数模板的特化

1.语法和使用

在这里插入图片描述
因此我们就可以这样特化

// 函数模板 -- 参数匹配
//基础的函数模板
template<class T>
bool Less(T left, T right)
{return left < right;
}
//函数模板的特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}

在这里插入图片描述
特化之后,如果函数模板的参数跟特化的类型是匹配的,那么就会走特化,并不会走函数的基础模板

跟我们之前学的函数模板推演实例化时如果有更匹配的会优先去匹配那个更匹配的,这个原则是一样的

2.建议

不建议大家使用函数模板的特化,而建议大家直接写一个同名的非函数模板即可,

因为:
1.函数模板的特化和直接写一个同名的非模板函数的工作量是一样的
2.函数模板的特化的要求更多
比如:必须要有一个基础的函数模板
而且函数形参表: 必须要和模板函数的基础参数类型完全相同
3.而且因为const,引用和指针的关系,导致函数模板的特化变得更加复杂
4.直接写一个同名的非函数模板:简单明了,代码的可读性高,容易书写
在这里插入图片描述

3.类模板的特化

1.全特化

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

语法跟函数模板的特化非常相似
都是需要一个基础模板等等…

template<class T1,class T2>
class C
{
public:C(){cout << "C<T1,T2>" << endl;}
};template<>
class C<int,double>
{
public:C(){cout << "C<int,double>" << endl;}
};

在这里插入图片描述

2.偏特化

偏特化:任何针对模版参数进一步进行条件限制而设计的特化版本
偏特化有两种表现形式:
部分特化和对参数进行进一步限制

偏特化也要求必须要有基础模板

1.部分特化

比如说特化第一个参数是int

template<class T1,class T2>
class C
{
public:C(){cout << "C<T1,T2>" << endl;}
};template<>
class C<int, double>
{
public:C(){cout << "C<int,double>" << endl;}
};template<class T>
class C<int,T>
{
public:C(){cout << "C<int,T>" << endl;}
};int main()
{C<int, double> c1;C<int, char> c2;C<char, int> c3;return 0;
}

在这里插入图片描述

2.对参数进行进一步的限制

偏特化并不仅仅是指特化部分参数,也可以用来对参数进行进一步的限制

针对于我们一开始引入的比较Date*的例子,我们可以使用类模板来解决这个问题

template<class T>
class D
{
public:bool less(const T& a, const T& b){return a < b;}
};//偏特化一个专门用于指针比较的类
template<class T>
class D<T*>
{
public:bool less(const T* a,const T* b){return *a < *b;}
};
int main()
{cout << D<int>().less(2, 1) << endl; // 可以比较,结果正确Date d1(2024, 8, 5);Date d2(2024, 8, 10);cout << D<Date>().less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << D<Date*>().less(p1, p2) << endl; // 可以比较,结果正确return 0;
}

在这里插入图片描述
在这里插入图片描述
注意:这里不建议在偏特化指针类型时加上&

bool less(const T* const& a,const T*& b)

因为如果加上了&,就容易出现这种错误
在这里插入图片描述
此时只需要在引用前面加上const即可

bool less(const T* const& a,const T* const& b)

在这里插入图片描述

4.匹配顺序

模板参数的匹配原则:
会优先匹配更匹配的

如果跟全特化匹配,就匹配全特化
否则如果跟偏特化更匹配,就匹配偏特化
如果跟偏特化也不匹配,就匹配原模板

三.模板的分离编译

1.什么是分离编译

在这里插入图片描述

2.模板的分离编译

我们知道一般的类或者函数都是可以分离编译的,
短小,简单的函数定义为内联函数放在头文件当中
其他函数仅仅将声明放在头文件,定义放在源文件当中

对于带有模板的类或者函数,支持分离编译吗?

下面我们以函数模板为例,演示一下:
例如下面这份程序,对非模板函数f1和模板函数f2进行了分离编译
在这里插入图片描述
一编译,结果发现连接错误,原因是找不到模板函数f2的地址
在这里插入图片描述
为什么会这样呢?
下面我们来分析一下
首先,经过之前的学习,我们知道:
在这里插入图片描述
今天我们需要留意的是:
在这里插入图片描述
因此我们可以得出如下结论:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.解决方法

1.显式实例化(不推荐)

在定义该函数的源文件当中进行显式实例化

func.cpp
//显式实例化
template
void f2<int>(const int& a);//f2的定义
template<class T>
void f2(const T& a)
{cout << "f2<T>" << endl;
}

在这里插入图片描述
不过这种方法并不好用,甚至非常难用
比如,我现在不想f2(1)这么调用了
我想f2(1.1)这么调用,让T实例化为double
此时又链接错误了…
在这里插入图片描述

2.分离编译放在头文件当中

那么怎么办呢?
要不然干脆不进行声明跟定义分离,这样当然可以
要不然声明跟定义分离,但是将定义也放在头文件当中
这样的话,用声明的地方一定有定义
编译阶段就能够正常实例化出模板参数的类型了

通常情况下,这种包含模板的头文件习惯性命名为.hpp文件
当然命名为.h文件也是可以的
在这里插入图片描述
这种时候你想怎么玩就怎么玩

四.模板的总结

在这里插入图片描述

以上就是C++ 模板进阶的全部内容,希望能对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/471618.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1036 跟奥巴马一起编程 (15)

美国总统奥巴马不仅呼吁所有人都学习编程&#xff0c;甚至以身作则编写代码&#xff0c;成为美国历史上首位编写计算机代码的总统。2014 年底&#xff0c;为庆祝“计算机科学教育周”正式启动&#xff0c;奥巴马编写了很简单的计算机代码&#xff1a;在屏幕上画一个正方形。现在…

HTTP缓存技术

大家好我是苏麟 , 今天说说HTTP缓存技术 . 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) HTTP缓存技术 HTTP 缓存有哪些实现方式? 对于一些具有重复性的 HTTP 请求&#xff0c;比如每次请求得到的数据都一样的&#xff0c;我们可以把这对「请求-响…

GPIO八种工作模式

目录 一、推挽输出 二、开漏输出 三、复用推挽输出 四、复用开漏输出 五、浮空输入 六、上拉输入 七、下拉输入 八、模拟输入 GPIO八种配置模式&#xff0c;原理和使用场景&#xff0c;硬件原理如下图&#xff1a; 一、推挽输出 1、 原理 当控制栅极为低电平时&#x…

leetcode刷题记录:暴力搜索算法01 - 回溯

参考&#xff1a;labuladong的算法小抄 https://labuladong.online/algo/essential-technique/backtrack-framework/ 这篇太牛了&#xff0c;一个模板把所有的排列组合子集问题全秒了。 1. 简介 暴力搜索算法&#xff1a;回溯、dfs、bfs。这些都可以看做是从二叉树算法衍生出来…

leetcode hot100不同路径

本题可以采用动态规划来解决。还是按照五部曲来做 确定dp数组&#xff1a;dp[i][j]表示走到&#xff08;i&#xff0c;j&#xff09;有多少种路径 确定递推公式&#xff1a;我们这里&#xff0c;只有两个移动方向&#xff0c;比如说我移动到&#xff08;i&#xff0c;j&#x…

问题:宋朝为了加强皇帝对司法权的直接控制建立了() #微信#微信问题:宋朝为了加强皇帝对司法权的直接控制建立了() #微信#微信

问题&#xff1a;宋朝为了加强皇帝对司法权的直接控制建立了&#xff08;&#xff09; A.大理寺 B.刑部 C.审刑院 D.廷尉 参考答案如图所示

【C/C++内存管理详解】

C/C内存管理详解 1. C/C内存分布2. C语言中动态内存管理方式3. C中动态内存管理3.1 new/delete操作内置类型**3.2 new和delete操作自定义类型** 4. operator new与operator delete函数4.1 operator new与operator delete函数 5. new和delete的实现原理5.1 内置类型5.2 自定义类…

【无标题】Matlab 之axes函数——创建笛卡尔坐标区

**基本用法&#xff1a;**axes 在当前图窗中创建默认的笛卡尔坐标区&#xff0c;并将其设置为当前坐标区。 应用场景1&#xff1a;在图窗中放置两个 Axes 对象&#xff0c;并为每个对象添加一个绘图。 要求1&#xff1a;指定第一个 Axes 对象的位置&#xff0c;使其左下角位于…

C++ bfs反向搜索(五十七)【第四篇】

今天我们来学习bfs的反向搜索。 1.反向搜索 反向搜索&#xff1a;是从目标状态出发进行的搜索&#xff0c;一般用于终点状态唯一&#xff0c;起点状态有多种&#xff0c;且状态转移是可逆的&#xff08;无向边&#xff09;情况。 例题&#xff1a;在一个长度为 n 的坐标轴上&a…

JVM(5)面试篇

1 什么是JVM&#xff1f; 关联课程内容 基础篇-初识JVM基础篇-Java虚拟机的组成 回答路径 JVM的定义作用功能组成 1、定义&#xff1a; JVM 指的是Java虚拟机&#xff08; Java Virtual Machine &#xff09;。JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是…

单片机学习笔记---直流电机驱动(PWM)

直流电机介绍 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极&#xff0c;当电极正接时&#xff0c;电机正转&#xff0c;当电极反接时&#xff0c;电机反转 直流电机主要由永磁体&#xff08;定子&#xff09;、线圈&#xff08;转子&#xff09;和换向器…

C语言学习day14:数组定义和使用

定义变量&#xff1a; 数据类型 变量 值 数组定义&#xff1a; 数据类型 数组名[元素个数]{值1,值2,值3} 代码&#xff1a; int main() {//定义变量//数据类型 变量 值//数组定义//数据类型 数组名[元素个数]{值1,值2,值3}//数组下标 数组名[小标]//数组下标是…