在C++中按引用和按值传递的区别

43
我想要澄清按值传递和按引用传递之间的区别。我画了一张图,如下所示:Enter image description here因此,通过按值传递,将创建一个相同对象的副本,并分配新的引用,使局部变量指向新的副本。如果函数修改该值,则无论是按值传递还是按引用传递,都会在调用函数的作用域内看到这些修改。

请参阅http://img363.imageshack.us/img363/7202/passbyvaluereferencehg1.jpg - Flame
5个回答

68

我认为许多混淆是由于没有明确表达“按引用传递”的含义所致。当有些人说“按引用传递”时,他们通常指的不是参数本身,而是被引用的对象。其他一些人则认为“按引用传递”意味着在被调用者中不能更改该对象。例如:

struct Object {
    int i;
};

void sample(Object* o) { // 1
    o->i++;
}

void sample(Object const& o) { // 2
    // nothing useful here :)
}

void sample(Object & o) { // 3
    o.i++;
}

void sample1(Object o) { // 4
    o.i++;
}

int main() {
    Object obj = { 10 };
    Object const obj_c = { 10 };

    sample(&obj); // calls 1
    sample(obj) // calls 3
    sample(obj_c); // calls 2
    sample1(obj); // calls 4
}

有些人认为1和3是按引用传递,而2则是按值传递。另一些人说除了最后一个之外,所有参数都是按引用传递的,因为对象本身并没有被复制。
我想在这里给出我所谓的“按引用传递”的定义。关于它的概述可以在这里找到:按引用传递和按值传递的区别。第一个和最后一个是按值传递的,中间两个是按引用传递的。
    sample(&obj);
       // yields a `Object*`. Passes a *pointer* to the object by value. 
       // The caller can change the pointer (the parameter), but that 
       // won't change the temporary pointer created on the call side (the argument). 

    sample(obj)
       // passes the object by *reference*. It denotes the object itself. The callee
       // has got a reference parameter.

    sample(obj_c);
       // also passes *by reference*. the reference parameter references the
       // same object like the argument expression. 

    sample1(obj);
       // pass by value. The parameter object denotes a different object than the 
       // one passed in.

我支持以下定义:

如果被调用的函数的相应参数具有引用类型并且引用参数直接绑定到参数表达式(8.5.3/4),则仅当参数(1.3.1)按引用传递。在所有其他情况下,我们必须使用按值传递。

这意味着以下内容是按值传递的:
void f1(Object const& o);
f1(Object()); // 1

void f2(int const& i);
f2(42); // 2

void f3(Object o);
f3(Object());     // 3
Object o1; f3(o1); // 4

void f4(Object *o);
Object o1; f4(&o1); // 5

1是按值传递,因为它没有直接绑定。实现可能会复制临时变量,然后将该临时变量绑定到引用上。2是按值传递,因为实现会初始化一个字面量的临时变量,然后将其绑定到引用上。3是按值传递,因为参数没有引用类型。4由于同样的原因也是按值传递。5是按值传递,因为参数没有引用类型。根据8.5.3/4和其他规则,以下情况是按引用传递:

void f1(Object *& op);
Object a; Object *op1 = &a; f1(op1); // 1

void f2(Object const& op);
Object b; f2(b); // 2

struct A { };
struct B { operator A&() { static A a; return a; } };
void f3(A &);
B b; f3(b); // passes the static a by reference

1
能否解释一下,“引用参数直接与实参表达式绑定”的含义? - yesraaj
如果参数类型与参数不同或不是参数的派生类,并且没有转换运算符转换为参数类型,则参数表达式不会直接绑定到引用参数。 - Johannes Schaub - litb
如果参数是右值(例如示例中的整数字面量42),您可以在标准中找到详细的定义。 - Johannes Schaub - litb
在 "void f1(Object const& o); f1(Object());" 中,为什么实现允许复制临时对象? - Iraimbilanja
Iraimbilanja,因为标准规定如此(请参阅8.5.3p5)。实现允许创建副本,但不一定要这样做。事实上,当将rvalue(Object())传递给Object的复制构造函数时,它不能这样做,以防止无限递归(需要再次复制:p)。 - Johannes Schaub - litb
自从C++11引入了右值引用,如果在参数是引用类型的情况下通过值传递临时对象,这种说法就感觉非常不正确了。更新一下会很棒:D - Drax

11

当按值传递时:

void func(Object o);

然后调用

func(a);

你将在堆栈上构建一个Object,并且在func的实现中,它将被o引用。这可能仍然是浅拷贝(ao的内部可能指向相同的数据),因此a可能会更改。但是,如果oa的深层副本,则a将不会更改。

通过引用传递:

void func2(Object& o);

然后调用。
func2(a);

您将仅提供一种引用a的新方式。 "a"和"o"是同一对象的两个名称。在func2中更改o将使这些更改对调用者可见,调用者通过名称"a"知道该对象。


6
我不确定我是否正确理解了你的问题。它有点不清楚。然而,可能让你感到困惑的是以下内容:
1. 通过引用传递时,将传递到被调用的函数中的是指向同一对象的引用。对对象所做的任何更改都将反映在原始对象中,因此调用者也会看到这些更改。 2. 通过值传递时,将调用复制构造函数。默认的复制构造函数只会进行浅拷贝,因此,如果被调用的函数修改对象中的整数,这将不会被调用函数看到,但如果函数更改由对象内部指针指向的数据结构,则由于浅拷贝,调用者将看到更改。
我可能误解了你的问题,但是我还是想试着回答一下。

2
根据我的理解,这些文字是错误的。正确应该是:“如果函数修改该值,则按引用传递时,修改结果也会在调用函数的作用域内生效;而按值传递时则不会。”

1

我对“如果函数修改了该值,则无论是按值传递还是按引用传递,这些修改都会出现在调用函数的范围内”这句话的理解是,它们是错误的

当按值传递时,在被调用的函数中进行的修改不会在调用函数的范围内。

要么您输入引用的文字有误,要么它们已经从上下文中提取出来,使得看起来是错误的,正确的。

请确保您已正确引用您的来源,如果没有错误,请提供更多包含该语句的源材料文本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接