文章目录
模版初阶
一、泛型编程
引入:如何实现一个通用的交换函数呢?
使用函数重载可以实现,但是有几点不好的地方:
(1)代码复用率较低,只要有新类型出现就要重新写一个函数。
(2)代码的可维护性较低。
解决方法:泛型编程——编写与类型无关的代码,是代码复用的一种手段,模版是泛型编程的基础。
模版又分为:(1)函数模版 (2)类模版
二、函数模版
1.概念
函数模版与类型无关,在使用时被实例化,更具函数实参类型产生特定的类型的函数版本。
2.格式
template<typename T1,typename T2,..... typename Tn>
返回值类型 函数名(参数列表){};
注意:使用不同类型参数调用的函数不是同一个函数,因为函数地址不同。
3.函数模版的原理
函数模版并不是函数,是编译器通过参数类型产生具体的该类型的函数的磨具,所以模版就是把我们应该做的事情交给了编译器(编译器也有一些优化,稍微降低了代码膨胀的问题)。
在编译阶段,编译器需要根据传入的实参类型来推演生成对应类型的函数以提供调用。
函数模版,类模版
第一次编译:不在乎T具体是啥类型,对函数模版本身进行编译。
第二次编译:在函数模版模版调用处,分析T的类型,进行二次编译。
4.函数模版的实例化
用不同类型参数使用函数模版时就是函数模版的实例化。
(1)隐式的实例化
template<typename T>
T add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 5, a2 = 10;
double b1 = 1.1, b2 = 3.0;
add(a1,a2);
//add(a1,b1); Error
/*只有一个模版参数,不能推演T究竟是什么类型
注意!:编译器不会对模版进行类型转化的操作
*/
add(a1,(int)b1);
return 0;
}
(2)显示的实例化
在函数名字后面加<>中指定参数的模版类型。
void test()
{
int a = 100;
double b = 10;
//显示实例化
add<int>(A,b);
}
5.模版参数的匹配规则
<1>函数模版与普通函数相同
C++编译器优先使用普通函数,但是可以通过<>限定使用模版。如果函数模版可以生成一个更好的参数匹配,就选择模版。
三、类模版
类模版的模版参数不可以自动类型推导,必须指定。
1.格式
template<class T1,,,,,class Tn>
class 模版名称
{
//类成员定义
}
2.类模版的成员函数在外部实现
template<class T>
class Date
{
public:
T _a;
T _b;
Date(T a,T b);
void showDate();
}
//用Date<T>指明类域
template<class T>
Date<T>::Date(T a,T b)
{
//....
}
template<class T>
void Date<T>::showDate()
{
//....
}
类模版的实例化
模版的名字不是一个类,实例化 的结果才是一种类。
模版进阶
一、非类型的模版参数
模版参数类型分为类型形参和非类型形参。
类型形参:跟在class或者typename后的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模版的一个参数,在(函数)中可以将该参数当做常量来使用。
namespace LZH
{
template<class T, size_t N = 100>
class arr
{
public:
T& operator[](size_t index)
{
return _arr[index];
}
//、、、、、
private:
T _arr[N];
size_t _size;
}
}
注意:(1)类型模版参数和非类型模版参数均可以用缺省值。
(2)并且类中不可以对非类型模版参数进行修改。
(3)浮点数,类对象和字符串不可以作为非类型模版参数。
(4)非类型模版参数必须在编译期就能确认结果。
非类型的模版参数可以在某些时候代替宏,这样就可以实现用同一类型来创造不同大小的对象。
二、模版的特化
1. 概念
通常情况下,使用模版可以实现一些与类型无关的代码,但对于一些特殊的类型可能会得到一些错误结果。此时就需要进行模版的特化,即在模版的基础上针对特殊类型进行特殊化
模版特化分为函数模版特化和类模版特化。
2. 函数模版特化
函数模版特化的步骤:
1.有一个基础的函数模版
2.template后面必须接一个<>
3.函数名后跟<>,尖括号中指定要特化的类型。
4.函数形参表:必须要和模版参数的基础参数类型相同。
template<class T>
bool IsEqual(T& left, T& right)
{
return left==right;
}
//如果要比较字符串就需要特化处理,比较ascall值,进行类模版的特化
template<>
bool IsEqual<char*>(char*& left, char*& right)
{
if(strcmp(left,right))
return true;
return false;
}
注意;一般情况下函数模版遇到不能处理的类型为了实现简单通常都是将该函数直接给出。
特化的本质:显示指定实例化的模版。
3. 类模版的特化
(1)全特化
全特化就是将模版参数列表中所有的参数都确定化。
template<class T1, class T2>
class Date
{
public:
Date(){cout<<"Date(T1,T2)"<<endl;}
private:
T1 _a;
T2 _b;
};
//想对T1,T2分别是int和char时做一些特殊处理
template<>
class Date<int,char>
{
public:
Date(){cout<<"Date<int,char>"<<endl;}
private:
T1 _a;
T2 _b;
}
(2)偏特化
对任何模版参数进行进一步的条件限制的版本。
template<class T1, class T2>
class Date
{
public:
Date(){cout<<"Date(T1,T2)"<<endl;}
private:
T1 _a;
T2 _b;
};
<1>部分特化
指定部分参数特化
//指定第二个参数是int
template<class T1>
class Date<T1,int>
{
public:
Date(){cout<<"Date(T1,int)"<<endl;}
private:
T1 _a;
int _b;
};
<2>参数的进一步限制
对模版参数进行进一步的限制,比如限制参数的类型。
//俩个参数都特化为指针类型
template<class T1,class T2>
class Date<T1*,T2*>
{
public:
Date(){cout<<"Date<T1*,T2*>"<<endl;}
private:
T1 _d1;
T2 _d2;
}
4.模版分离编译
(1)什么是分离编译
一个程序由若干个文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程。
(2)模版的分离编译
声明在.h,定义在.cpp,调用在其他地方。
//a.h
#include <iostream>
template<class T>
T Add(const T& left,const T& right);
void func(int a,int b); //普通函数
//a.cpp
#include "a.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void func(int a,int b)
{
cout<<a<<endl;
cout<<b<<endl;
}
//main.cpp
#include "a.h"
int main()
{
//函数模版不支持分离编译,会报链接错误
Add(1,2);
Add(1.0,2.0);
func();//普通函数
return 0;
}
在CPP文件中,当“a.h”在预处理阶段展开后,这个文件中就只有函数声明和函数调用,那么编译器在编译这个文件时就可以通过。
在liunx系统下,普通函数变成汇编时会变成call _Z4funcii(?),但此时只有函数修饰名,没有函数地址(就在括号里加了问号),Add函数也相同,变成了call _Z3Addii(?)和call _Z3Adddd(?)。
编译时只有声明,没有函数定义,只能确定函数的名称,检查参数的匹配等,但是没有函数地址,地址链接时再去找。
在链接时候都问题就出现了,因为在其他目标文件去找时,此时就会去a.cpp文件等符号表去找,然后func函数找到了,但是Add没有找到。
为什么模版函数没有找到呢?
因为函数模版没有实例化T(不知道T是什么类型,因为在链接之前每个文件是相对独立的),没有生成具体到代码,没有函数地址,因此链接出错。
(3)总结
模版分离编译:定义的地方不能实例化,实例化的地方,没有定义。
(4)解决方法
(1)显示指定实例化
(2)将声明和定义放在一个一个文件“xxx.hpp”中(或者“xxx.h”)。