C++文件编译与连接,头文件

文件编译与连接

IDE vs

1 编译

compile 编译时检查是否有编译错误,检查完毕后会生成.obj文件。在文件中有声明但是没有定义的函数在compile时都默认有这个函数。

1.1预处理阶段

在编译过程中:(可以输出.i文件并查看)

  1. 检查所用函数是否有至少有声明
  2. 检查语法错误
  3. 是否有函数定义不唯一(同一文件)
    报错编码开头是C

这些#开头的都是预处理指令:

#include

编译时将xxx文件中的所有内容复制到引用的位置。

例如有个p文件如下:

int p(int a)
{
	return a;
# include "EndBrace.h"

EndBrace.h如下:

}

此时编译p文件是不会报错的。

这也解释了文件中只是差一句#include ,目标文件大小差了很多(iostream有五万多行)

include <> 和 ""的区别

一、引用的头文件不同
#include< >引用的是编译器的类库路径里面的头文件。
#include“ ”引用的是你程序目录的相对路径中的头文件。
二、用法不同
#include< >用来包含标准头文件(例如iostream).
#include“ ”用来包含非标准头文件。
三、调用文件的顺序不同
#include< >编译程序会先到标准函数库中调用文件。
#include“ ”编译程序会先从当前目录中调用文件。
四、预处理程序的指示不同
#include< >指示预处理程序到预定义的缺省路径下寻找文件。
#include“ ”指示预处理程序先到当前目录下寻找文件,再到预定义的缺省路径下寻找文件。

来源,可以简单记为标准库用<>,自定义头文件用""

同时区分c++与c的一个参照可以是库文件有没有后缀,iostream没有后缀因此是c++,stdlib.h有.h后缀因此是c

#define x y

用x代替y

如下代码:

#define aaa int
aaa p(aaa a)
{
    return a;
}

编译后会把替换的变回去,变成:

int p(int a)
{
    return a;
}
#if condition … #endif

如果condition为真,执行#if 到#endif的代码,否则不执行
在编译时如果condition为真,则代码被编译,否则相当于没有这段代码

这种方法可以将测试代码加进来。当需要开启测试的时候,只要将常量变1就好了。而不要测试的时候,只要将常量变0。

#ifndef xxx … #endif

检查xxx有没有被定义过,如果有则#ifndef到#endif的代码被忽略,没有则正常执行
在多个头文件相互引用,且头文件中有定义时,可以避免重复定义。使用用例:

#ifndef _LOG_H
#define _LOG_H

struct Player{}
#endif
#pragma once

与#ifndef…#endif功能相同,但是更简洁,vs在新建头文件时自动产生,无需指定使用范围(不像if…endif等需要圈出一个代码范围)

1.2 c++转化成机器指令

.obj就是机器指令
中间产物还有.asm文件,这个文件是汇编语言(设置assembler output为assembly-only listing产生该文件)

1.3 编译器优化

debug模式会更慢,因为有很多中间产物可以生成,用来方便查看。这时编译器没有优化。
release模式编译器优化开启,速度更快。(其他可以看到的比如.asm中的汇编更短)

2 连接

build 将把项目中的文件都连接起来(link)并检查连接错误,这个检查范围是项目下的所有文件

项目所有文件中:

  1. 确定入口(有且只有一个entry point,不过它不一定叫main)
  2. 检查所使用的变量或函数声明是否有定义(没有使用该函数或变量就不用去查找,也就不关心是否有实现)(判断声明和定义是否相同,包括函数名、返回值、参数个数、参数类型,这里参考重载方法)
  3. 是否有函数定义不唯一(所有文件中)
    报错编码开头是LNK

一些对变量声明的连接错误:

情况1

只有一个main文件如下

#include <iostream>
using namespace std;

void Log(const char* message);

// Mu中用了Log,并且Mu被调用
int Mu(int a)
{
    Log("Mu");
    return a;
}

int main(){
	cout<< Mu(1)<<endl;
    cin.get();
}

Log已经有声明,但是没有实现,在compile环节不会报错。但是build时,Log有声明但是没有定义,在Mu函数中要使用Log时会报错。此时将Mu中的Log注释掉就不会报错。

情况2

依旧只有一个main文件

#include <iostream>
using namespace std;

void Log(const char* message);

// Mu中使用Log,但Mu没有在当前文件被调用
int Mu(int a)
{
    Log("Mu");
    return a;
}

int main(){
//    cout<< Mu(1)<<endl;
    cin.get();
}

此时Log函数并没有被使用,但还是出现Log没有实现的连接错误。这是因为当前文件没使用不代表其他文件不会调用Log,因此只要有的函数调用这个没有实现的声明,不管这个函数在当前文件有没有使用,都会报错。

情况3

还是这个main

#include <iostream>
using namespace std;

void Log(const char* message);

// Mu只能在当前文件使用
static int Mu(int a)
{
    Log("Mu");
    return a;
}

int main(){
//    cout<< Mu(1)<<endl;
    cin.get();
}

当指定Mu只能在当前文件中使用时,没有用Mu就不会报错了。一些编译器会提示Mu没有被使用。

情况4

如下图所示。在Log.h中定义了Log函数,main.cpp和Log.cpp都有函数调用了Log函数(声明省略),此时需要在main.cpp和Log.cpp中都include Log.h,此时就会有连接错误。因为include相当于复制代码,如1.1中所说,因此两个cpp文件中都有了Log函数的定义,所以报错。
请添加图片描述

三种修改方式:

  1. (推荐).h文件不写定义只写声明
  2. 将.h中的函数变为静态static,即Log函数变为各自文件内部可见的函数,其他文件不可调用,也就是main.cpp和Log.cpp将会有各自的Log函数
  3. 将.h中的函数变为内联inline(内联会直接将函数体替换掉所使用的函数名的位置)

3 你说头文件有啥用?

C++编译时,当前文件使用到的函数并没有在当前文件定义的情况下,需要在这个文件中添加该函数的声明(declaration)。但如果有多个文件都要使用多个相同函数时,没有必要在每个文件中都写一遍这些声明,而是把它们写在头文件中,然后include即可(类比iostream),这样一行include就解决了。

引入头文件的问题

由于include是复制,因此引入多个头文件可能会出现重复定义的问题:
请添加图片描述
Log.h中包含struct定义,common.h include Log.h,main.cpp include Log.h和common.h,此时就会出现重复定义的情况(编译环节报错)。

解决办法:
保证相同的内容只定义一次

  1. (推荐)头文件添加#pragma once,(vs在创建头文件时会自带)
  2. 头文件添加#ifndef…#endif
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值