多文件编程:c/c++分文件写法(入门)

前言

一个 C++ 项目通常会采取 声明与定义分离 的方式进行编写,其基本遵循:头文件中写声明,源文件中写定义
此外,为了区分头文件与源文件,会采用不同的文件后缀:

.h: 头文件
.cpp: 源文件
(当然还有其他的)

这么做有什么好处?

  • 方便管理与维护项目
    如果你的项目只用一个文件:将所有的代码都写到一个源文件中。倘若代码量上千甚至更多,那么当你需要滑动屏幕查找某个函数时,这就很费劲。
    相反,如果你将其拆分为多个文件,将具有同一逻辑功能的代码放入同一文件中,并用文件名加以标识,那么当你需要修改某个逻辑功能对应的代码时,只需要找到对应文件即可。

  • 避免不必要的重复编译
    一个项目只有一个 main.cpp 文件,所有的代码都在其中,并且已经项目编译好了。当你需要改动部分代码时,哪怕只是一行,整个文件都要重新进行编译。显然我们更期望编译器只编译改动部分的代码。

    因为 c++ 编译器是以 文件 为单位进行编译,将源文件编译为 .obj 文件,然后在进行链接。

    所以可以将 main.cpp 合理的拆分为多个文件,当我们改动某部分代码,假设这些代码只在一个文件中,那么编译器只需要重新编译此文件,而不会将所有文件都重新编译一遍,减少了我们的等待时间。

  • 可以用来隐藏内部实现
    比如我写了一个函数 print(),我不想别人知道我是怎么实现这个函数的,只告诉别人它的功能,那么就可以提供其编译好的 静态库(或动态库)以及对应的 头文件 给用户,这样用户就只能使用,而不会知道内部是怎么实现的。

  • … …

那么怎么写呢?先来看一个简单案例:


简单案例

首先需要知道一个简单规则:当使用某函数时,此函数必须已经被定义

  • 在 main 中使用 print 函数:

    #include <iostream>
    
    void print()		// 定义
    {
        std::cout << "hello" << std::endl;
    }
    
    int main()	
    {
        print();
        return 0;
    }
    

上面的代码也可以用下面的代码替换:

  • 通过前置声明

    #include <iostream>
    
    void print();		// 前置声明
    
    int main()	
    {
        print();
        return 0;
    }
    
    void print()		// 定义
    {
        std::cout << "hello" << std::endl;
    }
    

因此你可以用前置声明的方式去理解分文件写法:

  • 将 print 的前置声明放到 print.h 文件中
  • 将 print 的定义放到 print.cpp 文件中,并导入 print.h
  • 在需要使用 print 函数的文件 (main.cpp) 导入 print.h

【扩展】#include <xxx> 与 #include “xxx”

  1. 作用:两种方式都是用来导入头文件,可以理解为:#include <file.h> (或者 #include “file.h”)被预处理器更换为 file.h 中的内容
  2. 差异:
    (1)前者被称为 标准包含 或者 系统包含,编译器会在标准库的头文件目录查找指定的文件,这些目录通常是编译器预定义的,比如 GCC 在 linux 上会查找 /usr/include、/usr/local/include 等目录。
    (2)后者称为 局部包含 或者 用户定义包含,编译器首先会搜索当前文件目录查找指定的文件,如果没有找到,编译器会继续在标准库的头文件目录查找。

因此你将 #include <iostream> 换为 #include “iostream” 仍然能正常运行,但是不建议这么做,因为后者的效率显然没有前者高,同时没有标识性。


【例】项目结构如下:

在这里插入图片描述

  • print.h 内容
    在这里插入图片描述

  • print.cpp 内容
    在这里插入图片描述

  • main.cpp 内容
    在这里插入图片描述

你会发现:main.cpp 与 print.cpp 都导入了 print.h

  • main.cpp:因为它需要使用 print 函数,必然需要导入 print.h
  • print.cpp:print.h 中只是声明,没有定义,故在 print.cpp 中导入 print.h,以告诉编译器 print 函数的实现在什么地方。

在编译器编译此项目,会将 print.cpp、main.cpp 编译为 .obj 文件,然后在进行链接形成最后的可执行文件,下面来编译并运行此项目:
在这里插入图片描述
总结一下分文件的基本写法:将原本的单文件划分为不同模块,将不同模块放到不同文件中。在头文件中写函数声明,对应的源文件中先导入头文件,再写函数定义;在需要使用到此函数的文件中,导入头文件即可。

下面来看几个注意事项:


1. 避免头文件被重复引用

当一个项目有很多头文件时,难免会造成有的头文件被多次引用(导入),为了

  • 防止编译错误
    如果一个头文件中包含有一个宏的定义、变量的定义或者函数的定义等,那么当它被多次引用时,很可能造成重复定义的错误。
  • 一定程度提高编译效率
    虽然现代编译器通常会优化掉一些有重复引用引起的无效代码,但是重复引用仍然可能会增加编译器负担,降低编译效率。
  • … …

我们可以采用如下方面来避免:

  • 宏定义

    #ifndef _NAME_H
    #define _NAME_H
    
    // 头文件内容
    
    #endif
    

    需要注意:宏 _NAME_H 必须是独一无二的,一般为头文件的文件名
    简单说说它是怎么实现避免重复引用的:

    #ifndef _NAME_H
    当第一次引用次头文件时,
    宏 '_NAME_H' 没有被定义,
    那么就会执行它下面的代码。
    
    #define _NAME_H 
    现在定义了 _NAME_H
    
    // 头文件内容
    
    #endif 	
    
    当之后再引用此头文件时,
    _NAME_H 已经定义了,
    因此跳过 
    '#ifndef _NAME_H''#endif'
    之间的内容
    
  • #pragma once
    #pragma once 写在头文件的第一行,那么编译器会保证此文件只会被导入一次。它的效率比 宏定义 要高,但是需要注意在一些老版本编译器上不支持此指令。


2. 命名空间 和 类的声明与定义分离

Student.h:

#pragma once

namespace std {
	class Student {
	public:
		void play();
	private:
		string name;
	};
}

那么需要 注意命名空间与类名
Student.cpp:

#include "Student.h"

void std::Student::play()
{
	// ...
}

// 或者也可以换为
namespace std {
	void Student::play() 
	{
		// ...
	}
}

3. 尽量不要在头文件中使用 using namespace xxx;

如果你对 c++ 的命名空间不太了解的,可见作者的另外一篇文章 c++ namespace

在 C++ 中,标准库中的所有函数、类等都被定义在 std 这一命名空间中,如果你在头文件中使用了 using namespace std;,那么导入此头文件的所有源文件对于 std 中所有定义都是直接可见的,可能会造成命名冲突。


4. 在头文件中定义常量

有时我们需要再头文件中定义 常量,以供其他源文件使用,但是为了避免编译错误,你可以如下定义:

  • 可用 constexpr 修饰的常量直接定义在头文件中
    constexpr 是 c++11 引入的新特性,可以用来修饰基本类型(比如 int、char 等),但是复杂类型无法修饰(比如 string 等)
  • 使用 extern 修饰 const
    // file.h	
    extern const string ss;		// 声明
    
    // file.cpp 
    const string ss = "ss";		// 定义
    

最后

在写头文件与源文件的过程中你会发现一个重复性的工作:需要一个函数需要被写两次,并进行一些增删,比如:

// 头文件
namespace std {
	void fic(const string& s = "");
} 

// 源文件
namespace std {
	void fic(const string& s) {		// 删除默认参数
		// ...
	}	
}

代码少时没多大感觉,但较多时就比较麻烦,这里推荐作者自己写的一个命令行小工具(我称为 header_to_file),能够 读取头文件中的声明语句,自动生成定义语句,并输出到源文件中避免重复性工作,有兴趣的前往 header_to_file 了解。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小白轻松上手,Python编程常用的30个经典操作以及代码演示

当谈到经典的Python编程案例时&#xff0c;通常涉及各种基础和进阶的编程任务. 30个常见的案例&#xff0c;涵盖了从基本操作到稍复杂的应用&#xff1a; 基础操作 1.Hello World: 打印"Hello, World!"到控制台。 print("Hello, World!")2.变量和数据…

智能猫砂盆不好用?三款热门智能猫砂盆推荐!

为什么现在那么多人会淘汰掉普通的猫砂盆&#xff0c;转而去购买智能猫砂盆呢&#xff1f;因为智能猫砂盆的自动铲屎功能是真的香啊&#xff0c;有智能猫砂盆在&#xff0c;就不用每天都自己去铲屎了&#xff0c;我只需要隔三四天去清理一下集便仓就好了&#xff0c;对于我们这…

电脑桌面日历记事本怎么弄 好用的桌面日历记事本

在这个数字化的时代&#xff0c;电脑已成为我们日常生活中不可或缺的伙伴。我常常在电脑上记录各种事项&#xff0c;以便随时查看和提醒自己。而我最钟爱的记事方式&#xff0c;莫过于使用桌面日历记事本。 想象一下&#xff0c;你的电脑桌面上有一个直观的日历&#xff0c;每…

[经典]Axrue部件库:Fluent Design部件库

部件库预览链接&#xff1a;&#xff08;请与班主任联系获取文档&#xff09; 支持版本: Axrure RP 8 文件大小: 2.66 MB 文档内容介绍 基本部件&#xff1a; 常规&#xff1a;3款 基本输入&#xff1a;50款 集合&#xff1a;50款 对话框/弹窗&#xff1a;3款 文本&#…

【BUG】RestTemplate发送Post请求后,响应中编码为gzip而导致的报错

BUG描述 20240613-09:59:59.062|INFO|null|810184|xxx|xxx||8|http-nio-xxx-exec-1|com.xxx.jim.xxx.XXXController.?.?|MSG接收到来自xxx的文件请求 headers:[host:"xxx", accept:"text/html,application/json,application/xhtmlxml,application/xml;q0.9,*…

谷歌摸鱼神器来了:推出AI会议替身,一键总结提问发言_会议预约 ai对话

饱受会议折磨的打工人&#xff0c;终于可以解放了&#xff01; 就在刚刚举办的Google Cloud Next’23大会上&#xff0c;谷歌宣布了一系列科技新进展&#xff0c;最瞩目的要属其中的“开会AI替身”了。 只需要一句“帮我参加”&#xff0c;AI就能替你开会&#xff0c;并在合适…

接口测试课程结构

课程大纲 如图&#xff0c;接下来的阶段课程&#xff0c;依次专项讲解如下专题&#xff0c;能力级别为中级&#xff0c;进阶后基本为中高级&#xff1a; 1.接口基础知识&#xff1b; 2.抓包工具&#xff1b; 3.接口工具&#xff1b; 4.mock服务搭建&#xff08;数据模拟服务&am…

解决了一个java Bug:Exception in thread “main“ java.lang.NullPointerException

写代码&#xff0c;遇到了个问题。 很纳闷&#xff0c;跟着人家写的代码。只能去查资料。 赶紧去找&#xff0c;自己的代码 逆天&#xff0c;赶紧改&#xff01; 成功了&#xff01;&#xff01;&#xff01;

Android常用设计模式(小白必看)

不要担心冗长&#xff0c;3分钟解决面试和学习问题&#xff0c;收藏再看 目的&#xff1a;当作一种模板&#xff0c;结合自身特点&#xff0c;针对项目需求来使用 目录 单例模式 特点&#xff1a; 实现方式&#xff1a; 1、饿汉式 2、线程安全的懒汉式 3、双重校验锁 使…

内网渗透第7天 socker代理 会不会???我们使用cs以及msf来建立代理 代理真的容易???步骤弄明白了吗??

我们在进行内网渗透的时候&#xff0c;第一步要解决的就是网的问题&#xff0c;网络我们都不通或者我们都不能进行访问怎么能进行后门的渗透。我们今天就来讲讲怎么进行建立代理的。这的图非常的简单&#xff0c;也没有waf等阻碍&#xff0c;我们的目的就是攻击机器可以对web进…

RockYou2024 发布史上最大密码凭证

参与 CTF 的每个人都至少使用过一次臭名昭著的rockyou.txt单词表&#xff0c;主要是为了执行密码破解活动。 该文件是一份包含1400 万个唯一密码的列表。 源自 2009 年的 RockYou 黑客攻击&#xff0c;创造了计算机安全历史。 多年来&#xff0c;“rockyou 系列”不断发展。…

ASP.NET MVC Lock锁的测试

思路&#xff1a;我们让后台Thread.Sleep一段时间&#xff0c;来模拟一个耗时操作&#xff0c;而这个时间可以由前台提供。 我们开启两个或以上的页面&#xff0c;第一个耗时5秒(提交5000)&#xff0c;第二个耗时1秒(提交1000)。 期望的测试结果&#xff1a; 不加Lock锁&…

如何在Facebook上保护你的个人资料安全?

随着社交媒体的普及和个人信息的数字化&#xff0c;保护个人资料安全成为越来越重要的议题。特别是在使用像Facebook这样的平台时&#xff0c;我们需要特别注意如何保护我们的数据免受未经授权的访问和滥用。本文将探讨一些实用的方法&#xff0c;以及如何增强你在Facebook上的…

10359-002J 同轴连接器

型号简介 10359-002J是Southwest Microwave的2.92 mm连接器。该连接器外壳材料是不锈钢 CRES 合金 UNS-30300&#xff0c;接触材料是 Becu UNS-C17300&#xff0c;接触镀层是金 MIL-DTL-45204&#xff0c;捕捉材料是 ULTEM 1000。 型号特点 电缆螺母&#xff1a;不锈钢&#x…

RightFont 8.7.0 Mac专业字体管理工具

RightFont 适用于 macOS 的终极字体管理器应用程序&#xff0c;提供无缝的字体管理体验。它结合了速度、直观的功能和专业的功能&#xff0c;使用户能够轻松预览、安装、组织和共享字体。 RightFont 8.7.0 Mac下载 RightFont 8.0的新增功能 RightFont 8.0 带来了全新的智能选…

安防视频监控/云存储/视频汇聚EasyCVR平台播放设备录像不稳定,是什么原因?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;EasyCVR基于云边端一体化架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;可提供7*24小时实时高清视频监控、云端录像、云存储、录像检索与回看、智能告警…

初创企业:如何执行OKR周期?

对于早期创业公司&#xff0c;Tita的OKR教练关于执行OKR周期推荐不是“季度年度”&#xff0c;而是一下三个执行周期&#xff1a; 一个月&#xff1a;”这个月我们在做什么 “是关键问题 团队负责人在月末前的周一上午聚在一起&#xff0c;记录下一个月的功能发布。这是一个自…

Games101——光珊化——深度缓存——shading着色 1

深度缓存 如何解决远近的问题&#xff0c;能正确的覆盖 按照画作来说&#xff0c;先画出远处的物体&#xff0c;再画出近处的物体&#xff0c;近处会将其覆盖&#xff0c;这种算法叫做画家算法 但事实上&#xff0c;排序不仅要花更多的时间&#xff0c;而且排序并不容易&…

使用webrtc-streamer查看rtsp实时视频

1.下载webrtc-streamer 2.解压运行webrtc-streamer.exe 在浏览器访问127.0.0.1:8000&#xff0c;点击窗口可以看到本机上各窗口实时状态&#xff0c;点击摄像头可以显示摄像头画面。 5.安装phpstudy&#xff0c;并建立网站。&#xff08;具体过程自己网上搜&#xff09; 6.打开…

手把手带你本地部署大模型

这篇文章的唯一目的是实现在本地运行大模型&#xff0c;我们使用LMStudio这个工具协助达成这个目标。 文章目录 一&#xff0c;下载安装LM Studio二&#xff0c;本地部署大模型1&#xff0c;搜索模型2&#xff0c;下载大模型3&#xff0c;加载大模型4&#xff0c;测试大模型5&a…