是否通过值/引用定义了实现或行为?

时间:2017-07-25 15:29:29

标签: c++

问:在C ++中,是否通过行为实现严格定义了值传递/引用,您能提供权威引用吗?

我与朋友在C ++中进行了转换值/引用的转换。我们对pass-by-value / reference的定义产生了分歧。我知道传递指向函数的指针仍然是值传递,因为指针的值被复制,并且该复制在函数中使用。随后,取消引用函数中的指针并对其进行变异将修改原始变量。这是出现分歧的地方。

他的立场:仅仅因为指针值被复制并传递给函数,对解除引用的指针执行操作就能够影响原始变量,因此它具有传递引用的行为,将指针传递给一个功能。

我的立场:将指针传递给函数会复制指针的值,函数中的操作可能会影响原始变量;然而,仅仅因为它可能会影响原始行为,这种行为并不构成它被引用传递,因为它是定义这些术语,传值/引用的语言的实现。

引用最高投票答案给出的定义:Language Agnostic

通过参考

  

当参数通过引用传递时,调用者和被调用者对参数使用相同的变量。如果被调用者修改了参数变量,则效果对于调用者的变量是可见的。

传递值

  

当参数按值传递时,调用者和被调用者具有两个独立变量且值相同。如果被调用者修改了参数变量,则调用者看不到该效果。

阅读这些后我仍然有一种模糊的感觉。例如,值/引用引用可以支持我们的任何一个声明。任何人都可以清楚这些定义是否源于行为或实施并提供引用的定义?谢谢!

编辑:我应该更加小心我的词汇量。让我澄清一下我的问题。在质疑传递引用时,我的意思是纯粹谈论& 引用的C ++实现,而是理论。在C ++中,& 传递引用是否为真PBR,因为它不仅可以修改原始值,还可以修改值的内存地址。这导致了这个例子,指针也算作PBR?

void foo(int ** bar){
    *bar = *bar+(sizeof(int*));
    cout<<"Inside:"<<*bar<<endl;
}

int main(){
    int a = 42;
    int* ptrA = &a;
    cout<<"Before"<<ptrA<<endl;
    foo(&ptrA);
    cout<<"After:"<<ptrA<<endl;
}

输出将是After ptrA等于Inside,这意味着该函数不仅可以修改a,还可以修改ptrA。因此,它是否将参数调用定义为一种理论:不仅能够修改值,还能修改值的内存地址。对于复杂的例子,我们深表歉意。

4 个答案:

答案 0 :(得分:3)

你在这里谈了很多指针,它们确实在大多数情况下通过值传递,但是你没有提到实际的C ++引用,它们是实际的引用。

int a{};
int& b = a;

// Prints true
std::cout << std::boolalpha << (&b == &a) << std::endl;

如您所见,这两个变量具有相同的地址。简单地说,特别是在这种情况下,引用充当变量的另一个名称。

C ++中的引用很特别。与指针不同,它们不是对象。您不能拥有引用数组,因为它需要引用具有大小。根本不需要参考存储。

那么通过引用实际传递变量呢?

看看这段代码:

void foo(int& i) {
    i++;
}

int main() {
    int i{};

    foo(i);

    // prints 1
    std::cout << i << std::endl;
}

在这种特殊情况下,编译器必须有一种方法来发送引用绑定到哪个变量。实际上,引用不需要任何存储,但它们也不需要也没有。在这种情况下,如果禁用了优化,则编译器很可能使用指针实现引用的行为。

当然,如果启用了优化,它可能会跳过传递并完全内联函数。在这种情况下,引用不存在,或者没有任何存储,因为原始变量将直接使用。

其他类似的优化也发生在指针上,但这不是重点:重点是,实现引用的方式是实现定义的。它们很可能是在指针方面实现的,但它们并不是强制实现的,并且实现引用的方式可能因情况而异。引用的行为由标准定义,实际上是传递引用。

指针怎么样?他们算作参考传递吗?

我会说不。指针是对象,就像intstd::string一样。您甚至可以传递对指针的引用,允许您更改原始指针。

但是,指针确实有引用语义。它们确实不是引用,就像std::reference_wrapper也不是引用一样,但它们具有引用语义。我不会调用传递指针“通过引用传递”,因为你没有实际的引用,但你确实有引用语义。

很多东西都有引用语义,指针,std::reference_wrapper,资源句柄,甚至是GLuint,它们都是opengl对象的句柄,都有引用语义,但它们不是引用。您没有对实际对象的引用,但您可以通过这些句柄更改指向对象。

您可以阅读其他有用的文章和答案。它们都非常有关于价值和参考语义的信息。

答案 1 :(得分:2)

通过值/引用传递(您忘记了一个通过使用指针将地址传递到内存中的位置)是C ++实现的一部分。

  

还有一种方法可以将变量传递给函数,这是通过地址传递的。通过地址传递参数涉及传递参数变量的地址(使用指针)而不是参数变量本身。因为参数是地址,所以函数参数必须是指针。然后,该函数可以取消引用指针以访问或更改指向的值。

看看我一直认为是权威的来源:Passing Arguments by Address

关于在传递值时复制的值,您需要更正。这是C ++中的默认行为。将值传递给函数的优点是,当值传递给函数时,函数不能更改原始值,这可以防止在更改参数值时出现任何不需要的错误和/或副作用。

传递Value的问题是,如果您将整个结构或类多次传递到函数中,则会导致巨大的性能损失,因为您将传递您尝试传递的值的整个副本并且在案例中对于类中的mutator方法,您将无法更改原始值,因此最终会创建您尝试修改的数据的多个副本,因为您将被强制从函数本身返回新值而不是从内存中数据结构所在的位置。这完全没有效率。

您只想在不必更改参数值时传递值。

以下是Passing Arguments by Value主题的一个很好的来源。

现在,您将要使用&#34; Pass by Reference&#34;当您需要在数组,类或结构的情况下更改参数的值时的行为。通过将引用传递给数据结构所在的内存中的位置来更改数据结构的值更有效。这样做的好处是你不必从函数中返回新值,而是函数可以直接更改你在内存中所提供的引用值。

请点击此处详细了解Passing an Argument by Reference

编辑:关于在使用指针时是否通过引用或值传递非const的问题,我觉得答案很清楚。当使用指向非const的指针时,它既不是。将指针作为参数传递给函数时,实际上是&#34;传递值&#34;将ADDRESS放入函数中,并且由于它是非const所在的内存中位置的ADDRESS的副本,因此您可以更改该位置的数据值而不是指针本身的值。如果你不想将位于由值传递的指针所指向的地址的数据的值作为参数更改为函数,那么将指向参数的指针设置为const是一种很好的形式,因为函数不会正在改变数据本身的价值。

希望这是有道理的。

答案 2 :(得分:1)

引用与指针不同。引入引用的主要原因是支持运算符重载。 C ++派生自C,在此过程中,Pointers继承自C.正如Stroustrup所说: C ++继承了C的指针,因此我无法在不引起严重兼容性问题的情况下将其删除。

因此,实际上有三种不同的参数传递方式:

  • 按值传递
  • 通过引用传递&amp;
  • 通过指针传递。

现在,传递指针与通过引用传递具有相同的效果。那么如何决定你想要使用什么?回到Stroustrup所说的话:

这取决于您要实现的目标: 如果要更改传递的对象,请通过引用调用或使用指针;例如void f(X&amp;);或无效f(X *); 如果你不想改变传递的对象并且它很大,请通过const引用调用;例如void f(const X&amp;); 否则,按值调用;例如void f(X);

参考:http://www.stroustrup.com/bs_faq2.html#pointers-and-references

答案 3 :(得分:1)

这些术语是关于传递的变量,在本例中是指针。如果将指针传递给函数,那么传递的变量就是指针 - 保持对象的地址 - 指向一个对象而不是它指向的对象。

如果按值传递指针,那么chaning它指向函数的对象不会影响传递给函数的指针。

如果通过引用传递指针,则可以更改指针指向的函数,并修改传递给此函数的指针。

这是如何定义的。否则你可能会争辩说,如果你有一个全局std::map<int,SomeObject>并且你传递一个int作为该对象的关键字,那么它也是一个引用传递,因为你可以修改该全局地图中的对象,以及来电者会看到这些变化。因为这个int也只是指向对象的指针。