-4

我知道在 Java 中,当您将一个类分配给一个变量(例如。ArrayList<Integer> list = new ArrayList<>())时,该变量是对该对象的引用。在 C++ 中,如果我写vector<int> list,这也是参考吗?但是如果它是一个引用,为什么当我将一个对象传递给一个函数时,它又被复制了一次?里面究竟存储了list什么?

当我返回一个对象(例如,带有函数头vector<int> make_vector(){...})时,我是否返回了对本地对象的新克隆的引用?

4

2 回答 2

4

与 Java 相比,我会说“C++ 具有不受约束的变量”。C++中的变量可以是对象,任何对象类型都可以是变量的类型。

而且,C++有对象的变量(即引用类型的变量),并不是所有的对象都是变量(即动态创建的对象)。

就像在 Java 中一样,C++ 中的变量是有范围的(或者更确切地说,是有范围的变量的名称)。但是,当变量对象时,对象本身与变量具有相同的范围,这赋予了 C++ 的表现力和能力,因为您可以像管理变量的范围一样轻松地管理大多数对象的生命周期。


相比之下,除了基本类型之外,Java 中的对象永远不是变量,变量也永远不是对象。相反,变量是可能(或可能不)处理对象的不透明“句柄”。然而,对象本身仍然是无形的,它的生命周期是不确定的。


至于调用函数:您并没有真正将变量传递给函数。相反,您评估函数调用表达式,这反过来又需要评估函数参数表达式。表达式具有值,而值始终是对象。然而,函数值可以绑定到对象或引用变量(即形式函数参数),并且分别复制或不复制对象。在Java中,由于只有一种变量,你总是将不透明的对象句柄作为函数参数传递,并且句柄被复制,即你最终得到两个句柄,两个句柄都用于同一个对象(或者没有句柄,如果句柄的值为null)。


我不太确定 Java 变量的正确术语是什么。我想称它们为“引用”,尽管如果您同时大量谈论 C++ 和 Java,那会有些混乱。从小使用 C 这样的语言长大的人可能喜欢称它们为“指针”(因为它们的行为很像 C 和 C++ 中的指针),但这在 Java 中并没有本质上的任何含义。(尽管出于某种原因,Java 有一个名为“Null 指针异常”的异常,但是对于只知道 Java 而一无所知的人来说,这个名称将非常难以解释。)

于 2013-11-11T00:17:43.880 回答
0

C++ 静态/线程/自动/动态存储持续时间对象中有四种类型的变量。但为了保持讨论简单,让我们集中讨论两个最常见的(自动/动态)。

自动变量。

它们在声明时创建,并在超出范围时销毁。

void func1();
{
    MyClass x;  // Creates an object of MyClass.

    // DoStuff
} // x is destroyed because it goes out of scope.

相同的规则适用于对象中的变量。不同之处在于范围是包含它们的对象的生命周期。

class AnotherClass
{
    MyClass   member1;
    MyClass   member2;
};

void test()
{
    AnotherClass   ac; // The object ac is created here.
                       // The members (member1 and member2) are created at the same time.
                       // and they live as long as the object ac lives.
} // Everything destroyed here.

// If we dynanically allocate ac (see below).
// then the members will live until the parent object is destroyed.
// Which happens when you call delete.

动态变量。

这些是通过使用关键字创建的,new并且在使用关键字明确删除它们之前一直存在delete

void func2()
{
    MyClass& y = new MyClass();   // Creates a dynamic object
                                  // This will live until it is explicitly deleted.

} // You forgot to call delete the object still exists but there is no variable
  // pointing at it (so we have lost all references and it is not leaked).

直接使用动态变量并不常见;在大多数情况下,您将它们包装在会自动调用delete您的智能指针中。最简单的智能指针是std::shared_ptr. 它基本上是一个指针周围的引用计数包装器。因此,当您在引用计数周围复制它时,随着变量被破坏,计数会增加,当它达到零时,它会被破坏。

void func3()
{
    std::shared_ptr<MyClass>  z  = new MyClass(); // This is the closes we have to a Java variable
                                                  // It is reference counted.
                                                  // When no more values exist then it is deleted.

    std::shared_ptr<MyClass>  a  = z;             // a and z point (refer) to the same object.
 }
 // The object is destroyed (because both a and z are automatic variables).
 // At this point they have gone out of scope decrementing the count.
 // The count reached zero and the object was destroyed (with delete)

参考

术语引用的问题在于它在不同语言中重叠并且在两种语言中意味着不同的东西。

在 Java 中,所有变量都是引用(忽略 Java 原始类型)。某处有一个对象(由 new 创建),您可以拥有一堆引用该对象的变量。对于大多数 C/C++ 程序员来说,这听起来就像一个指针。

void myJavaFunc()
{
     MyClass  a   = new MyClass;
     MyClass  b   = a;             // both a and b refer to the same variable.

     //
     b.doStuff();   // Both 'a' and 'b' refer to the same object.
                    // So anything you do via b is visable to 'a'
}

GC 会跟踪对象有多少引用,当它达到零时,它会清理它。

在 C++ 中,引用是对现有对象的另一个名称(它是别名)。一旦创建了引用,就不能更改它正在别名的对象。

void MyCPPFunc()
{
     MyClass  a;           // Creates a local object.
     MyClass& b = a;       // Creates a reference variable b that is an alias for a.

     //
     b.doStuff();   // Both 'a' and 'b' refer to the same object.
                    // So anything you do via b is visable to 'a'
 }

差异。
在 C++ 中变量和引用不能为 NULL。
在 C++ 中,引用不能被重新定位(指向另一个对象)。

笔记。在 C++ 中,指针可以为 NULL(但不是对象)。

回到问题:

问题一:

如果我写向量列表,这也是参考吗?但是如果它是一个引用,为什么当我将一个对象传递给一个函数时,它又被复制了一次?列表中究竟存储了什么?

vector<int>    list;      // This creates an object in the local scope.

vector<int>&   A = list;  // This creates a reference to the list object.
vector<int>    B = list;  // This creates a new object B that is a copy of list.

当您调用函数时,您可以使用对象或引用作为参数类型。

 void myDoStuffOne(vector<int>  p1)
 {
      // Here p1 is an object.
      // because it is an object it is created as a copy of the parameter
      // that was passed at the call point.
      //
      // Since they are different objects manipulating p1 does not change
      // the value of the original variable.
 }

 void myDoStuffTwo(vector<int>&  p1)
 {
      // Here p1 is a reference.
      // because it is an reference it is alias of the parameter that was passed.
      // at the call point. So it refers to the same object.
      //
      // Any action on this object is the same as manipulating the original
      // object and you can see the difference when the function returns.
 }

 // also worth noting are const reference parameters.
 void myDoStuffThress(vector<int> const&  p1)
 {
      // Here p1 is a const reference.
      // Just like a normal reference except the function is not allowed to 
      // mutate the state of the object (also useful for passing temporary objects).
 }

问题2:

当我返回一个对象(例如,使用函数头向量 make_vector(){...})时,我是否返回了对本地对象的新克隆的引用?

所以像这样的函数:

 std::vector<int> make_vector()
 {
       std::vector<int>   result;

       // fill vector in some way.

       return result;
 }

当您按值返回对象时,您必须将对象复制出函数。所以从技术上讲,是的,应该制作一份副本。(请注意,如果您要返回本地对象,则必须按值返回。如果要返回对象的成员,则可以通过引用(或 const 引用)返回)。

实际上是复制品。答案总是否定的。

在 C++03 中,编译器经过高度优化并消除了返回副本(技术称为 RVO 和 NRVO 优化)。生成的对象是在函数外部需要它的地方创建的。这在 99% 的时间内都有效(由统计数据组成,但效率很高)。

在 C++11 中添加了一项新技术,可以解决剩下的 1% 的问题。当对象按值返回时,其内容不会被复制而是被移动(这通常涉及复制几个指针,而且通常非常简单)。

于 2013-11-11T01:43:40.667 回答