【 C++私房菜】模板的入门与进阶

目录

一、模板的定义

a.函数模板的调用

b.类模板的定义

2、模板的重载

3、非类型模板参数和模板类型参数

4、模板的编译

二、模板的特化

1、函数模板特化

2、类模板特化

a.全特化

b.偏特化

三、模板相关定义


一、模板的定义

a.函数模板的调用

理在的 C+编译器实现了 C++新增的一项特性——函数模板(function template)。函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如 int、double) 替换。通过将类型作为参数传递给模板。可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序。因此有时也被称为通用编程,由于类型是用参数表示的,因此模板特性有时也被称为参数化类型(parameterized types)。下面介绍为何需要这特性以及其工作原理。

C++的函数模板功能可以自动完成一个节省时间,且可靠的过程。函数模板允许以任意类型的方式来定义函数。一个函数模板就是一个公式,可以用来生成针对特定类型的函数模板。例如,可以这样建立一个交换模板:

 template<typename AnyType>//template<class AnyType>void Swap(AnyType &a,AnyType &b){AnyType t = a;a = b;b = t;}

第一行指出,要建立一个模板,并将类型命名为 AnyType。关键字 template 和 typename 是必需的,也可以使用关键字 class 代替 typename。另外,必须使用尖括号。类型名可以任意选择 (这里为 AnyType),只要遵守C++命名规则即可。许多程序员都使用简单的名称,如 T。余下的代码描述了交换两个 AnyType值的算法。模板并不创建任何函数,而只是告诉编译器如何定义函数。需要交换int的函数时,编译器按模板模式创建这样的函数,并用 int 代 AnyType。同样,需要交换double 的函数时,编译器将模板模式创建这样的函数,并用 double 代 AnyType。

提示:如果需要多个将同一种算法用于不同类型的函数,请使用模板。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数时,应使用关键字typename 而不是使用class。

注意,函数模板不能缩短可执行程序。最终仍将由独立的函数定义,就像以手工方式定义了这些函数一样。最终的代码不包含任何模板,而只包含了为程序生成的实际函数。使用模板的好处是,它使生成多个函数定义更简单、更可靠。

更常见的情形是,将模板放在头文件中,并在需要使用模板的文件中包含头文件。 注:在模板定义中,不能将模板参数列表置空。


b.类模板的定义

类模板(class template)是用来生成类蓝图的。与函数模板不同的是,编译器不能为类模板推断模板类型参数。

类似函数模板,类模板以关键字 template 开始,后跟模板参数列表。在类模板(及其成员)的定义中,我们将模板参数当作替身,代替使用模板时用户需要提供的类型或值。

 template<class T1, class T2, ..., class Tn>class 类模板名{// 类内成员定义};// 动态顺序表// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具template<class T>class Vector{public:Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}// 使用析构函数演示:在类中声明,在类外定义。~Vector();void PushBack(const T& data);void PopBack();// ...size_t Size() { return _size; }T& operator[](size_t pos){assert(pos < _size);return _pData[pos];}private:T* _pData;size_t _size;size_t _capacity;};// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表template <class T>Vector<T>::~Vector(){if (_pData)delete[] _pData;_size = _capacity = 0;}

当使用一个类模板时,我们必须提供额外信息。我们现在知道这些额外信息是显式模板实参(explicit template argument)列表,它们被绑定到模板参数。编译器使用这些模板实参来实例化出特定的类。例如 vector<int> v;当我们的vector模板实例化一个类时,它会重写vector模板,将模板参数T的每一个实例都替换为给定的模板实参。

一个类模板的每个实例都形参一个独立的类。类型vector<int>与任何其他vector类型都没有关联,也不会对任何其他vector类型的成员由特殊访问权限。

如果一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化,这一特性使得即使某这类型不能完全符合模板操作的要求,我们仍能使用该类型实例化类。

默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。

在类代码内简化模板类名的使用:当我们使用一个模板类时必须提供模板实参,但如果在类模板自己的作用域中,我们可以直接使用模板名而不提供实参:

 template < typename T> class BlobPtr{public:BlobPtr& operator++(); //前置++BlobPtr& operator--(); //前置--//...    }

当我们处于一个类模板的作用域内时,编译器处理模板自身引用时就好像我们以及提供了与模板参数匹配的实参一样。即,就好像这样编写代码一样: BlobPtr<T>& operator++()

在类模板外使用模板类名:当我们在类模板外定义其成员时,我们并不在类的作用域中,直到遇到类名才进入类的作用域。:

 template < typename T> BlobPtr<T> BlobPtr<T>::operator++(int)//后置++{BlobPtr ret=*this;++*this;return ret;}

由于返回类型位于类的作用域开,我们必须指出返回类型是一个实例化的BlobPtr,它所用类型与类实例化所用类型一致。在函数体内,我们已经进入类的作用域,因此在定义ret时无须重复模板实参。如果不提供模板实参,则编译器将假定我们使用的类型与成员实例化所用类型一致。

在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参。

类模板中的static成员:对于任何其他static数据成员相同,模板类的每一个static数据成员必须由且仅有一个定义。但是,类模板的每个实例都有一个独有的static对象。因此与定义模板的成员函数相似,我们将static数据成员也定义为模板:

 template<class T>size_t Foo<T>::ctr = 0;

类似其他任何成员函数,一个static成员函数只有在使用时才会实例化。


2、模板的重载

需要多个对不同类型使用同一种算法的函数时,可使用模板。然而,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,重载的模板的函数特征标必须不同。例如新增一个交换模板,用于交换两个数组中的元素。原来的模板的特征标为(T &,T&),而新模板的特征标为(T ,T,int)。注意,在后一个模板中,最后一个参数的类型为具体类型(int),而不是泛型。因此,并非所有的模板参数都必须是模板参数类型。

 template<typename AnyType>void Swap(AnyType &a,AnyType &b,int c);​template<typename AnyType>void Swap(AnyType &a,AnyType &b);

3、非类型模板参数和模板类型参数

模板参数分类类型形参与非类型形参。

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

在我们的Swap函数中有一个模板类型参数(type parameter)。一般来说,我们可以将类型参数看作类型的说明符,就像内置类型或类类型说明符一样使用。特别的是,类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于声明变量或类型转换:

 template<class T>T foo(T* p){T tmp=*p; //tmp的类型将是p指向的类型。//...return tmp;}

类型参数前必须使用关键字class或template。在模板参数列表中,这两个关键字的含义相同,可以互换使用。一个模板参数列表中可以同时使用这两个关键字。

除了定义类型参数,还可以在模板中定义非类型参数(nontype parameter)。一个非类型模板参数表示一个值而非一个类型。我们通过一个特定的类型名,而非关键字class或typename来指定非类型参数。 当一个模板被实例化时,非类型模板参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。

 template <unsigned N,unsigned M>int compare(const char (&p1)[N] ,const char (&p2)[M])//这里是数组的引用,引用形参绑定到对应的实参上,即绑定到数组上。{return strcmp(p1,p2);}

如上代码中,编写了一个处理字符串字面常量的模板函数。这种字面常量是const char的数组。由于不能拷贝一个数组,因此我将函数参数定义为数组的引用。模板定义为了两个非类型的参数,从而我们可以比较不同长度的字符串字面常量。第一个参数表示第一个数组的长度,第二个参数表示第二个数组的长度。

compare("hello","C++");因此在调用此语句时,编译器会用字面常量的大学来代替N和M,从而实例化模板。编译器会在一个字符串字面常量的末尾插入一个空字符来作为终结符。因此编译器会实例化为 int compare(const char (&p1)[6] ,const char (&p2)[4])

一个非类型参数可以是个整型,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整数参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须是具有静态的生存期。不能使用一个普通(非static)局部变量或动态对象作为指针或引用非类型模板参数的实参。指针参数也可以用nullptr或一个值为0的常量表达式来实例化。

在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数,例如,指定数组大小。

 // 定义一个模板类型的静态数组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. 非类型的模板参数必须在编译期就能确认结果。

4、模板的编译

当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。当我们使用(而不是定义)模板时,编译器才生成代码,这一特性影响了我们如何组织代码以及错误何时被检测到。 通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。 模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义。(函数模板和类模板成员函数的定义通常放在头文件中,模板不能声明和定义分离在两个文件中。)

使用模板的注意事项!!!

当使用模板时,所有不依赖于模板参数的名字都必须是可见的,这是由模板的提供者来保证的。而且,模板的提供者必须保证,当模板被实例化时,模板的定义,包括类模板的成员的定义,也必须是可见的。 用来实例化模板的所有函数、类型以及与类型关联的运算符的声明都必须是可见的,这是由模板的用户来保证的。


二、模板的特化

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

  struct Stu{char name[40];double grades;int age;}

由于C++允许将一个结构赋给另一个结构,假如交换两个Stu结构的内容。使用上述Swap模板函数,依旧可行。

然而,假识想交换 grades 和 age 成员,而不交换 name 成员,则需要使用不同的代码,但 Swap()的参数将保持不支(两个Stu结构的引用),因此无法使用模板重载来提供其他的代码。 然而,可以提供一个具体化函数定义称为显式具体化(explicit specialization),其中包含所需的代码,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。

具体化机制随着C++的演变而不断变化。下面介绍C++标准定义的形式。

  • 对于给定的函数名,可以有非模板函数、模板函数、显示具体化模板函数以及它们的重载的版本。

  • 显示具体化的原型和定义应以 template<>打头,并通过名称来指出类型。

  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。

下面是交换Stu结构体的几个函数的声明:

 //非模板函数void SWap(Stu &a,Stu &b);//模板函数template<typename T>void Swap(T &,T &)//显式具体化template <> void Swap<Stu>(Stu&,Stu&);

Swap<Stu>中的<Stu>是可选的,因为函数的参数类型表名,这是Stu的一个具体化。因此,该定义也可以这样写 template <> void Swap(Stu&,Stu&);

 //在函数名后的<>中指定模板参数的实际类型//情况1template<typename T>void Swap(T a, T b){T t = a;a = b;b = t;} ​//情况2template<typename T>void Swap(T& a, T& b){T t = a;a = b;b = t;}​int main(){int a = 1;double  b = 1.1;Swap<double>(b, a);//情况1可以使用,情况2不可以使用cout << a << " " << b << endl;}

情况2中将类型double生成一个显示实例化。由于第一个参数的类型是double&,不能指向int变量a。

在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。


1、函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板 。

  2. 关键字template后面接一对空的尖括号<> 。

  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型。

  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

 // 函数模板 -- 参数匹配template<class T>bool greater(T left, T right){return left > right;}// 对Less函数模板进行特化template<>bool greater<Date*>(Date* left, Date* right){ return *left > *right;}int main(){cout << greater(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << greater(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << greater(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成return 0;}

2、类模板特化

a.全特化

全特化是指将模板参数列表中所有参数都确定化。

 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;} 

b.偏特化

偏特化是指任何针对模版参数进一步进行条件限制设计的特化版本。

 template<class T1, class T2>class Data{public:Data() {cout<<"Data<T1, T2>" <<endl;}private:T1 _d1;T2 _d2;};

偏特化由两者表现方式:部分特化和限制参数的特化

部分特化:将参数类表中的一部分参数特化。

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

限制参数的特化

 //两个参数偏特化为指针类型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); // 调用特化的指针版本}

匹配顺序:全特化 -> 偏特化 -> 原模板


三、模板相关定义

类模板(class template) :模板定义,可从它实例化出特定的类。类模板的定义以关键宇 template 开始,后跟尖括号对<和>,其内为一个用逗号分隔的一个或多个模板参数的列表,随后是类的定义。

默认模板实参(default template argument):一个类型或一个值,当用户未提供对应模板实参时,模板会使用它。

显式实例化(explicit instantiation):一个声明,为所有模板参数提供了显式实参。

实例 (instantiation) :编译器从模板生成的类或函数。

函数模板(function template): 模板定义,可从它实例化出特定函数。函数模板的定义以关键宇 template 开始,后跟尖括号对<和>,其内为一个用逗号分隔的一个或多个模板参数的列表,随后是函数的定义。

成员模板(member template): 本身是模板的成员函数。成员模板不能是虚函数。

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

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

相关文章

头歌C++之For循环性质编程实训

目录 第1关:求1到n间所有整数的和 本关必读 本关任务 测试说明 第2关:求s=a+aa+aaa+aaaa+aa...a的值 本关必读 本关任务 测试说明 第3关:求1!+2!+3!+⋯+n!的值 本关必读 本关任务

确定软件项目范围基准 5个重点

软件项目范围基准明确了项目的边界、目标和主要交付成果&#xff0c;有助于提高项目成本、进度和资源估算的准确性&#xff0c;便于实施项目控制&#xff0c;而且还可以帮助我们清楚分派责任&#xff0c;防止范围蔓延&#xff0c;从而提升项目的成功率。 如果没有明确确定范围基…

Pointnet++改进优化器系列:全网首发AdamW优化器 |即插即用,实现有效涨点

简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入AdamW优化器,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步

网络基础---初识网络

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、局域网…

算法训练营Day59(单调栈)

下一个更大元素II 503. 下一个更大元素 II - 力扣&#xff08;LeetCode&#xff09; 最直接的方法&#xff0c;我自己写的。。 class Solution {public int[] nextGreaterElements(int[] nums) {int len nums.length*2;int [] nums2 new int[len];for(int i 0;i<len;i…

Nav2笔记

1、源码安装 git clone https://github.com/ros-planning/navigation2.git -b humble 1.1 一键安装依赖 wget http://fishros.com/install -O fishros && . fishros rosdepc install -r --from-paths src --ignore-src --rosdistro $ROS_DISTRO -y 1.2 编译 colc…

动态规划学习——背包问题

问题&#xff1a; 有一个背包&#xff0c;有最大的可以承受的重量Weight 有一些物品&#xff0c;每个物品都有相应的重量和价值 给两个数组w[]和v[]&#xff0c;其中w[i]表示第i个物品的重量&#xff0c;v[i]表示第i个物品的价值 求如何拿才能在不超过背包承重的情况下拿到的最…

Hive之set参数大全-16

配置 HiveServer2 中 Tez Workload Manager (WM) Application Master (AM) 注册的超时时间 在 Hive 中&#xff0c;hive.server2.tez.wm.am.registry.timeout 是一个参数&#xff0c;用于配置 HiveServer2 中 Tez Workload Manager (WM) Application Master (AM) 注册的超时时…

高斯分布的应用,正态分布的实践应用,什么是极大似然估计法

目录 高斯分布的应用 正态分布的实践应用 什么是极大似然估计法 高斯分布的应用

【leetcode刷刷】235. 二叉搜索树的最近公共祖先 、701.二叉搜索树中的插入操作 、450.删除二叉搜索树中的节点

235. 二叉搜索树的最近公共祖先 class Solution:def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:# 递归if not root: return if root.val p.val: return pif root.val q.val: return qleft Noneright Noneif root.val > p.…

LeetCode2859. Sum of Values at Indices With K Set Bits

文章目录 一、题目二、题解 一、题目 You are given a 0-indexed integer array nums and an integer k. Return an integer that denotes the sum of elements in nums whose corresponding indices have exactly k set bits in their binary representation. The set bits…

前后端交互—使用自己的服务器开发项目

代码下载 完善服务器 对于上一篇博客开发的服务端项目&#xff0c;还需要增加文章列表查询&#xff0c;文章删除&#xff0c;文章更新三个接口。 文章列表查询接口 验证表单数据 在路径 schema/user.js 中增加 articles 验证表单数据&#xff0c;其中使用的 .allow() 表示…

【misc | CTF】攻防世界 适合作为桌面

天命&#xff1a;这题还挺繁琐的&#xff0c;知识点还不少 目录 步骤1&#xff1a;图片隐写 步骤2&#xff1a;Winhex查看ascii码 步骤1&#xff1a;图片隐写 拿到这张图片&#xff0c;不可能扔进ps会有多图层&#xff0c;普通图片也就一个图层而已 但居然可以有隐写图片这…

【C语言】深入理解指针(3)数组名与函数传参

正文开始——数组与指针是紧密联系的 &#xff08;一&#xff09;数组名的理解 &#xff08;1&#xff09;数组名是数组首元素的地址 int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *parr &arr[0]; 上述代码通过&arr[0] 的方式得到了数组第一个元素的地址&#xff0c;…

绿色制造的行业标杆OEKO-TEX STeP认证

STeP是“OEKO-TEX国际环保纺织协会”推出的一个独立的第三方认证体系。STeP全称Sustainable Textile & Leather Production&#xff08;可持续纺织和皮革生产&#xff09;&#xff0c;是面向纺织和皮革供应链中环保且负有社会责任的生产工厂推出的透明认证体系。 STeP认证的…

【Mybatis plus】使用分页查询,报错 Parameter ‘xxx‘ not found. Available parameters are xxx

文章目录 0 先给出错误场景java entity 实体类java mapper 接口方法Java mapper 所对应的 mapper.xml 信息异常信息 1 解决办法step1: 给 mapper 接口方法加上具名参数指定&#xff0c;如下&#xff1a;step2: 修改 mapper.xml 的查询信息&#xff0c;都加上 具名 限定 2 异常原…

快快销ShopMatrix 分销商城多端uniapp可编译5端-代理商收益管理:差价奖励和销售额统计

代理商收益管理是一种针对代理商的利润分配模式&#xff0c;主要通过差价奖励和销售额统计来实现。这种模式的核心思想是通过激励代理商的销售行为&#xff0c;提高代理商的积极性和销售效率&#xff0c;从而实现整个销售网络的增长。 差价奖励是代理商收益管理中的一种常见方…

电商系统设计到开发03 引入Kafka异步削峰

一、前言 系统设计&#xff1a;电商系统设计到开发01 第一版设计到编码-CSDN博客 接着上篇文章&#xff1a;电商系统设计到开发02 单机性能压测-CSDN博客 本篇为大制作&#xff0c;内容有点多&#xff0c;也比较干货&#xff0c;希望可以耐心看看 已经开发的代码&#xff0…

配置ARP安全综合功能示例

组网图形 ARP安全简介 ARP&#xff08;Address Resolution Protocol&#xff09;安全是针对ARP攻击的一种安全特性&#xff0c;它通过一系列对ARP表项学习和ARP报文处理的限制、检查等措施来保证网络设备的安全性。ARP安全特性不仅能够防范针对ARP协议的攻击&#xff0c;还可以…

uniapp点击事件报错 Cannot read property ‘stopPropagation‘ of undefined

问题产生&#xff1a;在列表上有个小按钮&#xff0c;可点击弹出选择框。 列表本身可点击进入详情页。所以想用click.stop来阻止点击小按钮时候&#xff0c;触发列表的点击事件。 结果&#xff1a;如图所示 解决方案&#xff1a;发现自己用的是icon&#xff0c;在icon上加click…