• C++学习之路-编译链接的细节


    很多时候,我们会将函数的声明和实现分离。函数的声明通常声明在.h文件中,函数的实现通常实现在.cpp文件中。比如,我们想要定义两个数的加法运算,就可以这样写:

    在add.h中声明函数

    #pragma once
    int add(int a, int b);
    double add(double a, double b);
    
    • 1
    • 2
    • 3

    在add.cpp中实现函数

    #include"add.h"
    
    int add(int a, int b)
    {
    	return a + b;
    }
    
    double add(double a, double b)
    {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们知道,这样肯定没问题。但是,我们了解了模板,掌握了泛型的概念。可以用模板来优化函数重载,于是我们可能就会这样改进上述代码:

    在add.h中声明模板

    #pragma once
    
    template <class T>
    T add(T a, T b);
    
    • 1
    • 2
    • 3
    • 4

    在add.cpp中实现模板

    #include"add.h"
    
    template <class T>
    T add(T a, T b)
    {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    此时,我们想要使用该模板函数,只需要在使用文件中引入add.h即可。比如,我们在main.cpp中使用:

    #include
    #include"add.h" // 引入头文件
    using namespace std;
    
    int main()
    {
    	cout << add<int>(1, 2) << endl;
    	cout << add<double>(1.1, 2.2) << endl;
    	getchar();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    但是,这种情况却会报错:无法解析这两个函数,也就意味着编译器找不到这两个函数

    在这里插入图片描述

    编译链接过程

    我们先了解一下正常的函数实现和分离的编译链接过程(即不用模板的情况下)。三个文件,一个包含了add的实现分离,一个调用函数的main

    add.h文件

    在这里插入图片描述

    add.cpp文件

    在这里插入图片描述

    mian.cpp文件

    在这里插入图片描述

    那么,这三个文件在编译器中是如何工作的呢?

    首先,我们需要知道的是.h文件是不被C++编译器单独编译的,编译器只会编译cpp文件,而且是单独编译!!!!也就意味着main.cpp和add.cpp是单独编译的,也就意味着main.cpp和add.cpp在编译时,互相不知道对方的存在,只是老老实实的将代码编译器为汇编代码。

    下图,展示了整个编译链接过程:

    在这里插入图片描述

    着重说明:调用函数的时候(无论是在哪调用函数,该历程是在mian.cpp中调用),会编译为call函数地址的汇编代码,但是这个代码地址是不准确的,链接的时候会修复这个地址,从而找到真正实现的地方,达到正确调用的目的。

    编译器为什么不单独编译.h文件呢?

    因为.h文件的作用就是起到声明的作用,规范项目整洁,增强代码可读性。因此,在任何文件中包含某个.h文件,就会将该.h文件里的所有东西,原封不动的复制进来,跟着cpp文件一起编译,所以不会单独编译.h文件。

    也就是说,main.cpp中,其实是这样的代码。直接把两个函数的声明放在这,这样的话调用函数的时候,就知道是个函数,所以就会编译通过,不会报错。但是,函数的实现在哪,这里是不知道的,因此call 函数地址时,这个地址基本上都是错的。

    在这里插入图片描述

    从编译链接的原理解释,为什么模板不可以声明和实现分离

    add.h 声明模板

    #pragma once
    
    template <class T>
    T add(T a, T b);
    
    • 1
    • 2
    • 3
    • 4

    add.cpp 中实现模板

    #include"add.h"
    
    template <class T>
    T add(T a, T b)
    {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    此时,我们想要使用该模板函数,只需要在使用文件中引入add.h即可。比如,我们在main.cpp中使用:

    #include
    #include"add.h" // 引入头文件
    //template 
    //T add(T a, T b);
    using namespace std;
    
    int main()
    {
    	cout << add<int>(1, 2) << endl;
    	cout << add<double>(1.1, 2.2) << endl;
    	getchar();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们了解了编译链接原理->对每一个cpp文件单独编译。所以我们在编译add.cpp时,就相当于编译下面的代码。由于没有调用具体的函数,因此在add.cpp中不会产生任何具体的函数。

    template <class T>
    T add(T a, T b);
    
    template <class T>
    T add(T a, T b)
    {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    也就是说,add.obj是空的,没有任何函数的汇编代码。

    我们在模板中学过,只要没有具体的调用函数,模板就不会自动生成任何函数,因为模板不知道该生成什么类型的函数。

    在这里插入图片描述

    因此,在链接的时候,main函数中call 函数地址,就找不到具体实现的地方,因此就会链接失败。所以就会提示:无法解析add函数,这是因为编译器找不到add函数的具体调用地址。

    在这里插入图片描述

    可以看出,错误是LNK,也就是链接错误。正与我们分析的一模一样。

    所以,模板想要生成具体的函数实现,就必须满足单独编译cpp文件时,在当前cpp文件内出现了该模板的调用,才会生成具体的函数实现,否则不会产生任何动作。总之:想要使用模板,就不要将模板的声明和实现分离。

    模板的声明和实现一般用来放在.hpp文件中

    在这里插入图片描述

    在这里插入图片描述

    hpp一般用来存放,声明和实现不分离的文件。到时候,直接在调用处包含该hpp文件即可。

    在这里插入图片描述

    hpp也不会参与编译,跟h文件是一样的。哪个文件包含hpp文件,就会将hpp文件的所有代码复制到该文件。

  • 相关阅读:
    Redis的GEO结构
    云计算技术大数据概述及其知识点
    如何实现三维虚拟数字人直播1:全身姿态预估
    【Vue五分钟】五分钟了解webpack实战配置案例详情
    P10.2机器学习笔记--李宏毅(self-attention机制)
    爬虫系列:爬虫验证码识别
    【electron】 打包应用修改图标和进程名字
    AdaptFormer学习笔记
    uniapp使用@microsoft/signalr(报错“ReferenceError: require is not defined“)
    数据产品经理日常工作
  • 原文地址:https://blog.csdn.net/weixin_45452278/article/details/126440628