来自C ++ Primer第5版,它说:
int f(int){ /* can write to parameter */}
int f(const int){ /* cannot write to parameter */}
这两个功能难以区分。但是如您所知,这两个函数在更新参数方面确实存在差异。
有人可以向我解释一下吗?
修改
我想我没有很好地解释我的问题。我真正关心的是为什么C ++不允许这两个函数同时作为不同的函数,因为它们在“是否可以写入参数”方面确实不同。直观地说,应该是!
修改
通过将参数值复制到参数值,传递值的性质实际上是传递。即使对于引用和指针,其中复制的值是地址。从调用者的角度来看, const 或非const 是否传递给函数不会影响复制到参数的值(当然还有类型)。
复制对象时,顶级const 和低级const 之间的区别很重要。更具体地说,复制对象时忽略顶级const (不是低级const 的情况),因为复制不会影响复制的对象。复制或复制的对象是否为 const 无关紧要
因此对于呼叫者来说,区分它们是没有必要的。可能,从功能的角度来看,顶级const 参数不会影响接口和/或功能的功能。这两个功能实际上完成了同样的事情。为什么要打扰实施两份?
答案 0 :(得分:10)
允许这两个函数同时作为不同的函数,因为它们在“是否可以写入参数”方面确实不同。直观地说,应该是!
函数重载基于调用者提供的参数。在这里,调用者确实可以提供const
或非const
值,但逻辑上它应该与被调用函数提供的功能没有区别。考虑:
f(3);
int x = 1 + 2;
f(x);
如果f()
在每种情况下做不同的事情,那将会非常混乱!调用f()
的代码的程序员可以合理地期望相同的行为,自由地添加或删除传递参数的变量,而不会使程序无效。这种安全,理智的行为是你想要证明异常的出发点,而且确实存在一个 - 当函数重载ala时,行为可以变化:
void f(const int&) { ... }
void f(int&) { ... }
所以,我猜你发现这是非直观的: C ++为非引用提供了比参考更多的“安全性”(通过仅支持单个实现来强制执行一致的行为)。
我能想到的原因是:
const&
参数将具有更长的生命周期时,他们可以选择最佳实现。例如,在下面的代码中,在T
内返回对F
成员的引用可能会更快,但如果F
是临时的(如果编译器匹配{ {1}})然后需要按值返回。这仍然非常危险,因为调用者必须知道返回的引用只有参数的存在才有效。T f(const F&); T& f(F&); // return type could be by const& if more appropriate
const F&
之类的限定符 - 如:const T& f(const F&); T& f(F&);
此处,const
类型的某些(大概是F
成员 - )变量基于T
被公开为const
或非const
- 调用const
时参数的ness。当希望扩展具有非成员函数的类时(为了保持类的极简主义,或者在编写可用于许多类的模板/ algos时),可以选择这种类型的接口,但这个想法类似于f()
成员函数与const
类似,您希望非vector::operator[]()
向量允许v[0] = 3
,而const
向量不允许{。}}。
当值被值接受时,它们会在函数返回时超出范围,因此没有有效的方案涉及返回对参数的一部分的引用并希望传播其限定符。
根据引用规则,您可以使用它们来获得您想要的行为 - 您只需要注意不要意外地修改by-non-const-reference参数,因此可能需要采用类似的做法以下为非const参数:
const
除了为什么语言禁止基于值T f(F& x_ref)
{
F x = x_ref; // or const F is you won't modify it
...use x for safety...
}
的重载参数的问题之外,还有一个问题是为什么它不坚持const
的一致性 - 声明和定义中的内容。
对于const
/ f(const int)
...如果要在头文件中声明一个函数,那么最好不要包含f(int)
限定符,即使实现中的后一个定义也是如此文件将有它。这是因为在维护期间程序员可能希望删除限定符...将其从标题中删除可能会触发客户端代码的无意义重新编译,因此最好不要坚持它们保持同步 - 事实上这就是编译器没有的原因。如果它们不同,则会产生错误。如果你只是在函数定义中添加或删除const
,那么它就接近于在分析函数行为时代码的读者可能关心const的实现。如果在头文件和实现文件中都有const
,那么程序员希望将其设置为非const
并忘记或决定不更新头文件以避免客户端重新编译,那么它就更危险了因为在尝试分析当前实现代码导致关于函数行为的错误推理时,程序员可能会从头文件中获得const
版本。这是一个非常微妙的维护问题 - 只与商业编程真正相关 - 但这是指南不在界面中使用const
的基础。此外,从界面中省略它更简洁,这对于客户端程序员阅读API更好。
答案 1 :(得分:4)
由于调用者没有区别,并且没有明确的方法来区分对具有顶级const参数的函数的调用和没有调用函数的函数,因此语言规则忽略顶级函数。这意味着这两个
void foo(const int);
void foo(int);
被视为同一声明。如果您要提供两个实现,则会出现多重定义错误。
函数定义与顶级const有所不同。在一个方面,您可以修改参数的副本。在另一方面,你不能。您可以将其视为实现细节。对于来电者来说,没有区别。
// declarations
void foo(int);
void bar(int);
// definitions
void foo(int n)
{
n++;
std::cout << n << std::endl;
}
void bar(const int n)
{
n++; // ERROR!
std::cout << n << std::endl;
}
这类似于以下内容:
void foo()
{
int = 42;
n++;
std::cout << n << std::endl;
}
void bar()
{
const int n = 42;
n++; // ERROR!
std::cout << n << std::endl;
}
答案 2 :(得分:3)
在“C ++编程语言”第四版中,Bjarne Stroustrup写道(§12.1.3):
不幸的是,为了保持C兼容性,在参数类型的最高级别忽略const。例如,这是两个相同函数的声明:
void f(int);
void f(const int);
所以,与其他一些答案相反,似乎没有选择这个C ++规则,因为这两个函数或其他类似理论的不可区分性,而是作为一个不太理想的解决方案,为了兼容性。
实际上,在 D 编程语言中,可能会有这两个重载。然而,与此问题的其他答案可能暗示的相反,如果使用文字调用函数,则首选非常量重载:
void f(int);
void f(const int);
f(42); // calls void f(int);
当然,您应该为重载提供等效的语义,但这不是特定于此重载方案,几乎无法区分的重载函数。
答案 3 :(得分:1)
正如评论所说,在第一个函数内部,如果已经命名,则可以更改参数。它是被调用者int的副本。在第二个函数内部,对参数的任何更改(仍然是被调用者的int的副本)都将导致编译错误。 Const是一个你不会改变变量的承诺。
答案 4 :(得分:1)
一个函数仅从调用者的角度来看是有用的。
由于调用者没有区别,因此这两个函数没有区别。
答案 5 :(得分:0)
我认为无法区分的用于重载和编译器,而不是用于调用者可以区分的术语。
编译器不区分这两个函数,它们的名称以相同的方式被破坏。这导致编译器将这两个声明视为重新定义时的情况。
答案 6 :(得分:0)
回答你问题的这一部分:
我真正关心的是为什么C ++不允许这两个函数同时作为不同的函数,因为它们与“是否可以写入参数”实际上是不同的。直观地说,应该是!
如果你再考虑一下,它根本就没有用 - 实际上,它没有多大意义。正如其他人所说的那样,当函数按值获取参数并且它也不关心时,调用者不会受到影响。
现在,让我们假设重载解析也在顶级const
上运行。像这样的两个声明
int foo(const int);
int foo(int);
会声明两个不同的功能。其中一个问题是这个表达式会调用哪些函数:foo(42)
。语言规则可以说文字是const,并且在这种情况下将调用const“重载”。但那是最不重要的问题。
感觉足够邪恶的程序员可以这样写:
int foo(const int i) { return i*i; }
int foo(int i) { return i*2; }
现在你有两个重载,它们在语义上与调用者等效,但完全不同。现在那会很糟糕。我们可以编写接口来限制用户的工作方式,而不是他们提供的东西。