C++学习之路(二):头文件 ,编译和链接

前言

本文主要是学习C++的include,编译和链接,借助Visual Studio工具加深了对于include的理解。

一. 头文件

头文件一般都是以.h为后缀的文件,每个C/C++的通常由头文件和定义文件组成,头文件承载着函数和类,下面来看个头文件的简单使用。
log.h

void log(const char* msg);

main.cpp

#include "log.h" // 引入上面的头文件

int main() {
    
    
	log("hello world !");
	return 0;
}

以上就是"include"和头文件最简单的使用。那么include到底是怎么被引入的呢?

事实上,编写好的头文件内容,在预处理的时候是直接被拷贝到目标文件中,关于这一点是不是感到很惊讶?

下面我们用代码来证明一下:

end.h

} // 对这里就一个花括号

math.cpp

int add(int a, int b) {
    
    
	return a + b;
#include "end.h" // 这里的}给替换成了#include "end.h"

main.cpp

#include<iostream>

int add(int a, int b);

int main() {
    
    
	std::cout << add(2, 3) << std::endl;
	return 0;
}

运行后成功打印2和3的结果,并且输出来。
add结果
是不是感到很惊讶,为了更加的清除了解到底发生了什么,我们请Visual Studio来帮忙。
点击项目的属性:
项目属性
在如下图选择"预处理到文件"中选择是,然后点击应用。(注意配置和平台的选择,需要和你运行的时候选择一样的,我直接选择所有配置)
预处理到文件
再次编译math.cpp文件,在如下目录中找到math.i文件
math.i
math.i的内容如下:

#line 1 "D:\\codework\\vswork\\Project2\\Project2\\math.cpp"
int add(int a, int b) {
    
    
	return a + b;
#line 1 "D:\\codework\\vswork\\Project2\\Project2\\end.h"
}
#line 4 "D:\\codework\\vswork\\Project2\\Project2\\math.cpp"

从这里可以很清楚的看到,是直接把end.h的内容拷贝过来的。同理,加上#include<iostream>,其实是把iostream.h的内容全部拷贝过来。

二.编译

C++的编译其实是把每一个.cpp文件都编译成后缀名为.obj的二进制文件(Visual Studio下)。
.obj文件你可以在你项目的平台和配置下找到,我的是在\x64\Debug目录下(每个人都不一样)。虽然它是个二进制的文件,但是我们可以借助Visual Studio修改,输出汇编指令:
输出文件-汇编
针对main.cpp源文件,会在我的\x64\Debug目录下找到main.asm文件,打开之后就是一堆的汇编指令文件。

三.链接

3.1 Link

首先看两段代码:
log.cpp

#include<iostream>

void log(const char* msg) {
    
    
	std::cout << msg << std::endl;
}

main.cpp

// 注意这里没有include<iostream>
// log是函数声明,学习C语言的都知道,使用函数需要声明一下
void log(const char* msg);

int main() {
    
    
	log("hello world !");
	return 0;
}

看到上面的代码你一定会很惊讶,为什么没有include任何东西,main.cpp可以正常运行起来。在main.cpp代码中只是声明了log函数,那么IDE是如何找到的,这里就是用到了链接(link)。

可以做个测试来测试下:
我们将log.cpp中方法名改为"logr",再编译main.cpp,你会发现编译不会报错,那是因为此时还没有链接(link),如果我们再运行的时候,就会发生错误。

error LNK2019: 无法解析的外部符号 "void __cdecl log(char const *)" (?log@@YAXPEBD@Z),函数 main 中引用了该符号

注意error LNK,这里说明这个是一个链接错误。点击运行之后链接器会进行链接,这时候链接器找不到void log(const char* msg);函数就会报link错误。

3.2 多个函数Link

再创建一个log0.cpp,代码如下:

// 函数签名和log.cpp签名一样
void log(const char* msg) {
    
    
}

原来的log.cpp

#include<iostream>

void log(const char* msg) {
    
    
	std::cout << msg << std::endl;
}

运行之后报如下错误:

error LNK2005: "void __cdecl log(char const *)" (?log@@YAXPEBD@Z) 已经在 log.obj 中定义

这个也是个link错误,原因是将log函数重复定义导致的,链接器在链接的时候不知道链接哪个函数。对于这种情况你可以在其中一个函数前面加上static(后续会对static做一个学习总结),比如在log0.cpp函数前面加上static,不让链接器去链接。或者直接删除其中一个。

3.3 include

对于函数main.cpp的函数声明,可以写一个.h的头文件代替函数声明,这样的好处是使用log函数的时候,就不需要每次使用每次都要声明。

log.h

#ifndef __LOG_H__
#define __LOG_H__

void log(const char* msg);

#endif

main.cpp

#include "log.h"
// 原来的log函数声明不需要了
int main() {
    
    
	log("hello world !");
	return 0;
}

事实上根据前面的include章节可以知道,这里的main.cpp等价于加函数声明的main.cpp。

结束

通过上面的总结,了解了:

  1. include预处理命令,是在编译之前就处理了,本质是直接将.h文件内容添加到目标文件中。
  2. 编译是将源代码文件编译成一个个的.obj文件。
  3. 运行的时候会将编译好的.obj文件进行链接(link)

猜你喜欢

转载自blog.csdn.net/RQ997832/article/details/123766810