C++ 头文件说明

如果一个程序足够大,代码功能很多,可以想象,不可能把代码写在一个cpp文件里。我们需要模块化,这样的好处很多,方便分工合作,可读性提高,调用也方便。

这个要怎么做呢?

很简单直接当前cpp文件目录下再新建一个test.cpp,如下:

里面就一句代码:

int a=50;

就行;

然后ConsoleAppliciation1.cpp主程序代码:

#include <iostream>
#include <string>
using namespace std;
extern int a;int main()
{cout << a;
}

然后编译运行(编译器会自动编译所有文件):

很明显,认到了外部变量a; 

关键是这个语句 extern int a;声明a是个外部变量。起了作用,如果没有这个声明,则程序无法正常执行。

这个是针对多文件的吗?

不只是,这个是声明int a 是个外部变量。

只要在main函数里,访问变量a前,按上下顺序没有看到变量的a的定义,都需要用这个extern声明一下,否则编译器不认识。

比如,如下语句:


#include <iostream>
#include <string>
using namespace std;
extern int a;int main()
{cout << b;
}
int b = 60;

可以看到 编译直接报错,如下:

不能识别变量“b",所以我们也要在最前面加上extern int b;即使在同一个文件中。

这样再次编译就正常了。 

那么函数也是同样的道理,比如test.cpp 如下代码:

#include <iostream>
void test()
{std::cout << 100;}

主文件:

#include <iostream>
#include <string>
using namespace std;
void test();
int main()
{test();
}

可以看到在最前面有void test();声明,那么这里为什么没有extern关键字?其实这里你加上也是可以的,因为跟变量不同,函数声明可以省略extern,默认是extern;

那么同理,你的函数定义是main函数后面, 即使在同一个文件中,你也是要声明一下函数的。

那么现在有一个问题,既然访问其它文件的变量需要extern声明一下,如果不加声明,是否可以在两个cpp文件里定义了一个同名的变量。

比如test.cpp

int a;

主文件cpp:

#include <iostream>using namespace std;
int a;
int main() {}

答案是不行,会报错,如下:

如果你很疑惑,那么看下例代码,同样是未声明extern;你就能理解了,跟这个差不多的道理:

#include <iostream>using namespace std;
int a;
int main() {}
int a;

结果都是不能重定义,这一点要明白。

最终的结果是两个cpp,不能定义一样的变量。但这并不是因为全局变量的作用域于所有cpp文件,全局变量作用域只是针对当前cpp文件。

不能定义是因为链接obj时是不能有相同的变量名。所以取消extern声明,也是不能定义相同的变量。

这种现象如果很难理解,你可以在单文件里也找到类似现象。

#include <iostream>using namespace std;
extern int a;
int main() {}
int a;

下面的那个int a的作用域是从定义处开始,到文件结束,不是对于整个CPP文件的,但你不能说,根据作用域,就可以在最上面再定义一个int a了(取消extern声明),这很显然会引起冲突的。 

只不过一个是编译时报错,不能重定义,而两个cpp里不能重定义,是编译通过了(因为编译时都是一次次单独编译CPP文件的),变成中间文件obj 链接的时候会报错,不能有相同的外部链接变量。

那么,可以想象,如果分工合作,在多个cpp文件中,定义全局变量,总会不小心定义了相同的全局变量。

 要如何解决:

1.可以使用static修饰变量,这样的全局变量,就只针对单cpp文件,不能被其它cpp文件访问,也不会引起冲突,如:test.h

static int a;

这样就没问题了。 

2.可以使用命名空间的方法,这里就不示例代码了,只是提供一种思路。

3.那么函数,跟变量是同样的道理,不能在多个cpp文件里重复定义,只能定义一次,然后extern声明引用。如果实在要定义,加上static关键字。声明不被外部链接。只在当前cpp文件里有效。

明白了上面这些,我们可能需要使用大量的声明,变量和函数(以后还会有类)

如果要有多个cpp文件要使用这些,那么每次都打这么一串代码,实在麻烦。而且阅读体验也较差。

所以我们可以把这些声明做成一个头文件。

比如现在有test.cpp文件,如下代码:

#include<iostream>
using namespace std;
int a = 5;
void test()
{cout << "\ntest函数\n";
}

然后是test.h头文件:

 void test();extern int a;

主文件ConsoleApplication1.cpp 包含头文件,然后调用:

#include <iostream>
#include"test.h"
using namespace std;int main() {cout << "a值:" << a;test();
}

运行结果:

这里的#include"test.h",实际是预处理命令,即:将test.h里的代码插入当前位置(非编译).

所以实际是这样:

#include <iostream>
void test();
extern int a;
using namespace std;int main() {cout << "a值:" << a;test();
}

本质跟我们之前手动声明是一样的。

那么这里又有几个问题,既然#include只是简单的插入代码,

或者为什么test.h和test.cpp声明和定义要分开写呢?我全写在test.h里。

首先回答test.h里是可以定义变量,或者具体函数代码,但十分不建议这样写,如果你定义了变量a,前面说过了,多个文件#include test.h,链接时会报重复定义。这样极容易造成混乱。

所以标准的做法是分开写,如果一个cpp里的变量和函数要被其它文件使用,声明部分要分开写成一个头文件。以表示共用。

#ifndef

讲到头文件,这里不得不提一下ifndef 宏定义相关。

比如,将上面test.h代码改成这样:


#ifndef TEST_H
#define TEST_H
void test();
extern int a;#endif

上面的语句意思是,如果没有定义宏TEST_H,则执行#endif之前的语句,就是定义宏TEST_H,然后声明函数test和变量a.

这样做的好处是防止代码重复包含,比如一个cpp里多次引用了头文件,它只会插入一次代码,如果识别到了宏TEST_H. 

如类似这样:

#include <iostream>using namespace std;
#include"test.h"
#include"test.h"
int main() {test();
}

注意这里的宏定义只是对当前cpp文件进行判断,因为本质上宏定义也是预处理,只是单纯替换文本。

所以

#ifndef TEST_H
#define TEST_H

#endif

这些语句,就像你在一个文件里#define max 100

然后你在另一个文件里使用max,是不合法的,只作用于当前文件,所以如果在另一个文件用#ifndef 判断max,肯定也是未定义的。 

引申:那么如果你在test.h里面定义了一个变量a,而不是声明,就不要指望这个#ifndef 帮你解决前面多个文件包含test.h冲突的问题,而且这样即使解决了(比如编译器改了逻辑,宏列表对所有cpp有效),也只是假象,因为它会把代码都清掉了(除了第一次文件),其它文件根本就访问不到这个变量a,自然也不会有冲突了。

我们要做的就是按规矩写代码。这里就告诉了我们为什么要这样做。这样就能避免很多问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/898355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lambda 表达式的语法:

在 Java 中&#xff0c;Lambda 表达式&#xff08;也称为匿名方法&#xff09;是一种简洁的表示方法接口&#xff08;Functional Interface&#xff09;实现的方式。它是 Java 8 引入的特性&#xff0c;目的是提高代码的简洁性和可读性。 Lambda 表达式的语法&#xff1a; La…

C#零基础入门篇(18. 文件操作指南)

## 一、文件操作基础 在C#中&#xff0c;文件操作主要通过System.IO命名空间中的类来实现&#xff0c;例如File、FileStream、FileInfo等。 ## 二、常用文件操作方法 ### &#xff08;一&#xff09;文件读取 1. **使用File.ReadAllText方法读取文件内容为字符串** …

每日一题--内存池

内存池&#xff08;Memory Pool&#xff09;是一种高效的内存管理技术&#xff0c;通过预先分配并自主管理内存块&#xff0c;减少频繁申请/释放内存的系统开销&#xff0c;提升程序性能。它是高性能编程&#xff08;如游戏引擎、数据库、网络服务器&#xff09;中的核心优化手…

【Linux系统】Linux进程终止的N种方式

Linux系列 文章目录 Linux系列前言一、进程终止的概念二、进程终止的场景三、进程终止的实现3.1 程序退出码3.2 运行完毕结果正常3.3 运行完毕结果异常3.4 程序异常退出 总结 前言 进程终止是操作系统中&#xff0c;进程的一个重要阶段&#xff0c;他标志着进程生命周期的结束…

正则表达式引擎深入探讨

正则表达式引擎&#xff08;Regular Expression Engine&#xff09;是正则表达式得以“活起来”的核心。它是一个精密的软件组件&#xff0c;负责接收正则表达式和输入文本&#xff0c;解析模式并执行匹配或替换操作&#xff0c;最终输出结果——可能是简单的“是否匹配”&…

java面试题,什么是动态代理?、动态代理和静态代理有什么区别?说一下反射机制?JDK Proxy 和 CGLib 有什么区别?动态代理的底层

什么是动态代理&#xff1f; 动态代理是在程序运行期&#xff0c;动态的创建目标对象的代理对象&#xff0c;并对目标对象中的方法进行功能性增强的一种技术。 在生成代理对象的过程中&#xff0c;目标对象不变&#xff0c;代理对象中的方法是目标对象方法的增强方法。可以理解…

【工具类】Java的 LocalDate 获取本月第一天和最后一天

博主介绍&#xff1a;✌全网粉丝22W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

嵌入式开发之STM32学习笔记day06

基于STM32F103C8T6的开发实践——从入门到精通01 1. 引言 STM32系列微控制器是STMicroelectronics推出的一款高性能、低功耗的32位微控制器&#xff0c;广泛应用于嵌入式系统中。STM32F103C8T6是其中非常受欢迎的一款&#xff0c;凭借其强大的性能、丰富的外设接口和低廉的价格…

学习使用 Git 和 GitHub 开发项目的教程推荐

Git 和 GitHub 是现代软件开发中不可或缺的工具&#xff0c;无论你是个人开发者还是团队成员&#xff0c;掌握它们都能极大提升效率。本文精选了一系列优质教程资源&#xff0c;涵盖从基本 Git 命令到进阶多人协作的内容。这些教程既有文字形式&#xff0c;也有视频或交互式资源…

golang中的接口

1.简介 在go中的接口是以一种类型,一种抽象的类型。接口(interface)是一组函数method的集合,go中的接口不能包含任何变量。在go中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序的多态和高内聚低耦合的思想。go中的接口也是…

AI 浪潮下,职场的变与不变

如今&#xff0c;AI 如迅猛飓风&#xff0c;极速席卷职场&#xff0c;彻底搅乱了原有的秩序。你是否留意到&#xff0c;身边的工作方式正悄然生变&#xff1f;今天&#xff0c;【探星 AI 研习社】就为大家深入剖析&#xff0c;AI 如何改写职场剧本。无论你是大学生还是职场资深…

汇川EASY系列之以太网通讯(MODBUS_TCP做主站)

汇川Easy系列以太网通讯中(MODBUSTCP,plc做主站),终于可以不用使用指令就可以完成了,全程通过简单的配置就可通讯。本文将通过EASY系列PLC与调试助手之间完成此操作。具体演示如下; 关于主站和从站的介绍 A/请求:即主动方 向被动方发送的一个要求的信息。 B/主站:发…

npm error gyp info

在使用 npm 安装 Node.js 包时&#xff0c;可能会遇到各种错误&#xff0c;其中 gyp 错误是比较常见的一种。gyp 是 Node.js 的一个工具&#xff0c;用于编译 C 代码。这些错误通常发生在需要编译原生模块的 npm 包时。下面是一些常见的原因和解决方法&#xff1a; 常见原因及…

Oracle 19C分区表索引小结

一、大佬说&#xff08;杨廷琨&#xff09; LOCAL索引的最大好处是在进行分区操作&#xff0c;比如TRUNCATE PARTITION, DROP PARTITION时&#xff0c;不会出现索引INVALID的情况&#xff0c;不影响索引的可用性。由于GLOBAL索引所有的数据存储在一起&#xff0c;因此当执行分…

AutoHub场景演示|带您领略智能自动化操作的全新体验

AutoHub是一款由OpenCSG推出的基于前沿大型语言模型&#xff08;LLM&#xff09;的浏览器自动化工具&#xff0c;旨在通过智能对话交互和自动化技术&#xff0c;帮助用户更高效地浏览网页和完成任务。它不仅能够自动化繁琐的网页操作&#xff0c;还能够为用户提供精准的信息检索…

深入解析 Linux 声卡驱动:从架构到实战

在嵌入式 Linux 设备中&#xff0c;音频功能的实现离不开 Linux 声卡驱动。而 ALSA (Advanced Linux Sound Architecture) 作为 Linux 内核的音频框架&#xff0c;提供了一整套 API 和驱动模型&#xff0c;帮助开发者快速集成音频功能。本篇文章以 WM8960 音频编解码器&#xf…

thinkphp5模型查询数据库,查出来的字段直接修改成另外的名字

在ThinkPHP5中,如果你希望在查询数据库时将返回的字段名直接修改为其他名称,可以通过以下几种方式实现: 方法1:使用 field 方法指定字段别名 在查询时通过 field 方法直接为字段指定别名(使用 AS 关键字)。 示例代码: // 使用Db类查询 $result = Db::name(user)->…

关于前端指令

在前端开发中&#xff0c;指令&#xff08;Directives&#xff09;通常指在框架中使用的一种特殊的语法或机制&#xff0c;用于扩展 HTML 的功能。常见的指令主要存在于前端框架中&#xff0c;如 Vue.js、Angular 等。下面我们将分别介绍 Vue.js 和 Angular 中的常用指令&#…

虚拟地址空间(下)进程地址空间(上)

一.关于页表组成 1.权限&#xff08;rwx) 作用&#xff1a;如1.让代码区变成只读的 2.写时拷贝的实现&#xff1a;子进程创建时其页表指向的父进程代码和数据权限都是只读的&#xff0c;子进程试图修改&#xff0c;触发错误&#xff0c;系统开始写时拷贝。 来源&#xff1a;…

【区块链 + 航运物流】丰溯 - 区块链溯源平台 | FISCO BCOS 应用案例

丰溯是顺丰科技推出的区块链溯源平台&#xff0c; 采用 FISCO BCOS 底层开源框架&#xff0c; 为农副食品、 冷链生鲜等企业客户及消费 者提供关键流通节点的溯源信息服务&#xff0c;形成从源头到消费者端全链路透明的信息链。 在商贸消费领域&#xff0c; 溯源一直是保障产品…