class mystring {
friend ostream& operator<<(ostream &out, const mystring ss) {
out << ss.s;
return out;
}
private:
string s;
public:
mystring(const char ss[]) {
cout << "constructing mystring : " << ss << endl;
s = ss;
}
};
void outputStringByRef(const mystring &ss) {
cout << "outputString(const string& ) " << ss << endl;
}
void outputStringByVal(const mystring ss) {
cout << "outputString(const string ) " << ss << endl;
}
int main(void) {
outputStringByRef("string by reference");
outputStringByVal("string by value");
outputStringByRef(mystring("string by reference explict call mystring consructor"));
outputStringByVal(mystring("string by value explict call mystring constructor"));
} ///:~
考虑到上面的例子,我们不能修改pass-by-reference变量,也不能修改pass-by-value变量。每种方法的输出都是相同的。因为这两种方法没有区别,为什么C ++支持这两种方法?
感谢。
答案 0 :(得分:20)
两者之间存在差异。请考虑以下事项:
#include <iostream>
#include <string>
using std::string;
string g_value;
void callback() {
g_value = "blue";
}
void ProcessStringByRef(const string &s) {
callback();
std::cout << s << "\n";
}
void ProcessStringByValue(const string s) {
callback();
std::cout << s << "\n";
}
int main() {
g_value = "red";
ProcessStringByValue(g_value);
g_value = "red";
ProcessStringByRef(g_value);
}
输出:
red
blue
仅仅因为引用是函数内的const,并不意味着不能通过其他引用修改引用(具有多个引用或指向它的指针的情况称为“别名”)。因此,传递const引用和传递const值之间存在不同 - 在引用的情况下,对象可能在调用之后发生更改。在值的情况下,被调用者具有私有副本,该副本不会更改。
由于他们做了不同的事情,C ++允许你选择你想要的东西。
无论哪种方式都会对绩效产生影响 - 当您通过价值时,必须制作副本,这需要付出相应的代价。但是编译器然后知道只有你的函数可能有对该副本的任何引用,这可能允许其他优化。在返回callback()
之前,ProcessStringByRef无法加载字符串的内容以进行打印。如果编译器认为这样做更快,ProcessStringByValue可以。
通常你关心副本,而不是指令的执行顺序,因为副本通常更昂贵。通常情况下,尽可能通过引用传递非常重要的对象进行复制。但是,即使实际上没有出现混叠,通过阻止某些优化,混叠的可能性有时会对性能产生严重影响。这就是为什么存在“严格别名规则”以及C99中的restrict
关键字。
答案 1 :(得分:8)
f(const string&)
接受const
引用的字符串:f
直接在引用传递的字符串对象上运行:不涉及复制。 const
会阻止对原始对象的修改。
f(const string)
采用字符串值,这意味着f
会获得原始字符串的副本。即使您删除const
,当按值传递时,f
返回时对字符串的任何修改都会丢失。
我不清楚你的意思是“为什么C ++支持这两种方法?”。这只是适用的一般重载规则。
答案 2 :(得分:6)
f(string s)
传递字符串s的值,换句话说,它创建一个副本并使用您传递的字符串的值对其进行初始化。对副本的任何更改都不会传播到您传递给调用函数的原始字符串。
在f(const string s)
中,const是多余的,因为无论如何都无法改变原始值。
在f(const string& s)
中,不会复制字符串,但会传递对它的引用。这通常在你有一个大对象时完成,所以“pass-by-value”会产生开销(这就是c ++支持这两种方法的原因)。通过引用传递意味着您可以更改传递的“大”对象的值,但由于const说明符,您无法修改它。这是一种“保护”。
答案 3 :(得分:3)
您的对象可以包含mutable成员,即使使用const引用
也可以修改该成员答案 4 :(得分:1)
使用outputStringByRef
时,必须确保传递引用的变量保持活动状态,并且只要outputStringByRef
需要它就保持不变。使用outputStringByVal
您传递的变量可能会死亡或超出范围,而该函数的副本仍然可以。
答案 5 :(得分:1)
在能够修改函数中的字符串方面(几乎)没有区别。然而,在传递的内容方面存在很大差异。 const mystring &ss
重载对字符串进行const引用。虽然它无法修改它,但它是相同的内存。如果字符串很长,这可能是一个很重要的因素(假设字符串未使用copy-on-write实现)。 const mystring ss
表单正在复制字符串,因此将处理不同的内存。
实际上const mystring &ss
表单可以更改字符串,如果使用了const_cast<mystring&>
- 虽然我不建议这样做。
答案 6 :(得分:1)
想象一下,您想打印一个无法复制的对象:
Thread th;
// ...
cout << th; // print out informations...
参考版本不会复制线程,但会使用th
的地址并为其创建别名。另一个版本会在传递值时尝试复制线程 - 但复制对于这样的对象可能没有意义(那么会有一个额外的线程吗?)。
可能会帮助您将复制对象视为克隆对象。在C ++中复制上面的th
并不仅仅意味着拥有与Java相同的对象的另一个句柄 - 它意味着隐式克隆所表示的对象,并拥有它的完整副本。所以问题类似于“为什么Java同时支持Object.clone
和复制引用?” - 两者有不同的目的。
毕竟,还有一个表现问题。您不希望复制每次传递资源饥饿对象的内容。