C缺陷和陷阱-笔记(6)

目录

一、形参、实参与返回值

声明或定义

C语言中形参与实参匹配的规则

二、检查外部类型

因为同一个外部变量名在两个不同的文件中被声明为不同的类型

忽略了声明函数的返回类型

三、头文件


一、形参、实参与返回值

任何C函数都有一个形参列表,列表中的每个参数都是一个变量,该变量在函数调用过程中被初始化。下面这个函数有一个整型形参:
Int.
abs( int n)

{

return n<0? - n:n;

}

而对某些函数来说,形参列表为空。例如,
void 

eatline()
{
   int c;
  do c = getchar();
  while( c != EOF && c !=' \ n');
}

函数调用时,调用方将实参列表传递给被调函数。在下面的例子中,a-b是传递给函数abs的实参:


if (abs( a - b) > n)

printf( " difference is out of range\n")

一个函数如果形参列表为空,在被调用时实参列表也为空。例如,
eatlire( );
任何一个C函数都有返回类型,要么是void,要么是函数生成结果的类型。

声明或定义

如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或定义,那么就不会有任何与返回类型相关的麻烦。

函数square 计算它的双精度类型参数的平方值:
double 
square( double x)
{
       return x*x:
}
以及,一个调用square 函数的程序:
main()
{
        printf( " %g\ n, square(0.3)):
}
要使这个程序能够运行,函数square 必须要么在main之前进行定义:
double 

square( double x)

{
       return x*x:
}

main()
{
        printf( " %g\ n, square(0.3)):
}

如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。

main()
{
printf ( " %g\ n, square(0.3));
}
因为函数main假定函数square 返回类型为整型,而函数square 返回类型实际上是双精度类型,当它与square 函数连接时就会得出错误的结果。

C语言中形参与实参匹配的规则

ANSIC 允许程序员在声明时指定函数的参数类型:
double square (double);

上面的语句说明函数square 接受一个双精度类型的参数,返回一个双精度类型的结果。

根据这个声明,square (2)是合法的;整数2将会被自动转换为双精度类型,

square ((double )2)square (2.0)实际效果上是一样。

如果一个函数没有float 、short 或者char类型的参数,在函数声明中完全可以省略参数类型的说明(注意,函数定义中不能省略参数类型的说明)。

因此,即使是在ANSIC 中,像下面这样声明square 函数也是可以的:
double square();
这样做依赖于调用者能够提供数目正确且类型恰当的实参。

这里,“恰当”并不就意味着“等同”:float 类型的参数会自动转换为double 类型,short 或char类型的参数会自动转换为int类型。例如,对于下面的函数:
int

isvowel( cnar c)

{

            return c == 'a' ||  c == 'e' II c == 'i' II c == 'o'  ||  c == 'u';
}

因为其形参为char类型,所以在调用该函数的其他文件中必须声明:
int isvowei(char);

否则,调用者将把传递给isvowel 函数的实参自动转换为int类型,这样就与形参类型不一致了。如果函数isvowel 是这样定义的:
int isvowel( int c) {
return c == 'a' ||  c == 'e' c = 'i' II c== 'o' c == 'u';

}

面这个程序虽然简单,却不能运行
main()
{
double si 
s= sgrt(2)
printf( sg \ n",s);
)
那么调用者就无需进行声明,即使调用者在调用时传递给isvowel 函数一个char类型的参数也是如此。

double sgrt(double)
main(
douple s s= sgrt(2):
{
printf(%g\n,s):
}

二、检查外部类型

假定我们有一个C程序,它由两个源文件组成。一个文件中包含外部变量n的声明:
excern int n ;

另一个文件中包含外部变量n的定义:
long n:

这里假定两个语句都不在任何一个函数体内,因此n是外部变量。
这是一个无效的C程序,因为同一个外部变量名在两个不同的文件中被声明为不同的类型。

因为同一个外部变量名在两个不同的文件中被声明为不同的类型

当这个程序运行时,可能会出现的情况:
1.C语言编译器足够“聪明”,能够检测到这一类型冲突。编程者将会得到一条诊断消息,报告变量n在两个不同的文件中被给定了不同的类型。

2.读者使用的C语言实现对int类型的数值与long类型的数值在内部表示上是一样的。尤其是在32位计算机上,一般都是如此处理。在这种情况下,程序很可能正常工作,就好像n在两个文件中都被声明为long(或int)类型一样。本来错误的程序因为某种巧合却能够工作,这是一个很好的例子。

3.变量n的两个实例虽然要求的存储空间的大小不同,但是它们共享存储空间的方式却恰好能够满足这样的条件:

赋给其中一个的值,对另一个也是有效的。这是有可能发生的。举例来说,如果连接器安排int类型的n与long类型的n的低端部分共享存储空间,这样给每个long类型的n赋值,恰好相当于把其低端部分赋给了int类型的n。本来错误的程序因为某种巧合却能够工作,这是一个比第2种情况更能说明问题的例子。

4.变量n的两个实例共享存储空间的方式,使得对其中一个赋值时,其效果相当于同时给另一个赋了完全不同的值。在这种情况下,程序将不能正常工作。

因此,保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型。

忽略了声明函数的返回类型

main()
{
double s;

s=sqrt(2);

printf( " %g\ n, s);
}
这个程序没有包括对函数sqrt的声明,因而函数sqrt的返回类型只能从上下文进行推断,

C语言中的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整型的函数。因此,这个程序完全等同于下面的程序:
extern int sgrt()
main()
{
         double s ;
         s = sgrt(2):
         printf( %g\ n, s);
}

当然,这种写法是错误的。函数sqrt返回双精度类型,而不是整型。因此,这个程序的结果是不可预测的。

三、头文件

有一个好方法可以避免大部分此类问题,这个方法只需要我们接受一个简单的规则:每个外部对象只在一个地方声明。这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。特别需要指出的是,定义该外部对象的模块也应该包括这个头文件。

创建一个文件,比如叫做file.h ,它包含了声明:

extern char filename[];


需要用到外部对象filename 的每个C源文件都应该加上这样一个语句:
# include "file.h"


最后,我们选择一个C源文件,在其中给出filename 的初始值。我们不妨称这个文件为file.c:
# include " file.h"
char filename[ ]="/etc/passwd";


注意,源文件file.c实际上包含filename 的两个声明,这一点只要把include 语句展开就可以看出:
extern char filename[ ];
char filename[ ] = '/etc/passwd";


只要源文件file.c中filename 的各个声明是一致的,而且这些声明中最多只有一个是filename 的定义,这样写就是合法的。

让我们来看这样做的效果。头文件file.h 中声明了filename 的类型,因此每个包含了file.h 的模块也就自动地正确声明了filename 的类型。源文件file.c定义了filename ,由于它也包含了file.h头文件,因此filename 定义的类型自动地与声明的类型相符合。如果编译所有这些文件,filename 的类型就肯定是正确的!


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值