首页 > 其他分享 >如何防止头文件被重复包含或引用?

如何防止头文件被重复包含或引用?

时间:2023-06-12 10:07:35浏览次数:37  
标签:头文件 变量 重复 int test 引用 extern include 声明


#include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:

#include "x.h"
#include "x.h"

显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。但是下面的代码:
#include "a.h"
#include "b.h"

看上去没什么问题。如果a.h和b.h都包含了一个头文件x.h。那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。

多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以使用条件编译。如果所有的头文件都像下面这样编写:

#ifndef _HEADERNAME_H
#define _HEADERNAME_H

...//(头文件内容)

#endif



那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。如果头文件被再次包含,通过条件编译,它的内容被忽略。符号_HEADERNAME_H按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。

但是,你必须记住预处理器仍将整个头文件读入,即使这个头文件所有内容将被忽略。由于这种处理将托慢编译速度,所以如果可能,应该避免出现多重包含。


test-1.0使用#ifndef只是防止了头文件被重复包含(其实本例中只有一个头件,不会存在重复包含的问题),但是无法防止变量被重复定义。


# vi test.c
-------------------------------
#include <stdio.h>
#include "test.h"

extern i;
extern void test1();
extern void test2();

int main()
{
   test1();
   printf("ok/n");
   test2();
   printf("%d/n",i);
   return 0;
}


# vi test.h
-------------------------------
#ifndef _TEST_H_
#define _TEST_H_

char add1[] = "www.shellbox.cn/n";
char add2[] = "www.scriptbox.cn/n";
int i = 10;
void test1();
void test2();

#endif



# vi test1.c
-------------------------------
#include <stdio.h>
#include "test.h"

extern char add1[];

void test1()
{
   printf(add1);
}



# vi test2.c
-------------------------------
#include <stdio.h>
#include "test.h"

extern char add2[];
extern i;

void test2()
{
   printf(add2);
   for (; i > 0; i--) 
       printf("%d-", i);
}



# Makefile
-------------------------------
test:    test.o test1.o test2.o
test1.o: test1.c
test2.o: test2.c
clean:
   rm test test.o test1.o test2.o



错误:

test-1.0编译后会出现"multiple definition of"错误。


错误分析:

由于工程中的每个.c文件都是独立的解释的,即使头文件有

#ifndef _TEST_H_

#define _TEST_H_

....

#enfif

在其他文件中只要包含了global.h就会独立的解释,然后每个.c文件生成独立的标示符。在编译器链接时,就会将工程中所有的符号整合在一起,由于文件中有重名变量,于是就出现了重复定义的错误。


解决方法

在.c文件中声明变量,然后建一个头文件(.h文件)在所有的变量声明前加上extern,注意这里不要对变量进行的初始化。然后在其他需要使用全局变量的.c文件中包含.h文件。编译器会为.c生成目标文件,然后链接时,如果该.c文件使用了全局变量,链接器就会链接到此.c文件 。


test-2.0

# vi test.c
-------------------------------
#include <stdio.h>
#include "test.h"

int i = 10;
char add1[] = "www.shellbox.cn/n";
char add2[] = "www.scriptbox.cn/n";
extern void test1();
extern void test2();

int main()
{
   test1();
   printf("ok/n");
   test2();
   printf("%d/n",i);
   return 0;
}


# vi test.h
-------------------------------
#ifndef _TEST_H_
#define _TEST_H_

extern i;
extern char add1[];
extern char add2[];

void test1();
void test2();

#endif



# vi test1.c
-------------------------------
#include <stdio.h>
#include "test.h"

void test1()
{
   printf(add1);
}


# vi test2.c
-------------------------------
#include <stdio.h>
#include "test.h"

void test2()
{
   printf(add2);
   for (; i > 0; i--) 
       printf("%d-", i);
 
}

 二、链接指示符:extern


如果希望调用其他程序设计语言(尤其是C)写的函数,那么,调用函数时必须告诉编译器使用不同的要求.例如,当这样的函数被调用时,函数名或参数排列的顺序可能不同,无论是C++函数调用它,还是用其他语言写的函数调用它.

    程序员用链接指示符(linkage directive)告诉编译器,该函数是用其他的程序设计语言编写的.


    链接指示符有两种形式:

    单一语句(single statement)形式

    复合语句(compound statement)形式


    当复合语句链接指示符的括号中包含有#include时,在头文件中的函数声明都被假定是用链接指示符的程序设计语言所写的.


    链接指示符不能出现在函数体中.


vi externC.cpp
-------------------------------------
#include <iostream>
extern "C" double sqrt(double);
int main()
{
    using std::cout;
    using std::endl;
    double result = sqrt(25);
    cout << "result = " << result << endl;
    return 0;
}

g++ externC.cpp


     如果我们希望C++函数能够为C程序所用,我们也可以使用extern "C"链接指示符来使C++函数为C程序可用.




    作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:

    void foo(int x, int y); 


    该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。


    为了实现C和C++的混合编程,C++提供了C链接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

cppExample.h
-----------------------------------
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
//被extern "C"限定的函数或变量首先是extern类型的;extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
//被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;
extern "C" int add(int x, int y);
//extern int add(int x, int y);
#endif



cppExample.cpp
-----------------------------------
#include "cppExample.h"

int add( int x, int y )
{
    return x + y;
}



cFile.c
-----------------------------------
#include <stdio.h>
//这样会编译出错
//#include "cppExample.h"

extern int add(int x, int y);

int main(int argc, char* argv[])
{
    printf("%d/n", add(2, 3));
    return 0;
}



-----------------------------------
 
gcc cFile.c cppExample.cpp

 三、变量定义与声明的区别

我们在程序设计中,时时刻刻都用到变量的定义和变量的声明,可有些时候我们对这个概念不是很清楚,知道它是怎么用,但却不知是怎么一会事,下面我就简单的把他们的区别介绍如下:




    变量的声明有两种情况:


    (1) 一种是需要建立存储空间的(定义、声明)。例如:int a在声明的时候就已经建立了存储空间。 


    (2) 另一种是不需要建立存储空间的(声明)。例如:extern int a其中变量a是在别的文件中定义的。


定义性声明(defining declaration) "或者称为"定义(definition)",而后者是"引用性声明(referncing declaration) "。从广义的角度来讲声明中包含着定义,但是并非所有的声明都是定义,例如:int a它既是声明,同时又是定义。然而对于extern a来讲它只是声明不是定义。一般的情况下我们常常这样叙述,把建立空间的声明称之为"定义",而把不需要建立存储空间称之为"声明"。 很明显我们在这里指的声明是范围比较窄的,也就是说非定义性质的声明。



例如:在主函数中 

int main()
 
{
 
    extern int A; //这是个声明而不是定义,声明A是一个已经定义了的外部变量
 
                  //注意:声明外部变量时可以把变量类型去掉如:extern A;
 
    dosth();      //执行函数
 
}

 
int A;            //是定义,定义了A为整型的外部变量(全局变量)



    外部变量(全局变量)的"定义"与外部变量的"声明"是不相同的,外部变量的定义只能有一次

,它的位置是在所有函数之外,而同一个文件中的外部变量声明可以是多次的,它可以在函数之内(哪个函数要用就在那个函数中声明)也可以在函数之外(在外部变量的定义点之前)。系统会根据外部变量的定义(而不是根据外部变量的声明)分配存储空间的。对于外部变量来讲,初始化只能是在"定义"中进行,而不是在"声明"中。所谓的"声明",其作用,是声明该变量是一个已在后面定义过的外部变量,仅仅是在为了"提前"引用该变量而作的"声明"而已。extern只作声明,不作定义。 



    用static来声明一个变量的作用有二:


    (1) 对于局部变量用static声明,则是为该变量分配的空间在整个程序的执行期内都始终存在


    (2) 外部变量用static来声明,则该变量的作用只限于本文件模块

标签:头文件,变量,重复,int,test,引用,extern,include,声明
From: https://blog.51cto.com/u_16081664/6459615

相关文章

  • 使用.net4引用Delph写的动态链接库DLL,you经验的大佬看一下
    vs2017、net4、无法引用?是Delph动态链接库的问题吗?也把dll放bin同目录底下啦这个是用vs打开的dll是机器代码请有经验的大佬指点一手......
  • linux使用flock文件锁解决脚本重复执行问题
    linux的crontab命令,可以定时执行操作,最小周期是每分钟执行一次。关于crontab实现每秒执行可参考我之前的文章《linuxcrontab实现每秒执行》现在有个问题,如果设定了任务每分钟执行一次,但有可能一分钟内任务并没有执行完成,这时系统会再执行任务。导致两个相同的任务在执行。例如:?......
  • 3 引用 指引正确的方向
    MIN返回一系列数字中最小的数字使用绝对引用来避免复制/粘贴时的变化Excel中绝对引用的语法就是添加美元符号。如果你在列前加上美元符号,那么只有行会改变;如果你在行前加上美元符号,那么只有列会改变;如果你使用了两个美元符号,即在行和列前都加了美元符号,那么这个引用将完全固......
  • spdlog使用头文件或dll的宏设置
    宏作用SPDLOG_HEADER_ONLYspdlog只使用头文件FMT_HEADER_ONLYfmtlib只用用头文件SPDLOG_COMPILED_LIBspdlog使用dllSPDLOG_SHARED_LIBspdlog使用dll,实际可以不设置,单独设置SPDLOG_COMPILED_LIB就可以SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACESPDLOG_TRAC......
  • 使用Python处理声音文件(一):让歌曲重复两次
    说明:1、需要首先安装Python扩展库scipy。2、本文代码只适用于未压缩的WAV声音文件。参考代码:-......
  • Python连接两个字符串并去除首尾重复子串
    代码功能:查找两个字符串的首尾重复部分最大长度,连接两个字符串,并去除两个字符串的首尾重复部分。例如,1234和2347这两个字符串,前面字符串的234子串和后面字符串的234字串重复,两个字符串连接成为12347。参考代码与运行结果:......
  • Python批量导入Excel文件中的不重复数据到SQLite数据库
    自从2015年开始,为了上课方便,我编写了一个课堂管理系统并陆续增加了很多功能,已连续使用7个学期,在使用过程中也经常修补和完善其中一些细节。这个软件也是《Python可以这样学》最后一章的完整案例,涉及tkinter、数据库、多线程、Word文件操作、Excel文件操作、进程管理、二进制序列化......
  • 78 求随机数 不重复的值
    packagecom.fqs.test;importjava.util.Random;importjava.util.Scanner;publicclasshello{publicstaticvoidmain(String[]args){//获取三个不相等的随机数intweishu=6;getNo(weishu);}/*//获取一个4位随机数的数组......
  • c# Winform 防止重复打开同一窗体
     方式一,foreach(FormfrminApplication.OpenForms){if(frmisform1){form1.Activate();form1.WindowState=FormWindowState.Normal;return;}}Form1form1=newForm1();form1.Show(); 方式二,Form1F1;if(......
  • 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
    privatestaticvoidstringSubLen(Stringmsg){intmax=0;intleft=0;Map<Character,Integer>map=newHashMap<>();for(inti=0;i<msg.length();i++){if(map.containsKey(msg.charAt(i))){intdiff=i......