声明(十六)
3、函数声明符
函数声明符(function declarator)具有以下语法格式:
pointeropt direct-declarator attribute-specifier-sequenceopt ( parameter-type-listopt )
ISO/IEC 9899:2024标准对函数声明符的使用作了一些限制:
-- 函数声明符不能返回函数类型或者数组类型。
typedef int Arr[3]; typedef int Func(int); Arr f1(void); //非法,返回类型是数组类型。 Func f2(void); //非法,返回类型是函数类型。
-- 函数形参声明中唯一可以使用的存储类说明符是register存储类说明符。
int f1(register int); //合法。 int f2(static int); //非法。
register存储类说明符的作用是优化提示,建议编译器尽快处理指定数据;但是否采纳建议取决于实现。
-- 转换后作为函数定义一部分的函数声明符形参类型列表中的形参不能具有不完整类型。
struct s; int f1(struct s s1) //非法,这里struct s类型是不完整类型。 { ... return 0; } struct s{ int i; double d; }; int f2(struct s s2) //合法,这里struct s类型是完整类型。 { ... return 0; }
函数声明符中可选的属性说明符序列(attribute-specifier-sequenceopt)适用于函数;形参声明中可选的属性说明符序列适用于形参。
int f1([[maybe_unused]] int); //属性适用于形参。 int f2 [[nodiscard]] (void); //属性适用于函数。
形参类型列表(parameter-type-listopt)指定函数形参类型,并可为函数形参声明标识符。
int func(int); //等价于int func(int parameter);。
parameter可以是任何合法标识符。
如果函数形参具有数组类型,数组类型将转换成对应的指针类型。如果数组类型[ ]中存在类型限定符,数组类型转换成对应的限定指针类型。
int func(int [const *]); //等价于int func(int * const);。
如果数组类型[ ]中存在static存储类说明符,这样的数组类型声明为函数形参类型时转换成指针类型;每次函数调用,指针指向数组第一个元素,该数组的元素数应至少与数组大小表达式指定的元素数相同。
int func(int n, const int arr[static n]) { int sum = 0; for(int i=0; i<n; i++) sum += arr[i]; return sum; } ... const int a1[3] = {1, 3, 5}; const int a2[5] = {2, 4, 6, 8, 10}; func(5, a1); //非法,数组a1元素数小于5。 func(5, a2); //合法,数组a2元素数等于5。
如果函数形参声明为返回T类型的函数,函数形参将转换成指向返回T类型的函数的指针。
int func(int (void)); //等价于int func(int (*)(void));。
如果形参类型列表以省略号(...)结束,则不提供逗号(,)后形参的数量和类型。可以使用<stdarg.h>头文件中定义的宏访问与省略号(...)对应的实参。
//求一组数中的最大值。 int maximumValue(int N, ...) { int maximum, temp; va_list number; va_start(number, N); maximum = va_arg(number, int); for(int i=0; i<N-1; ++i) { temp = va_arg(number,int); if(maximum<temp) maximum = temp; } va_end(number); return maximum; }
如果形参类型列表只存在一个void类型的未命名形参,则表示函数没有参数。
int func(void); //函数func是一个无参数、返回类型为int类型的函数。
对于不存在形参类型列表的函数声明符,其效果与声明了由关键词void构成的形参类型列表一样。
int func(); //等价于int func(void);。
在函数形参声明中,如果一个标识符既可作为typedef名,也可作为形参名,应作为typedef名。
typedef short Short; int f1(Short); //等价于int f1(short);,Short作为typedef名。 int f2(unsigned Short); //等价于int f2(unsigned);,Short作为形参名。 int f3(Short sh); //等价于int f3(short);,Short作为typedef名。
如果函数声明符不是函数定义的一部分,形参可以是不完整类型,并且在声明符序列中可以使用[*]表示变长数组类型。
//函数声明。 int func(int [*]); //函数定义。 int func(int *ptr) { ... return 0; }
除非声明形参是函数定义的形参类型列表成员之一,否则形参声明中的存储类说明符(如果存在)将被忽略。
int f1(register int); //register存储类说明符可能会被忽略。 int f2(register int i) //register存储类说明符不会被忽略。 { ... return 0; }
如果两个函数类型兼容,两者返回类型应兼容,形参数量和最后省略号(...)的使用应保持一致,相应形参应具有兼容类型。
//f1、f2和f3具有兼容类型。 int f1(int n, int a[n]); int f2(int n, int a[*]); int f3(int n, int a[]); //f5和f6具有兼容类型,与f4不兼容。 int f4(int, int); int f5(int, ...); int f6(signed, ...);
确定类型兼容性和复合类型时,具有数组类型或者函数类型的形参视为转换后的类型;限定类型的形参视为具有声明类型的非限定版本。
//f1和f2类型兼容。 int f1(int, int [*]); int f2(int, int *); //f3和f4类型兼容。 int f3(const int); int f4(int);