我来自Java背景,已经开始使用C++中的对象。但我想到的一件事是,人们经常使用指向对象的指针,而不是对象本身,例如这样的声明:

Object *myObject = new Object;

而不是:

Object myObject;

或者不用函数,让我们说testFunc(),如下所示:

myObject.testFunc();

我们必须写下:

myObject->testFunc();

但我不明白我们为什么要这样做。我认为这与效率和速度有关,因为我们可以直接访问内存地址。我说得对吗?


当前回答

指针直接引用对象的内存位置。Java没有这样的东西。Java具有通过哈希表引用对象位置的引用。使用这些引用,您无法在Java中执行类似指针算术的操作。

回答你的问题,这只是你的喜好。我更喜欢使用类似Java的语法。

其他回答

从技术上讲,这是一个内存分配问题,但这里有两个更实际的方面。这与两件事有关:1) 作用域,当您定义一个没有指针的对象时,在代码块中定义该对象后,您将无法再访问该对象,而如果您使用“new”定义指针,则您可以从具有指向该内存的指针的任何位置访问它,直到您对同一指针调用“delete”。2) 如果要向函数传递参数,则需要传递指针或引用以提高效率。当您传递一个对象时,该对象将被复制,如果这是一个使用大量内存的对象,这可能会占用CPU(例如,您复制了一个充满数据的向量)。当您传递一个指针时,您传递的是一个int(取决于实现,但大多数是一个整数)。

除此之外,您还需要了解“new”在堆上分配了需要在某个时候释放的内存。当您不必使用“new”时,我建议您使用“堆栈上”的常规对象定义。

C++提供了三种传递对象的方法:通过指针、引用和值。Java限制您使用后一种类型(唯一的例外是int、boolean等原始类型)。如果你想使用C++而不仅仅是一个奇怪的玩具,那么你最好了解这三种方式之间的区别。

Java假装不存在“谁和什么时候应该销毁这个?”这样的问题。答案是:《垃圾收集器》,棒极了。然而,它不能提供100%的内存泄漏保护(是的,java可以泄漏内存)。实际上,GC给你一种错误的安全感。你的SUV越大,离撤离者的距离就越长。

C++让您面对面地了解对象的生命周期管理。好吧,有一些方法可以解决这个问题(智能指针家族、Qt中的QObject等等),但它们都不能像GC那样以“火即忘”的方式使用:您应该始终记住内存处理。你不仅应该关心破坏一个物体,还必须避免多次破坏同一个物体。

还不害怕吗?好的:循环引用——你自己处理,人类。记住:每一个对象都要被精确地杀死一次,我们C++运行时不喜欢那些处理尸体的人,只留下死去的人。

所以,回到你的问题。

当您通过值(而不是指针或引用)传递对象时,每次执行“=”操作时,都会复制对象(整个对象,无论是几个字节还是一个巨大的数据库转储-您足够聪明,可以避免后者,不是吗?)。要访问对象的成员,请使用“”(点)。

当通过指针传递对象时,只复制几个字节(32位系统为4个,64位系统为8个),即该对象的地址。为了向所有人展示这一点,当您访问成员时,可以使用这个花哨的“->”运算符。或者可以使用“*”和“.”的组合。

当你使用引用时,你会得到一个伪装成值的指针。这是一个指针,但您可以通过“.”访问成员。

还有,再一次让你大跌眼镜:当你声明几个用逗号分隔的变量时,然后(注意指针):

每个人都有类型值/指针/引用修饰符是单独的

例子:

struct MyStruct
{
    int* someIntPointer, someInt; //here comes the surprise
    MyStruct *somePointer;
    MyStruct &someReference;
};

MyStruct s1; //we allocated an object on stack, not in heap

s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'

s1.someReference.someInt = 5; //now s1.someInt has value '5'
                              //although someReference is not value, it's members are accessed through '.'

MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.

//OK, assume we have '=' defined in MyStruct

s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one

但我不明白为什么我们要这样使用它?

如果您使用:

Object myObject;

在函数内部,此函数返回后,myObject将被销毁。因此,如果您不需要函数外的对象,这很有用。此对象将放在当前线程堆栈上。

如果在函数体内部写入:

 Object *myObject = new Object;

那么,一旦函数结束,myObject指向的Object类实例将不会被销毁,并且分配在堆上。

现在,如果您是Java程序员,那么第二个示例更接近于对象分配在Java下的工作方式。此行:Object*myObject=new Object;等效于java:Object myObject=new Object();。不同的是,在javamyObject下,它将被垃圾收集,而在c++下,它不会被释放,您必须在某处显式调用“delete myObject;”否则会导致内存泄漏。

自从c++11以来,您可以通过在shared_ptr/unique_ptr中存储值来使用安全的动态分配方式:新对象。

std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");

// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared"); 

此外,对象通常存储在容器中,如map-s或vector-s,它们将自动管理对象的生命周期。

在内存利用率很高的领域,指针很方便。例如,考虑一个极大极小算法,其中将使用递归例程生成数千个节点,然后使用它们来评估游戏中的下一个最佳移动,解除分配或重置(如智能指针)的能力显著减少了内存消耗。而非指针变量继续占用空间,直到其递归调用返回值。

指针直接引用对象的内存位置。Java没有这样的东西。Java具有通过哈希表引用对象位置的引用。使用这些引用,您无法在Java中执行类似指针算术的操作。

回答你的问题,这只是你的喜好。我更喜欢使用类似Java的语法。