C++入门篇(15)之模板知识进阶

文章目录

  • 前言
  • 模板的非类型参数
  • 模板的特化
  • 模板的分离编译问题

前言

前面的章节,我们大致结束了stl基础,现在即将向模板进阶和继承多态开始出发


对于模板,博主在前面章节简答的介绍过,而此篇文章主要讲模板的一些更高级应用

模板的非类型参数

在前面的章节中,我们使用到的模板参数一直是class,但实际上,其模板参数不止有class,比如我们有此下模板:

#define N 100;
template<class T>
class Array
{
  private:
    T array[N];
};

如果我们定义一个对象—Array a;,可以明显的知道,a的size等于100;

如果我们需要定义一个size等于200的Array对象呢? 很明显,我们需要修改N;

如果我们需要定义一个size等于100,一个size等于200的Array对象

呢? 很明显,我们没办法实现;

而这也就是博主需要引出来的模板的非类型参数

那么我们的非类型参数,应该怎么写呢?

template<class T,int N>
class Array
{
  private:
    T array[N];
};

定义size等于100的Array对象: Array a;

定义size等于200的Array对象: Array a;

注意点:

  • 该模板的N其实是常数,一旦传参以后,便不可以修改
  • N的类型只能是整型家族成员(int,long,char),不可以为浮点,字符串,自定义类型等等

模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如

template<class T>
bool IsEqual(const T& left, const T& right)
{   
    return left == right;
}

如果是基础类型,可以得到正确结果,但如果我们的T是char*呢?

比如有char s1[]= "123",char s2[] ="123",然后分别传给left和right,那么很明显这样就会得到错误答案;因为此时left和right比较的是指针地址,并不是字符串;

所以,针对这种情况,引出了模板的特化

模板的特化:指对于具体的类型进行特殊化处理; 其中特化又分为全特化和偏特化

①模板的全特化: template后面只有<>,对于需要特化的类型,使用<>写在函数名后或者类名后

template<>     
bool Isequal<char*,char*>(char*& left,char* right) //函数模板的特化
{
    return strcmp(left,right);    //如果传入char*,就会调用该版本
}

②模板的偏特化: 即只特化某一部分类型, 其中偏特化又分为两种

第一种: template后面有<>保留但不特化的类型,对于需要特化的类型,使用<>写在函数名后或者类名后

//第一种:只特化一部分的类型
template<class T2>     
class Isequal<char*,T2> //只特化第一个类型
{
    public:
    Date(){cout<<"全特化:char* ,T2"}    

第二种: 告诉我们特化的是什么类型

//第二种,告诉我们特化的是什么类型
template<class T1,class T2>     
class Isequal<T1*,T2*> //告诉我们如果传的是指针,那么就会调用此模板
{
    public:
    Date(){cout<<"全特化:T* ,T*"}    
}

template<class T1,class T2>     
class Isequal<T1&,T2&> //告诉我们如果传的是引用,那么就会调用此模板
{
    public:
    Date(){cout<<"全特化:T& ,T&"}   
}

模板的分离编译问题

对于函数来说,它支持分离编译(即函数的声明写在.h文件中,函数的定义写在.cpp文件中);

但是对于模板来说,并不支持这样,原因是为何呢?我们先看一下程序在运行之前(编译),会经历一些什么阶段

假设目前有三个文件,分别是hp.h,hp.cpp,test.cpp,其中前两个分别是对应的分离文件

hp.h的内容有:

#pragma once
int func();

template <class T> class Array;

hp.cpp的内容有:

#pragma once
int func()
{
    return 5;
}

template <class T> 
bool is(int i)
{
	return i%2 ? true :false;    
}

test.cpp的内容为:

#include 
#include 
using namespace std;
int main()
{
    func();
    is(2);
    return 0;
}

在前面的c语言章节,博主讲解过程序的预处理,我们知道,上面三个文件将会经理下面四个阶段:

  • 预编译: 将hp.cpptest.cpp分别变成以后缀.i结尾的文件,在这个阶段,会发生 删除注释,展开头文件,宏替换和条件编译
  • 编译: 将hp.itest.i分别变成以后缀.s结尾的文件,在这个阶段,会发生 检查语法错误,生成汇编语言
  • 汇编: 将hp.stest.s分别变成以后缀.o结尾的文件,在这个阶段,会发生 将汇编生成二进制
  • 链接: 寻找地址

前面三个阶段,我们可能很好理解,但是链接这里有点迷糊,举个例子:

test.cpp文件在变成二进制之前,按照上面步骤,它会把hp.h的内容展开到自己文件,但是这仅仅只是个声明,主函数调用两个函数时候,并不能找到函数的定义,这时候系统会给这两个函数打一个标记,代表先不管它; 等前三个阶段完成以后,链接阶段干的就是去找函数定义的位置,然后把地址发送给主函数里面的两个调用函数;

而正是这一个步骤,刚好模板会报错,为什么呢? 我们在刚学模板的时候直到,模板只有调用才会生成对于的代码;

hp.cpp中,只有模板的定义,没有调用,那么在第一个阶段,预编译时候模板定义的代码就被删除了

等到链接阶段,test.cpp去找寻模板地址时候,发现找不到定义模板的地址,就自然会报错了

因此,模板并不支持分离编译,记好哦~~


你可能感兴趣的:(c++,c++,c语言,开发语言)