include *.c与 include *.h

这是对 老谭的C语言教程一个问题 的讨论。

我浏览了 ChinaUinx 网站上网友的 留言,现在将个人认为有 价值 的回复转帖如下:

(如果侵犯回帖的权益,请通知我即刻删除,谢谢):

 

1#---------------->>>网友Shan_Ghost

在 .h 文件里实现函数有什么不妥之处,include .c就有同样的问题;或者说include .c能解决什么问题,在头文件里实现函数就也能解决什么问题。

而且,.h里声明函数虽然会导致某些特定问题,但有经验的C/C++程序员一看到就可以有心理准备,知道该如何对待;而include .c文件,出同样的问题就会隐蔽得多。


再次,对于一个C/C++程序员来说,凡没有声明在 .h 里的函数,都是模块的内部函数;那么只要保持本模块内部的同步,这个函数可以改名、改接口、改返回值——改它的内部行为就更不用说了。
【模块的内外部函数】
但是,一旦include了.c文件,那么这种保证将荡然无存。对新接触这些include了.c文件的工程代码的人来说,这个软件必然处处都是炸弹,任何地方都不敢碰不敢摸,否则它就会在完全八杆子打不着的地方爆炸——然后哭笑不得的发现,它把你的内部实现include进去了。

这是对最基本的编程规则的挑衅,也是对最基本的编程理念的颠覆。

 

2#----------------->>>Shan_Ghost

a.h <——————— b.h
|                                     |
a.c                               b.c
|                                    |
a1.h a2.h                     b1.h b2.h
  |                              |
   —————————
                   |
                   c.h

如上,一个很复杂的库(以3D游戏举例吧),对外提供两个头文件(a.h 和 b.h),配合a.lib 和 b.lib,已经提供了全部可公开接口。

其中,a.h提供了3D加速算法,b.h提供了2D加速算法;

a.h提供的算法,内部被分解后,使用a1.h 和 a2.h提供的一组正交算法实现(比如矩阵、坐标变换等等);同时还必须使用b.h提供的一些2D接口;

b.h提供的算法,内部被分级后,使用b1.h 和 b2.h提供的另一组正交算法实现(比如颜色空间变换、图像的高速缩放等等)


最后,无论是3D接口还是2D接口,最终都要根据硬件选择不同的基本指令,而这个工作由c来完成。


最终效果就是: 用户仅仅看到了a.h和b.h(实际使用时,include a.h,则 b.h就会自动被include进来),可以完成各种2d、3d计算任务;而内部的a1.h、a2.h、b1.h、b2.h,他们统统看不到;更底层的c.h就更不用说了。

不仅如此,虽然a和b都包含了c,而且a最后又包含了b;但a1和a2提供的接口名字完全可以和b1、b2模块内部的名字重复而不会导致任何问题——只要保证b.h没有include b1.h/b2.h就可以了。


PS:

1、注意在c/c++中,在.h文件中include一个.h文件,和在.c/.cpp文件里include一个.h文件,含义是完全不同的。
在.h中include就相当于public声明,这个被include的.h文件就必须向其他“能访问到include它的那个.h文件”的用户公开;而在.c中include则相当于private声明,被include的.h文件对外部用户来说是不存在的。

善用这个特性,没有什么架构是非include .c文件否则就表达不出来的。



2、对c/c++编译器来说,一个文件的扩展名究竟是.h还是.c并不被它们关心——改名成 .share 也一样能正常工作。

举例来说,仅仅写一个.h文件也能编译出.o/.obj文件,只要你把函数实现也写进去就不会出现xx未声明的错误;仅仅写一个.c而没有同名的.h更是司空见惯之事。

说白了,用.h标记头文件、.c标记实现文件,只是为了有条理的组织代码而已。

所以,遵循这个规定,和遵循写程序要写注释、变量命名要有意义是一回事,仅仅是一个程序员的职业素养的体现,也是一个公司管理水平的体现。

3#----------------->>>一介村夫

凡是反对include .c(这里特指将一个大的.c文件拆成若干小文件然后包含的方法)都给个解决方案出来:

你做一个商业函数库供别人使用,如果里面包含可见非公开函数,你怎么能保证你的用户在连接你的库和其它另一具有同样特征的函数库的时候绝对不会发生冲突?

拿不出万无一失的解决方案就别吐我,先吐自己吧!(这句话不宜,冲动了)

 

4#---------------->>>Shan_Ghost

把一个大的.c文件拆成若干小的 .share 文件,然后 include xxx.share 行不行?

你们谭派弟子就是想标新立异,也换个不那么容易令人混淆的方式行不?(这句话不宜,冲动了)

 

5#---------------->>>Shan_Ghost



QUOTE:
原帖由 albcamus 于 2009-10-27 14:45 发表
include .c是极其丑陋的编码风格, C允许这样做,是完全多余的。



扩展名毫无意义,仅仅是一个约定而已。


这和《设计模式》开宗明义所谈论的东西一样: 俩木匠讨论怎么结合两块木材,他们会顺口说出几种不同的“接榫”;一旦接榫名字确定,尺寸确定,那么别的什么废话都不用说了。


.c .h就是一种最基础的接榫设计。
和其他模式不同,这种设计没有任何副作用——除了不能阻止那些一知半解、标新立异的、发现 include xxx.c 居然也能工作便以为发现了技术新大陆的谭派弟子搞破坏以外。

这和所谓匈牙利命名法以及其他很多命名约定、编程规范一样。你遵守了,习惯了,不会带来任何负担,却让你和其他程序员有了共同语言。


所谓在.h中include、在.c中include的差别;内部头文件和公开头文件的区别,那些连最基本的.c .h规定都不想遵守的谭派弟子,怎么可能理解。

 

6#--------------------->>>language(PDP-7)

QUOTE:
原帖由 一介村夫 于 2009-10-27 15:02 发表
你做一个商业函数库供别人使用,如果里面包含可见非公开函数,你怎么能保证你的用户在连接你的库和其它另一具有同样特征的函数库的时候绝对不会发生冲突?




你这完全是无视了 static 关键字。如果你在认为可能发生命名冲突的内部函数声明前面加个 static,那就根本不会发生冲突,所有声明为 static 的全局符号都只在本模块中可见。

 

7#----------------------->>>Shan_Ghost

1、 对c/c++编译器来说,扩展名不对应任何功能
      这就好像 “匈牙利命名法” 不会给编译器增加任何功能一样。
      同样,绝不存在什么“因为用了匈牙利命名法,结果导致程序无法完成什么功能”的情况。

      这点我反复强调过,反复车轱辘回来很没意思。


2、既然扩展名不提供任何功能,那么遵循扩展名的一般约定就不会产生/抑制任何技术问题。
      唯一的差异,就是愿不愿意拿规范当回事。

      愿意遵守规范,那么无论是在 .h里实现、在 .inc 里实现(STL的典型做法)还是在 .share .code 里实现,都可以在尽量不违反基本规范的前提下解决问题——除了扩展名有所改变,不会有其他任何痛苦;但整个项目一下子就q清爽多了,其他人就不需要疑惑究竟哪些.c文件敢碰,哪些.c摸都别摸了。


3、正如前面一位朋友所说的: 命名空间是做什么的? std:xx 和 std_xxx有本质区别吗?

如果真有合理的模块划分、并且还无法利用命名空间的话, 模块名_xxx 是否可以保证没有冲突?


4、事实上,如果真想解决问题,在.h+.c框架内完全可以做到。无非是对源码组织的要求更高了一点而已。

在 xx.h中include yy.h,相当于公开被引用的yy.h的接口;在xx.c中include yy.h,相当于隐藏 yy.h 提供的接口。
把某个内部模块先独立编译成静态库,然后再把这个内部静态库编译进供别人使用的模块(也编译成库),这样就可以避免内部模块的符号被暴露出去。
递归应用以上规则,有什么解决不了的?


5、wo level hen high, I dou can ba han yu pin sound xie de nobody neng look dong.(呵呵。。。)

 

8#----------------------->>>CMAX

村夫,你说了这么多我想我明白你的意思了,但我认为shan_ghost也明白你的意思了,并且他有提出了一个解决方案,只是这个解决方案在你看来和include .c相同罢了,可能shan_ghost说的过于正统化了,我就我的理解说下:

解决村夫你这种情况,确实使用正统的方式已经难以解决了,需要采用include .c的方式了,但约定俗成下.c文件是不应该被include的,那么这个时候为何不将这个需要被include的.c文件改名为.share或者.c_share文件,并且对这种后缀的文件做一个重点说明,这样可以有效的解决你那个问题,也给后边维护的人一个提醒,这个文件被share了,如果修改会有风险,而不是面对一通的.c文件,不晓得那个被share过,那个没有,连想修改的勇气都没有了.

规范是用来遵守的,虽然有时候规范里会发生解决不了的事,我们可以用规范以外的方式完成,但不要给这种方法也套上规范的外衣,让别的不知情的人一头雾水.

采用命名规范也是一种方式,不过面向全世界范围也确实有其风险,至于风险是多少,就得自己评估了.

总之使用什么方式是自己选择的事,但也要开始没参与过的人可以用约定俗成的规范理解你的做法.

 

9#---------------------->>>fluke

我没有深入研究过这方面,不过还是想问一下,extern能不能解决你这个问题?
a.c要用到b.c里面定义的函数(又不愿意在头文件暴露出来),那么在a.c里面用extern声明一下是在外部有实体可以链接,这个方案看起来貌似可以解决哦。

 

10#--------------------->>>ivhb

回复 #72 一介村夫 的帖子

你说的情况确实存在。考虑到include无非就是合并文件。
是不是可以不硬编码实现,而放到工具层面解决。
比如makefile中,用cat,type(windows)。
。。。。。嗯,这也不好,编译时候,出错点定位就不直观了。

 

.......

【上面已是一场论战了,目前不能把握,先留着以待剖解】

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值