为什么我要使用引用变量呢

Why should I use reference variables at all?

本文关键字:变量 引用 我要 为什么      更新时间:2023-10-16

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

我来自Java,学习C++的基础知识,阅读了关于引用和引用变量的文章。他们有多危险,如何小心对待他们等等

因此,在我的脑海中出现了一个简单的问题:我为什么要麻烦使用这种复杂的、因此可能会引起问题的东西呢?

它在某种程度上值得吗,还是只是RAM大约64MB大的时代的遗迹?

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

问题与引用本身无关。

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

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

引用是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

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

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

由于您刚从Java毕业,我可以想象您在使用C/C++让您做的许多危险的东西时遇到了问题。在我看来,区别在于:

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

你不喜欢的东西("指针/引用很危险")正是我喜欢的("它让我做很多事情,如果我搞砸了,那是我的错")。在我看来,这是一个编程语言品味的问题。

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

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"

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

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

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

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

在定义复制构造函数时,这一点尤为重要。如果你要写:

MyType( MyType other );

,由于编译器会必须调用复制构造函数才能将参数复制到此构造函数。(事实上,编译器只接受构造函数它引用了复制构造函数,正是为了避免这种情况问题。)

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

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

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

除了作为参数和返回值之外,参考文献有些罕见。如果您使用一个作为类成员,对于例如,你必须意识到你不能给这个类上古典赋值语义。(如果课程没有支持任务。)作为全局变量,它们可能用于隐藏所指内容的实现,但这是极其重要的稀有的作为一个局部变量,我所知道的唯一用途是缓存返回引用的另一个表达式的结果:例如,如果我在短块内访问someMap[aKey]多次对于代码,写是有意义的

ValueType& element = someMap[aKey];

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

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

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

  • ,因为您无法分配给它们。最糟糕的情况是,这对编译器来说没有任何意义,但编译器有可能利用这些知识。

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

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

通常,我最终会使用很多引用,因为除非我真的需要一个实际的副本,否则我通常不想复制对象,而引用通常是有效的。

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

像这样:

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);

例如,因为它们迫使您草签,这会降低的危险性