多文件编程: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 oncenamespace 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 
    extern 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.mzph.cn/news/871716.shtml

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

相关文章

写真图片视频打赏系统源码全开源无加密

这是一款开源的写真图片及视频打赏系统源码&#xff0c;顾名思义他可以做写真图片打赏站也可以做视频打赏站&#xff0c;支付对接了易支付&#xff0c;拥有独立代理后台&#xff0c;全部源码无加密&#xff0c;另外也可以配合付费进群使用。支付扣量、域名防洪这些基本的就不介…

小白如何学习软件开发

众所周知&#xff0c;软件开发技术是IT技术的核心技术&#xff0c;也是从事IT职业的技术学习首选&#xff0c;因此不少人会去学习&#xff0c;下面我给大家分享关于软件开发学习方法有哪些&#xff0c;欢迎阅读! 1、明确学习目的 学习编程能锻炼思维&#xff0c;使我们的逻辑思…

openlayers WebGL裁剪图层,双图层拼接显示

本篇介绍一下使用openlayers WebGL裁剪图层&#xff0c;双图层拼接显示 1 需求 WebGL裁剪图层&#xff0c;双图层拼接显示 2 分析 图层prerender和postrender事件的使用 WebGL scissor方法的使用 scissor方法指定了一个裁剪区域&#xff0c;用来将绘图区域限制在其限定的盒…

【LeetCode】2187. 完成旅途的最少时间

1. 题意 2. 分析 二分法有一个关键特征&#xff1a;如果答案answer满足题意&#xff0c;那么对于任何整数i&#xff0c;如果有i>answer&#xff0c;那么i也会是一个存在的解&#xff0c;只不过不是最优解。 本题想要找出一个达到 totalTrips 趟需要的最少时间成本t&#x…

FreeRTOS 入门 知识

什么是FreeRTOS FreeRTOS 是一个轻量级的实时操作系统&#xff08;RTOS&#xff09;&#xff0c;由 Richard Barry 在 2003 年开发&#xff0c;并且由亚马逊的 FreeRTOS 项目&#xff08;一个由 Amazon Web Services (AWS) 支持的开源项目&#xff09;进一步推动和发展。FreeR…

麒麟系统开发笔记(十四):在国产麒麟系统上编译libmodbus库、搭建基础开发环境和移植测试Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140387947 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

Java--接口的定义与实现

1.Java的接口是一种约束 2.定义一些方法&#xff0c;让不同的人实现 3.方法都是 public abstract 4.常量都是public static final 5.接口不能被实例化&#xff1a; 接口中没有构造方法 6.接口可以多继承&#xff1a; 使用implements即可实现 7.必须要重写接口中的方法…

在家上网IP地址是固定的吗?

在数字化时代&#xff0c;互联网已成为我们日常生活中不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们都离不开网络的支持。然而&#xff0c;当我们在家中接入互联网时&#xff0c;可能会产生这样一个疑问&#xff1a;在家上网IP地址是固定的吗&#xff1f;下面一…

秋招Java后端开发冲刺——MyBatisPlus总结

一、 基本知识 1. 介绍 yBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上增加了大量功能和简化操作&#xff0c;以提高开发效率。 2. 特点 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有项目产生影响。依赖少&#xff1a;仅仅依赖 …

CV05_深度学习模块之间的缝合教学(1)

1.1 在哪里缝 测试文件&#xff1f;&#xff08;&#xff09; 训练文件&#xff1f;&#xff08;&#xff09; 模型文件&#xff1f;&#xff08;√&#xff09; 1.2 骨干网络与模块缝合 以Vision Transformer为例&#xff0c;模型文件里有很多类&#xff0c;我们只在最后…

嘉立创EDA隐藏地线或者

https://prodocs.lceda.cn/cn/pcb/side-panel-left-net/#%E9%A3%9E%E7%BA%BF

50+dfm模型素人网红路人实时直播替换DFLive模型dfm格式

作为一名直播达人&#xff0c;我投入了大量时间和精力在网上收集和购买各种直播所需的模型资源。这些资源不仅包括男模、女模&#xff0c;还有明星脸、大众脸、网红脸以及各类稀有的素人模型。为了回馈广大直播爱好者&#xff0c;我将这些宝贵资源整理成一个合集&#xff0c;供…

elasticsearch性能调优方法原理与实战

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

ROS1导航状态机与ROS2导航行为树

ROS1和ROS2导航框架中用到的各种底层算法基本相同&#xff0c;比如代价地图&#xff0c;全局路径规划和局部路径规划等&#xff0c;它们最大的不同在于整个系统框架设计。 一&#xff0c;ROS1 导航状态机 ROS1导航功能包move_base是一个状态机&#xff0c;从软件设计上来看&am…

sip协议栈简介

SIP协议栈简介 SIP协议栈流程 数据链路层&#xff1a;当SIP消息从网络中传输到达TCP/IP协议栈时&#xff0c;首先被接收到的是数据链路层的数据帧。数据链路层会对数据帧进行解封装&#xff0c;得到网络层的IP数据报。 网络层&#xff1a;网络层会对IP数据报进行解析&#xf…

【GDCPC2024】【min_25筛】J.另一个计数问题

题目 传送门 思路 考场上的思路和正解差远了&#xff0c;属实是反演学魔怔了。 首先&#xff0c;对于所有的 x x x&#xff0c;它可以通过 2 x 2x 2x 和 2 2 2 连通&#xff0c;而 2 2 2 又可以和所有 m i n p ≤ ⌊ n 2 ⌋ minp\leq \left\lfloor\frac{n}{2}\right\…

浏览器插件利器--allWebPluginV2.0.0.16-alpha版发布

allWebPlugin简介 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有浏览器。它将现有ActiveX插件直接嵌入浏览器&#xff0c;实现插件加载、界面显示、接口调用、事件回调等。支持chrome、FireFo…

江协科技51单片机学习- p27 I2C AT24C02存储器

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Wikijs 部署教程

以下是一个 Wikijs 部署的简单教程&#xff0c;涵盖了使用 Docker 和直接安装两种方式&#xff1a; 方法一&#xff1a; 使用 Docker (推荐) Docker 是一个方便快捷的方式来部署 Wikijs&#xff0c;它可以避免许多手动配置步骤。 安装 Docker: 按照 https://docs.docker.com/…

JRE、JVM、JDK分别是什么。

JDK JDK的英文全称是Java Development Kit。JDK是用于制作程序和Java应用程序的软件开发环境。JDK 是 Java 开发工具包&#xff0c;它是 Java 开发者用来编写、编译、调试和运行 Java 程序的集合。JDK 包括了 Java 编译器&#xff08;javac&#xff09;、Java 运行时环境&…