思维导图附上
看不清楚戳这里【模板思维导图】
链接部分思维导图是备注展开的,图片是没展开的。
1.为什么要有模板?
其实用现实生活中的话都可以回答,比如冰棍模具,我们可以根据这个模具的样子,加入不同的调料,得到形状造型相同,但内部调料类型不同,的不同冰棍。这也是我们C++模板,具体到一类容器来说,容器相同,但内部类型不同。都用同一个类模板创造的。
总结就是:告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码
2.模板的原理
我们对模板的使用,并不是直接使用模板,而是我们提供类型,而编译器会根据模板,生成一份对应我们提供类型的实例化类,我们也就可以调用它来创建类对象了。注意:提供不同类型,会生成不同实例化类。
此图把原理分析的很清楚了:
3.模板的参数
1)类型模板参数
类型模板参数就是class/typename 加上类型组成的范式类型列表。
class:
template<class T1, class T2,......,class Tn>
typename:
template<typename T1, typename T2,......,typename Tn>
两者在此处并没有很大的区别,typename比class多一个“哄骗”声明。就是在编译阶段告诉编译器,我后面的是一个类型,你让它过,等它实例化以后你自己去找。编译器就说好让它通过了。但到了后续不一定能成功运行。因为编译器去找了,但不一定是一个类型,有可能是一个类静态成员变量。甚至还有可能不存在这个东西。只有实例化后才能知道。
//typename就和int那些声明一样,但是int那些声明是确定的,typename的声明是不一定有保障的 int a; //a一定是int typename T::iterator it; //告诉编译器这个iterator这是T(范式)类型中的类型,你等它实例化后自己去找 //与作用限定符 类::内部类/typedef别名/静态成员变量
2)非类型模板参数
非类型模板参数是指,除了class/typename+类型名称,还可以整形家族+变量名称都可以给缺省
比如:array 静态数组
template<class T,size_t N=1000>; class array{ private: T _array[N]; int _size; };
4.模板的分类
注意:模板的作用域就是该函数模板或者类模板
1)函数模板
函数模板定义:
template<class T1, class T2,......,class Tn> 返回值类型 函数名(参数列表) {}
函数模板实例化:
//以Add函数为例: template<class T> T Add(T left, T right) { return left + right; } Add(1, 2); //调用实例化 Add<int>(1,2);//显示实例化,告诉编译器我是什么类型,可以对我的数据进行强转
2)类模板
类模板的定义:
emplate <class T1,class T2,...,class Tn> class 类模板名 { //类内的成员函数和成员变量 }
类模板的实例化:
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector<int>才是类型 Vector<int> s1; Vector<double> s2;
5.模板的特化
我们不论在用函数模板还是类模板的时候,都有一些情况下需要调整:
比如:我们想要比较两个指针所指向的整形对象的大小,我们把int*传到函数模板,x,y是指针,但它没有解引用操作,只是直接比较地址。所以得到的结果不符合我们的需求,或者我们想针对部分类型执行其他代码(有一定代码限制的作用),需要更改,这里就可以用到我们的模板的特化。
template<class T> bool Less(T x,T y) { return x < y; }
1)函数模板特化:
接着上面的例子,我们可以做以下特化操作:
//全特化,很具体 template<> bool Less<int*>(int* x,int* y) { return *x < *y; } int main() { int a=10,b=20; Less(10,20); //匹配函数模板 Less(&a,&b); //优先匹配int*特化模板 return 0; }
其实在函数特化这部分,真不如函数重载,函数模板特化可以少用。
bool Less(int* x,int* y) { return *x < *y; }
2)类模板特化
我们以 Less类 来举例:
template<class T1 , class T2> struct Less { bool operator()(const T1& x, const T2& y) const { return x < y; } };
1. 全特化: 所有模板参数给出具体类型。
// 对Less类模板特化 template<> struct Less<Date,Date> { bool operator()(Date x, Date y) const { return x < y; } };
2. 偏特化: 部分特化,一个继续是泛型,一个给出具体类型。
template<class T1> struct Less<T1,int> { bool operator()(T1 x, int y) const { return x < y; } };
把指针泛型进一步提出来,也属于偏特化
template<class T1,class T2> struct Less<T1*,T2*> { bool operator()(const T1* x, const T2* y) const { return *x < *y; } };
模板匹配原则:优先匹配现成的,有完全符合我的就用他,因为它很具体,里面函数体的执行也是量身定做。 让我想到一句要爱具体的人,不要爱抽象的人 因为具体的的人更懂我们哈哈,共勉!
6.类模板的声明定义分离
我们借思维导图来学习下:
声明定义分离有两种情况:
- 声明定义都放在头文件中(.h 或 .hpp),但在类模板里只声明,类模板外定义;
- 声明放在头文件(.h)————定义放在源文件(.cpp)
第一种情况: 在编译阶段,会展开头文件,调用该成员函数实例化,声明,会根据调用对象,生成找到定义地址位置的相关代码,再链接时,生成最终执行代码
第二种情况: 在(.cpp)文件中,就算包含了头文件,但没有实例化,在编译阶段就被编译器优化掉了,到了main函数调用的时候,链接时,就报错了。编译错误
第二种解决方案: 我们在(.cpp)文件中显式实例化一个任意类型的对象,类模板就会被保留下来,再到链接的时候整合代码,根据我们的调用生成新的类型的对象。对函数进行调用。
总结
本章重点就是讲解模板的使用,类模板更加重要,还有就是模板分离部分,很关键。思维导图持续优化~
看猫共勉,诸君加油~