关于模板函数声明与定义的问题[通俗易懂]

c++primer上说:c++模板函数的声明与定义通常放在头文件中,而普通的函数通常是声明放在头文件中,定义放在源文件中,为什么会有这样的区别呢?模板函数与普通成员函数到底有什么区别?测试代码:tem.h#ifndef_TEM_H#define_TEM_HtemplateTadd(Ta,Tb);//{//returna+b;//}

大家好,又见面了,我是你们的朋友全栈君。

c++ primer上说:c++模板函数的声明与定义通常放在头文件中,而普通的函数通常是声明放在头文件中,定义放在源文件中,为什么会有这样的区别呢?模板函数与普通成员函数到底有什么区别?


测试代码:

tem.h

#ifndef _TEM_H
#define _TEM_H
template<typename T> T add(T a,T b);
//{

//return a+b;
//}
#endif

tem.cpp

using namespace std;
//#include “tem.h”

template <typename T> T add(T a, T b){return a+b;}
template int add(int,int);//实例化定义,必须放在模板定义的后面

main.cpp

#include <iostream>
#include “tem.h”
using namespace std;
int main()
{

cout << add(1,2);
return 0;
}

首先明确:

对普通函数来说,声明放在头文件中,定义放在源文件中,其它的地方要使用该函数时,仅需要包含头文件即可,因为编译器编译时是以一个源文件作为单元编译的,当它遇到不在本文件中定义的函数时,若能够找到其声明,则会将此符号放在本编译单元的外部符号表中,链接的时候自然就可以找到该符号的定义了。

而对模板函数来说,首先明确,模板函数是在编译器遇到使用模板的代码时才将模板函数实例化的。若将模板函数声明放在tem.h,模板定义放在tem.cpp,在main.cpp中包含头文件,调用add,按道理说应该实例化int add(int,int)函数,即生成add函数的相应代码,但是此时仅有声明,找不到定义,因此此时,它只会实例化函数的符号,并不会实例化函数的实现,即这个时候,在main.o编译单元内,它只是将add函数作为一个外部符号,这就是与普通函数的区别,对普通函数来说,此时的add函数已经由编译器生成相应的代码了,而对模板函数来说,此时并没有生成add函数对应的代码。此时编译main.cpp单元不会报错,但链接就会出现add函数未定义的错误。

因此,我们可以通过显式的实例化定义,即通过加上语句temmplate int add(int,int),编译器看到此语句将会生成add方法的int版本,这样的话,再链接就不会报错了。此外,这样做通常也能够提高编译的效率。试想,如果在tem.h文件内定义模板,假如有三个源文件均包含了该头文件且均使用了模板(假定均调用了add模板的int版本),则在这三个源文件内必然都会生成add函数的实例。显然效率不高。而如果像上面那样使用该模板,则只会在tem.cpp文件中实例化。

最后,对于类模板来说,也同样符合上面的原则。在实际类模板的实例化时,实际上是分几步的,首先当然是类模板的实例化,然后还有类成员函数的实例化,我们知道在类的定义中,其实只是声明了类的成员函数,编译器实际上是把类的成员函数编译成修改名称后的全局函数的,因此在使用类模板的时候,首先会初始化类模板,同时初始化类模板相应的构造函数,使用类模板的实例调用相应的成员函数时,才会初始化类模板的成员函数。如果类模板的成员函数的定义与类的定义不在同一个编译单元中(分离式编译),此时调用类的成员函数便会出现未定义的错误。而当我们像代码中那样在某个地方显式的调用它的时就不会出现此类问题了。

总结:其实很明显,明确一点就可以了,即编译器只要遇到使用模板函数时就会实例化相应的函数,若在此编译单元内没有模板函数的定义,它当然不能够实例化成功了。因此通常情况下模板函数的声明与定义均放在同一文件内,因此这样就保证了在使用模板的地方一定可以实例化成功了。同时,由编译器保证只生成某种类型的一个实例版本,不用担心重复实例化的问题。

c++primer上面只说了类模板的成员函数可以不在头文件中定义,却始终感觉说得不清不楚,因为实际上像普通类那样类的定义与实现放在不同的文件中的话,是会链接出错的。总之,若你不想出现任何未定的错误,将类模板或函数模板的定义与声明放在同一个文件中就行了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/126778.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 文件读写

    文件读写

    2021年11月19日
    55
  • SpringMVC 拦截器的使用「建议收藏」

    SpringMVC 拦截器的使用「建议收藏」SpringMVC拦截器的使用1.拦截器作用2.单个拦截器3.多个拦截器参考资料:https://spring-mvc.linesh.tw/1.拦截器作用SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。可以定义拦截器链,连接器链就是将拦截器按着顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。拦截器和过滤器的功能比较类似,有以下区别:过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术;拦截器是SpringM

    2025年6月10日
    3
  • 00005__VSCode__xml格式化代码插件

    00005__VSCode__xml格式化代码插件1XML或HTML格式化代码缩进安装插件:Vetur2格式化代码的快捷键如下OnWindows:Shift+Alt+FOnMac:Shift+Option+FOnUbuntu:Ctrl+Shift+I

    2022年7月16日
    28
  • Java中父类引用指向子类对象好处

    Java中父类引用指向子类对象好处比如对于 HashMap 与 Map 的理解 HashMap 是 Map 接口的常用实现类 Map 是一个接口 是一个键值对集合 建议使用实现接口的方式使用 Map 如 Map String Object parameters newHashMap lt gt 不建议这样 HashMap String Object parameters newHa String Object String Object

    2025年7月9日
    5
  • ReLU和BN层简析[通俗易懂]

    ReLU和BN层简析[通俗易懂]卷积神经网络中,若不采用非线性激活,会导致神经网络只能拟合线性可分的数据,因此通常会在卷积操作后,添加非线性激活单元,其中包括logistic-sigmoid、tanh-sigmoid、ReLU等。sigmoid激活函数应用于深度神经网络中,存在一定的局限性,当数据落在左右饱和区间时,会导致导数接近0,在卷积神经网络反向传播中,每层都需要乘上激活函数的导数,由于导数太小,这样经过几次传播后,靠…

    2022年10月9日
    2
  • css-day05笔记-清除浮动&学成网布局准备工作

    css-day05笔记-清除浮动&学成网布局准备工作typora-copy-images-to:media第01阶段.WEB基础:css-day05笔记-清除浮动&学成网布局准备工作一.清除浮动1.为什么要清除浮动因为父级盒子很多情况下,不方便给高度,但是子盒子浮动就不占有位置,最后父级盒子高度为0,就影响了后面的标准流盒子。总结:由于浮动元素不再占用原文档流的位置,所以它会对后面的元素排版产生影响准确地说…

    2022年5月28日
    33

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号