【C++】模板(进阶)

server/2025/1/22 20:12:41/

本篇我们来介绍更多关于C++模板的知识。模板初阶移步至:【C++】模板(初阶) 

1.非类型模板参数

1.1 非类型模板参数介绍

模板参数可以是类型形参,也可以是非类型形参。类型形参就是我们目前接触到的一些模板参数。

//类型模板参数
template<class T, class Container = vector<T>, class Compare = Less<T>>

非类型模板参数就是用一个常量作为模板的参数,在模板中可以将该参数当作常量使用。

//N为非类型模板参数
template<class T, size_t N = 10> //N给缺省值
template<size_t N> //N没给缺省值

比如说我们要弄一个数组长度是固定的栈。

template<size_t N>
class Stack
{
private:int _a[N]; //定长数组//...
};

在使用的时候就可以传想要的N的值。

int main()
{Stack<5> st1; //存5个数据Stack<10> st2; //存10个数据return 0;
}

1.1.1 和C语言宏对比

这个和C语言的宏区别还是很大的,C语言的宏只能存5个或者10个数据,不可以像上面这样st1存5个值,st2又能存10个数据。

#define N 5  //宏,此时st1和st2都是存5个数据
class Stack
{
private:int _a[N]; //定长数组//...
};int main()
{Stack st1; Stack st2; return 0;
}

1.1.2 底层原理及注意事项

非类型模板参数和传类型模板参数,模板的底层原理都是一样的,都是生成了不同的类,如果是函数模板就是生成了不同的函数

如果非类型模板参数给了缺省值,可以不传参,但是要加上<>。

template<size_t N = 5> //给缺省值
class Stack
{
private:int _a[N]; //定长数组//...
};int main()
{Stack<> st1; //不传参Stack<10> st2; //传参return 0;
}

不传参也不加<>的写法在C++20才支持,这里还是建议加上<>。 

注意:

1.非类型模板参数只能用于整形(bool也算整形),浮点数(C++20才支持)、类对象以及字符串是不允许作为非类型模板参数的。

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

 我们也可以有多个非类型模板参数。

template<size_t N = 5, bool fg = true> //给缺省值

1.2 array介绍

array是一个容器,底层就是一个静态的数组,它就用到了非类型模板参数

相关文档:array - C++ Reference  ,使用时包含头文件 #include <array>

第一个模板参数T类型模板参数,第二个模板参数N非类型模板参数。 

array支持迭代器也支持下标访问,array就没有头删尾删、头插尾插这样的接口了,因为它是定长的。

我们来用一下array,假如要定义一个类型为int,长度为10的数组。

array<int, 10> a1;

等同于 int a1[10]; 

array对于数组越界的检查是比较严格的,比如下面的例子。

int a2[10];
cout << a2[10] << endl;//读越界的位置

未报错。

但是 array的越界检查是比较严格的,会直接报错

补充一句:array的数据是存在栈上的,vector的数据是存在堆上的。 

2.模板的特化

我们在使用模板的时候,有些情况下对于一些特殊类型的结果可能不是我们想要的。这时我们就可以使用模板的特化。

2.1 函数模板的特化

比如说我们用函数模板实现两个数的小于比较。

template<class T>
bool Less(T left, T right)
{return left < right;
}

在通常情况下这个模板是没有问题的,像传一些int、double这样的内置类型。但是如果传自定义类型可能结果就不是我们想要的,比如说Date类,我们就可以用模板的特化。

函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字 template 后面接一对 空的尖括号<>
3. 函数名后 跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}

当我们传参类型为Date*时,就会直接走特化的模板,传别的类型的参数时,还是走函数模板那一套。

2.2 类模板的特化

2.2.1 全特化

全特化就是将模板参数列表中所有的参数都确定化。假如这里有一个如下的类。

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

对这个类全特化就是在类名后面加上<>,<>里面写上具体类型。

template<>
class Date<int, char>
{
public:Date(){cout << "Date<int, char>" << endl;}
};

如果我们传int和char类型的参数过去,就直接匹配调用特化了的类,其余情况都调用正常的类模板。

int main()
{Date<int, int> d1;Date<int, char> d2;return 0;
}

2.2.2 偏特化(半特化)

偏特化有两种表现方式,部分特化参数更进一步的限制

部分特化

这种特化就是只特化一部分,如下。

template<class T1>
class Date<T1, char>
{
public:Date(){cout << "Date<int, char>" << endl;}
};

只要是第二个参数传的是char类型,就会匹配到这个偏特化(半特化)的类,其余情况还是匹配正常的类模板。

int main()
{Date<int, int> d1;Date<char, char> d2;Date<double, char> d3;return 0;
}

当全特化和偏特化(半特化)同时存在时,会优先选择全特化

template<class T1, class T2> //正常类模板
class Date 
{
public:Date(){cout << "Date<T1, T2>" << endl;}
};template<>  //全特化
class Date<int, char>
{
public:Date(){cout << "Date<int, char>" << endl;}
};template<class T1>  //偏特化
class Date<T1, char> 
{
public:Date(){cout << "Date<T1, char>" << endl;}
};int main()
{Date<int, char> d2; //优先选择全特化return 0;
}

 参数更进一步的限制

这种特化针对模板参数更进一步的条件限制所设计出来的一个特化版本。比如说下面这个。

template<class T1, class T2>
class Date<T1*, T2*>   //指针
{
public:Date(){cout << "Date<T1*, T2*>" << endl;}
};

这个特化的意思就是,只要参数传的是指针不管什么类型的指针,都走这个模板。比如说下面的例子。

int main()
{Date<int, char> d1;Date<int*, char*> d2; //传的指针Date<double*, char*> d3; //传的指针Date<int*, double*> d4; //传的指针Date<char*, int*> d5; //传的指针return 0;
}

除了特化成指针,还能特化成引用

template<class T1, class T2>
class Date<T1&, T2&>    //引用
{
public:Date(){cout << "Date<T1&, T2&>" << endl;}
};
Date<int&, char&> d6; //传的引用
Date<double&, char&> d7; //传的引用
Date<int&, double&> d8; //传的引用
Date<char&, int&> d9; //传的引用

两者混合也可以,一个指针一个引用。

template<class T1, class T2>
class Date<T1&, T2*>
{
public:Date(){cout << "Date<T1&, T2*>" << endl;}
};

易错点

我们先看下面这段代码。

template<class T1, class T2>
class Date<T1*, T2*>
{
public:Date(){cout << "Date<T1*, T2*>" << endl;T1 t;cout << typeid(t).name() << endl;}
};
int main()
{Date<int*, int*> d1;
}

代码中的t是什么类型?是 int* 还是 int?

是int。

如果传过去的是int**,t的类型就是int*。

Date<int**, int*> d1;

在特化的时候,除了指针这里会比较容易混淆,引用也是一样,比如下面这个例子。

template<class T1, class T2>
class Date<T1&, T2&>
{
public:Date(){cout << "Date<T1&, T2&>" << endl;T1 t;cout << typeid(t).name() << endl;}
};
void test6()
{Date<int&, int&> d1;
}

此时t的类型就是int,而不是int类型的引用。

所以,我们想定义一个指针或者引用的时候应该要像下面这样。

template<class T1, class T2>
class Date<T1&, T2*>
{
public:Date(){cout << "Date<T1&, T2*>" << endl;int a = 0;T1& t1 = a;  //定义引用T2* t2 = &a; //定义指针//...}
};

因为T1和T2在上面的情况下都是int类型,而不是我们想要的引用和指针。

3.模板分离编译及优缺点

3.1 模板的分离编译

我之前的很多篇博客用到了声明和定义分离的方法,主要是一些像模拟实现这样的代码较多的程序,随着我们更深入的学习,一个程序的代码量是会越来越多的,声明和定义分离可以让我们的代码更有条理。

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

程序要运行,一般要经历这几个步骤:预处理 -> 编译 -> 汇编 -> 链接

 而模板,是不支持声明和定义分离的,会发生链接错误。

所以建议模板的声明和定义放在同一个文件中,不要分离。 

3.2 模板的优缺点

【优点】
        1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
        2. 增强了代码的灵活性
【缺陷】
        1. 模板会导致代码膨胀问题,也会导致编译时间变长
        2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

本次分享就到这里,我们下篇再见~


http://www.ppmy.cn/server/160554.html

相关文章

Kafka 日志存储 — 日志索引

每个日志分段文件对应两个索引文件&#xff1a;偏移量索引文件用来建立消息偏移量到物理地址之间的映射&#xff1b;时间戳索引文件根据指定的时间戳来查找对应的偏移量信息。 1 日志索引 Kafka的索引文件以稀疏索引的方式构造消息的索引。它并不保证每个消息在索引文件中都有…

【Vim Masterclass 笔记18】第八章 + S08L35:Vim 的可视化模式(二)

文章目录 S08L35 Visual Mode - Part 21 利用可视化模式控制代码块的缩进2 缩进宽度的设置3 仅对选中区域执行替换操作4 利用可视化模式实现文本对齐 写在前面 本篇为 Vim 可视化模式的第二部分&#xff0c;主要介绍了可视化模式在代码缩进方面的应用。该视频应该录制于 2018 年…

高效实现 Markdown 转 PDF 的跨平台指南20250117

高效实现 Markdown 转 PDF 的跨平台指南 引言 Markdown 文件以其轻量化和灵活性受到开发者和技术写作者的青睐&#xff0c;但如何将其转换为易于分享和打印的 PDF 格式&#xff0c;是一个常见需求。本文整合了 macOS、Windows 和 Linux 三大平台的转换方法&#xff0c;并探讨…

蓝桥杯算法|基础笔记(1)

**时间复杂度** 一、概念理解 时间复杂度是用来衡量算法运行时间随输入规模增长而增长的量级。它主要关注的是当输入规模趋向于无穷大时&#xff0c;算法执行基本操作的次数的增长趋势&#xff0c;而不是精确的运行时间。 二、分析代码中的基本操作 确定关键操作 在一段代码…

opencv笔记2

图像灰度 彩色图像转化为灰度图像的过程是图像的灰度化处理。彩色图像中的每个像素的颜色由R&#xff0c;G&#xff0c;B三个分量决定&#xff0c;而每个分量中可取值0-255&#xff0c;这样一个像素点可以有256*256*256变化。而灰度图像是R&#xff0c;G&#xff0c;B三个分量…

机器学习10-解读CNN代码Pytorch版

机器学习10-解读CNN代码Pytorch版 我个人是Java程序员&#xff0c;关于Python代码的使用过程中的相关代码事项&#xff0c;在此进行记录 文章目录 机器学习10-解读CNN代码Pytorch版1-核心逻辑脉络2-参考网址3-解读CNN代码Pytorch版本1-MNIST数据集读取2-CNN网络的定义1-无注释版…

软考信安24~工控安全需求分析与安全保护工程

1、工控系统安全威胁与需求分析 1.1、工业控制系统概念及组成 工业控制系统是由各种控制组件、监测组件、数据处理与展示组件共同构成的对工业生产过程进行控制和监控的业务流程管控系统。工业控制系统通常简称工控系统(ICS) 。 工控系统通常分为离散制造类和过程控制类两大…

通过idea创建的springmvc工程需要的配置

在创建的spring mvc工程中&#xff0c;使用idea开发之前需要配置文件包括porm.xml、web.xml、springmvc.xml 1、porm.xml 工程以来的spring库&#xff0c;主要包括spring-aop、spring-web、spring-webmvc&#xff0c;示例配置如下&#xff1a; <project xmlns"http:/…