OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 9934|回复: 3

自定义头文件如何避免重复定义等问题,仕途坎坷,未解决,清闲的大虾关注下,急!

[复制链接]

81

主题

270

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
452
金钱
452
注册时间
2014-8-11
在线时间
87 小时
发表于 2014-11-24 13:31:52 | 显示全部楼层 |阅读模式
5金钱
[mw_shl_code=c,true]/*****keyscan.h*****/ #ifndef __keyscan_H #define __keyscan_H //#include<reg52.h> sfr P3 = 0xB0; unsigned char i,j,k=0; unsigned char KeyScanCode[]={0xef,0xdf,0xbf,0x7f}; unsigned char KeyCodeTable[]={0xee,0xed,0xeb,0xde,0xdd,0xdb,0xbe,0xbd,0xbb,0x7e,0x7d,0x7b}; unsigned char GetKey(); #endif[/mw_shl_code]
[mw_shl_code=c,true]/******lcd1602.h*****/ #ifndef __lcd1602_H #define __lcd1602_H //#include<reg52.h> #include<intrins.h> #define Delaynop(){_nop_();_nop_();_nop_();} sfr P2 = 0xA0; sfr P0 = 0x80; sbit RW=P2^1; sbit RS=P2^0; sbit EN=P2^2; void Delayms(unsigned int); void Display_String(unsigned char); bit LCD_Busy_Check(); void LCD_Write_Command(unsigned char); void LCD_Wdat(unsigned char); void Init_LCD(); void LCD_Pos(unsigned char); #endif[/mw_shl_code]
lcd1602.c   keyscan.c这里就不贴出来了
笔者遇到最大的问题就是参数重复定义,因为刚开始的时候上述两个头文件都用到了IO口,所以之前我就都调用了reg52.h这个头文件
编译程序后系统提示了一麻麻错误,redefinition!
那么问题就来了
因为我两个头文件里都用到了IO,必须都调用reg5.h,思索良久才想到去找系统自定义的头文件,打开reg52.h,发现把所有的IO都定义好了,还有一些寄存器
于是我把reg52.h里我有用到的定义复制到了我自定义的头文件里,编译后redefinition都好了,但是出现我没有定义位参数RS RW EN
于是我在头文件里第一次定义位是这样的:
[mw_shl_code=c,true]#define RW P2^1; #define RS P2^0; #define EN P2^2;[/mw_shl_code]
但编译后还是一样的错误,后来想了下define 是让我的参数等于一个已有定义的变量或者常量,
还没解决本质问题,后来就想到了sbit。其实是很简单的问题,但由于第一次自定义头文件,总是难以避免的。

而后就是_nop()_了,提示在我的头文件lcd1602.h里_nop_()是未定义的函数,由于只在这个头文件里调用了_nop_(),
所以我就直接 [mw_shl_code=c,true]#include<intrins.h>[/mw_shl_code] 当然还有一种办法就是同之前一样,去#include<intrins.h>里面把_nop_()的函数定义copy过来;

翻过这篇,那么笔者的问题来了
问题一:我自定义两个头文件A B,而我又不想去系统头文件里copy,而是直接调用,假入A B都需要调用system.h,那我能不能A调用system.h(泛指系统头文件),
B中#include<A.h>?
问题二:外部文件引用extern!在头文件A.h中定义了函数function(void)(泛指),而B.c中又用到了A中的function(void),我能不能在B.h中
extern function(void),这样我在B.c中直接调用A中的function(void),需不需要在B.c或B.h中#include<A.h>,因为我在想假如需要#include<A.h>的话,
又何必去extern function(void)?
问题三:主程序编译其他错误





第一次是提示我调用函数时前后传递参数不匹配,后来我就把*str定义成unsigned char code类型,然后就提示我different length of parameter list,
真是搞不定,不知哪里又犯低级错误了

说得比较多,哎!!!
最后一个问题:keil C51自定义的头文件是不是要手动放到系统安装目录头文件下,不能再软件里添加进去?

最佳答案

查看完整内容[请看2#楼]

你一个.c文件配一个.h文件,除了你本文件定义声明的外,你用到那个.h直接包含就行了,看你的问题应该是你定义函数和声明函数不一致的我问题。
我是一只菜鸟,但我会大鹏展翅
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

10

主题

561

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1841
金钱
1841
注册时间
2014-6-27
在线时间
982 小时
发表于 2014-11-24 13:31:53 | 显示全部楼层
你一个.c文件配一个.h文件,除了你本文件定义声明的外,你用到那个.h直接包含就行了,看你的问题应该是你定义函数和声明函数不一致的我问题。
回复

使用道具 举报

81

主题

1002

帖子

0

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1876
金钱
1876
注册时间
2014-9-10
在线时间
208 小时
发表于 2014-11-24 15:22:29 | 显示全部楼层
小小蜗牛
回复

使用道具 举报

81

主题

270

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
452
金钱
452
注册时间
2014-8-11
在线时间
87 小时
 楼主| 发表于 2014-11-24 15:33:37 | 显示全部楼层
extern与头文件(*.h)的区别和联系  


        用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字?

        如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的声明)不就可以了么,为什么还要用extern呢??

       这个问题一直也是似是而非的困扰着我许久,经过实践和查找资料,有如下总结:

一、头文件

        首先说下头文件,其实头文件对计算机而言没什么作用,她只是在预编译时在#include的地方展开一下,没别的意义了,其实头文件主要是给别人看的。

         我做过一个实验,将头文件的后缀改成xxx.txt,然后在引用该头文件的地方用

#include"xxx.txt"

         编译,链接都很顺利的过去了,由此可知,头文件仅仅为阅读代码作用,没其他的作用了!

        不管是C还是C++,你把你的函数,变量或者结构体,类啥的放在你的.c或者.cpp文件里。然后编译成lib,dll,obj,.o等等,然后别人用的时候,最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
       但对于我们程序员而言,他们怎么知道你的lib,dll...里面到底有什么东西?要看你的头文件。你的头文件就是对用户的说明。函数,参数,各种各样的接口的说明。
        那既然是说明,那么头文件里面放的自然就是关于函数,变量,类的“声明”(对函数来说,也叫函数原型)了。记着,是“声明”,不是“定义”。

       那么,我假设大家知道声明和定义的区别。所以,最好不要傻嘻嘻的在头文件里定义什么东西。比如全局变量:
/*xx头文件*/
#ifndef _XX_头文件.H
#define _XX_头文件.H
int A;
#endif

        那么,很糟糕的是,这里的int A是个全局变量的定义,所以如果这个头文件被多次引用的话,你的A会被重复定义,显然语法上错了。只不过有了这个#ifndef的条件编译,所以能保证你的头文件只被引用一次,不过也许还是不会出岔子,但若多个c文件包含这个头文件时还是会出错的,因为宏名有效范围仅限于本c源文件,所以在这多个c文件编译时是不会出错的,但在链接时就会报错,说你多处定义了同一个变量,

Linking...
incl2.obj: error LNK2005: "int glb" (?glb@@3HA) already defined in incl1.obj
Debug/incl.exe: fatal error LNK1169: one or more multiply defined symbols found

注意!!!

二、extern

        这个关键字真的比较可恶,在定义变量的时候,这个extern居然可以被省略(定义时,默认均省略);在声明变量的时候,这个extern必须添加在变量前,所以有时会让你搞不清楚到底是声明还是定义。或者说,变量前有extern不一定就是声明,而变量前无extern就只能是定义。注:定义要为变量分配内存空间;而声明不需要为变量分配内存空间。

     下面分变量和函数两类来说:

(1)变量

尤其是对于变量来说。
extern int a;//声明一个全局变量a
int a; //定义一个全局变量a

extern int a =0 ;//定义一个全局变量a 并给初值。
int a =0;//定义一个全局变量a,并给初值,

        第四个 等于 第 三个,都是定义一个可以被外部使用的全局变量,并给初值。
       糊涂了吧,他们看上去可真像。但是定义只能出现在一处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现一次,而那个extern int a可以出现很多次。

        当你要引用一个全局变量的时候,你就必须要声明,extern int a; 这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。注:extern int a; 中类型int可省略,即extern a; 但其他类型则不能省略。

(2)函数
       函数,对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。 但由于函数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体(还有以分号结尾),所以函数定义和声明时都可以将extern省略掉,反正其他文件也是知道这个函数是在其他地方定义的,所以不加extern也行。两者如此不同,所以省略了extern也不会有问题。
    比如:
/*某cpp文件*/
int fun(void)
{
      return 0;
}

很好,我们定义了一个全局函数
/*另一cpp文件*/
int fun(void);
我们对它做了个声明,然后后面就可以用了
加不加extern都一样
我们也可以把对fun的声明 放在一个头文件里,最后变成这样
/*fun.h*/
int fun(void);   //函数声明,所以省略了extern,完整些是extern int fun(void);
/*对应的fun.cpp文件*/
int fun(void)
{
     return 0;
}//一个完整的全局函数定义,因为有函数体,extern同样被省略了。
       然后,一个客户,一个要使用你的fun的客户,把这个头文件包含进去,ok,一个全局的声明。没有问题。
但是,对应的,如果是这个客户要使用全局变量,那么要extern 某某变量;不然就成了定义了。

        总结:

        对变量而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的变量,方法有2种:(1)在A文件中必须用extern声明在B文件中定义的变量(当然是全局变量);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的变量声明,也即在这个头文件中必须用extern声明该变量,否则,该变量又被定义一次。

       对函数而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的函数,方法有2种:(1)在A文件中用extern声明在B文件中定义的函数(其实,也可省略extern,只需在A文件中出现B文件定义函数原型即可);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的函数原型,在头文件中函数可以不用加extern。

******************************************************************************************************************************************************

       对上述总结换一种说法:

         (a)对于一个文件中调用另一个文件的全局变量,因为全局变量一般定义在原文件.c中,我们不能用#include包含源文件而只能包含头文件,所以常用的方法是用extern  int a来声明外部变量。   另外一种方法是可以是在a.c文件中定义了全局变量int global_num ,可以在对应的a.h头文件中写extern int global_num ,这样其他源文件可以通过include a.h来声明她是外部变量就可以了。

      (b)还有变量和函数的不同举例

          int fun(); 和 extern int fun(); 都是声明(定义要有实现体)。  用extern int  fun()只是更明确指明是声明而已。

          而 int a;   是定义      

               extern int a; 是声明。

(3)此外,extern修饰符可用于C++程序中调用c函数的规范问题。

 比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

C++语言在编译的时候为了解决的多态问题,会将名和参数联合起来生成一个中间的名称,而c语言则不会,因此会造成链接时找不到对应的情况,此时C就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间名。

三、extern和头文件的联系
        这种联系也解决了最初提出的2个问题:

(a)用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字?

       (b)如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的声明)不就可以了么,为什么还要用extern呢??

         答案:如果一个文件(假设文件名A)要大量引用另一个文件(假设文件名B)中定义的变量或函数,则使用头文件效率更高,程序结构也更规范。其他文件(例如文件名C、D等)要引用文件名B中定义的变量或函数,则只需用#include包含文件B对应的头文件(当然,这个头文件只有对变量或函数的声明,绝不能有定义)即可。

********************************************************************************************************************************************

       那是一个被遗忘的年代,那时,编译器只认识.c(或.cpp)文件,而不知道.h是何物的年代。
       那时的人们写了很多的.c(或.cpp)文件,渐渐地,人们发现在很多.c(或.cpp)文件中的声明变量或函数原型是相同的,但他们却不得不一个字一个字地重复地将这些内容敲入每个.c(或.cpp)文件。但更为恐怖的是,当其中一个声明有变更时,就需要检查所有的.c(或.cpp)文件,并修改其中的声明,啊~,简直是世界末日降临!
       终于,有人(或许是一些人)再不能忍受这样的折磨,他(们)将重复的部分提取出来,放在一个新文件里,然后在需要的.c(或.cpp)文件中敲入#include   XXXX这样的语句。这样即使某个声明发生了变更,也再不需要到处寻找与修改了---世界还是那么美好!
        因为这个新文件,经常被放在.c(或.cpp)文件的头部,所以就给它起名叫做“头文件”,扩展名是.h.
        从此,编译器(其实是其中预处理器)就知道世上除了.c(或.cpp)文件,还有个.h的文件,以及一个叫做#include命令。
我是一只菜鸟,但我会大鹏展翅
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-29 00:23

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表