表达式(四)
2、函数调用
函数调用表达式的语法格式如下所示:
postfix-expression(argument-expression-listopt)
其中postfix-expression是后缀表达式,表示被调用函数,大多数情况下是函数指示符转换的结果;argument-expression-list是实参表达式列表;实参表达式列表可能为空,也可能是一个或者多个使用逗号(,)分隔的实参表达式;整个表达式表示函数调用,例如:f1()、f2(3)、f3(3,5)。
表示被调用函数的表达式应为指向返回类型为void类型或者完整对象类型(数组类型除外)的函数的指针。
实参数量应与形参数量相等;实参应可以给具有对应形参非限定版本类型的对象赋值。
(注:ISO/IEC 9899:2024标准删除了旧风格函数声明和函数定义的相关内容;因此这里仅考虑函数原型(prototype)的情况。)
int func(int i) { return i; } ... int a = 3; func(a); //合法,实参和形参数量相等,类型兼容。 func(a, 5); //非法,实参和形参数量不相等。 func(3.14); //合法,其值可赋值给形参类型对象。 func(DBL_MAX); //非法,实参从double类型转变成int类型时溢出。 func(&a); //非法,实参类型为int *类型,不能赋值给形参类型对象。
实参可以是任何完整对象类型的表达式。函数调用时会对实参进行评估,并给每个形参赋值对应实参的值。函数可以修改形参值,但不影响实参值。使用指向对象的指针作为实参,函数可以改变实参指向对象的值。
void func(int i, int *ptr) { i += 2; *ptr = 0; } ... int a = 5; int b = 7; func(a, &b); printf("a = %d\n", a); //输出a = 5。 printf("b = %d\n", b); //输出b = 0。
声明为数组类型或者函数类型的形参会转变成指针类型。
#define TYPE(x) _Generic((x), \ int (*)(int, int *): "int (*)(int, int *) type", \ default: "unknown type." \ ) int func(int n, int arr[n]) { int sum = 0; for(int i=0; i<n; i++) sum += arr[i]; return sum; } ... printf("%s\n", TYPE(func)); //输出int (*)(int, int *) type。
如果表示被调用函数的表达式是指向返回类型为对象类型的函数指针,则函数调用表达式类型与该对象类型是相同类型;否则函数调用表达式类型是void类型。
#define TYPE(x) _Generic((x), \ int : "int type", \ int (*)(int, int *): "int (*)(int, int *) type", \ default: "unknown type." \ ) int func(int n, int arr[n]) { int sum = 0; for(int i=0; i<n; i++) sum += arr[i]; return sum; } ... int arr[] = {2,4,6,8}; printf("%s\n", TYPE(func)); //输出int (*)(int, int *) type。 printf("%s\n", TYPE(func(3,arr))); //输出int type。
函数原型声明符中的省略号会导致参数类型转换在最后一个声明形参(如果存在。)后停止;每个尾随参数(trailing argument)都会执行整数提升,具有float类型的尾随参数(trailing argument)会提升为double类型;这称为默认参数提升(default argument promotions)。
int func(unsigned char ch, ...); ... unsigned char ch1 = 'A'; unsigned char ch2 = 'B'; unsigned char ch3 = 'C'; func(ch1, ch2, ch3); //ch1不会整数提升,ch2和ch3会整数提升。
如果函数定义类型与表示被调用函数表达式指向的类型不兼容,其行为是未定义的。
int func(int i) { return i; } ... int number = 5; int *ptr = &number; func(ptr); //函数调用非法,实参类型与形参类型不兼容。
在函数指示符和实参评估后,函数调用前存在一个序列点(sequence point)。调用函数(包括其它函数调用)中的每次评估,如果没有在被调用函数体执行之前或者之后明确排序,则相对于被调用函数执行的排序是不确定的。
func(f1(), f2(), f3())
上述表达式中f1()、f2()、f3()调用的先后顺序是不确定的;但所有副作用应在func函数调用前完成。
函数不能交错执行;但可以递归调用,无论是直接调用,还是通过其它函数间接调用。
unsigned factorial(unsigned number) { if(number <= 1) return 1; else return number*factorial(number - 1); }