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

news2024/9/23 11:24:11

前言

一个 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.coloradmin.cn/o/1910770.html

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

相关文章

【SQL】做项目时用到的语句整理(去重/多表关联)

1. 对日期去重&#xff08;groupby&#xff09; 需要&#xff1a;新建一张表&#xff0c;对原来表中的某个列(href)进行去重&#xff0c;并按照最新的日期进行排版 适用&#xff1a;如果有一张表&#xff0c;我们重复往里面存入数据&#xff0c;有一些除了日期以外&#xff0…

【大模型LLM面试合集】大语言模型架构_MoE论文

1.MoE论文 参考文章&#xff1a; Mixture of Experts-IntroductionUnderstanding the Mixture-of-Experts Model in Deep Learning 论文相关&#xff1a; 论文名称&#xff1a;Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer论文地址&a…

上传图片,base64改为文件流,并转给后端

需求&#xff1a; html代码&#xff1a; <el-dialog v-model"dialogPicVisible" title"新增图片" width"500"><el-form :model"picForm"><el-form-item label"图片名称&#xff1a;" :label-width"10…

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

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

STM32Cubemx配置生成 Keil AC6支持代码

文章目录 一、前言二、AC 6配置2.1 ARM ComPiler 选择AC62.2 AC6 UTF-8的编译命令会报错 三、STM32Cubemx 配置3.1 找到stm32cubemx的模板位置3.2 替换文件内核文件3.3 修改 cmsis_os.c文件3.4 修改本地 四、编译对比 一、前言 使用keil ARM compiler V5的时候&#xff0c;编译…

解读‘‘不要卷模型,要卷应用‘‘

前言 2024 年 7 月 4 日&#xff0c;世界人工智能大会暨人工智能全球治理高级别会议全体会议在上海世博中心举行。百度创始人李彦宏在产业发展主论坛上发言&#xff0c;呼吁不要卷模型&#xff0c;要卷应用。 目录 四个要点 积极的观点 不合理性 总结 四个要点 李彦宏的呼吁…

PMON的解读和开发

提示&#xff1a;龙芯2K1000PMON相关记录 文章目录 1 PMON的发展和编译环境PMONPMON2000 2 PMON2000的目录结构3 Targets目录的组成4 PMON编译环境的建立5 PMON2000的框架6 异常向量表7 Pmon的空间分配8 PMON的汇编部分(starto.S或sbdreset.S)的解读Start.SC代码部分dbginit 9 …

SpringCloud Alibaba Sentinel网关流量控制实践总结

官网地址&#xff1a;https://sentinelguard.io/zh-cn/docs/api-gateway-flow-control.html GitHub地址&#xff1a;GitHub Sentinel 网关限流 【1】概述 Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。 Sentinel 1.6.0 引入了 Sentinel API …

命令执行(RCE)面对各种过滤,骚姿势绕过总结

1、什么是RCE RCE又称远程代码执行漏洞&#xff0c;可以让攻击者直接向后台服务器远程注入操作系统命令或者代码&#xff0c;从而控制后台系统。 2、RCE产生原因 服务器没有对执行命令的函数做严格的过滤&#xff0c;最终导致命令被执行。 3、命令执行函数 PHP代码执行函数…

潜在空间可视化(Latent space visualization)

在“深度学习”系列中&#xff0c;我们不会看到如何使用深度学习来解决端到端的复杂问题&#xff0c;就像我们在《A.I. Odyssey》中所做的那样。我们更愿意看看不同的技术&#xff0c;以及一些示例和应用程序。 1、引言 上次&#xff08;Autoencoders - Deep Learning bits #…

PLC物联网关在工业自动化领域的应用的意义-天拓四方

随着信息技术的飞速发展&#xff0c;物联网技术正逐步渗透到各个行业领域&#xff0c;其中&#xff0c;工业自动化领域的PLC与物联网的结合&#xff0c;为工业自动化的发展开辟了新的道路。PLC物联网关作为连接PLC与物联网的重要桥梁&#xff0c;其重要性日益凸显。 PLC物联网…

STM32 低功耗模式 睡眠、停止和待机 详解

目录 1.睡眠模式&#xff08;Sleep Mode&#xff09; 2.停止模式&#xff08;stop mode&#xff09; 3.待机模式&#xff08;Standby Mode&#xff09; STM32提供了三种低功耗模式&#xff0c;分别是睡眠模式&#xff08;Sleep Mode&#xff09;、停止模式&#xff08;Stop …

Python基于you-get下载网页上的视频

​ 1.python 下载地址 下载 : https://www.python.org/downloads/ 2. 配置环境变量 配置 python_home 地址 配置 python_scripts 地址 在path 中加入对应配置 3. 验证 ​ C:\Users>python --version Python 3.12.4C:\Users>wheel version wheel 0.43.04. 下载 c…

公开课备课思路有哪些?

在准备公开课时&#xff0c;你是否曾感到迷茫&#xff0c;不知从何下手&#xff1f;作为一名教师&#xff0c;我们深知公开课的重要性&#xff0c;它不仅是展示个人教学水平的舞台&#xff0c;也是与同行交流、学习的机会。那么&#xff0c;如何高效地备课&#xff0c;让公开课…

Linux--深入理与解linux文件系统与日志文件分析

一、文件与存储系统的 inode 与 block 1.1 硬盘存储 最小存储单位:扇区( sector )每个扇区存储大小:512 字节1.2 文件存取--block block(块),每个 block 块大小为:4k由连续的八个扇区组成一个 block 块是文件索引最小的单位每个 block 块中包括:文件数据文件数据:就…

开源数字人项目Hallo

硬件条件&#xff1a; gpu最低12G 软件&#xff1a; cuda需支持 Python选择3.10吧&#xff0c;我的版本3.11 源码&#xff1a; GitHub - fudan-generative-vision/hallo: Hallo: Hierarchical Audio-Driven Visual Synthesis for Portrait Image Animation models文件&…

v-model双向绑定的实现原理

在前端处理表单时&#xff0c;我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦&#xff0c;这里我们先用input属性写入输入框&#xff0c;代码如下&#xff1a; <template><view class"out"&g…

从零开始搭建互联网医院系统:技术与案例解析

随着信息技术的飞速发展和人们对医疗服务需求的增加&#xff0c;互联网医院逐渐成为医疗服务的重要模式。本文将详细介绍从零开始搭建互联网医院系统的关键技术和具体案例&#xff0c;帮助读者理解如何构建一个高效、可靠的互联网医院系统。 一、互联网医院系统的核心技术 1…

【EI稳定检索】第五届大数据、人工智能与软件工程国际研讨会(ICBASE 2024)

>>>【独立出版&#xff0c;Ei稳定检索】<<< 第五届大数据、人工智能与软件工程国际研讨会&#xff08;ICBASE 2024&#xff09; 2024年09月20-22日 | 中国温州 一轮截稿时间&#xff1a;2024年7月8日 二轮截稿时间&#xff1a;2024年8月5日 大会简介 *会议…

全能型CAE/CFD建模工具SimLab 详解Part1: Geomtry,轻松集成力学、电磁学、疲劳优化等功能

SimLab的建模功能 SimLab集成了结构力学&#xff0c;流体力学&#xff0c;电磁学&#xff0c;疲劳和优化等功能&#xff0c;是全能型的CAE / CFD建模工具。 具有强大的几何、网格编辑功能&#xff0c;能够快速的清理复杂模型&#xff0c;减少手动修复的工作量&#xff0c;提高…