【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

相关文章

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

C++ 函数 strftime 和时间格式示例详解

《C++函数strftime和时间格式示例详解》strftime是C/C++标准库中用于格式化日期和时间的函数,定义在ctime头文件中,它将tm结构体中的时间信息转换为指定格式的字符串,是处理... 目录C++ 函数 strftipythonme 详解一、函数原型二、功能描述三、格式字符串说明四、返回值五

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

C++作用域和标识符查找规则详解

《C++作用域和标识符查找规则详解》在C++中,作用域(Scope)和标识符查找(IdentifierLookup)是理解代码行为的重要概念,本文将详细介绍这些规则,并通过实例来说明它们的工作原理,需... 目录作用域标识符查找规则1. 普通查找(Ordinary Lookup)2. 限定查找(Qualif

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一