对象变量如何在C ++中工作?

时间:2013-11-11 00:11:04

标签: c++ object

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

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

2 个答案:

答案 0 :(得分:4)

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

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

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


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


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


我不确定Java变量的正确术语是什么。我想把它们称为“引用”,尽管如果你同时谈论C ++和Java,那有点令人困惑。使用像C这样的语言长大的人可能会把它们称为“指针”(因为它们的行为与C和C ++中的指针非常相似),但这并不意味着Java内部存在任何内在因素。 (虽然由于某种原因,Java有一个称为“空指针异常”的异常,但是对于只知道Java而不知道其他任何东西的人来说,这个名称将非常难以解释。)

答案 1 :(得分: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(但不是对象)。

回到问题:

问题1:

  

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

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%的问题。 当按值返回对象时,其内容不会被复制但是被移动(这通常涉及复制几个指针并且通常非常简单)。