【问题标题】:Why should I use reference variables at all? [closed]为什么我应该使用引用变量? [关闭]
【发布时间】:2012-10-04 13:51:36
【问题描述】:

作为我的第一门编程语言,我学习了 Java,但由于我转到另一所大学,我现在正在学习 C++。

来自 Java 并学习了 C++ 的基础知识,我阅读了有关引用和引用变量的内容。以及它们有多危险,以及如何小心对待它们等等。

所以在我的脑海中出现了一个简单的问题: 我为什么要费心使用那种复杂的,因此可能会引起问题的东西?

这是值得的,还是只是 RAM 大约 64MB 大的时代的遗物?

由于很多答案都提到了指针:这个概念显然来自石器时代,恕我直言。除了高性能计算,我什至不会碰那些东西。

【问题讨论】:

  • 它们没有指针那么危险。
  • 你应该问自己的是:既然我得到了引用,为什么我需要指针?(顺便说一下,引用和智能指针更像是它们的 Java 对应物比常规指针)。
  • 除了按值调用之外,您在 java 中只有引用。
  • 您能否举个例子说明您认为参考文献是一个坏主意,而您认为其他(并说明什么)更好?
  • 试图通过过滤 Java 理解来学习 C++ 是个坏主意。试着忘记你所知道的关于 Java 的一切。您可能不会意识到 Java 在传递对象时会混淆指针。在 C++ 中,一切都是透明的。

标签: c++ pointers reference


【解决方案1】:

问题与引用本身无关。

问题在于,在 C++ 中,对象生命周期的管理方式与 Java 或其他使用垃圾收集器的运行时环境不同。 C++ 没有标准的内置垃圾收集器。 C++ 对象的生命周期可以是自动的(在本地或全局范围内)或手动的(在堆中显式分配/释放)。

C++ 引用只是对象的简单别名。它对对象生命周期一无所知(为了效率)。程序员必须关心它。一个例外是引用绑定到临时对象的特殊情况。在这种情况下,临时对象的生命周期会延长到绑定引用的生命周期。详情here

引用是 C++ 基本概念的重要组成部分,您无法避免在 90% 的任务中使用它们。否则你必须使用指针,这通常更糟糕:-)

例如,当您需要将对象作为函数参数按引用而不是按值传递时,您可以使用引用:

void f(A copyOfObj);       // Passed by value, f receives copy of passed A instance
void f(A& refToObj);       // Passed by ref, f receives passed A instance itself, modifiable
void f(const A& refToObj); // Passed by const ref, f receives passed A instance itself, non modifiable

【讨论】:

  • 使用引用rather 比使用value 更有效吗?
  • @Acidi,在大多数情况下是的。按值传递对象将导致制作对象副本,这在大对象和/或非平凡对象复制ctor/赋值运算符的情况下可能会很昂贵。通过引用传递对象只会导致复制指针。
  • 不知道生命周期...除非它延长生命周期。
  • 虽然我同意@Rost,但我不确定“通过引用传递对象将导致仅复制指针”这句话。鉴于通常与引用和指针混淆,我认为在这里提及“指针”可能会产生误导。
【解决方案2】:

如果您将大型对象作为函数参数按值传递,可能会出现非常实际的性能下降,通过将它们作为引用传递来解决。此外,如果通过引用而不是值传递对象,则可以修改函数内部的对象。

【解决方案3】:

引用是有限制的指针。引用是访问对象的一种方式,但不是对象本身。如果您的代码使用这些限制是有意义的,那么使用引用而不是指针可以让编译器警告您意外违反它们。

【讨论】:

  • 我不确定引用是否是任何类型的指针...鉴于指针是具有自己的内存地址的对象(与其存储的内存地址分开)并且引用不是另一个变量,但它引用的变量相同。简单地说:使用指针我们有 2 个对象/变量(指针本身和指向的值),而引用只有一个,仅由 2 个不同的名称标识。
【解决方案4】:

由于您刚从 Java 中走出来,我可以想象您遇到了很多 C/C++ 允许您做的危险事情的问题。在我看来,区别就在这里:

  • C/C++ 可以让你做很多强大的危险的的东西。你可以做很多伟大的事情,但也可以在自己的腿上开枪
  • Java 限制以安全的名义可以做的事情

你不喜欢的(“指针/引用是危险的”)正是我喜欢的(“它让我可以做很多事情,如果我搞砸了,那是我的错”)关于 C++。在我看来,这是编程语言品味的问题。

引用是为了避免指针的一些危险,但另一方面,除了明显的快速访问之外,即使是指针也有用途。例如,假设您有一个低级对象,它做了一些有用的工作,并在多个高级对象之间共享。可以出于各种原因共享它(例如,它每次执行有用的工作时都会更新内部状态,因此复制它不是一种选择)。

class HL{
    private:
    LL &object;

    public:
    HL(LL &obj){
        object = obj; // initialization of reference
    }
    // a no-argument constructor is not possible because you HAVE to
    // initialize the reference at object-creation (or you could initialize
    // it to a dummy static object of type LL, but that's a mess)
    void some_routine(){
        ....
        object.useful_work();
        ...
    }
}

在这种情况下使用引用强制内部object的初始化。另一方面,如果对象提供的功能对于高级对象来说只是可选,那么指针就是一种方法:

class HL{
    private:
    LL *object;

    public:
    HL(LL &obj){
        object = &obj; // initialization of reference
    }
    HL(){} // no-argument constructor possible
    void some_routine(){
        ....
        if (object != NULL)
           object->useful_work();
        ...
    }
}

此外,对于引用调用的使用,当您将大结构传递给函数时,这是最有用的,例如vector<int>.

void function(vector <int> a); // call by value, vector copied every time
void function(vector <int> &a); // call by reference, you can modify
                                // the vector in the function
void function(const vector <int> &a); // if you don't want to be able to modify
                                      // it, you can add const for "protection"

【讨论】:

    【解决方案5】:

    实际的问题是“为什么使用引用而不是指针”。

    我还没有完全弄清楚。目前,我使用引用

    • 因为你不能分配给他们。最坏的情况,这对编译器没有任何意义,但编译器有机会利用这些知识。

    • 通常当我想返回一个始终存在的指针,或者指示一个参数是必需的并且可能不是空指针(没有空引用)时。这不是硬性规定;有时,实际的指针感觉更自然。

    • 当事情只是要求引用时,比如运算符重载。

    一般来说,我最终会使用很多引用,因为我通常不想复制对象,除非我真的需要一个实际的副本,而且引用通常会起作用。

    【讨论】:

    • +1 for,表示需要一个参数,表示返回的对象不能为null。
    【解决方案6】:

    引用最频繁的使用是避免昂贵的深拷贝; 默认情况下,C++ 具有值语义,如果您编写如下内容:

    void f( std::vector<double> d );
    

    每次调用f 时都会复制整个向量。所以你写 类似:

    void f( std::vector<double> const& d );
    

    其中一个特别重要的地方是定义复制构造函数时。如果你要写:

    MyType( MyType other );
    

    ,你最终会得到无限递归,因为编译器会 必须调用复制构造函数才能将参数复制到此构造函数。 (其实编译器只会接受一个构造函数 它引用了复制构造函数,正是为了避免这种情况 问题。)

    在构造函数之外,这确实是一种优化,可以 被认为是过早优化。然而在实践中,几乎 通用约定是通过引用 const 来传递类类型, 而不是按价值。

    引用也可以用作输出参数;与指针相比, 它们的优点是它们不能为空。

    引用也可以用来为类数据提供一个“窗口”。 经典的例子是operator[]std::vector中的重载; 它返回一个引用,以便您可以修改、获取地址等。 数组中的实际元素。

    除了作为参数和返回值,在上述情况下, 参考文献很少见。如果你使用一个作为班级成员,对于 例如,你必须知道你不能给类经典 赋值语义。 (如果班级没有,这不是问题 支持赋值。)作为全局变量,它们可能用于隐藏 所指内容的实施,但这非常 稀有的。作为一个局部变量,我知道的唯一用途是缓存 另一个返回引用的表达式的结果:例如,如果 我在很短的时间内多次访问someMap[aKey] 代码,写起来很有意义:

    ValueType& element = someMap[aKey];
    

    一开始,然后使用element。 (请注意,这只会使 有意义,因为 someMap[aKey] 返回一个引用。)

    【讨论】:

      【解决方案7】:

      引用使引用调用更容易,因为调用者在传递对象时不必使用&amp; 运算符。

      像这样:

      MyObject ob;
      
      void foo_ref(MyObject& o){ ...}
      void foo_ptr(Myobject* o){ ...}
      
      //call
      
      foo_ref(ob);
      foo_ptr(&ob);
      

      此外,它们可以在函数中初始化指针:

      MyObject* ob = nullptr;
      MySubject* sub = nullptr;
      
      void foo_init(MyObject *& o, MySubject *& s) {
      
         o = new MyObject;
         s = new MySubject;
      }
      
      //call
      
      foo_init(ob, sub);
      

      如果没有对指针的引用,此示例只能使用指向指针的指针,这会使代码看起来很糟糕,因为您必须首先取消对每个参数的引用

      MyObject* ob = nullptr;
      MySubject* sub = nullptr;
      
      void foo_init(MyObject ** o, MySubject ** s) {
      
         *o = new MyObject;
         *s = new MySubject;
      }
      
      //call
      
      foo_init(&ob, &sub);
      

      【讨论】:

      • 引用使得按引用调用成为可能,因为您不能将任意对象的地址传递给函数。
      【解决方案8】:

      例如,因为它们会强制您进行初始化,这会降低它们的危险性

      【讨论】:

      • 这看起来更像是评论..
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-05
      • 1970-01-01
      • 1970-01-01
      • 2016-06-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多