实际上,Java 的工作方式相同。请允许我解释一下:
Object obj = new Object();
List<Object> list = new LinkedList<Object>();
list.add(obj);
是什么类型的obj
?它是对Object
. 实际的对象在堆上的某个地方浮动——在 Java 中您唯一能做的就是传递对它的引用。您将对该对象的引用传递给列表的add
方法,列表本身就存储了该引用的副本。您可以稍后修改命名引用obj
,而不会影响存储在列表中的该引用的单独副本。(当然,如果您修改对象本身,您可以通过任一引用看到该更改。)
C++ 有更多选择。您可以模拟 Java:
class Object {};
// ...
Object* obj = new Object;
std::list<Object*> list;
list.push_back(obj);
是什么类型的obj
?它是一个指向Object
. 当您将它传递给列表的push_back
方法时,列表本身会存储该指针的副本。这与 Java 具有相同的语义。
但是,如果您从效率的角度考虑…… C++ 指针/Java 引用有多大?4 字节或 8 字节,具体取决于您的架构。如果您关心的对象大约是那个大小或更小,为什么还要费心把它放在堆上,然后到处传递指向它的指针呢?只需传递对象:
class Object {};
// ...
Object obj;
std::list<Object> list;
list.push_back(obj);
现在,obj
是一个实际的对象。您将它传递给列表的push_back
方法,该方法本身存储该对象的副本。在某种程度上,这是一个 C++ 习语。它不仅对指针是纯开销的小对象有意义,而且在非 GC 语言中它也使事情变得更容易(堆上没有任何可能意外泄漏的东西),并且如果对象的生命周期自然绑定到列表中(即,如果它从列表中删除,那么在语义上它应该不再存在),那么您不妨将整个对象存储在列表中。它还具有缓存局部性优势(std::vector
无论如何,当在 中使用时)。
你可能会问,“那为什么push_back
要使用引用参数呢?” 有一个足够简单的理由。每个参数都按值传递(同样,在 C++ 和 Java 中)。如果你有一个std::list
of Object*
,那很好——你传入你的指针,然后复制那个指针并传递给push_back
函数。然后,在该函数内部,制作该指针的另一个副本并将其存储到容器中。
这对指针来说很好。但是在 C++ 中,复制对象可以任意复杂。复制构造函数可以做任何事情。在某些情况下,将对象复制两次(一次复制到函数中,再复制到容器中)可能会导致性能问题。所以push_back
通过 const 引用获取它的参数——它从原始对象直接复制到容器中。