多文件编程

什么是多文件编程

多文件编程,指的是把不同功能的函数封装到不同的文件中的编程方式,其中.c文件内装有函数本体,.h文件内放入函数声明,以供其他函数调用。多文件编程也被称为模块化编程,其中一个.c文件和一个.h文件被称为一个模块。

我们初学c语言函数调用时,习惯将功能函数放在主函数下方,在主函数上面进行函数声明。对于小规模的编程来说这样确实可以,但对于大规模的C语言项目来说,包含很多个函数,写在一个文件中不利于查找、组织、识别。于是就诞生了多文件编程。

人为的将复杂项目中的很多函数分成了不同的功能模块,分开放在不同的.c文件中,并用不同的.h文件进行声明,这就是多文件编程。
在这里插入图片描述
 
 

< > 与 " "

对于库函数,我们习惯用#include <xxx.h>进行声明,如#include <stdio.h>。而在多文件编程中,我们用#include "xxx.h"进行声明,如#include “function1.h”。这两种声明方式有什么区别呢?

#include <stdio.h>

代表编译时直接在软件设置的指定路径中寻找里面是否有stdio.h的库文件。如果有,直接加载;如果没有,报错(无法找到库文件)。

#include “function1.h

代表编译时先寻找你正在编辑的源代码文件所在的文件夹里面有没有function1.h的库文件。如果有,优先加载这个文件(指定路径下如果也有function1.h,它就被无视了),如果没有,才会在指定路径中寻找是否有function1.h的库文件。如果有,直接加载;如果没有,报错(无法找到库文件)。
 
 

多文件编程方法

例如,当需要开发包含两个功能函数(function1和function2)的C程序时,传统的做法是在main.c中分别编写main函数、function1函数和function2函数,在main函数之前声明两个功能函数,并调用。

而如果用多文件编程,则过程如下:

  1. 创建两个.c文件,function1.c和function2.c
  2. 分别在两个.c文件内定义需要的函数
  3. 创建两个.h文件,文件名与两个.c文件一致,即function1.h和function2.h
  4. 在两个.h文件中分别包含各自所需的库文件,声明在.c中定义的函数
  5. 在main.c文件中包含两个.h文件,在main函数中直接调用两个自定义函数

又例如,当开发更加复杂的项目(如学生管理系统)时,我们可以将库函数调用、宏定义、结构体定义、全局变量定义等整个项目都需要使用的内容放在一个头文件中,在需要使用的文件内包含该文件。

在这个头文件中,就包含了若干库函数和结构体定义,除了代码的头两行。那么代码的头两行是干什么的呢?
 
 

变量重复定义与头文件重复包含

 

·变量重复定义

若在不同功能函数中都定义了相同内容(如结构体),则会出现变量重复定义。

a.h:
struct stu {
	char name[10];
	int grade;
}
 
b.h:
#include "a.h"
 
c.h:
#include "a.h"
 
main.c:
#include "b.h"
#include "c.h"

该代码中结构体stu在main中进行了两次定义,出现了变量重复定义。
 

·头文件重复包含

在不同头文件中包含了相同头文件,这种情况就叫做头文件重复包含。

a.h:
int fun();
 
b.h:
#include "a.h"
 
c.h:
#include "a.h"
 
main.c:
#include "b.h"
#include "c.h"

该代码中main函数内定义了两次fun() ,出现了头文件重复包含。
 

·为什么要避免重复包含?

  1. 在编译c程序时候,编译器会将你源程序中#include的头文件完整的展开,如果你在同一个.c下多次包含相同的头文件,会导致编译器在后面的编译步骤多次编译该头文件,使整个项目编译速度变的缓慢,后期的维护修改变得困难。
  2. 头文件重复包含可能会使程序在编译链接的时候因变量重复定义而崩溃。
     

·如何解决

1. #ifndef

//条件编译
//test.h
#ifndef _TEST_H_
#define _TEST_H_                //一般是文件名的大写
//文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,
//………………………………
//………………………………
//………………………………
#endif

优点

  1. 不光可以保证同一个文件不被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不被同时包含
  2. 受C/C++语言标准的支持,不受编译器的任何限制

缺点

  1. 如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,编译器却硬说找不到声明的状况
  2. 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式

2. #pragma once

//#pragma once
//test.h
#pragma once
//………………………………
//………………………………
//………………………………

优点

  1. 你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题
  2. 大型项目的编译速度也因此提高了一些

缺点

  1. 就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含
  2. 而#pragma once方式却不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好
     

·小总结

对于变量重复定义的问题,头文件尽量只有声明,不要有定义!
对于头文件重复包含的问题,根据实际情况使用#ifndef#pragma once

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值