【C++杂货铺】模板进阶

2024-04-11 22:28
文章标签 模板 c++ 进阶 杂货铺

本文主要是介绍【C++杂货铺】模板进阶,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


目录

🌈前言🌈

📁 泛型编程

📁 函数模板

 📂 概念

 📂 格式

 📂 class 和 typename

 📂 原理

 📂 函数模板实例化

 📂 匹配原则

📁 类模板

 📂 格式

 📂 实例化

📁 非类型模板参数

📁 模板特化

 📂 概念

 📂 函数模板的特化

 📂 类模板的特化

1. 全特化

2. 半特化/偏特化

📁 模板的分离编译

 📂 概念

 📂 模板的分离编译

 📂 解决方法

📁 模板总结

📁 总结


🌈前言🌈

        欢迎收看本期【C++杂货铺】,本期内容讲解C++中重要语法之一——模板,模板的使用,解决了我们需要重复做的工作,大大提高了我们开发效率。接下来,让我们看看模板的一些概念吧。其中包括了模板的分类,以及一些特殊用法。

        如果你还没有模板基础,可以快速阅览下面这篇文章,当然,本文也会简单介绍模板初阶的一些基本内容。

【C++杂货铺】模板-CSDN博客

📁 泛型编程

        在C++中,存在一个模具,通过给这个模具填充不同类型,来获得不同的具体类型的编码。

        泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。

        其实,模板跟函数是有很大相同点,

        1. 函数传参,传递的是对象;模板传参,传递的是类型

        2. 函数是在运行时传参;模板是在编译时传参(模板需要在编译时实例化)

        

📁 函数模板

 📂 概念

        函数模板代表一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定版本。

 📂 格式

template<class T1,class T2...>
返回值类型  函数名(参数列表)
{函数体
}

 📂 class 和 typename

        其中class 可以和typename互换,两者本质上没有区别。只是在某些场景下,必须使用typename,来声明是模板类。 

        class 和 typename 都是用于声明模板参数,在大多数情况下都是等价的,在某些情况下有微小的差异。

        在模板内部中,当引用模板参数时,如果是一个类型,可以使用class或typename。但当你引用一个模板类型参数时,必须使用typename,因为编译器无法确定这个模板类型参数是否为类型。

template <typename T>
void foo() {typename T::SomeType* ptr;  // 此处必须使用 typename
}

        这里T::someType是一个类型依赖于模板参数T,因此必须使用typename来告诉编译器T::someType是一个类型而不是成员变量或函数。

 📂 原理

        函数模板是一个蓝图,本身并不是函数,是编译器根据使用方式产生特定具体类型函数的模具,所以模板将本来我们应该做的重复事情交给了编译器干。

        在编译期间,编译器根据传入的实参类型推演生成实参类型队形类型的函数。

 📂 函数模板实例化

 隐式实例化:编译器根据实参推演模板函数的实际类型。

 显示实例化:在函数名后的<>中指定模板参数的实际类型。

 📂 匹配原则

1. 非模板函数可以和一个同名的函数模板同时存在,且该函数模板被实例化为这个非模板函数;

2. 非模板函数和同名函数模板,如果其他条件相同,在调动时有点调用非模板函数,如果模板可以产生一个更好的匹配函数,则会选择模板

📁 类模板

 📂 格式

template<class T1,class T2...>
class 类模板名称
{类成员定义
};

 📂 实例化

        类模板实话与函数模板实例化不同,类模板实例化需要在类模板名字后面跟<>,然后将实例化的类型放在<>即可,类模板名称不是真正的类,实例化的结果才是真正的类。

vector<int> v1;
vector<double> v2;

        以上就对模板初阶做了一遍回顾,总体上来说,介绍了函数模板和类模板的概念,以及使用方式,拓展了class和typename的细微差异。

📁 非类型模板参数

        模板参数分 类型形参非类型形参。此外模板参数可以跟函数一样,有缺省参数。

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

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

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

注意:

        1. 在大多数版本下,浮点数,类对象以及字符串是不允许作为非类型模板参数的。

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

📁 模板特化

 📂 概念

        通常情况下,使用模板可以实现与类型无关的代码,但对于一些特殊特殊类型可能会得到一些错误结果,需要特殊处理。

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

        在大多数情况下,Less是能正常比较的,但在特殊场景下,就得到错误结果,例如p1和p2,Less内部没有比较p1和p2指向的对象内容,而是比较的p1和p2指针的地址,无法到达预期而错误。

        此时,需要对模板进行特化,即在原模板的基础上,针对特殊类型进行特殊化的实现方式,模板特化分为,函数模板特化和类模板特化。

 📂 函数模板的特化

函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字 template 后面接一对空的尖括号 <>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了return 0;
}

        但是,一般情况下如果函数模板遇到不能处理或者处理有错误的类型,为了实现简单通常都是将该函数直接给出重载版本。

bool Less(Date* left, Date* right)
{return *left < *right;
}
        该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

 📂 类模板的特化

1. 全特化

全特化即是将模板参数列表中所有的参数都确定化。
template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
template<>
class Data<int, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}
private:int _d1;char _d2;
};
void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}

2. 半特化/偏特化

任何针对模版参数进一步进行条件限制设计的特化版本。
template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};

偏特化有两种表现形式:

1. 部分特化

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};

2. 参数更进一步的限制

        偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{ 
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}private:T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data(const T1& d1, const T2& d2): _d1(d1), _d2(d2){cout<<"Data<T1&, T2&>" <<endl;}private:const T1 & _d1;const T2 & _d2; };
void test2 () 
{Data<double , int> d1; // 调用特化的int版本Data<int , double> d2; // 调用基础的模板 Data<int *, int*> d3; // 调用特化的指针版本Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}

📁 模板的分离编译

 📂 概念

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

 📂 模板的分离编译

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

 📂 解决方法

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者 xxx.h 其实也是可以的 。推荐使用这种。
2. 模板定义的位置显式实例化 。这种方法不实用,不推荐使用。

📁 模板总结

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

📁 总结

        以上,我们就对模板初阶做了回顾,模板进阶进行了讲解,其中包含了非类型模板参数,就是在模板内部当做常量使用;讲解了模板的特化,即针对特殊类型,产生对应类型的模板实例化,防止产生错误。

        还拓展了模板的分离编译,得出结论,不要讲模板进行分离编译,这会导致模板定义无法实例化,从而导致无法使用。

        如果感觉本期内容对你有帮助,欢迎点赞,收藏,关注Thanks♪(・ω・)ノ

这篇关于【C++杂货铺】模板进阶的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/895343

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

使用Java填充Word模板的操作指南

《使用Java填充Word模板的操作指南》本文介绍了Java填充Word模板的实现方法,包括文本、列表和复选框的填充,首先通过Word域功能设置模板变量,然后使用poi-tl、aspose-words... 目录前言一、设置word模板普通字段列表字段复选框二、代码1. 引入POM2. 模板放入项目3.代码

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

Python进行word模板内容替换的实现示例

《Python进行word模板内容替换的实现示例》本文介绍了使用Python自动化处理Word模板文档的常用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录技术背景与需求场景核心工具库介绍1.获取你的word模板内容2.正常文本内容的替换3.表格内容的

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、