为什么要在C ++函数中最后添加默认参数?

时间:2009-07-14 06:18:33

标签: c++ function default calling-convention

为什么要在C ++函数中最后添加默认参数?

9 个答案:

答案 0 :(得分:29)

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

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++

答案 1 :(得分:21)

如果您定义以下功能:

void foo( int a, int b = 0, int c );

您如何调用该函数并为a和c提供值,但是将b保留为默认值?

foo( 10, ??, 5 );

与其他一些语言(例如Python)不同,C / C ++中的函数参数不能通过名称限定,如下所示:

foo( a = 10, c = 5 );

如果可能,那么默认参数可以是列表中的任何位置。

答案 2 :(得分:5)

想象一下,你有这个原型的功能:

void testFunction(bool a = false, bool b = true, bool c);

现在假设我调用了这样的函数:

testFunction(true, false);

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

答案 3 :(得分:3)

正如大多数答案所指出的那样,在参数列表中的任何位置都有默认参数会增加函数调用的复杂性和模糊性(对于编译器而言,可能更重要的是对于函数的用户而言)。

关于C ++的一个好处是,通常有一种方法可以做你想做的事情(即使它并不总是一个好主意)。如果你想拥有各种参数位置的默认参数,你几乎可以肯定这样做是通过编写简单转向的重载并内联调用完全参数化的函数来实现的:

 int foo( int x, int y);
 int foo( int y) {
     return foo( 0, y);
 }

你有相同的:

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

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

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

答案 6 :(得分:1)

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

答案 7 :(得分:0)

这是关于呼叫约定的问题。 呼叫大会: 调用函数时,参数将从右向左推入堆栈。 e.g。

fun(int a, int b, int c);

堆栈是这样的:                        一个                        b                        C 所以,如果你从左到右设置默认值,如下所示:

fun(int a = 1, int b = 2, int c);

并且这样打电话:

fun(4,5);

你的通话意味着设置a = 4,b = 5,c =无值; //这是错的!

如果你声明这样的函数:

fun(int a, int b = 2, int c = 3);

并像这样打电话: fun(4, 5);

你的通话意味着设置a = 4,b = 5,c =默认值(3); //哪个是对的!

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

答案 8 :(得分:0)

Jing Zeng是对的。我想在此发言。调用函数时,参数将从右向左推入堆栈。例如,假设你有这个任意函数。

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 ++中函数头的最右边位置。这意味着编译器就是这样设计的。