9
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"));
} ///:~

考虑到上面的例子,我们不能修改传递引用变量,也不能修改传递值变量。每种方法的输出都是一样的。既然这两种方法没有区别,为什么C++两种方法都支持?

谢谢。

4

7 回答 7

25

两者是有区别的。考虑以下:

#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++ 让你选择你想要的。

无论哪种方式都会对性能产生影响 - 当您通过价值传递时,必须制作副本,这会产生成本。但是编译器然后知道只有你的函数可能有对该副本的任何引用,这可能允许其他优化。ProcessStringByRef 在返回之前无法加载要打印的字符串内容callback()。ProcessStringByValue 可以,如果编译器认为这样做更快。

通常你关心的是副本,而不是指令的执行顺序,因为通常副本要贵得多。因此,通常,您尽可能通过引用传递非平凡的对象。但是混叠的可能性有时会对性能产生非常严重的影响,即使实际上没有混叠发生,也会阻止某些优化。这就是为什么存在“严格的别名规则”以及restrictC99 中的关键字。

于 2009-12-25T13:06:13.180 回答
9

f(const string&)const通过引用获取字符串:f直接对通过引用传递的字符串对象进行操作:不涉及复制。const但是可以防止修改原始对象。

f(const string)接受一个字符串值,这意味着f给定一个原始字符串的副本。即使你 drop const,当按值传递时,f返回时对字符串的任何修改都会丢失。

我不知道您所说的“为什么 C++ 支持这两种方法?”是什么意思。它只是适用的一般重载规则。

于 2009-12-25T11:40:23.217 回答
7

f(string s)按值传递字符串 s,换句话说,它创建一个副本并使用您传递的字符串的值对其进行初始化。对副本的任何更改都不会传播到您传递给调用函数的原始字符串。In f(const string s)const 是多余的,因为无论如何您都无法更改原始值。

相反f(const string& s),字符串不会被复制,但您传递对它的引用。这通常在您有一个大对象时完成,因此“按值传递”会产生开销(这就是 c++ 支持这两种方法的原因)。通过引用传递意味着您可以更改您传递的“大”对象的值,但由于 const 说明符,您无法修改它。这是一种“保护”。

于 2009-12-25T12:02:54.507 回答
3

您的对象可能包含一个可变成员,即使使用 const 引用也可以对其进行修改

于 2009-12-25T11:54:35.413 回答
1

outputStringByRef您必须确保您传递引用的变量在需要时保持活动和不变outputStringByRef。使用outputStringByVal您传递的变量可能会死亡或超出范围,并且该函数具有的副本仍然可以。

于 2009-12-25T11:39:26.503 回答
1

在能够修改函数中的字符串方面(几乎)没有区别。但是,在传递的内容方面存在很大差异。重载采用对字符串的const mystring &ssconst 引用。虽然它不能修改它,但它是被寻址的同一个内存。如果字符串很长,这可能是一个很大的因素(假设字符串不是使用copy-on-write实现的)。该const mystring ss表单正在制作字符串的副本,因此将寻址不同的内存。

实际上,如果使用 a ,const mystring &ss表单可以更改字符串const_cast<mystring&>- 尽管我不建议在这里这样做。

于 2009-12-25T12:04:14.597 回答
1

想象一下,您要打印一个无法复制的对象:

Thread th;
// ...
cout << th; // print out informations...

参考版本不会复制线程,但会获取它的地址th并为其创建一个别名。另一个版本会在按值传递时尝试复制线程 - 但复制对于这样的对象可能没有意义(那么会有一个额外的线程吗?)。

将复制对象视为克隆对象可能对您有所帮助。在 C++ 中复制th上述内容不仅仅意味着拥有与 Java 中相同的对象的另一个句柄 - 它确实意味着隐式克隆所表示的对象,并拥有它的完整副本。所以这个问题类似于“为什么 Java 支持Object.clone引用的复制和复制?” - 两者都有不同的目的。

毕竟,还有性能问题。您不想每次为资源匮乏的对象传递一些东西时都进行复制。

于 2009-12-25T13:05:13.243 回答