为什么要在 C++ 函数中最后添加默认参数?
9 回答
简化语言定义并保持代码可读性。
void foo(int x = 2, int y);
要调用它并利用默认值,您需要如下语法:
foo(, 3);
这可能让人觉得太奇怪了。另一种选择是在参数列表中指定名称:
foo(y : 3);
必须使用一个新符号,因为这已经意味着什么:
foo(y = 3); // assign 3 to y and then pass y to foo.
ISO 委员会考虑并拒绝了这种命名方法,因为他们不愿意在函数定义之外为参数名称引入新的意义。
如果您对更多 C++ 设计原理感兴趣,请阅读Stroustrup的 The Design and Evolution of C++。
如果定义以下函数:
void foo( int a, int b = 0, int c );
您将如何调用该函数并为 a 和 c 提供值,但将 b 保留为默认值?
foo( 10, ??, 5 );
与其他一些语言(例如 Python)不同,C/C++ 中的函数参数不能通过名称来限定,如下所示:
foo( a = 10, c = 5 );
如果这是可能的,那么默认参数可以在列表中的任何位置。
想象一下,你有一个带有这个原型的函数:
void testFunction(bool a = false, bool b = true, bool c);
现在假设我这样调用函数:
testFunction(true, false);
编译器应该如何确定我打算为哪些参数提供值?
正如大多数答案所指出的那样,在参数列表中的任何位置都可能存在默认参数会增加函数调用的复杂性和歧义(对于编译器,对于函数的用户来说可能更重要)。
C++ 的一个好处是,通常有一种方法可以做你想做的事(即使它并不总是一个好主意)。如果您想为各种参数位置设置默认参数,您几乎可以肯定地通过编写重载来实现这一点,这些重载只是转身并内联调用完全参数化的函数:
int foo( int x, int y);
int foo( int y) {
return foo( 0, y);
}
你有相当于:
int foo( int x = 0, int y);
作为一般规则,函数参数由编译器处理并按从右到左的顺序放置在堆栈中。因此,应该首先评估任何具有默认值的参数是有意义的。
(这适用于 __cdecl,它往往是 VC++ 和 __stdcall 函数声明的默认值)。
这是因为它使用参数的相对位置来查找它们对应的参数。
它可以使用类型来识别未给出可选参数。但是隐式转换可能会干扰它。另一个问题是编程错误,可以解释为可选参数丢失而不是缺少参数错误。
为了允许任何参数成为可选参数,应该有一种方法来识别参数以确保没有编程错误或消除歧义。这在某些语言中是可能的,但在 C++ 中是不可能的。
标准委员会必须考虑的另一件事是默认参数如何与其他功能交互,例如重载函数、模板解析和名称查找。这些功能已经以非常复杂且难以描述的方式相互作用。使默认参数能够出现在任何地方只会增加复杂性。
这是关于呼叫约定的问题。调用约定:调用函数时,参数从右到左压入堆栈。例如
fun(int a, int b, int c);
堆栈是这样的: a b c 所以,如果你像这样从左到右设置默认值:
fun(int a = 1, int b = 2, int c);
并像这样调用:
fun(4,5);
你的电话意味着设置 a = 4, b = 5, and c = no value; // 这是错误的!
如果你像这样声明函数:
fun(int a, int b = 2, int c = 3);
并像这样调用:
fun(4, 5);
你的电话意味着设置 a = 4, b = 5, and c = default value(3); // 哪个是对的!
总之,你应该把默认值从右到左。
景曾是对的。我想在这里补充一下我的意见。调用函数时,参数从右到左压入堆栈。例如,假设您有这个任意函数。
int add(int a, int b) {
int c;
c = a + b;
return c;
}
这是函数的堆栈帧:
------
b
------
a
------
ret
------
c
------
上图就是这个函数的栈帧!如您所见,首先将 b 压入堆栈,然后将 a 压入堆栈。之后,函数返回地址被压入堆栈。函数返回地址保存 main() 中最初调用函数的位置,在函数执行完成后,程序的执行将转到该函数的返回地址。然后任何局部变量,例如 c 被压入堆栈。
现在关键是参数从右到左压入堆栈。基本上,提供的任何默认参数都是文字值,它们存储在可执行文件的代码部分中。当程序执行遇到一个没有相应参数的默认参数时,它会将这个字面值压入栈顶。然后它查看 a 并将参数的值压入栈顶。堆栈指针始终指向堆栈顶部,即您最近推送的变量。因此,您作为默认参数压入堆栈的任何文字值都位于堆栈指针的“后面”。
编译器首先快速将任意默认文字值首先推送到堆栈上可能更有效,因为它们没有存储在内存位置中,并且快速构建堆栈。想想如果变量先被压入堆栈,然后是字面量,会发生什么。与从电路或 CPU 寄存器中提取文字值相比,访问 CPU 的内存位置需要相对较长的时间。由于将变量压入堆栈与文字相比需要更多时间,因此文字必须等待,然后返回地址必须等待,局部变量也必须等待。效率上可能不是一个大问题,但这只是我的理论,为什么默认参数总是在 C++ 中函数头的最右边位置。