能否用痰盂盛饭——谈谈在头文件中定义外部变量
能否用痰盂盛饭——谈谈在头文件中定义外部变量 - 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中两次定义这个外部变量的情况,这是绝对不允许的。这就是“非痰盂派”认为在头文件中不可以写外部变量定义的理由。
能否用痰盂盛饭——谈谈在头文件中定义外部变量相关推荐
- C++中头文件中定义的变量
1.在头文件.h中定义static变量,如: static int x;其实就等效于每个引用该头文件的源文件中,定义一个变量名为x的整型静态全局变量,每个文件中的x变量均属于本源文件,各文件中的互不相 ...
- 【自我修养】不要嘻嘻哈哈的在头文件中定义变量
在头文件中直接定义变量甚至定义加上赋值,是非常没有修养的行为,新手是经常这样干,有的老手也不注意,这是不应该的. 在头文件中定义变量会出现这些问题: 1,出现变量重复定义的错误.如果你在头文件中定义了 ...
- c语言头文件中定义inline static相关函数的优劣
头文件中常见static inline函数,于是思考有可能遇到的问题,如头文件经常会被包含会不会产生很多副本?网上说法不一.于是自己验证.经过arm-none-eabi-gcc下测试后得出结论. in ...
- 编写一个头文件,头文件中定义一个宏cube(x)用于求一个数的平方
<程序设计基础实训指导教程-c语言> ISBN 978-7-03-032846-5 p145 7.1.2 上级实训内容 [实现内容17]编写一个头文件,头文件中定义一个宏cube(x)用于 ...
- 能不能在头文件中定义全局变量?
首先,这是一篇科普文,所以 比较杂,我尽量写清楚一些. 1.ANSI C标准是什么?GNU又是什么?ld是什么? ANSI C是C语言的标准规范,是国际标准化组织制定的国际标准. 虽然 ANSI C规 ...
- c语言头文件可以定义全局变量,C语言在头文件中定义全局变量
C语言在头文件中定义全局变量 头文件定义全局变量等问题 全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么? 可以,在不同的C文件中以static形式来声明同名全局变量.头文件中不可以直接定 ...
- 头文件中定义全局变量
引言 这些天写的程序中用到了全局变量,一开始是在一个文件下做测试后来把文件按逻辑拆分了一下,不同的代码被放在了几个文件中,最后用Makefile来编译就遇到了今天的话题,怎么在头文件中定义全局变量呢? ...
- C语言如何使用其他文件定义的结构体?(C++报错:无法转换到不完整的类【需在头文件中定义结构体??】)
文章目录 20210725 但是,我在使用的时候报错提示:无法转换到不完整的类?? 20210726 这样? 调用时直接加个extern就好,头文件管都不用管? 20210725 但是,我在使用的时候 ...
- 关于在头文件中定义变量
注意头文件中不可以放变量的定义!!!一般情况下头文件中只放变量的声明,因为头文件要被其他文件包含(即#include),如果把定义放到头文件的话,就不能避免多次定义变量,C++不允许多次定义变量,一个 ...
最新文章
- 阿里员工在用的黑科技 今年云栖要公开了!
- linux磁盘虚拟化
- Docker知识3:Docker的体系简介
- Git 远程仓库的管理和使用
- Introducing Document Management in SharePoint 2010 介绍SharePoint 2010中的文档管理
- 【数据结构与算法】之深入解析“最小覆盖子串”的求解思路与算法示例
- netty系列之:从零到壹,搭建一个SOCKS代理服务器
- Angular 个人深究(四)【生命周期钩子】
- atcoder 2643 切比雪夫最小生成树
- ArrayList 有序集合 c#
- 记录C++ Builder 6.0开发过程中的一个linker error
- 需要知道的面向对象设计的基本原则
- 集中器到服务器传输协议,集中器130通讯协议(捷先数码).doc
- 开关电源设计书籍推荐
- 友华pt926g超级密码_获取电信PT926G光猫超级管理员及账号密码
- 正斜杠(左斜杠)和反斜杠(右斜杠)
- 网工解惑:何为二层交换机,它与三层交换机的区别在哪里?
- 东八区指定时间换算时区
- TensorFlow之文本分类算法-2
- 一战赚了1090亿,“恐怖”的张一鸣!