iis5.1 发布网站,中国私企建筑公司十大排名,如何制作自己的网站?,湖南建工交通建设有限公司网站文章目录 函数模板函数模板的原理函数模板的实例化模板参数的匹配原则 类模板类模板的定义格式类模板的实例化 非类型模板参数typename 与class模板的特化函数模板特化类模板特化全特化偏特化 模板的分离编译 函数模板
函数模板的原理
template typename T //模板参数… 文章目录 函数模板函数模板的原理函数模板的实例化模板参数的匹配原则 类模板类模板的定义格式类模板的实例化 非类型模板参数typename 与class模板的特化函数模板特化类模板特化全特化偏特化 模板的分离编译 函数模板
函数模板的原理
template typename T //模板参数 ——类型
void Swap(T x1, T x2)
{T tmp x1;x1 x2;x2 tmp;
}
int main()
{int a 0, b 1;double c 1.1, d 2.2;swap(a, b);swap(c, d);int* p1 a;int* p2 b;swap(p1, p2);return 0;
}函数模板是一个蓝图它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码对于字符类型也是如此
函数模板的实例化
用不同类型的参数使用函数模板时称为函数模板的实例化。模板参数实例化分为隐式实例化和显式实例化
1、隐式实例化让编译器根据实参推演模板参数的实际类型
templateclass T
T Add(const T left , const T right)
{return left right;
}
int main()
{int a1 10, a2 20;double d1 10.1, d2 20.2;//函数模板根据调用自动推导模板参数的类型 实例化对应的参数 cout Add(a1, a2) endl;cout Add(d1, d2) endl;//实参传递的类型 推演T的类型 cout Add( a1, (int)d1 ) endl;cout Add( (double)a1, d1) endl;//显示实例化 用指定的类型实例化 相当于隐式类型转换 cout Addint (a1, d1) endl;cout Adddouble(a1, d1) endl;return 0;
}2、显式实例化在函数名后的中指定模板参数的实际类型
templateclass T
T Add(const T left , const T right)
{return left right;
}
int main()
{int a1 10, a2 20;double d1 10.1, d2 20.2;//函数模板根据调用自动推导模板参数的类型 实例化对应的参数 cout Add(a1, a2) endl;cout Add(d1, d2) endl;//实参传递的类型 推演T的类型 cout Add( a1, (int)d1 ) endl;cout Add( (double)a1, d1) endl;//显示实例化 用指定的类型实例化 相当于隐式类型转换 cout Addint (a1, d1) endl;cout Adddouble(a1, d1) endl;return 0;
}templateclass T T * Alloc(int n )
{return new T[n];
}int main(){//有些函数无法自动推导函数模板的类型实例化对应的参数只能显式实例化double *p1 Alloc double(10);return 0;}模板参数的匹配原则
类模板
类模板 ,无法推演实例化所以类模板都是显式实例化
class Stack
{
public :Stack(int capacity 3){_array new T[capacity];_size 0;_capacity 0; }void Push(const T data){_array[_size] data;}~Stack(){free(_array);_size _capacity 0;}
private :T * _array;int _size;int _capacity;
};
int main()
{Stack int s1(); // int Stack double s2();//double Stack char s3();//char//Stack int ,doule s2();return 0;
}类模板的定义格式
函数类模板的声明和定义分离
templateclass Tclass Stack
{
public://声明 Stack(int capacity );void Push(const T data){_array[_size] data;}~Stack(){free(_array);_size _capacity 0;}
private:T* _array;int _size;int _capacity;
};
//定义
templateclass TStackT::Stack(int capacity )
{_array new T[capacity];_size 0;_capacity 0;
}
int main()
{Stack int s1(); // int Stack double s2();//double Stack char s3();//char//Stack int ,doule s2();return 0;
}对于普通类类名和类型是一样的但是对于类模板 类名和类型是不一样的 上面的代码中Stack是类名 但是Stack T 是类型
类模板的实例化
非类型模板参数
模板参数可分为类型形参和非类型形参
类型形参 出现在模板参数列表中跟在class或typename关键字之后的参数类型名称。
非类型形参 用一个常量作为类函数模板的一个参数在类函数模板中可将该参数当成常量来使用。
如果此时有一个需求实现一个静态数组的类就需要用到非类型模板参数
templateclass T, size_t N //N非类型模板参数
// N是常量 且N必须是整形
class StaticArray
{
public:size_t arraysize(){return N;}
private:T _array[N]; //利用非类型模板参数指定静态数组的大小
};
int main()
{StaticArrayint, 10 a1; //定义一个大小为10的静态数组cout a1.arraysize() endl; //10StaticArrayint, 100 a2; //定义一个大小为100的静态数组cout a2.arraysize() endl; //100return 0;
}
注意
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。 2. 非类型的模板参数必须在编译期就能确认结果。
typename 与class
一般来说typename 和class 没有什么区别但是在有一种情景下是有区别的
templateclass Container
void Print( const Container v )
{//Container::const_iterator it v.begin();是不行的// 因为编译不确定Container::const_iterator是类型还是对象// typename的作用就是明确告诉编译器这里是类型等模板实例化再去找typename Container::const_iterator it v.begin();while (it ! v.end() ){cout *it ;it;}cout endl;
}int main()
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);Print(v);listint lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);Print(lt1);return 0;
}以上情景需要使用typename
模板的特化
通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理
模板的特化 即在原模板类的基础上针对特殊类型所进行特殊化的实现方式。 模板特化中分为函数模板特化 和 类模板特化。
函数模板特化
1、首先必须要有一个基础的函数模板。 2、关键字template后面接一对空的尖括号。 3、函数名后跟一对尖括号尖括号中指定需要特化的类型。 4、函数形参表必须要和模板函数的基础参数类型完全相同否则不同的编译器可能会报一些奇怪的错误。
templateclass T
bool Less(T left ,T right)
{return left right;
}
//函数模板的特化
template
bool Lessint * (int * left, int* right)
{return *left *right;
}
int main()
{int a 1, b 2;cout Less(1, 2);cout endl;cout Less(a, b);return 0;
}类模板特化
不仅函数模板可以进行特化类模板也可以针对特殊类型进行特殊化实现并且类模板的特化又可分为全特化和偏特化半特化。
全特化
全特化即是将模板参数列表中所有的参数都确定化。
例如对于以下类模板
templateclass T1, class T2
class Data
{
public:Data() {coutDataT1, T2 endl;}private:T1 _d1;T2 _d2;
};template
class Dataint, char
{
public:Data() {coutDataint, char endl;}private:int _d1;char _d2;
};void TestVector()
{Dataint, int d1;Dataint, char d2;
}偏特化
偏特化任何针对模版参数进一步进行条件限制设计的特化版本。
偏特化有以下两种表现方式 1、部分特化 ,将模板参数类表中的一部分参数特化
templateclass T1, class T2
class Data
{
public:Data(){coutDataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};// 将第二个参数特化为int
template class T1
class DataT1, int
{
public:Data() {coutDataT1, int endl;}
private:T1 _d1;int _d2;
};
int main()
{Datadouble, int d1;//偏特化Dataint, double d2;//调用基础的模板return 0;
}2、参数更进一步的限制
templateclass T1, class T2
class Data
{
public:Data(){coutDataT1, T2 endl;}
private:T1 _d1;T2 _d2;
};
//偏特化对类型的进一步限制
templateclass T1, class T2
class DataT1*, T2*
{
public:Data() { cout DataT1*, T2* endl; }
private:
};int main()
{Dataint, int d1;Dataint, double d2;Dataint*, double d3;Dataint*, double* d4;Datavoid*, void* d5;return 0;
}模板的分离编译
一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
在分离编译模式下我们一般创建三个文件
一个头文件用于进行函数声明 一个源文件用于对头文件中声明的函数进行定义 最后一个源文件用于调用头文件当中的函数
举个例子如果对一个加法函数模板进行分离编译 如果这三个文件生成可执行文件时会在链接阶段产生报错 这是为什么呢
C / C程序要运行起来一般要经历以下四个步骤 预处理、编译、汇编、链接
如果需要详细的了解这四个步骤请点击这里 这三个文件经过预处理后就只剩下两个文件了
Visual Studio平台
预处理后就进入编译阶段 虽然在 main.i 当中有调用Add函数的代码但是在 main.i 里面也有Add函数模板的声明在编译阶段并不会发现任何语法错误在编译阶段将 Add.i 和 main.i 翻译成了汇编语言即将 Add.i 和 main.i 变成了Add.s 和 main.s
进入汇编阶段利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件 即将 Add.s 和 main.s变成了Add.o和 main.o
最后将Add.o和 main.o进行链接操作生成a.out 但在链接时发现在main函数当中调用的两个Add函数实际上并没有被真正定义 原因是函数模板T还没有实例化可以将模板定义的位置显式实例化。这种方法不实用不推荐使用。
总结 编译阶段看有没有声明声明是一种承诺 在编译阶段检查声明查看函数名、参数、返回值是否对上如果对上则编译阶段通过 进入链接阶段编译器会拿着修饰后的函数去其他文件符号表查找如果查到则链接阶段通过
模板分离编译失败的原因 在函数模板定义的地方Add.cpp没有进行实例化而在需要实例化函数的地方main.cpp没有模板函数的定义无法进行实例化。
解决方法
将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。模板定义的位置显式实例化。这种方法不实用不推荐使用。
如果你觉得这篇文章对你有帮助不妨动动手指给点赞收藏加转发给鄃鳕一个大大的关注你们的每一次支持都将转化为我前进的动力