这两天和同事一起讨论C文件中的编译过程,这些个东西书上讲得不少,一个程序从写到到内存里运行起来,一般要经过预编译,编译,链接和装载四个过程,具体的每个过程,我们就不细讲了,因为网上和书都多得是资料。
我们谈谈一些个细节的问题,就是函数的声明和结构体的变量的extern使用。因为一直在C++上用类写,再加平时就很注意标准化和格式,所以习惯了自己的用法,同事程序里的函数的声明和使用方法很有特色,一问还真楞住了。
我们习惯于extern来声明一个变量,这等于告诉编译器这儿这个变量我没定义但别人定义了,你自己去找吧,找不找怎么办?能怎么办?报错呗。可是呢,我们用到结构体变量时
怎么用这个extern呢,比如下面这样:
在1.c文件里这样定义
typedef struct __mytest
{
int i;
int j;
}MYTEST;
MYTEST mt;
在2.c中使用:
extern MYTEST mt;
然后我们使用他,int i = mt.i;int j = mt.j;
OK,编译器给你一个ERROR,不能识别的mt中成员变量i,j,怎么回事儿,我们声明了,怎么还不能看到,是的,你看得到,可编译器不知道这个变量内部是什么玩意儿,所以你要想用,对不起,要把STRUCT 的结构在这个文件里再声明一次,那你会接着问,我一千个文件用得声明一千次呗,我只能说,是的,你太聪明了。那么有没有简单方法呢,答案是,
有。
你可以直接把结构声明到一个.h文件里,然后include他,这样你就不用没事儿就不断的重复声明这个东东了。也就是说你必须把类型的定义让编译器明明白白的知道。(这里顺便说明一个问题,你可以用标准的EXTERN的用法,即在C文件里声明变量,在头文件里EXTERN声明它,然后谁用这个变量,就包含这个头文件就可以了。)
另外,就是函数的处理问题,我们知道,我们在一个文件里写一个函数,在另一个文件里使用,一般我们是写一个头文件,让使用者包含进去,例如最典型的就是库函数,但我们在谭浩强老师的书上发现这样一句话,C中的函数使用和变量一样,要先声明后使用,OK,我们知道,我们包含头文件就等于声明了一个函数,而在C语言里,外部链接(External Linkage),是所有函数默认的声明,换句话说,如果你没有声明成内部链接(Internal Linkage),(具体的怎么声明成内、外部链接,后面会有例子),编译器就认为是外部链接,不管你有没有extern关键字。比如说:extern int test(int ) 和 int test(int)是一个东西。
然后我们又在里面发现一句话,如果这个函数返回值是int 或char型,那么你可以不必声明,直接使用。OK,这句话我们有商量的余地,我们在VC6,VC2010上测试,体现这句话的程序诸如下面:
int main()
{
mytest();
return 1;
}
char mytest()
{
return 'A';
}
这是无法通过的,LINKER找不到函数,没办法,那说明谭老师的这句话不对么,NO,这只能说明,IDE出于安全的目的进行了强制的检查。
再举上面提到的外部和内部链接:
1.c
void mytest()
{
while (1);
}
2.c
void mytest();//这里,没有带extern,但我们说过,默认函数是带External Linkage属性的。OK,即与extern void mytest();等同。
[static] void test();//这样声明表示编译器只能在本地模块里找这个函数,OK,加了static 这个关键字,声明时可以不用,但定义必须有这个关键字。(可实际情况声明和定义有一个都可以用,可能是编译器的原因)
另外,注意:STATIC函数的声明不能包含在头文件里,原因很简单,如果不同的CPP或C文件包含后会强制在本地搜寻,找不到而报错。原理很简单。int main()
{
mytest()
return 1;
}
static void test() //这里必须有static 这个关键字
{
printf("this is ok");
}
顺便说一下,在类中定义STATIC函数,类外使用时就不能再加这个关键字了,原因还是这个关键字可以限定使用的范围,就如我们现在上面这个例子一样。静态函数我们可以在整个类中访问,但如果你再外面再定义时又加了static 了以后,就只能在 这个类文件里使用了。出去就不能使用了。其实原理还是一样的。
这里讲一下内部链接(Internal Linkage):诸如下面:
static int b = 0;//内部链接
static int mypro();//内部链接---这里不再实现此函数体
int main(void)
{
static int a = 0;//非内部链接
}
因为b在不同的文件内声明同样名字的变量,会产生不同的内存地址,而在相同单元内无论如何声明都只代表同一个内存地址,具有Internal Linkage的标识符(变量)在编译后在目标文件中是局部符号,在链接时不做符号解析。
然后再讲讲重载,C语言支持重载,OK,啥叫重载,重载就是函数名字相同,函数的参数类型或数量不同,这里有一个很重要的问题,是一定要类型或者参数数量不一样,那么有童鞋会问,那么我返回值不同,不行么?你很好,很强大,但是,这真不行。为什么,你会紧接着问,我可以告诉你,这个太COOL了,因为我们编译器在里无法识别一种情况下的函数,那就是如下,
int fun(){return 0;}
float fnu{return 0.0}
void test() { int i = fun(); //第一种情况 float j = fun(); //第二种情况 fun(); //第三种情况 } OK,第一二种情况编译器很高兴的就识别出来你的程序调用的哪个函数,可是我们经常有第三种情况,神,你告诉编译器他该怎么办,他的函数表里怎么判断是调用的第一种还是第二种情况下的函数捏。奥特曼说:怪兽! 所以说,函数的重载是不能以返回值来判断的,但是,我这里要唐僧下,在C++的多态机制里,重写的函数是严格一致的,当然,如果返回值是父子的继承关系里,算是一个特例,