为什么默认参数应该最后添加到C++;功能? P>为什么在C++函数中添加默认参数?如果定义以下函数:< P>:< /P> void foo( int a, int b = 0, int c ); ------ b ------ a ------ ret ------ c ------

为什么默认参数应该最后添加到C++;功能? P>为什么在C++函数中添加默认参数?如果定义以下函数:< P>:< /P> void foo( int a, int b = 0, int c ); ------ b ------ a ------ ret ------ c ------,c++,function,default,calling-convention,C++,Function,Default,Calling Convention,如何调用函数并为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, fals

如何调用函数并为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);
foo(, 3);
fun(int a = 1, int b = 2, int c);
fun(4,5);

编译器应该如何确定我要为哪些参数提供值?

以简化语言定义并保持代码可读性

void foo(int x = 2, int y);
要调用该函数并利用默认值,您需要如下语法:

testFunction(true, false);
foo(, 3);
fun(int a = 1, int b = 2, int c);
fun(4,5);
这可能让人觉得太奇怪了。另一种选择是在参数列表中指定名称:

foo(y : 3);
必须使用新符号,因为这已经意味着:

foo(y = 3); // assign 3 to y and then pass y to foo.
命名方法被ISO委员会考虑并拒绝,因为他们不喜欢在函数定义之外引入参数名称的新意义


如果您对更多C++设计原理感兴趣,请阅读Stroustrup。

作为一般规则,函数参数由编译器处理,并按右至左顺序放置在堆栈上。因此,应该首先计算具有默认值的任何参数


(这适用于u cdecl,它往往是VC++和u stdcall函数声明的默认值)。

这是因为它使用参数的相对位置来查找它们对应的参数

它可以使用这些类型来标识未提供可选参数。但是隐式转换可能会干扰它。另一个问题是编程错误,它可能被解释为可选参数退出,而不是缺少参数错误


为了允许任何参数成为可选的,应该有一种方法来识别参数,以确保没有编程错误或消除歧义。这在某些语言中是可能的,但在C++中没有。

,大多数答案指出,默认参数在参数列表中的任何地方都会增加函数调用的复杂性和模糊性(对于编译器来说,并且对于函数的用户来说可能更重要)。 一个很好的关于C++的事情是,经常有一种方法来做你想要的(即使它不是一个好主意)。如果您想为各种参数位置设置默认参数,几乎可以肯定地通过编写重载来实现这一点,该重载只需返回并调用完全参数化的函数inline:

 int foo( int x, int y);
 int foo( int y) {
     return foo( 0, y);
 }
在这里,你可以看到:

 int foo( int x = 0, int y);

标准委员会必须考虑的另一件事是默认参数如何与其他特征交互,如重载函数、模板解析和名称查找。这些特性以非常复杂且难以描述的方式相互作用。使默认参数能够出现在任何地方只会增加复杂性。

这是一个关于调用约定的问题。 通话约定: 调用函数时,参数在堆栈中从右向左推送。 e、 g

堆栈如下所示: A. B C 因此,如果按如下方式从左到右设置默认值:

testFunction(true, false);
foo(, 3);
fun(int a = 1, int b = 2, int c);
fun(4,5);
这样称呼:

testFunction(true, false);
foo(, 3);
fun(int a = 1, int b = 2, int c);
fun(4,5);
您的呼叫意味着设置a=4、b=5和c=无值;//这是错误的

如果您这样声明函数:

testFunction(true, false);
foo(, 3);
fun(int a = 1, int b = 2, int c);
fun(4,5);
fun(inta,intb=2,intc=3)

这样称呼:
fun(4,5)

您的呼叫意味着设置a=4、b=5和c=默认值(3);//没错


总之,您应该将默认值从右向左放置。

Jing Zeng是正确的。我想在这里补充我的意见。调用函数时,参数从右向左推送到堆栈上。例如,假设你有这个任意函数

int add(int a, int b) {
  int c;
  c = a + b;
  return c;
}
以下是函数的堆栈帧:

void foo( int a, int b = 0, int c );
------
  b
------
  a
------
 ret
------
  c
------
上图是此函数的堆栈框架!如您所见,首先b被推到堆栈上,然后a被推到堆栈上。之后,函数返回地址被推送到堆栈上。函数返回地址保存main()中最初调用函数的位置,函数执行完毕后,程序的执行转到该函数的返回地址。然后将任何局部变量(如c)推送到堆栈上

现在关键是参数从右到左被推到堆栈上。基本上,提供的任何默认参数都是文本值,存储在可执行文件的代码部分。当程序执行遇到没有相应参数的默认参数时,它会将该文本值推送到堆栈顶部。然后它查看并将参数的值推送到堆栈的顶部。堆栈指针始终指向堆栈的顶部,即最近推送的变量。因此,作为默认参数推送到堆栈上的任何文本值都位于堆栈指针的“后面”

编译器首先快速地将任意默认文本值推送到堆栈上可能更有效,因为它们不存储在内存位置,并且快速地构建堆栈。想想如果先将变量推到堆栈上,然后再推到文本上会发生什么。与从电路或CPU寄存器中提取文本值相比,访问CPU的内存位置需要相对较长的时间。由于将变量推送到堆栈上比推送到文本上需要更多的时间,因此文本必须等待,然后返回地址必须等待,而局部变量必须等待