如果说难题最难的部分是基本概念,可能很多人都会持反对意见,但实际上也确实如此。
我高中的时候学物理,老师抓的重点就是概念——概念一定要搞清,于是难题也成了容易题。如果你能分析清楚一道物理难题存在着几个物理过程,每一个过程都遵守那一条物理定律(比如动量守恒、牛II定律、能量守恒),那么就很轻松的根据定律列出这个过程的方程,N个过程必定是N个N元方程,难题也就迎刃而解。即便是高中的物理竞赛难题,最难之处也不过在于:(1)混淆你的概念,让你无法分析出几个物理过程,或某个物理过程遵循的那条物理定律;(2)存在高次方程,列出方程也解不出。而后者已经是数学的范畴了,所以说,最难之处还在于掌握清晰的概念。
程序设计也是如此,如果概念很清晰,那基本上没什么难题(会难在数学上,比如算法的选择、时间空间与效率的取舍、稳定与资源的平衡上)。但是,要掌握清晰的概念也没那么容易。比如下面这个例子,看看你有没有很清晰透彻的认识。
//a.h
void foo();
//a.c
#include "a.h" //我的问题出来了:这句话是要,还是不要?
void foo()
{
return;
}
//main.c
#include "a.h"
int main(int argc, char *argv[])
{
foo();
return 0;
}
针对上面的代码,请回答3个问题:
1、a.c中的#include "a.h"这句话是不是多余的?
2、为什么经常见xx.c里面include对应的xx.h?
3、如果a.c中不写,那么编译器是不是会自动把.h文件里面的东西跟同名的.c文件绑定在一起?
(请针对上面3道题仔细考虑10分钟,莫要着急看下面的解释。:) 考虑的越多,下面理解的就越深。)
好了,时间到!请忘掉上面的3道题,以及对这3道题引发出的你的想法,然后再听我慢慢道来。正确的概念是:从C编译器角度看,.h和.c皆是浮云,就是改名为.txt、.doc也没有大的分别。换句话说,就是.h和.c没啥必然联系。.h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。这个声明有啥用?只是让需要用这些声明的地方方便引用。因为#include "xx.h"这个宏其实际意思就是把当前这一行删掉,把xx.h中的内容原封不动的插入在当前行的位置。由于想写这些函数声明的地方非常多(每一个调用xx.c中函数的地方,都要在使用前声明一下子),所以用#include "xx.h"这个宏就简化了许多行代码——让预处理器自己替换好了。也就是说,xx.h其实只是让需要写xx.c中函数声明的地方调用(可以少写几行字),至于include这个.h文件是谁,是.h还是.c,还是与这个.h同名的.c,都没有任何必然关系。
这样你可能会说:啊?那我平时只想调用xx.c中的某个函数,却include了xx.h文件,岂不是宏替换后出现了很多无用的声明?没错,确实引入了很多垃圾 ,但是它却省了你不少笔墨,并且整个版面也看起来清爽的多。鱼与熊掌不可得兼,就是这个道理。反正多些声明(.h一般只用来放声明,而放不定义,参见拙著《》)也无害处,又不会影响编译,何乐而不为呢?
翻回头再看上面的3个问题,很好解答了吧?
1、答:不一定。这个例子中显然是多余的。但是如果.c中的函数也需要调用同个.c中的其他函数,那么这个.c往往会include同名的.h,这样就不需要为声明和调用顺序而发愁了(C语言要求使用之前必须声明,而include同名.h一般会放在.c的开头)。有很多工程甚至把这种写法约定为代码规范,以规范出清晰的代码来。
2、答:1中已经回答过了。
3、答:不会。问这个问题的人绝对是概念不清,要不就是想混水摸鱼。非常讨厌的是中国的很多考试出的都是这种烂题,生怕别人有个清楚的概念了,绝对要把考生搞晕。
搞清楚语法和概念说易也易,说难也难。窍门有三点:1)不要晕着头工作,要抽空多思考思考,多看看书; 2) 看书要看好书,问人要问强人。烂书和烂人都会给你一个错误的概念,误导你。3)勤能补拙是良训,一分辛苦一分才。
-------------
乾坤一笑 写于2006年02月06日 转载请标明出处和原文链接
--------------------next---------------------
呵呵,你还是没搞清声明和定义的概念,也没搞清static用于声明和定义的意义各是什么。:)
以函数为例:定义的意思是构造一个函数让别人用;声明的意思是我想用一个别人定义的函数,但是我不知道它的原型是什么样子的,所以我要描述一下它的原型。static修饰函数,就是说这个函数是当前这个文件里面定义的函数,只在当前这个文件里面用,外面看不到。
因此,如果你想定义一个函数,只想在当前这个文件中用,那么你就要在定义的时候加上static修饰符。比如
static void foo()
{
return ;
}
而什么时候用声明?当你想用某个函数的时候。如果你的声明写的是:
static void foo();
就说明你想用一个当前文件中定义的foo()函数并且调用了它,如果当前文件中没有定义foo(),那编译器就找不到这个函数,就会报“缺少定义”错。
再翻回来说另一面,如果你在A文件里面定义一个foo(),在当前文件又定义一个foo(),那么就会报“重复定义全局符号的错”。
如果你在A文件里面定义了一个foo(),在当前文件又定义了一个static foo(),那么当前文件中使用的都是当前文件中的static foo(),而不会使用A文件中定义的foo()。
明白了吧?:p 篇幅有限,这里点到为止。如果感兴趣的话,可以看看我以前写的一篇blog《胡言乱语之link、define、declare、extern、static》
--------------------next---------------------
你搞不明白你自己的那个例子,就说明是不清楚,没弄明白声明和定义的概念。
比如你举的:
/* file1.c */
void func1(void);
static void func1(void);
void g(void)
{
extern void func1(void);
}
void func1(void) { ... } /* func1定义 */
/* end file1 */
这时func1是作为static 还是extern还是不确定?
func1(void){ ... } 定义前没有static 限制符,就是说明它不是局部的(本文件内的),就是说他是全局的函数。定义函数时不加static/extern修饰符,和加extern修饰符(此时被称作显式定义)都是全局函数。定义这个函数和上面写那些声明一点关系都没有,研究定义的某个函数是全局的还是局部的时候,不需要关注任何关于这个函数的声明。
变量的问题又恰巧不同,变量的修饰符extern和static有另外的含义
,比如 :
extern int i; //extern作用于变量仅用于声明,说明该变量在另外一个地方定义。
static int i = 1; //static作用于变量仅用于定义,函数外的static变量说明是一个本文件范围内的局部变量。
void foo()
{
static int j = 1; //函数内的static变量是静态变量(分配在静态存储区,有别于其他分配在栈中的变量),第一次进入函数时分配存储空间,退出函数时不释放,下次进入时不重新分配。
... ...
}
:) 你问的不算细,都是写程序中需要明确知道的,不然就有可能写出有隐患的程序来。
--------------------next---------------------
从网上搜了一下,发现解释和你所说的不同。有人如此解释:
"The first prototype with file scope must say "static."
All prototypes with block scope that appear before the first prototype with
file scope must also say "static."
After the first prototype with file scope, later prototypes may either say
"static" or "extern" or may omit the storage class entirely. "
()
据此试验了一把:
/* func1.c */
#include
static void func1(void);
void func2(void)
{
func1();
}
void func1(void)
{
printf("func1 in func1.c\n");
}
/* end func1.c */
/* func2.c */
#include
void func1(void)
{
printf("func1 in func2.c\n");
}
/* end func2.c */
/* func3.c */
extern void func1(void);
void func3(void)
{
printf("***begin***func3 calls func1 in func3.c\n");
func1();
printf("***end***func3 calls func1 in func3.c\n");
}
/* end func3.c */
/* static_func.c */
#include
extern void func1(void);
extern void func3(void);
int main()
{
func1();
func3();
return 0;
}
/* end static_func.c */
用mingw编译:
gcc -ansi static_func.c func1.c func2.c func3.c
编译无任何警告错误。
运行结果是:
func1 in func2.c
***begin***func3 calls func1 in func3.c
func1 in func2.c
***end***func3 calls func1 in func3.c
可见,在func1.c中定义的func1的确是static的。而在func1.c中,func1仅在声明中使用了static,在定义是并没有指定static。
--------------------next---------------------
折腾了半天,总结一下函数linkage的设定:
1、如果某函数的第1个文件作用域声明指定了static,那么该函数具有static linkage。
2、如果某函数声明指定了extern,那么该函数具有和先前文件作用域的函数声明同样的linkage
3、如果某函数声明指定了extern,且先前没有在文件作用域声明该函数,那么该函数具有extern linkage
4、如果某函数声明没有指定linkage,那么按extern处理
5、block作用域的函数声明不能指定static
6、如果同一编译单元的对于同一函数的声明既有extern,又有static,那么结果是未定义的。(gcc好像会给警告,vc默认不管)
应该就这么多了吧,不知道总结的对不对?
其实这些规则列出来很简单的,不知道为什么很多书偏偏没有这样一条一条说清楚。我也是看了半天标准,又从网上搜了半天,才大概弄明白,郁闷。
--------------------next---------------------