何时使用 new 和 delete

新手上路,请多包涵

我正在重新阅读前段时间关于 C++ 的一些代码(我现在正在学校学习 Java),我对何时必须使用 delete 感到有些困惑。

例如:声明两个对象时:

 Fraction* f1;
Fraction* f2;

并创建 f1f2 像这样:

 f1 = new Fraction(user_input1, user_input2);
f2 = new Fraction(user_input3, user_input4);

下次我想用 new 操作符创建一个新对象时,我必须先 delete 吗?我很困惑,因为我习惯于让 Java 中的垃圾收集器处理对象及其删除。在再次使用 new delete —?

 if (f1) delete f1;

if (f2) delete f2;

// initialize again...

原文由 qnob 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 370
1 个回答

与其告诉您何时使用 delete ,我将尝试解释您为什么仍然使用指针。因此,您可以决定何时使用动态对象、如何使用它们以及何时调用 delete (而不是)。


经验法则:

  • 尽可能使用静态对象,然后在需要时创建指向该实例的指针。否 delete 需要调用。
  • 如果您创建指向动态对象的指针,请创建清理代码。所以当你写 new 同时写 delete somehwere 在一个合适的位置(并确保它被调用)。
  • 对于每个 new 关键字都 需要 一个 delete 关键字。否则,您将占用机器的所有资源,导致应用程序崩溃或停止。它也会使系统变慢。

静态创建对象:

 Fraction f1;

  • 无需删除任何东西,这是在退出创建它的勺子时处理的。

动态创建对象:

 Fraction* f1;

现在你有了这个地址到堆上的一个内存块。这是一个无效的,因为你没有分配任何东西给它。好的做法是 - 根据您声明它的位置 - 为其分配 NULL (Windows)或 0 (跨平台)。

 Fraction* f1 = 0;


何时使用 delete

一旦您创建了一个动态对象,从而调用了 new 运算符,您就需要在某处调用 delete

 int main()
{

    Fraction* f1 = 0;    // Good practise to avoid invalid pointers
                         // An invalid pointer - if( f1 ){ Access violation }

    f1 = new Fraction(); // Could have done this at the previous line

    /* do whatever you need */

    if( f1 )
    {

        delete f1;
        f1 = 0;          // not needed since we are leaving the application

    }

    return 0;

}

在某些情况下,拥有一个 Fraction 数组或指向它的指针可能会很有用。在这里使用 int 为简单起见,与跳过错误处理相同:

 int arr[ 10 ];
int cur = -1;
int* Add( int fraction )
{
    arr[++cur] = fraction;
    return &arr[cur];
}

// Usage:
Add( 1 );
Add( 4 );

这里发生了一件事,没有通过动态对象分配任何内存。它们会自动释放。函数返回的指针是指向静态内存块的指针。

arr 作为指向 int 的指针时:

 int* arr[ 10 ];
int cur = -1;
int* Add( int* fraction )
{
    arr[++cur] = fraction;
    return arr[cur];
}

// Usage:
int* test;

test = Add( new int( 1 ) );
test = Add( new int( 4 ) );

现在您必须存储正在泄漏的块,因为您没有清理代码。

当您在每个 Add(...) delete test 之后调用时,您已经清理了内存,但是您丢失了存储在 int* arr[ 10 ] 中的值,因为它们指向保存值的内存。

您可以创建另一个函数并在完成这些值后调用它:

 void CleanUp()
{
    for( int a = 0; a < 10; ++a )
        delete arr[ a ];
}

小用法示例:

 int* test;
int  test2;

test  = Add( new int( 1 ) );
test2 = *Add( new int( 4 ) ); // dereference the returned value

/* do whatever you need */

CleanUp();


为什么我们要使用指针:

 int Add( int val )
{
    return val; // indeed very lame
}

当您调用需要参数(类型)的函数时,您 不是 在传递实例,而是传递它的副本。在上述函数中,您将返回该副本的副本。这将相当于所有涉及的内存的大量重复,并且您使您的应用程序非常慢。

考虑一下:

 class Test
{
    int  t;
    char str[ 256 ];
}

如果函数需要类型 Test ,则您正在复制 int 和 256 个字符。所以制作这个函数,所以它只需要一个指向 Test 的指针。然后使用指针指向的内存,不需要复制。

 int Add( int val )
{
    val++;
    return val;
}

在最后一个示例中,我们将 1 添加到 val 的副本中,然后返回该副本。

 int i = Add( 1 );

结果: i = 2;

 void Add( int* val )
{
    // mind the return type
    *val++;
}

在此示例中,您将地址传递给一个值,然后 - 在取消引用之后 - 将值加一。

 int i = 1;
Add( &i );

结果: i = 2;

现在您已将地址传递给 i ,而不是复制它。在函数中,您直接将 1 添加到该内存块的值。由于您更改了内存本身,因此您什么也没有返回。


无效指针/测试有效指针

有时您会遇到以下示例:

 if( p != 0 ) // or if( p )
{
    /* do something with p */
}

这只是为了检查指针 p 是否有效。然而,一个无效的地址——因此不指向你保留的内存(访问冲突)——也会通过。对于您的代码,无效指针是有效地址。

因此,要使用这样的检查,您必须使用 NULL (或 0 )指针。

 Fraction* f1 = 0;

f1 == 0 时,它不指向任何东西,否则它指向它所指向的任何东西。

当您在“主”类中有一个已创建或未创建的指针时,这很有用。

 class Fraction
{
    public:
    int* basicFeature;
    int* ExtendedFeature = 0; // NULL this pointer since we don't know if it
                              // will be used
    Fraction( int fraction )
    {
        // Create a pointer owned by this class
        basicFeature = new int( fraction );
    }
    Fraction( int fraction, int extended ) // mind the static
    : Fraction( fraction )
    {
        // Create a pointer owned by this class
        ExtendedFeature = new int( extended );
    }
    ~Fraction()
    {
        delete basicFeature;
        if( ExtendedFeature )
            // It is assigned, so delete it
            delete ExtendedFeature;
    }
}

在构造函数中,我们创建了两个指针,因此在析构函数中,我们正在清理这些指针。只检查 ExtendedFeature 因为这个可能会也可能不会被创建。 basicFeature 总是被创建。

您可以通过调用新函数来替换 if 语句,包括其在析构函数内的范围: removeExtendedFeature() 其中该函数实现为:

 Fraction::removeExtendedFeature()
{
    if( ExtendedFeature )
    {
        // It is assigned, so delete it
        delete ExtendedFeature;
        // Now it is important to NULL the pointer again since you would
        // get an access violation on the clean up of the instance of
        // the class Fraction
        ExtendedFeature = 0;
    }
}

和新的析构函数:

 Fraction::~Fraction()
{
    delete basicFeature;
    removeExtendedFeature();
}

归零的另一个功能可能是:

 int Fraction::getValue()
{
    int result = *basicFeature;
    if( ExtendedFeature )
        result += *ExtendedFeature;
    return result;
}


我为蹩脚的分数道歉,它具有越来越蹩脚的扩展功能。但作为一个例子,它可以达到目的。

原文由 brainoverflow 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题