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

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

在add.h中声明函数

#pragma once
int add(int a, int b);
double add(double a, double b);

在add.cpp中实现函数

#include"add.h"

int add(int a, int b)
{
	return a + b;
}

double add(double a, double b)
{
	return a + b;
}

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

在add.h中声明模板

#pragma once

template <class T>
T add(T a, T b);

在add.cpp中实现模板

#include"add.h"

template <class T>
T add(T a, T b)
{
	return a + b;
}

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

#include<iostream>
#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;
}

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

在这里插入图片描述

编译链接过程

我们先了解一下正常的函数实现和分离的编译链接过程(即不用模板的情况下)。三个文件,一个包含了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);

add.cpp 中实现模板

#include"add.h"

template <class T>
T add(T a, T b)
{
	return a + b;
}

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

#include<iostream>
#include"add.h" // 引入头文件
//template <class T>
//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;
}

我们了解了编译链接原理->对每一个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;
}

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

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

在这里插入图片描述

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

在这里插入图片描述

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

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

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

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值