能否用痰盂盛饭——谈谈在头文件中定义外部变量 - 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>
int main(void)
{
  printf("%f\n",sqrt( 9.0 ));
  return 0;
}

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

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

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

?
double add(double d1,double d2)
{
  return d1 + d2 ;
}

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

?
double add(double,double);

  2.c文件可以写为

?
#include "1.h"
int main(void)
{
  printf("%f\n",add(3.0,4.0));
  return 0;
}

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

?
#include "1.h"
double add(double d1,double d2)
{
  return d1 + d2 ;
}

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

?
typedef double DOUBLE;

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

?
typedef double DOUBLE;
DOUBLE add(DOUBLE,DOUBLE);

  这时的1.c为
1.c

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

  而2.c则为

?
#include "1.h"
int main(void)
{
  DOUBLE d1=3.0,d2=4.0;
  printf("%f\n",add(d1,d2));
  return 0;
}

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

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

  1. C++中头文件中定义的变量

    1.在头文件.h中定义static变量,如: static int x;其实就等效于每个引用该头文件的源文件中,定义一个变量名为x的整型静态全局变量,每个文件中的x变量均属于本源文件,各文件中的互不相 ...

  2. 【自我修养】不要嘻嘻哈哈的在头文件中定义变量

    在头文件中直接定义变量甚至定义加上赋值,是非常没有修养的行为,新手是经常这样干,有的老手也不注意,这是不应该的. 在头文件中定义变量会出现这些问题: 1,出现变量重复定义的错误.如果你在头文件中定义了 ...

  3. c语言头文件中定义inline static相关函数的优劣

    头文件中常见static inline函数,于是思考有可能遇到的问题,如头文件经常会被包含会不会产生很多副本?网上说法不一.于是自己验证.经过arm-none-eabi-gcc下测试后得出结论. in ...

  4. 编写一个头文件,头文件中定义一个宏cube(x)用于求一个数的平方

    <程序设计基础实训指导教程-c语言> ISBN 978-7-03-032846-5 p145 7.1.2 上级实训内容 [实现内容17]编写一个头文件,头文件中定义一个宏cube(x)用于 ...

  5. 能不能在头文件中定义全局变量?

    首先,这是一篇科普文,所以 比较杂,我尽量写清楚一些. 1.ANSI C标准是什么?GNU又是什么?ld是什么? ANSI C是C语言的标准规范,是国际标准化组织制定的国际标准. 虽然 ANSI C规 ...

  6. c语言头文件可以定义全局变量,C语言在头文件中定义全局变量

    C语言在头文件中定义全局变量 头文件定义全局变量等问题 全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么? 可以,在不同的C文件中以static形式来声明同名全局变量.头文件中不可以直接定 ...

  7. 头文件中定义全局变量

    引言 这些天写的程序中用到了全局变量,一开始是在一个文件下做测试后来把文件按逻辑拆分了一下,不同的代码被放在了几个文件中,最后用Makefile来编译就遇到了今天的话题,怎么在头文件中定义全局变量呢? ...

  8. C语言如何使用其他文件定义的结构体?(C++报错:无法转换到不完整的类【需在头文件中定义结构体??】)

    文章目录 20210725 但是,我在使用的时候报错提示:无法转换到不完整的类?? 20210726 这样? 调用时直接加个extern就好,头文件管都不用管? 20210725 但是,我在使用的时候 ...

  9. 关于在头文件中定义变量

    注意头文件中不可以放变量的定义!!!一般情况下头文件中只放变量的声明,因为头文件要被其他文件包含(即#include),如果把定义放到头文件的话,就不能避免多次定义变量,C++不允许多次定义变量,一个 ...

最新文章

  1. 阿里员工在用的黑科技 今年云栖要公开了!
  2. linux磁盘虚拟化
  3. Docker知识3:Docker的体系简介
  4. Git 远程仓库的管理和使用
  5. Introducing Document Management in SharePoint 2010 介绍SharePoint 2010中的文档管理
  6. 【数据结构与算法】之深入解析“最小覆盖子串”的求解思路与算法示例
  7. netty系列之:从零到壹,搭建一个SOCKS代理服务器
  8. Angular 个人深究(四)【生命周期钩子】
  9. atcoder 2643 切比雪夫最小生成树
  10. ArrayList 有序集合 c#
  11. 记录C++ Builder 6.0开发过程中的一个linker error
  12. 需要知道的面向对象设计的基本原则
  13. 集中器到服务器传输协议,集中器130通讯协议(捷先数码).doc
  14. 开关电源设计书籍推荐
  15. 友华pt926g超级密码_获取电信PT926G光猫超级管理员及账号密码
  16. 正斜杠(左斜杠)和反斜杠(右斜杠)
  17. 网工解惑:何为二层交换机,它与三层交换机的区别在哪里?
  18. 东八区指定时间换算时区
  19. TensorFlow之文本分类算法-2
  20. 一战赚了1090亿,“恐怖”的张一鸣!

热门文章

  1. AT2672 Coins
  2. [原创]快速排序(C++版)
  3. Oracle创建 表空间 用户 给用户授权命令
  4. 路径搜索 – Dijkstra 算法 (MATLAB实现)
  5. 160809325贺彦
  6. read write spinlock
  7. poj 2201 构造
  8. 致NLP学习者,该跟大佬学习做项目了,附资料
  9. ref:ThinkPHP Builder.php SQL注入漏洞(= 3.2.3)
  10. Arduino学习笔记07