作为我的第一门编程语言,我学习了 Java,但由于我转到另一所大学,我现在正在学习 C++。
来自 Java 并学习了 C++ 的基础知识,我阅读了有关引用和引用变量的内容。以及它们有多危险,以及如何小心对待它们等等。
所以在我的脑海中出现了一个简单的问题:我为什么要费心使用那种复杂的,因此可能会导致问题的东西呢?
它是否值得,或者只是 RAM 大约 64MB 大的时代的遗物?
由于很多答案都提到了指针:这个概念显然来自石器时代,恕我直言。除了高性能计算,我什至不会碰那些东西。
该问题与引用本身无关。
问题在于,在 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++。在我看来,这是一个编程语言品味的问题。
引用是为了避免指针的一些危险,但另一方面,除了明显的快速访问之外,即使是指针也有用途。例如,假设您有一个低级对象,它做了一些有用的工作,并在多个高级对象之间共享。它可以出于各种原因共享(例如,它每次执行有用的工作时都会更新内部状态,因此复制它不是一种选择)。
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();
...
}
}
在这种情况下使用引用会强制执行internal 的初始化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();
...
}
}
Also, as for use of the call-by-reference, that's the most useful when you're passing a big structure to a function, e.g. 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"
实际的问题是“为什么使用引用而不是指针”。
我还没有完全弄清楚。目前,我使用参考
因为你不能分配给他们。最坏的情况,这对编译器没有任何意义,但编译器有机会利用这些知识。
通常当我想返回一个始终存在的指针,或者指示一个参数是必需的并且可能不是 nullptr(没有空引用)时。这不是硬性规定;有时,实际的指针感觉更自然。
当事情只是要求引用时,比如运算符重载。
一般来说,我最终会使用很多引用,因为我通常不想复制对象,除非我真的需要一个实际的副本,而且引用通常有效。
引用最频繁的使用是为了避免昂贵的深拷贝;默认情况下,C++ 具有值语义,如果您编写如下内容:
void f( std::vector<double> d );
每次调用时都会复制整个向量f
。所以你写了这样的东西:
void f( std::vector<double> const& d );
这一点特别重要的一个地方是定义复制构造函数时。如果你要写:
MyType( MyType other );
,您最终会得到无限递归,因为编译器必须调用复制构造函数才能将参数复制到此构造函数。(事实上,编译器只会接受一个引用复制构造函数的构造函数,正是为了避免这个问题。)
在构造函数之外,这实际上是一种优化,可以被认为是过早的优化。然而,在实践中,几乎通用的约定是通过引用 const 来传递类类型,而不是通过值。
引用也可以用作输出参数;与指针相比,它们的优点是它们不能为空。
引用也可用于为类数据提供一个“窗口”。经典的例子是operator[]
in的重载std::vector
;它返回一个引用,以便您可以修改、获取数组中实际元素的地址等。
除了作为参数和返回值之外,在上述情况下,引用很少见。例如,如果你使用一个作为类成员,你必须知道你不能给类经典赋值语义。(如果类不支持赋值,这不是问题。)作为全局变量,它们可能用于隐藏所引用内容的实现,但这非常罕见。作为一个局部变量,我知道的唯一用途是缓存另一个返回引用的表达式的结果:例如,如果我someMap[aKey]
在一小段代码中多次访问,那么写:
ValueType& element = someMap[aKey];
在开始时,然后使用element
。(请注意,这仅有意义,因为someMap[aKey]
返回引用。)
引用使引用调用更容易,因为调用&
者在传递对象时不必使用运算符。
像这样:
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);
例如,因为它们强迫您进行初始化,这使它们的危险性降低