一、模板的引入

首先,请你认真思考一个问题,如何编写一个通用的加法函数呢?

1.对于我来说,首先第一反应是写一个宏函数来实现此功能,如:

#define ADD(x,y) ((x) + (y))

那么,这样写的缺点是什么呢?
(1)它没有参数检测,这个原因致使它能够完成该要求,同时也造就了它致命的缺陷,不能进行参数检测,故安全性不高。

(2)它不能像函数一样进行调试,而仅仅是在编译期间进行简单的参数替换,况且如果代码过长,会大幅增加代码量。

(3)宏函数可能会有副作用,在不该替换的时候进行替换,假设括号没加全。

(4)宏函数只能处理整数或枚举类型的数据,对于其他追加字符串,或其他外置类型的数据处理不了。

#include<iostream>
using namespace std;#define ADD(x,y) ((x) + (y))int main()
{cout<<ADD(1,3)<<endl;cout<<ADD(1.3,2.5)<<endl;cout<<ADD('A','G')<<endl;system("pause");return 0;
}

运行结果:

错误证明:

2、运用前面提到的函数重载解决通用加法函数,针对每个所需相同行为的不同类型重新实现它。

#include<iostream>
using namespace std;int Add(const int &_iLeft, const int &_iRight)
{return (_iLeft + _iRight);
}
float Add(const float &_fLeft, const float &_fRight)
{return (_fLeft + _fRight);
}int main()
{cout<<Add(1,3)<<endl;cout<<Add(1.3f,2.5f)<<endl;system("pause");return 0;
}

运行结果:

这种方法的缺点:

(1)只要有新类型出现,就要重新添加对应函数。
(2)除类型外,所有函数的函数体都相同,代码的复用率不高
(3)如果函数只是返回值类型不同,函数重载不能解决
(4)一个方法有问题,所有的方法都有问题,不好维护。

3、通过多态实现,使用公共基类,将需要用到的虚函数代码放在公共的基础类里面,通过基类的对象指针进行调用,派生类可重写也可不重写。

class B
{
public:virtual int add(int _x,int _y){return (_x+_y);}virtual float add(float _x,float _y){return (_x+_y);}
};
class INT_ADD:public B
{};
class FLOAT_ADD:public B
{};int main()
{B *b;INT_ADD i;FLOAT_ADD f;b = &i;cout<<b->add(1,3)<<endl;b = &f;cout<<b->add(1.8f,3.6f)<<endl;system("pause");return 0;
}

缺点:

(1)借助基类虚函数来编写适合各种类型的加法函数,只要有新类型出现,就要重新添加对应函数,代码利用率不高;

(2)对于以后实现的许多派生类,都必须调用各自某个特定的基类虚函数,代码维护更加困难

4、通过模板解决。

模板的分类:模板分为函数模板和类模板,他们分别允许用户构造模板函数和模板类。

既然上面的几种方法都有各自的缺陷,于是就引入了模板的概念,使用我们的模板解决上述问题,那将是最好不过了,下面用函数模板实现通用加法函数。

template <typename T>
T ADD(T x,T y)
{return (x+y);
}
int main()
{cout<<ADD(1,3)<<endl;cout<<ADD(1.3,2.5)<<endl;system("pause");return 0;
}

优点:利用模板机制可以显著减少冗余信息,能大幅度的节约程序代码,进一步提高面向对象程序的可重用性和可维护性。

二、函数模板

函数模板:

      所谓函数模板,实际上是建立了一个通用函数,其函数返回类型和形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板(注意它不是真正的函数)。代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本

(一)函数模板声明的一般格式:

(二)函数模板的使用

(1)模板函数的实例化:

当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参,此模板实参来为我们实例化一个特定版本的函数。当编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建模板的一个新“实例”,此过程称为模板的实例化

函数模板的实例化有两种调用方式,即显式实例化和隐式实例化。

template <typename T>
T ADD(T x,T y)
{return (x+y);
}
int main()
{cout<<ADD(1,3)<<endl;//隐式实例化cout<<ADD<float>(1.3f,2.5f)<<endl;//显式实例化system("pause");return 0;
}

自定义类型不能直接用模板函数进行实例化,除非自己实现重载。

注意:模板被编译了两次:

a>实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号

b>在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持     某些函数调用

(2)模板函数的使用规则

a>模板函数也可以定义为内联函数

template <typename T>
inline T ADD(T x,T y)
{return (x+y);
}

注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前

b>模板函数的不足之处:不能自动进行类型转换,由于需要生成代码,所以编译速度慢

要解决上述类型不同的问题,有以下三种方法:

1,采用显式实例化方式

template <typename T>
T ADD(T x,T y)
{return (x+y);
}
int main()
{cout<<ADD<int>(1,'d');system("pause");return 0;
}

2,将其中一个参数进行强制类型转换

template <typename T>
T ADD(T x,T y)
{return (x+y);
}
int main()
{cout<<ADD(1,(int)'d');system("pause");return 0;
}

3,多带一个参数模板(在类型中允许使用多个类型参数)

template <typename T,typename G>
T ADD(T x,G y)
{return (x+y);
}
int main()
{cout<<ADD(1,'d');system("pause");return 0;
}

d>模板参数

<1>【实参推演】
       从函数实参确定模板形参类型和值的过程称为模板实参推断,即函数模板实例化过程中,编译器用函数实参来为我们推断模板实参的过程。

注意:多个类型形参的实参必须完全匹配

<2>【类型形参转换】

编译器只会执行两种转换:
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用。

template <typename T>T ADD(const T& x,const T& y)
{return (x+y);
}
int main()
{int a = 10;int b = 20;int &ra = a;int &rb = b;cout<<"a + b ="<<ADD(ra,rb)<<endl;system("pause");return 0;
}

2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。

template <typename T>
T ADD(const T* x,const T* y)
{return (*x + *y);
}
int main()
{int a[5] = {0};int b[5] = {5,4,3,2,1};cout<<"a + b = "<<ADD(a,b)<<endl;system("pause");return 0;
}

<3>模板参数的分类

函数模板有两种类型参数:模板参数和调用参数

模板形参:类型形参和非类型形参

模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则,即我们常用的就近原则。

typedef int T;//将int类型重命名为Ttemplate <typename T>//函数模板
void Funtest(T t)
{cout<<"t type = "<<typeid(t).name()<<endl;//打印形参t的类型
}T gloab;//定义全局变量gloabint main()
{Funtest(10);cout<<"gloab type = "<<typeid(gloab).name()<<endl;system("pause");return 0;
}

代码分析:

总结:

模板形参说明
1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、模板形参表不能为空
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
e>函数模板的重载

1、同一般函数一样,函数模板也可以重载。

template<typename T>
T Max(const T& left, const T& right)
{return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{return Max(Max(a, b), c);
};
int main()
{
cout<<Max(1,2)<<endl;
cout<<Max(1.2,3.4,5.6)<<endl;
system("pause");
return 0;
}

2、模板函数与同名的非模板函数可以重载。

在这种情况下,函数的调用顺序是:首先寻找一个参数完全匹配的非模板函数,如果找到了就调用它此种情况下,编译器并不会为相应的函数模板产生匹配的模板函数,如果想让编译器为模板函数生成代码,则必须显示实例化,且生成的代码与普通函数不是同一份代码;若没有找到参数完全匹配的非模板函数,则寻找函数模板,将其实例化产生一个匹配的模板函

int Max(const int& left, const int & right)//普通函数
{return left>right? left:right;
}
template<typename T>
T Max(const T& left, const T& right)
{return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{return Max(Max(a, b), c);
};
int main()
{cout<<Max(10, 20, 30)<<endl;cout<<Max<>(10, 20)<<endl;cout<<Max(10, 20)<<endl;cout<<Max(10, 20.12)<<endl;cout<<Max<int>(10.0, 20.0)<<endl;cout<<Max(10.0, 20.0)<<endl;system("pause");return 0;
}

注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。

【说明】
1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例
化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板
函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,
那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,
而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

f>模板函数特化

1、定义:

有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情,这时候就需要对模板函数进行特化(即具体化)。

template <typename T>
int Compare(T s1, T s2)
{if(s1<s2)return -1;else if(s1>s2)return 1;else return 0;
}
int main()
{char* str1 = "abcd";char* str2 = "ghf";cout<<Compare(str1,str2)<<endl;system("pause");return 0;
}

template <typename T>
int Compare(T s1, T s2)
{if(s1<s2)return -1;else if(s1>s2)return 1;else return 0;
}
template<>//模板函数的特化
int Compare<const char*>(const char* s1,const char* s2)
{return strcmp(s1,s2);
}
int main()
{const char* str1 = "abcd";const char* str2 = "ghf";cout<<Compare(str1,str2)<<endl;system("pause");return 0;
}

2、模板函数特化形式如下:

1>关键字template后面接一对空的尖括号<>
2>再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3>函数形参表
4>函数体
template<>
返回值 函数名<Type>(参数列表)
{
     // 函数体
}

其次,需要注意的是给函数模板传参时一定要与特化参数列表中的参数格式一模一样,否则有可能即使写了特化,运行过程中也不会调用

template <typename T>
int Compare(T s1, T s2)
{if(s1<s2)return -1;else if(s1>s2)return 1;else return 0;
}
template<>//模板函数的特化
int Compare<const char*>(const char* s1,const char* s2)//注意参数类型为const char*
{return strcmp(s1,s2);
}
int main()
{char* str1 = "abcd";//定义实参类型为char*char* str2 = "ghf";//定义实参类型为char*cout<<Compare(str1,str2)<<endl;system("pause");return 0;
}

注意:

1.在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,
如果不匹配,编译器将为实参模板定义中实例化一个实例。

2.特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然

后使用该特化版本的每个源文件包含该头文件。

三、类模板

与函数模板类似,模板类也是模板,必须以关键字template开头,后接模板形参表。

【普通顺序表】

【模板类格式】
template<class 形参名1, class 形参名2, ...class 形参名n>
class 类名
{ ... };

template<typename T>
class SeqList
{
private :
T* _data ;
int _size ;
int _capacity ;
};

// 以模板方式实现动态顺序表

template<typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
template <typename T>
SeqList <T>:: SeqList()
: _size(0)
, _capacity(10)
, _data(new T[ _capacity])
{}
template <typename T>
SeqList <T>::~ SeqList()
{
delete [] _data ;
}
void test1 ()
{
SeqList<int > sl1;
SeqList<double > sl2;
}

【模板类的实例化】
只要有一种不同的类型,编译器就会实例化出一个对应的类。

SeqList<int > sl1;
SeqList<double > sl2;
  1. 当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模板形参,重新编写SeqList类,最后创建名为SeqList<int>和SeqList<double>的类

【非类型的类模板参数】

// 静态顺序表
//template<typename T, size_t MAX_SIZE>
template <typename T, size_t MAX_SIZE = 10> //带缺省模板参数
class SeqList
{
public :
SeqList();
private :
T _array [MAX_SIZE];
int _size ;
};
template <typename T, size_t MAX_SIZE>
SeqList <T, MAX_SIZE>::SeqList()
: _size(0)
{}
void Test()
{
SeqList<int> s1;
SeqList<int , 20> s2;
}

注意:浮点数和类对象是不允许作为非类型模板参数的

【类模板的特化】

全特化

template <typename T>
class SeqList
{
public :SeqList();~ SeqList();
private :int _size ;int _capacity ;T* _data ;
};
template<typename T>
SeqList <T>:: SeqList(): _size(0), _capacity(10), _data(new T[ _capacity])
{cout<<"SeqList<T>" <<endl;
}
template<typename T>
SeqList <T>::~ SeqList()
{delete[] _data ;
}
template <>
class SeqList <int>
{
public :SeqList(int capacity);~ SeqList();
private :int _size ;int _capacity ;int* _data ;
};

// 特化后定义成员函数不再需要模板形参

SeqList <int>:: SeqList(int capacity)
: _size(0)
, _capacity(capacity )
, _data(new int[ _capacity])
{
cout<<"SeqList<int>" <<endl;
}

偏特化(局部特化)

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

偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限
制所设计出来的一个特化版本。

// 局部特化两个参数为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
T1* _d3 ;
T2* _d4 ;
};
template <typename T1, typename T2>
Data<T1 *, T2*>:: Data()
{
cout<<"Data<T1*, T2*>" <<endl;
}
// 局部特化两个参数为引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public :
Data(const T1& d1, const T2& d2);
private :
const T1 & _d1;
const T2 & _d2;
T1* _d3 ;
T2* _d4 ;
};
template <typename T1, typename T2>
Data<T1 &, T2&>:: Data(const T1& d1, const T2& d2)
: _d1(d1 )
, _d2(d2 )
{
cout<<"Data<T1&, T2&>" <<endl;
}
void test2 ()
{
Data<double , int> d1;
Data<int , double> d2;
Data<int *, int*> d3;
Data<int&, int&> d4(1, 2);
}

模板的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。

【模板的分离编译】

解决办法:
1. 在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添
加 template class SeqList<int >; 一般不推荐这种方法,一方面老编译器可能不支持,另一方
面实例化依赖调用者。(不推荐)
2. 将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法。

模板总结
【优点】
模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
增强了代码的灵活性。
【缺点】
模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
出现模板编译错误时,错误信息非常凌乱,不易定位错误。

C++模板的那丢丢事儿相关推荐

  1. 英语学习—每天进步一丢丢系列(一)

    英语学习-每天进步一丢丢系列(一) 15岁觉得游泳难,放弃游泳,到18岁遇到一个你喜欢的人约你去游泳,你只好说我不会耶 18岁觉得英文难,放弃英文,28岁出现一个很棒但要会英文的工作,你只好说我不会耶 ...

  2. 上海交通大学保研计算机,经验分享 | 上海交通大学保研,一丢丢心得和体会。...

    原标题:经验分享 | 上海交通大学保研,一丢丢心得和体会. 本人情况 学校:非211一本 英语:六级 获奖:国家级奖项五项,省级奖项若干:专利两项:"三好学生"等荣誉称号若干. 排 ...

  3. linux内核管理pagecache的一丢丢知识整理

    pagecache是linux内核为了提高程序运行效率开辟出来的内存.通俗点理解,程序在硬盘里是整齐码放的,但是运行的时候是需要哪一块就把哪一块load到内存里使用,如果程序运行过程中发现需要的代码没 ...

  4. 关于数据中台的深度思考与总结,20000 字不到一丢丢。。。

    本文将总结下数据中台的相关理论知识.Flink平台化需要改进的点等等. 参考:<数据中台> 数据中台 数据汇聚 数据汇聚是数据中台必须提供的核心工具,把各种异构网络.异构数据源的数据方便地 ...

  5. 一个小菜鸟给未来的菜鸟们的一丢丢建议

    写这篇文章的主要原因是有个建筑行业的朋友觉得搞建筑身累心累,想转到我们这个it行业来加入我们的编程大军中,找我咨询了一哈. 在我了解了他的逻辑和理科这方面只是一般般的基础上,我给他的建议是:学习前端, ...

  6. mysql用 fifo 记录日志_MySQL一丢丢知识点的了解

    1. MySQL体系结构 从概念上讲,数据库是文件的集合,是依照某种数据模型组织起来并存放于二级存储器中的数据集合:数据库实例是程序,是位于用户与操作系统之间的一层数据管理软件,用户对数据库数据的任何 ...

  7. 网络 随笔 1-再补充一丢丢常识

    0. 电子信息专业要有这种高等教育就好多了 1. 网段 & 子网掩码 据个栗子:192.168.0.0/28 这里的192.168.0.0 是网段 28 则是 子网掩码的二进制数(前面连续的& ...

  8. SQL server一丢丢杂乱的整理

    1.sql server单用户多用户的切换 2.转换大小写 3.查询数据库中用户名.数据库名.表名.字段名等 4.删除sql server登录时用户名中的自动记录的用户 5.查询存储过程包含的字段 6 ...

  9. 介绍一款搜索引擎(Magi):也就比百度好用一丢丢

    Magi 搜索引擎介绍 作为一个上班族,每天都要跟搜索引擎打交道,搜索技术方案.bug修复.新技术等,平时生活中遇到一些不懂的概念也会去搜索. 但是现在的百度充斥着各种商业气息,一些有用的关键词的前几 ...

最新文章

  1. 几行代码实现老照片上色复原!
  2. 禁掉人脸识别!一群音乐人正在号召,禁止在音乐节上动用人脸识别
  3. Java Sort中Comparator的语义分析
  4. Java内部类(Inner Class)小记
  5. 1.springMVC+spring+Mybatis的整合思路
  6. 关于CS1061报错(XX不包含XXX的定义,并且找不到类型为XX的第一个参.....)的一种可能的解决的办法...
  7. 线程的应用-如何应用多线程
  8. storm metric的使用说明
  9. progressIndicator in SalesPipeline
  10. react登录页面_React 实现路由拦截
  11. 本土开源、立足全球 | COSCon'17
  12. JAVA多线程学习3--线程一些方法
  13. 前后端分离重复提交_阿里一面:如何保证API接口数据安全?
  14. 1.1 STL 概述
  15. 基于Java visualvm的可视化监控的使用
  16. 摄像机标定和 3D 重构
  17. java spring是用在哪里_什么是spring框架?spring框架到底有什么用?spring框架到底做了些什么?...
  18. 手摸手写一个互联网黑话生成器
  19. 129、易燃气体的分级
  20. 二分算法详解:整数二分及浮点数二分算法(Binary Search)(含算法模板)

热门文章

  1. 基于SpringMVC、Maven以及Mybatis的环境搭建 【转】
  2. Archive for required library: ‘WebContent/WEB-INF/lib/xxx.jar cannotn
  3. memcached 相关
  4. 几个支持生成Python代码的UML工具
  5. 用“已知”的办法解决“未知”的办法---.NET连动控件和统计数量
  6. 中石油训练赛 - 数学问题(思维)
  7. HDU - 3486 Interviewe(RMQ-st表+暴力)
  8. POJ - 3250 Bad Hair Day(单调队列/单调栈)
  9. SciencePlots科研绘图
  10. POJ2352 stars(树状数组)