【C++学习笔记】头文件详解

2023-05-16

个人整理学习用,非教材,有错误欢迎指正

头文件

  究竟什么是头文件?
  首先说明一个概念,所谓的文件后缀并不是必须的,在Linux下这种特点尤为明显。对于编译器来说,无论是.c文件 .cpp文件,抑或是.h文件,都是同一种文件形式,区别仅仅是你去执行该文件的方式。

上图是在Linux环境中编译一个.cpp文件 当使用 g++编译器时,a.cpp被视为一个CPP文件,可以正常编译,编译后的文件也能被正常运行。但使用gcc编译器编译时,gcc会把a.cpp视为一个C文件,而很明显,a.cpp并不符合C的语法。也就是说,编译器并不在意该文件的后缀。

  因此,头文件并不是一种天生的类型,而是我们为了后期编写程序便利才引入的。理论上讲,你可以在头文件里编写任何符合CPP/C语法的内容,它在单个文件内都可以被正确识别并执行。但是为了避免多个文件编译时产生错误,我们认为的制定了一些规则:
  1 .h 中仅存放 声明(declaration),而与其对应的 定义(definition)应存放在对应的.cpp文件中
  2 .h 文件头尾要添加 #ifndefine 语句
  这还要从编译器讲起。任何.cpp文件都需要4个步骤:预编译、编译、汇编、链接,才能最终转化为可执行文件。

  1. 预编译阶段:会完成预编译指令,如#define宏的替换,#include头文件的展开。生成对应 .ii文件
  2. 编译阶段:会代码转换为汇编代码,同时为每个文件生成一个地址符号表,记录在文件中出现的每个函数以及全局变量,若是declaration,则地址为0;若是definition,则记录其实际地址,生成对应 .s文件
  3. 汇编阶段:将所生成的汇编代码继续转换为机器代码,并为每个文件生成相对应的.o文件,并以二进制的形式存储
  4. 链接阶段:将所有的.o文件链接为一个可执行文件(g++默认为.out文件),并根据各个文件的地址符号表,生成一张完整的地址符号表,记录了每一个相对应的函数或全局变量相对应的、唯一的地址。若在此阶段出现地址重复或地址仍为0,则链接失败

在头文件中存储定义

=========================a.h=======================
int g;		// 注意,这里 *定义* 了一个全局变量 g
class A{	// *声明* 了 A类
private:
	int a;	// *声明* 了 A类中的一个变量 a
public:		// *声明* 了 A类中的一个函数func
	void func();
};
========================a.cpp======================
#include "a.h"
#include <iostream>
using namespace std;
void A::func(){
	g = 10;
	cout << "g = " << g << endl;
	cout << "a.func()" << endl;
}
int main(){
	A a;
	a.func();
	return 0;
}
========================b.cpp======================
#include "a.h"
#include <iostream>
using namespace std;
void func(){
	cout << "b.func()" << endl;
}

在这里插入图片描述
以上有三个文件,其中 a.cpp与 b.cpp都包含了 a.h头文件
尝试仅编译a.cpp并运行
在这里插入图片描述可以看到,即使 a.h中我们定义了一个全局变量,程序仍能正常执行,这也侧面说明了头文件规则并非规定
  这次我们加入b,将a、b一起编译,他们同时引用了头文件 a.h。我们可以用 --save-temps参数保存所有的中间文件
在这里插入图片描述这次出现了错误,提示了multiple definition of 'g
我们站看文件列表,可以清楚看到 a.o,b.o都已经生成,也就表明直至汇编阶段都成功执行,于是我们可以打开a.ii,也就是#include执行后的文件,看究竟是什么导致了err
在这里插入图片描述
在这里插入图片描述注意上图中 8~14 行代码,就是我们本来写在 a.h中的内容,而在经过预编译后,被直接插入进了 b.ii中。实际上,#include "a.h"在预编译阶段,就是将 a.h 中包含的代码片段插入到了原文件#include的那一行中。a.cpp也会做同样的事,即也定义了一个全局变量g。这就使得在最后的链接阶段的地址符号表中,一个全局变量对应两个地址,因此报错multiple definition of 'g。回头观察可以发现,这个错误是 /usr/bin/ld 发出的,ld是链接器,这也解释了为什么这种错误不会像普通编译错误一样提示你具体哪一行有问题,因为预编译阶段已经完成,而链接器无法分辨此语句在源文件的哪一行中
上述代码的正确版本

=========================a.h=======================
extern int g;	// 注意,这里 *声明* 了一个全局变量 g
class A{		// *声明* 了 A类
private:
	int a;		// *声明* 了 A类中的一个变量 a
public:			// *声明* 了 A类中的一个函数func
	void func();
};
========================a.cpp======================
#include "a.h"
#include <iostream>
using namespace std;
int g;	// 此处才是头文件中所声明 g的定义
void A::func(){
	g = 10;
	cout << "g = " << g << endl;
	cout << "a.func()" << endl;
}
int main(){
	A a;
	a.func();
	return 0;
}
========================b.cpp======================
#include "a.h"
#include <iostream>
using namespace std;
void func(){
	cout << "b.func()" << endl;
}

  而除此之外还有一种情况,当程序调用关系复杂的时候,可能会多次调用同一个头文件。如此时,在b.cpp中再#include "a.cpp"(此时将a.cpp也视为一个头文件,因此假设其中不包含定义与全局变量,但也#include "a.h"),编译 g++ b.cpp后会出现如下错误
在这里插入图片描述  有了上文的铺垫,再加上此处的提示,原因我们也很清楚,就相当于在b.cpp中,#include "a.h"处中定义了一个类A,紧接着#include "a.cpp" 处又定义了一个类A,造成redefinition of ‘class A’。为了让每个头文件仅有效地包含一次,我们需要在 a.h 与 a.cpp 中添加如下字段

========================a.h==========================
#ifndef _A_H_
#define _A_H_
extern int g;	// 注意,这里 *声明* 了一个全局变量 g
class A{		// *声明* 了 A类
private:
	int a;		// *声明* 了 A类中的一个变量 a
public:			// *声明* 了 A类中的一个函数func
	void func();
};
#endif
=======================a.cpp=========================
#ifndef _A_CPP_
#define _A_CPP_
#include "a.h"
void fa(){}
#endif

修改后效果如下
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++学习笔记】头文件详解 的相关文章

  • STM32启动文件学习

    2021 08 09 STM32启动文件学习 启动文件作用 xff1a 初始化堆栈指针SP初始化PC指针初始化中断向量表配置系统时钟调用C库 main函数进入到主程序 启动文件使用的ARM汇编指令 xff1a EQU xff1a 等于 AR
  • 03-串口(UART)的使用

    目录 第一节 UART概述1 1 UART的用处 xff1a 1 2 UART的优点1 3 UART原理与概念1 4 UART数据传输原理与过程1 5 关于电平转换1 6 UART内部具体机制 第二节 UART编程 第一节 UART概述 1
  • python基础知识之开发规范(一)

    1 脚本结构 1 1 头注释 对代码没有用 xff0c 是提供给系统或者解释器使用的 usr bin env 定义 指定路径下的Python解释器 xff0c 已经很少使用 1 2 导入 导入Python的一些功能函数到脚本中 Python
  • vivo真机调试的坑 & adb无线调试

    坑1 xff1a 安装失败 解决方案 xff1a 在工程的gradle properties中加上 android injected testOnly 61 false 坑2 xff1a 总是要输入密码才能安装 解决方案 xff1a 1 开
  • JavaScript --Node,js

    引用系统模块 const http 61 require http 创建 web 服务器 const serve 61 http createServer 当客户端 发送请求的时候 server on require req res 61
  • Cmake 2 静态链接

    代码地址cmake examples 01 basic C static library at master ttroy50 cmake examples GitHub 文件路径这样 cmake文件这样 1 生成静态链接库 2 填充包括目录
  • Cmake 3 动态链接库

    代码地址 cmake examples 01 basic D shared library at master ttroy50 cmake examples GitHub 文件结构 1 添加动态链接库 和静态链接一样 这个add libra
  • PelcoD_协议指令分析

    通过协议收发控制第三方云台转动 一般的云台指令协议格式例如 xff1a span class token comment 发送带正负号的垂直角度 span span class token keyword float span vert a
  • 【学习笔记】Esp32 Arduino 串口中断函数 缓冲区修改

    Esp32 Arduino 串口中断函数 缓冲区修改 一 前景描述1 遇到的问题2 开发环境 二 解决问题1 示例代码2 代码缺陷2 解决办法 三 最后的话 一 前景描述 最近需要用Esp32上传数据 xff0c 有一块数据采集板 xff0
  • C++程序编译过程

    C 43 43 程序编译的四个步骤 xff1a 编译预处理 xff0c 编译优化 xff0c 汇编 xff0c 链接 编译预处理 xff1a 处理以 开头的指令 xff0c 将源码 cpp 文件处理成 i 文件 编译优化 xff1a 将 i
  • 查询方式/中断方式/DMA方式的区别及适用范围

    区别 xff1a 查询方式 xff1a CPU与设备串行工作 数据传送与主程序串行工作 xff1b 中断方式 xff1a CPU与设备并行工作 数据传送与主程序串行工作 xff1b DMA方式 xff1a CPU与设备并行工作 数据传送与主
  • vscode、idea、vim 开发工具快捷键

    vscode vscode快捷键文字版 配置启用 禁用 VSCodeVim 插件的快捷键 xff1a Vim Toggle Vim Mode 项 配置启用 vimrc 文件 idea 配置启用 禁用 Idea Vim 插件的快捷键 xff1
  • TDK一体化 IMU 评估板SmartBug2.0 像七星瓢虫一样可爱

    继 2019 年最初的 SmartBug 取得成功后 xff0c 2023 年 1 月初 xff0c TDK 公司宣布宣布推出 InvenSense SmartBug 2 0 评估板 SmartBug2 0 外观与 SmartBug 相似
  • C++ 指针(二)char与指针

    一 char字符串数组和char指针 上一小节对指针的操作进行简单的介绍 xff0c 本小节主要介绍的是char类型和指针之间的一些联系 xff08 虽然使用std string很方便 xff0c 但是我觉得了解这个还是有必要的 xff09
  • stm32f103单线半双工uart通信程序

    文章目录 前言 一 使用步骤 1 打开STMcubemx 2 添加代码 总结 前言 在使用数字舵机时 所用到的通信方式为uart通信 但舵机只有三根接线 出去vcc和gnd 只有一条通信线 也就是说要实现双向通信 只能使用单线半双工模式 本
  • BlueROV加舵机控制以及走过的弯路

    BlueROV加舵机控制以及走过的弯路 因实验需求 xff0c 需要在BlueROV上加上一个一自由度的机械臂 xff0c 由一个水下舵机控制 xff0c 水下舵机需要通过PWM控制 xff0c PWM输出由手柄控制 思路也很简单 xff1
  • ORB_SLAM2 CMakeLIsts文件注释

    最近在学习ORB SLAM 发现基本找不到CMakeLists的代码注释 就决定自己注释一份 如果发现有问题的地方 欢迎和我交流 span class token function cmake minimum required span s
  • UART、RS232 、RS485 区别

    UART RS232 RS485 区别 UART RS232 RS485这些物理层的串口通信 xff0c 它们都是在同一时间发送一位 RS232 RS485只是串口通讯的变种 xff0c 理解了UART串口通讯 xff0c 那么RS232和
  • 在STM32中使用printf()的方法(可直接复制粘贴)

    1 使用printf的方法 1 1 重定向 在使用printf之前添加重定向代码 xff1a span class token macro property span class token directive hash span span
  • 【字符串】字符串长度与字节长度

    字符串长度 xff1a 字符串在遇到 0 之前一共有几个字符 字节长度 字符串里出现的所有元素 例如 xff1a char str 61 123abc 0123 字符串长度 xff1a 6 字节长度 xff1a 11 PS xff1a 如果

随机推荐

  • C语言-字符串拼接(不用strcat函数)

    include lt stdio h gt int main char str1 100 char str2 100 int i 61 0 j 61 0 printf 34 请输入字符串1 xff1a n 34 gets str1 prin
  • Qt(十四)——实现机器人完整导航功能

    Qt xff08 十四 xff09 实现机器人完整导航功能 目录 1 ui 设计2 核心代码 1 ui 设计 2 核心代码
  • vector容器存放自定义数据类型及指针

    include lt iostream gt using namespace std include lt vector gt class person public person string a int b name a age b s
  • unreal 启动报错:运行引擎需要D3D11兼容GPU(功能级别11.0,着色器模型5.0)处理

    问题 AMD核显电脑 xff0c 突然有一天开机后显示器显示效果发白 xff0c 刚开始没在意 xff0c 后来某天想使用Unreal时发现启动不了了 xff0c 弹窗报错 xff1a 运行引擎需要D3D11兼容GPU xff08 功能级别
  • Win10 RealSense L515 ORBSLAM2 配置全攻略

    目录 背景简介Step 1 准备 SDKStep 2 连接设备Step 3 测试例程Step 4 配置环境Step 5 相机标定Step 6 编写入口Step 7 实地运行附录A xff1a 获取内参代码附录B xff1a yaml 参数文
  • rviz仿真底盘移动与云台击打

    rviz仿真底盘移动与云台击打 底盘与云台通过坐标轴来模拟 xff0c 目标方块与子弹可视化通过marker仿真 其中底盘与云台固连 xff0c 底盘xy方向移动云台会同步移动 xff0c 云台可进行pitch和yaw轴旋转 xff0c 通
  • 机器视觉中坐标系转换

    机器视觉 立体视觉等等方向常常涉及到四个坐标系 xff1a 世界坐标系 相机坐标系 图像坐标系 像素坐标系 整体预览如下 1 世界坐标系和相机坐标系 世界坐标系 xff0c 也称为测量坐标系 xff0c 它是一个三维直角坐标系 Xw Yw
  • 机械臂DH参数总结

    DH参数 DH参数 xff08 Denavit Hartenberg parameters xff09 是一个用四个参数表达两对关节连杆之间位置角度关系的机械臂数学模型和坐标系确定系统 DH选的四个参数都的含义如下 xff1a link l
  • 机器人正解和逆解

    正解FK 给定机器人各关节的角度 xff0c 计算出机器人末端的空间位置 逆解IK 已知机器人末端的位置和姿态 xff0c 计算机器人各关节的角度值 挖个坑待完善
  • LeetCode—232 用栈实现队列 Cpp&Python

    LeetCode 225 用队列实现栈 Cpp amp Python 一 方法与思路二 C 43 43 代码三 Python代码 一 方法与思路 使用栈实现队列的下列操作 xff1a push x 将一个元素放入队列的尾部 pop 从队列首
  • E: Unable to locate package解决办法

    问题 xff1a E Unable to locate package 解决 xff1a sudo apt span class token operator span get updade
  • Yolo框架简介

    YOLO xff08 You Only Look Once xff09 是一种基于深度神经网络的对象识别和定位算法 xff0c 其最大的特点是运行速度很快 xff0c 可以用于实时系统 现在YOLO已经发展到v4版本 论文地址 xff1a
  • Ubuntu下编辑权限只读文件的方法

    首先要注意的是只读文件一般都是系统文件 xff0c 或者软件配置文件 xff0c 修改时要尤其谨慎 本文提供两种方法 xff1a 方法一 首先安装一个插件 span class token function sudo span apt sp
  • Ros下Aruco模块的使用

    生成ARUCO ROS MARKER 链接 http chev me arucogen 首先启动ros roscore 打开相机节点 xff0c 在此提供usb相机与Realsense D435i的启动方法 xff1a roslaunch
  • 脚气、灰指甲治疗实验方案

    脚气 xff08 已临床实验 xff09 脚气 xff0c 又叫足廯 香港脚 糜烂性脚气 症状 xff1a 80 都是这种类型 常见于多汗人群 角质层被汗水浸软 xff0c 发白了以后 xff0c 走动不断摩擦表皮脱落 xff0c 露出鲜红
  • VS2015显示“正在从以下位置加载符号“的解决办法

    解决方法 xff1a VS 工具 选项 调试 符号 看到 MicroSoft符号服务器 xff0c 去掉方框中的 xff0c 确定即可 xff0c 之后就不会再调试时加载
  • 【面包】STM32学习笔记(二) --- USART 串口通信学习总结

    tip xff1a 如有错误 xff0c 希望指出 xff0c 非常感谢 xff01 目录 简介一 USART是什么 xff1f 二 问答通信方式1 USART和UART区别2 单工 半双工 全双工区别 三 代码实验1 说明2 代码初始化配
  • [论文阅读笔记] Reciprocal n-body Collision Avoidance(ORCA/RVO2)

    论文阅读 Reciprocal n body Collision Avoidance ORCA RVO2 文章目录 论文阅读 Reciprocal n body Collision Avoidance ORCA RVO2 论文地址Intro
  • 在VSCode中搭建C++编译环境

    在VSCode中搭建C 43 43 编译环境 VSCode当中搭建C 43 43 环境下载VSCode下载MinGW配置文件撰写测试小程序 VSCode当中搭建C 43 43 环境 vscode作为一款轻量级编程软件深受编程人员喜爱 xff
  • 【C++学习笔记】头文件详解

    个人整理学习用 xff0c 非教材 xff0c 有错误欢迎指正 头文件 究竟什么是头文件 xff1f 首先说明一个概念 xff0c 所谓的文件后缀并不是必须的 xff0c 在Linux下这种特点尤为明显 对于编译器来说 xff0c 无论是