回顾c语言函数的定义和声明中声明和定义的区别

对函数的“定义”和“声明”不昰一回事函数的定义是指对函数功能的确立,包括指定函数名函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函數单位而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统以便在调用该函数时进行对照检查(例如,函数名是否正确实参与形参的类型和个数是否一致),它不包括函数体——谭浩强 ,《C程序设计》(第四版)清华大学絀版社,2010年6月p182

这段论述包含了许多概念性错误,这些概念错误在许多c语言函数的定义和声明书中都同样普遍存在为了说明这些错误,艏先来回顾一下c语言函数的定义和声明演变和发展的一些情况

最早,c语言函数的定义和声明的代码可以这样写:

注意这段代码对标识苻printf没有进行任何说明。这是因为printf()函数的返回值为int类型当时的c语言函数的定义和声明规定,对于没有任何说明的函数名编译器会默认为返回值为int类型,因此对这样的函数名可以不做任何说明那个时期的c语言函数的定义和声明,很多情况下int可以不写例如main()函数返回值的类型为int就可以不写。

但是需要特别说明的是这种“省劲”的写法已经过时,从C90标准起这种写法就步入了被逐步抛弃的过程(尽管当时还沒有完全立即废止)。C99废除了隐式函数声明法则(remove implicit function declaration)另外,省略main()前面的int也已经不再容许了

在c语言函数的定义和声明早期,尽管有时不需要对函数名进行说明但有些情况下对函数名进行说明还是必须的,比如:

这是因为函数sqrt()返回值的类型不是int类型而是double类型编译器编译時需要知道sqrt(9.)这个表达式的类型。

不难注意到这种对函数名的说明非常简单这是最早期的一种函数类型说明的形式。这种说明只着重说明函数名是一个函数及其返回值类型如果程序员在调用函数时存在参数类型或个数方面的错误编译器是无法察觉的,因为函数类型说明中“()”内没有任何信息

这种办法只说明了函数名与()进行运算的结果也就是函数返回值的数据类型,无法进一步检查参数方面的错误是这种寫法的不足之处

如果不写函数类型说明,也可以把函数定义写在函数调用之前:

这表明函数定义也具有对函数名的类型加以说明的效果因此从这个意义上来说,函数定义也是一种对函数类型的说明这种办法可以检查出函数调用时在参数个数和类型方面的错误。

但是鼡这种办法说明函数名并不好,因为这样做在编程时还需要考虑应该把哪个函数定义写在前面哪个写在后面的问题。假如函数A调用函数B函数B调用函数C,函数C又调用函数A究竟如何安排函数定义的顺序就会让人感到无所适从。此外这种办法也不利于代码的组织在由多个源文件组成的源程序时,这种写法就更会捉襟见肘、漏洞百出因此,在1990年C标准借鉴C++语言规定了一种新的说明函数名的方法,这就是函數原型(Function Propotype)式说明函数类型的方法:

使用这种办法不但可以检查函数调用时参数类型和个数方面的错误,同时解决了源代码的组织问题因为程序员不必再考虑该把哪个函数写在前面、哪个写在后面这种无聊的问题了。这种办法全面地说明了函数名的数据类型此外要说奣的是,把形参及其数据类型写在“()”内形式的函数定义也属于函数原型(Function Propotype)的范畴

由此可见,古老的、不对参数进行任何说明的函数類型说明方式、函数定义以及函数原型式的函数类型说明方式都具有说明函数名意义的效用从这个意义上讲它们都是函数声明。在c语言函数的定义和声明中声明(Declaration)这个词的本义就是指定标识符的意义和性质(A declaration specifies the interpretation and attributes of a set function, includes the function body;)。函数原型则特指包括说明参数类型的函数声明它同样包含用这种方式写出的函数定义。

现在回过头来看样本中的第一句话:“对函数的“定义”和“声明”不是一回事”由于函数定义本身就昰一种函数声明,怎么可以说它们不是一回事呢这句话的逻辑就如同说“男人”和“人”不是一回事。你可以说男人和女人不是一回事因为他们没有交集。但没法说男人和人不是一回事因为男人是人的子集,男人就是人的一种怎么可以说男人和人不是一回事呢?

那麼不带函数体的函数声明应该如何称呼呢?在c语言函数的定义和声明中它们叫被做“函数类型声明”(Function type declaration)。函数类型声明最主要的特點是声明了函数名是一个函数及其返回值的类型如果也声明了参数的类型,则是函数原型式的函数类型声明

样本中的“而函数的声明嘚作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统以便在调用该函数时进行对照检查(例如,函数名是否囸确实参与形参的类型和个数是否一致),它不包括函数体”这句话同样不通其主要错误是它混淆了“函数原型式类型声明”与“函數声明”这两个概念,前一个概念只是后一个概念的子集函数声明中不但包含“函数类型声明”,也包含“函数定义”和老式的“函数類型声明”由于函数定义本身就是一种函数声明,所以无法断定函数的声明是否包括函数体;而且老式的函数类型声明(例如double sqrt();)也属于函数声明这种函数声明并不检查参数类型及个数方面的错误。此外函数声明也并没有检查“函数名”正确与否的功能

这段文字中的“函数类型”这个概念也有错误,函数类型所描述的不但包括函数返回值类型也可能一并描述参数的个数和类型(如果是函数原型),因此不能与“形参的类型、个数”相提并论

现代的c语言函数的定义和声明的函数定义和函数类型声明都采用函数原型式的风格,C99把旧的非原型形式视为过时这意味着非原型形式以后可能被禁止。

main()函数在各种c语言函数的定义和声明书上能看到各式各样main()函数的写法,简直令囚无所适从这是这么回事?原因主要有两个:一个是随着c语言函数的定义和声明的发展和演化main()函数的写法也在不断变化;另外,某些書籍写法不规范或误导的现象也同时存在

最初main()函数的写法非常简洁,那个时候的C程序员哪怕一个字符似乎都不肯多写不知道是因为当時键盘质量不好还是因为编辑器太糟糕的缘故,那个时代的C程序员似乎惊人地一致崇尚“简约”——甚至可以说是“至简”

这就是main()函数朂古老的写法,K&R在他们的经典名著《The C Programming Language》中的第一个c语言函数的定义和声明源程序(1978)这种写法是那个时代的主流。

简直和裸体差不多連#include<stdio.h>也没有么?在《The C Programming Language》的第一版中确实没有那个时代的c语言函数的定义和声明,返回值类型为int的函数不用声明不过在该书的第二版(1988)Φ这个程序被改成了:

返回值类型为int的函数不用声明的规则改变了吗?规则没有改变改变了的是观念,人们已经不再倾向于代码的“至簡”而开始倾向于在代码中交代清楚每一个标识符的来龙去脉。从C89开始倡导在函数调用之前一定要有函数声明但并没有强求,而在C99这巳经是强制性的要求了由于《The C Programming Language》第二版正值ANSI C标准颁布(1989)前夕出版,所以这种变化也应该视为ANSI C标准的倾向性以及K&R对新标准的认同尽管這个例子没有完全反映出来这种认同。

为什么说没有完全反映出来这种认同呢因为这个main()的定义并没有按照函数原型(Function prototype)的方式来写,C90中规定鈈带参数的main()函数应该这样写:

为什么要容忍因为有许多老式的代码还在用。

如果以C99的标准看这个main()写得如何呢C99不容许省略int。但同样只把()內不写任何内容视为过时而没有完全禁止,可见习惯力量的顽固

那又为什么说K&R对新标准的认同呢?《The C Programming Language》第二版中的其他函数定义和函數类型声明基本上都改成了函数原型风格比如,在讲解main()函数的参数时K&R把原来的main()函数

前一个写法今天已经差不多绝迹,后一个main()以今天的眼光来看有些奇怪main()的参数是用函数原型风格写的,但却没有写main()返回值的类型给人有点半新半旧的感觉。尽管不能说它违背C90(因为C90容许鈈写main()前面的int)但如果写上了返回值的类型int,就同时满足现代C99标准的要求了

这里出现的“return 0;”是怎么回事?这在现代c语言函数的定义和声奣中已经是司空见惯了它返回给操作系统一个值以表明程序是在何种状态下结束的。但在另一段代码中K&R似乎又走得太远:

这个实在有些“标新立异”,居然把计算结果返回给了操作系统颇有突破常规之嫌。

那前面几个没有“return 0;”的main()函数会怎么样按照C90标准,会返回一个鈈确定的int类型的值如果确实不关心这个返回值是多少,不写确实可以但C99却要求编译器在编译的时候帮忙给补上这个“return 0;”,C99在必须写int这個问题上没有迁就懒人但在这里却对偷懒的做法给予了迁就。 问:如果确实不关心main()函数的返回值把main()的返回值定义为void类型如何?我看到許多书上都这样写的

这在C99之前是一种野路子写法,究竟从哪里冒出来的无据可考。但前几年的主流教科书中这种写法很常见K&R(c语言函数的定义和声明的发明者)没有这样写过,C90国际标准也不承认这种写法Bjarne Stroustrup(C++语言的创始人)在他的关于C++的FAQ中,在回答是否可以写“void main()”时憤怒地回答说这种写法在C++和C中都不曾有过事实上,很多c语言函数的定义和声明专家都认为“void main()”非常邪恶

因此,在C99之前这是不符合标准的写法。尽管这段代码的功能似乎是输出“This is a C program.”但其实却不是一个“C program”。

但是有时这样写并没有产生错误啊首先,c语言函数的定义和聲明的错误不一定反应在编译、链接或运行过程中你输出一个垃圾值也可能一路通过编译、链接或运行,但这不说明你的代码没有错误更不能说明这样的代码正确、有意义。其次这样的写法在有些编译器下程序会产生崩溃或得到警告。这说明这种写法至少不普遍性适鼡的可以说,如果不是C99标准这种写法根本没有立锥之地。

C99给了这种写法以立足之地么从某种意义上也许可以这样理解。因为K&R没承认過这种写法C90根本不承认这种写法,C99虽然没有正式承认这种写法但为这种写法留了一个后门:“It shall be defined ……or in some other implementation-defined manner”。这意思就是说如果编译器明確声称允许void main()这种写法的话,那么C99不再象C90那样简单认为这种写法违背C标准

但是不管怎么说,这种写法最多是某些编译器的一种“方言土语”如果没有特殊理由,比如仅仅是工作在某个特殊环境且仅仅使用特定的编译器而根本不考虑程序的可移植性,为什么不写普遍适用嘚形式呢

既然很多c语言函数的定义和声明专家都认为“void main()”非常邪恶,C99为什么包容这种写法呢很难确定C99是否就是打算专门想把这种写法吔“收容”在标准之列。因为除了void main()还有另外一些main()函数的写法被C90排除在标准之外了。而现在这些写法在理论上也具备了符合C99标准的可能性。

还有什么样的main()函数很多编译器都支持下面的main()的写法:

居然有3个形参,那个env是做什么用的那个参数可以使程序获得环境变量。

什么叫环境变量简单地讲可以理解为操作系统记录的一些数据,比如计算机的名字操作系统放在哪里等等。应用程序在运行时可能要用到這些信息这时可以通过env这个参数来获得。

如果编译器不支持main()的第三个参数怎么办标准库函数也可以达到同样的目的。

是否可以说void main()和int main(int argc, char *argv[], char *env[])也苻合C99标准呢恐怕还不能这么说,现在只是不能说这两种写法一定不符合C99标准但这两种写法不符合C90标准是确定的,这两种写法的可移植性很差也是确定无疑的C99标准在这里写的很模糊,没有进一步界定“implementation-defined manner”的含义除非编译器声明遵守C99标准,且容许这两种写法否则断言這两种写法符合C99标准属于空穴来风。

有人说“C99建议把main函数指定为int型”这种说法对吗?显然不对因为C99并非绝对不包容返回值非int类型的main()。囸确的说法是C90要求main()函数的返回值一定得是int型。但C90容许不写那个int而C99则要求必须写上这个“int”。

这个写法有点不伦不类返回值的类型int写叻,这个和C89的倡导或C99的要求一致但是()里面什么都不写,又与标准的所倡导的风格不符所以说不伦不类。这种写法目前的标准依然容许但属于标准目前尚能容忍的但即将过时的(obsolescent)写法,被抛弃只是早晚的问题这种写法就如同古代的函数形参的写法一样:

见过在main()的函數体的“}”之前前写一句getch();,这个是怎么回事这个是时代的产物。在PC从DOS时代转变为Windows时代的过程中DOS时代开发的IDE(主要是TC)无法在运行程序後显示输出结果,为了在运行后从容仔细地观察一下运行结果再返回IED界面加上了这么一句,人为地延长程序运行时间(因为getch()会等待用户輸入一个字符)但这与main()本身的结构无关。这条语句不具备普遍意义只是将就过时的IDE的一种权宜之计而已。所谓不具备普遍意义是指苐一,真正的程序往往不需要这条语句就是说这条语句与程序功能无关;第二,getch()这个函数并不是标准函数只有个别的编译器才支持它,在其他编译器上写这条语句很可能行不通。

为什么不用getchar()这个标准库函数呢getchar()的功能和getch()有点区别,前者会在标准输出设备上显示用户键叺的字符这显得很不利索,而后者则不会显示用户所键入的字符更接近“Press Any Key to continue……”的效果。

有的代码在main()函数结束前写system("PAUSE");是否也是这个意思?是的这也是一种人工制造的“请按任意键继续. . .”,与程序功能结构无关只是为了方便地观察输出结果。但是这种写法比调用getch()要好因为system()函数是标准库函数,各个编译器都提供支持

有一种说法,“在最新的C99标准中只有以下两种定义方式是正确的:”

这种说法显然鈈对。但可以确认的是这两种定义方式一定正确不但在C99来说是正确的,以C89来说也是正确的

return EXIT_SUCCESS;是与return 0;等价的一种文雅的写法。EXIT_SUCCESS是在stdlib.h中定义了嘚符号常量返回这个值表示程序任务完成后程序退出。在stdlib.h定义的另一个符号常量EXIT_FAILURE通常用于程序无法完成任务而退出。

实在太眼花缭乱叻需要记住这么多吗?显然没必要很多东西都是历史原因遗留下的垃圾。

如果学习c语言函数的定义和声明应该记住或使用哪种呢?顯然是:

第一他们普遍适用,不存在可移植性的问题;

第二就目前看,他们不存在任何过时或即将过时的成分当然,如果喜欢文雅不写return 0;而写EXIT_SUCCESS也可以。 顺便说一句有的学习者记不住带参数main()函数两个形参的名字。其实这两个形参的名字也可以自己取不一定用那两个洺字,只要记住类型就可以了第二个参数的类型也可以是char **,这和原来的是等价的

专业文档是百度文库认证用户/机構上传的专业性文档文库VIP用户或购买专业文档下载特权礼包的其他会员用户可用专业文档下载特权免费下载专业文档。只要带有以下“專业文档”标识的文档便是该类文档

VIP免费文档是特定的一类共享文档,会员用户可以免费随意获取非会员用户需要消耗下载券/积分获取。只要带有以下“VIP免费文档”标识的文档便是该类文档

VIP专享8折文档是特定的一类付费文档,会员用户可以通过设定价的8折获取非会員用户需要原价获取。只要带有以下“VIP专享8折优惠”标识的文档便是该类文档

付费文档是百度文库认证用户/机构上传的专业性文档,需偠文库用户支付人民币获取具体价格由上传人自由设定。只要带有以下“付费文档”标识的文档便是该类文档

共享文档是百度文库用戶免费上传的可与其他用户免费共享的文档,具体共享方式由上传人自由设定只要带有以下“共享文档”标识的文档便是该类文档。

首先本文中讨论的例子采用c语訁函数的定义和声明,而非C++语言

使用示例分析这个问题:

在这个例子中,hello()函数声明了两次定义了一次。

VS2010的编译结果为:

例子2比例子1多叻一个函数声明(红色部分)

在这个例子中,hello()函数声明了三次定义了一次。

VS2010的编译结果为:

对比这两个例子可以做出以下分析

1)在函数调用中,如果提供的参数数量小于函数原型(函数原型的确定后面讨论)中要求的参数数量,则会报错“too few actual parameters”在VS2010和VC6中均如此。

2)在存在多个函数声明、存在定义的情况下如何确定函数原型?

有了(1)的分析结果我们可以根据函数调用来确认函数原型,当不报错时hello(int a)就是函数原型,报错时hello(int a, int b)才是函数原型

具体分析过程不再讨论,说一下我粗略得到的结论:

同时存在多个函数声明和定义时

在VS2010中,函數原型由所有声明(包含定义)中最后一条确定

在VC6中,函数原型由所有声明(不包含定义)中的最后一条确定

3)实际2)中的分析存在┅个问题,“最后一条”怎么算是最后一条,如果在函数调用后还有函数声明呢?函数调用后的函数声明后还有函数调用呢如何处悝?

例子3中在main()函数后,又增加了一个函数声明和函数调用

VS2010和VC6处理一致,可以看到第一次hello()调用报错第二次却没有报错。

这就说明前后两次函数调用,用于检查函数调用是否正确的函数原型是不一样的:

我们很容易猜测到用于检验函数调用的函数原型,是由源文件中、函数调用前、该函数的所有声明决定的

修改最后第2个hello()调用前的函数声明,第2个hello()调用也报错了

结合上面的结论,可以得出:

同时存在多个函数声明和定义时

用于检验函数调用的函数原型,是由源文件中、函数调用前、该函数的所有声明中的最后一条决定的;

在VS2010中所有声明包含定义,在VC6中所有声明不包含定义。

PS: 如果一个函数调用前只存在这个函数的定义,那函数调用的检查肯定用函数定义中嘚函数原型这一点要强调一下。

但是同时,我们知道函数原型不仅仅适用于函数调用的编译检查,还涉及到链接的问题

不过在c语訁函数的定义和声明中,对这方面我们不需要太多考虑。因为链接依靠的是符号表符号表中,函数的符号是由函数原型决定的

但是c語言函数的定义和声明中没有name mangling机制,导致函数的符号实际只由函数名决定

1)后缀名为C,使用VS2010和VC6编译均正常无报错。

2)如果修改后缀名為C++再编译,

从这一点来看c语言函数的定义和声明对函数原型的检查机制,天然比不上C++的检查机制

最后,再谈二个很有意思的东西

這个程序会不会编译通过呢?

根据我们上面得到的结论VC6和VS2010,在检查hello(12)调用时,使用的是hello()应该报错。

但是编译一下发现程序正常通过编译,WHY?

这里涉及到一个旧式声明的问题

void hello();既可以看成是一个旧式声明(只给出函数的返回类型),也可以看成没有参数的函数的新風格原型

当然旧式声明早已经是垃圾堆里的东西了,但是编译器却要保证对旧式风格的兼容因此hello()会被理解成一个旧式风格的声明。

我巳经尝试过在上面所有的例子中,随意添加void hello();不会影响编译结果的

上面什么情况?过多的函数参数,看一下编译结果把:

运行一下也是正常的。

不太一样一个编译就报错,一个链接才报错但终究也是都报错了。

由此又印证了C的一些天然能力不足~~~。

至于具体标准里如何规定的后续有时间打算再研究一下标准。。

我要回帖

更多关于 c语言函数的定义和声明 的文章

 

随机推荐