近几年的编程中,我越来越倾向于以严格的字母顺序来安排函数在源代码中的出现顺序,比如将finalise函数的定义写在init函数定义的前面。其实这样也说不上有什么好处,只能算是一种洁癖。不过这样就会导致我经常遇到这种情况:func1和func2是两个内部函数,func1要调用func2,但是func1的定义却出现在func2的前面。此时我通常会将func2的声明写在func1的定义里,就像下面这样:
但是今天我将一个VC项目从C切换到C++后,发现上面这样的代码无法链接成功!链接器说找不到func2的定义。如果我要保持这两个函数的定义顺序的话,就只能:要么将func2的声明从func1的定义里拿出来,放在func1定义的前面;要么将func2定义前的static修饰符去掉。我在网上搜了很久,都没有找到C++标准关于这个问题的说明。我尝试过在func1中的func2声明前加上static,却得到“static functions with block scope are illegal”的错误消息。
不知在gcc中是否也会出现同样的现象。可惜我现在正在用公司的电脑,没有gcc可用。回去后在自己的笔记本上做做实验。
这时我想,也许以后是不是要在源文件的开头就把所有函数都声明一遍,以免出现这样的问题。但是转念一想,这样做实在是太麻烦了,而且好像也没见哪个开源项目的代码是这样做的。为了确认一下,我打开了手头的两个开源项目:libevent和sqlite。
libevent并没有这样做,只是在某些函数的定义前面声明了少量的内部函数。
sqlite则这样做了。那长达10万多行的唯一的c文件中,开头1万多行全是各式各样的类型定义、宏定义和函数声明,而且似乎所有的函数都被声明了一遍——包括全局函数。
那么我是不是应该学学sqlite的风格呢?似乎不是的——因为我读了读那份.c文件——即sqlite3.c的最开头的注释。原来sqlite项目为了使代码尽可能轻便、自包含,将所有头文件的内容全部复制到了这份c文件中。比较有趣的是,内部函数的声明也放在了头文件中。
另外,这次我特意留意了一下,这两个开源项目的代码都没怎么按照字母顺序来排列函数的定义。尤其是libevent,函数定义的出现顺序似乎并没什么规律。sqlite似乎好一些,功能相近的函数是放在一起的,比如winMutex*这类Windows平台下的锁函数就放在一起。
我想恐怕也要改改自己那没什么意义的洁癖了。考虑过后,我打算今后这样安排函数的定义顺序:
这样在没有IDE和源码阅读工具的情况下,读者就可以这样阅读我的代码:从头文件可以知道全局函数,然后从源文件的开头往下读,查找自己感兴趣的全局函数。如果碰到了感兴趣的辅助函数,可以在调用它的全局函数的前面找,如果找不到就看看文件的开头,通过它的声明知道它的出现顺序,然后再到文件结尾处找它的定义。
虽然可以用普通文本编辑器的查找功能来查找函数,但是根据我的经验,如果一个函数被调用很多次,那么用普通的查找功能来找到它的定义是一件很麻烦的事。