兼容类型(五)
五、函数类型的兼容性
C语言中函数的声明有两种风格:一种函数声明时指明形式参数的类型,称为函数原型(function prototype);另一种函数声明时不指明函数参数的类型,也称为旧风格(old-style)。
int funcOne(int income); //函数原型。 int funcTwo(); //旧风格。
funcOne的声明是函数原型;funcTwo的声明是旧风格。
在函数定义中也存在是否包含函数原型的情况。根据ISO/IEC 9899:2018标准第6.11.6 Function declarators节及第6.11.7 Function definitions节,不使用函数原型的函数声明、不使用函数原型的函数定义是一种过时的做法。有鉴于此,本文在讨论函数的兼容性时将不讨论旧风格的函数。
对于函数类型有两个重要特征,一个是函数的返回类型,另一个是参数的数量和类型。如果两个函数类型兼容,要求函数的返回类型兼容,参数的数量相等,对应参数的类型兼容。
int a(int); int b(int); int c(int, int); float d(int); int e(int, double);
a和b的类型是兼容的;a和c的类型是不兼容的,因为参数数量不同;a和d的类型是不兼容的,因为函数返回类型不同;a和e的类型是不兼容的,因为参数数量不同;c和e的类型是不兼容的,因为有一个参数类型是不兼容的。
关于函数参数兼容的几种特殊情况:
① 函数参数包含省略号。
省略号(...)作为函数的参数表示函数具有可变数量的参数,两个兼容类型的函数,如果一个使用了省略号,另一个就应该对应的使用省略号。
int a(int, ...); int b(int, ...); int c(int, int);
a和b的类型是兼容的;a、b与c的类型是不兼容的。
② 函数参数为数组类型。
函数声明时,如果函数参数是数组类型,函数参数与对应的指针类型相兼容。
int a(int *); int b(int [*]);
a和b的类型是兼容的,其中a的参数为一个int类型的指针,b的参数为一个int类型的变长数组。
③ 函数参数为函数类型。
函数声明时,如果函数参数是函数类型,函数参数与对应的指针类型相兼容。
int a(int (void)); int b(int (*)(void));
a和b的类型是兼容的,其中a的参数为一个返回类型为int类型没有参数的函数,b的参数为一个指向返回类型为int类型没有参数的函数的指针。
④ 函数参数有类型限定符的情况。
顶级类型限定符(top-level qualifier)是限定对象自身的类型限定符,以const类型限定符为例:
int numberOne = 10; const int numberTwo = 5; const int *ptrOne = &numberTwo; int * const ptrTwo = &numberOne; const int * const ptrThree = &numberTwo;
如果const是顶级类型限定符,其限定对象的值是不能改变的;其限定对象只能初始化,初始化后不能再被赋值。
对于numberTwo,类型限定符是顶级类型限定符,numberTwo的值是不能改变的;对于ptrOne,类型限定符不是顶级类型限定符,其限定的是指针指向的对象,而不是指针自身,ptrOne指针还可以指向其它实体;对于ptrTwo,类型限定符是顶级类型限定符,其限定的是指针自身,ptrTwo只能指向变量numberOne,而不能指向其它实体,指针的指向位置是不变的;对于ptrThree,*号左边的const不是顶级类型限定符,其限定的是指针指向的对象,*号右边的const是顶级类型限定符,其限定的是指针自身。
注:顶级类型限定符并不是ISO/IEC 9899:2018标准中的术语。
如果在函数参数中出现了顶级类型限定符,将和没有顶级类型限定符的非限定版本相兼容。
int a(int); int b(const int); int c(int *); int d(int * const);
b和d中的const类型限定符均为顶级类型限定符,如果没有顶级类型限定符,a和b的参数类型是相同的,c和d的参数类型是相同的;所以这里a和b的函数类型是兼容的,c和d的函数类型是兼容的。