能否用痰盂盛饭——谈谈在头文件中定义外部变量

摘要:
我们能用痰盂吃饭吗?-谈论在头文件中定义外部变量。-垃圾工。-博客公园能用痰盂吃饭吗?-讨论在头文件中定义外部变量。“我们能用痰盂吃饭吗?”不是技术问题,而是哲学问题。C语言中有一个类似的“痰盂”问题:除了函数原型和宏定义之外,头文件还可以包括结构类型定义和全局变量定义----谭浩强,《C编程学习指南》(第四版),清华大学出版社,2010年7月,p188可以在头文件中定义外部变量吗?

能否用痰盂盛饭——谈谈在头文件中定义外部变量 - garbageMan - 博客园

能否用痰盂盛饭——谈谈在头文件中定义外部变量

“能否用痰盂盛饭”并非是一个技术问题,而是一个哲学问题。
哲学问题没有标准答案,只存在不同的选择。
有一种观点认为,痰盂可以盛饭。理由是只要不漏能把饭吃到嘴里就行。我看这个理由任何人都无法反驳。
另有一种观点认为,痰盂是用来吐痰的,不可以用来盛饭。他们觉得用痰盂盛饭是一种不可理喻的行为。然而这种看法可能会被“痰盂派”视为一种不必要的洁癖。

C语言中也有类似的“痰盂”问题:

头文件除了可以包含函数原型和宏定义外,也可以包括结构体类型定义和全局变量定义
————谭浩强,《C程序设计》(第四版)学习辅导,清华大学出版社,2010年7月,p188

在头文件中究竟能否定义外部变量呢?这个问题同样有两种选择。
“痰盂派”会认为可以。比如
在 abnormal.h 中写

int e_v = 1 ;

然后在abnormal.c中写

复制代码
#include "abnormal.h"#include <stdio.h>
int main(void)
{
  printf("%d\n",e_v);
  return 0;
}
复制代码

没有人会说这段源程序有什么语法问题,它也没有违背C语言的任何规定。就像痰盂可以用于盛饭是一个道理。
但是,“非痰盂派”会觉得这段源程序透着一种莫名其妙的诡异:既然定义变量e_v是为了在abnormal.c中使用,那么把这个变量定义在abnormal.c中显然最直接也最便利,为什么要舍近求远地把它定义到abnormal.h文件中呢?
在“非痰盂派”的眼中,“*.h”文件不是用来写变量定义的,就如痰盂不是用来盛饭的一样。那么“*.h”文件是做什么用的呢?
首先来看最简单的情况

#include <stdio.h>
#include <math.h>
intmain(void)
{
printf("%f\n",sqrt( 9.0 ));
return0;
}

在这段代码中,出现了printf和sqrt这样两个标识符,由于编译器在编译时不认得这两个标识符,所以需要告诉编译器这两个标识符的含义。这就是代码前面两条预处理命令

#include <stdio.h>
#include <math.h>

的意义。这两个头文件中分别包含printf和sqrt这样两个标识符的类型声明。只有告诉了编译器这两个标识符的含义,编译器才能把它正确地编译成目标文件(*.obj或*.o)。(如果函数返回值类型为int可不声明,但这是一种落后的、逐渐被淘汰的风格)
所以“*.h”文件的一个基本功能就是提供函数类型声明。
另外要注意到的一点是,stdio.h、math.h是由库函数作者提供的,相当于给其他模块提供了一个使用“说明书”,使用库函数的模块用这个“说明书”向编译器说明自己所用到的在其他模块定义的函数名的数据类型。
下面举例进一步说明。
假设一个源程序由两个*.c源文件组成,第一个*.c提供求两个double类型数据和的函数,第二个使用这个函数。那么,第一个*.c文件的作者仅仅写出
1.c

doubleadd(doubled1,doubled2)
{
returnd1 + d2 ;
}

是远远不够的,因为这个1.c虽然可以编译目标文件(*.obj或*.o)以供连接时使用,但是在链接之前,2.c文件在编译时还需要告诉编译器add这个标识符的含义,否则无法正确地编译出与之相应的目标文件。为此,第一个*.c文件的作者还应该给出一个“说明书”——*.h文件
1.h

doubleadd(double,double);

2.c文件可以写为

#include "1.h"
intmain(void)
{
printf("%f\n",add(3.0,4.0));
return0;
}

这样就解决了第二个*.c文件的编译问题(add得到了说明)。
需要说明的是,在很多情况下1.c自己也往往需要包含这个1.h文件,因为这个1.c中可能有其他函数也需要调用这个add()函数,因而也需要这个函数类型声明。所以一般1.c写为
1.c

#include "1.h"
doubleadd(doubled1,doubled2)
{
returnd1 + d2 ;
}

再来看*.h文件中出现数据类型声明的情况。
假设在1.c中使用了一种新的用户定义的数据类型(不一定是“结构体类型”),例如

typedefdoubleDOUBLE;

这时通常也应该把该数据类型的声明写在“*.h"文件中提供给其他模块,除非这个类型仅仅在1.c中使用。
这时的1.h应该为
1.h

typedefdoubleDOUBLE;
DOUBLE add(DOUBLE,DOUBLE);

这时的1.c为
1.c

#include "1.h"
DOUBLE add(DOUBLE d1,DOUBLE d2)
{
returnd1 + d2 ;
}

而2.c则为

#include "1.h"
intmain(void)
{
DOUBLE d1=3.0,d2=4.0;
printf("%f\n",add(d1,d2));
return0;
}

在这里共有两处用到了DOUBLE类型,一次是定义d1,d2这两个变量,另一次是调用add()函数。由于在1.h中这种类型已经得到了声明,所以这段代码可以顺利通过编译。
除了函数类型声明、数据类型声明出现在*.h文件中,宏定义也可能出现在*.h中。
如果1.c和2.c都需要一个共同的常数,把这常数作为一个符号常量写在1.h中,显然可以避免你写你的、我写我的,从而造成大家不协调一致的错误。因为这时大家都是在参照着同一个常数(宏)在写代码。
但是,如果把外部变量的定义写在*.h中会出现什么情况呢?很显然,会出现在1.c和2.c中两次定义这个外部变量的情况,这是绝对不允许的。这就是“非痰盂派”认为在头文件中不可以写外部变量定义的理由。

免责声明:文章转载自《能否用痰盂盛饭——谈谈在头文件中定义外部变量》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Ftp进行文件的上传和下载十三.Java中的泛型和枚举下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

OC 学习第一天

1. OC简介 OC是一种面向对象的计算机语言。 OC实在C语言的基础上增加了一层最小的面向对象语法,完全兼容C语言。 2. OC学习目标 - 语法学习 - 建立面向对象思维能力 -建立基本项目需求分析能力 3. 面向对象思想 面向对象是一种对现实世界理解和抽象的方法,关注的是解决问题需要哪些对象,将功能封装进对象,强调具备了功能的对象。 4. 类与对象...

双端队列的应用

双端队列是一种特殊队列。它是在线性表的两端对插入和删除操作限制的线性表。双端队列能够在队列的不论什么一端进行插入删除操作。#include <stdio.h> #define QUEUESIZE 8 typedef char ElemType; typedef struct DQueue { ElemType qu...

滴滴开源 Vue 组件库— cube-ui

cube-ui 是滴滴去年底开源的一款基于 Vue.js 2.0 的移动端组件库,主要核心目标是做到体验极致、灵活性强、易扩展以及提供良好的周边生态—后编译。 自 17 年 11 月开源至今已有 5 个月,在这个过程中 cube-ui 受到了不少的关注,同时从社区中也收到了很多很好的反馈和建议。我们也一直在迭代更新,从最初的 1.0 版本到最近发布的 1....

经典alsa 录音和播放程序

这里贴上虚拟机ubuntu下alsa的录音程序(capture.c)和播放程序(playback.c)的源码。 首先要测试一下自己的ubuntu是否打开了声音。这个可以打开/系统/首选项/声音  来调节。另外也可以在终端下输入alsaMixer 来调节,之前我的耳机就是只能放音不能录音,因为没有打开一些设置,在进入alsamixer界面后,按F4也就是ca...

Blocks学习笔记总结

  本文是对Apple的《Blocks Progromming Gude》学习的笔记总结。   对象时C级别的语法和运行时特性。和标准C函数很类似,但除了可执行代码外,还可能包含了变量自动绑定(栈)或内存托管(堆)。所以一个block维护一个状态集(数据),可以在执行的时候用来影响程序行为。Block用来作为回调特别有用。   你可以在MAC OS 10....

go函数

一。定义       1. 关键字:func       2.函数名:       3.参数列表:          1. 实参:传入的参数          2.虚参:函数定义时的,占位参数,是局部变量和外部或者全局变量冲突,互不影响。          3.变长参数:arr ...int,得放在最后                4.返回值:    ...