2

考虑以下代码。此代码取自《 使用 C++ 的面向对象编程!-Chapter 12.Templates》一书。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
class vc
{
    int size2;
    int *v;
public :

    vc(int size1);
    vc(int *a);
    ~vc()
    {
        printf("\n calling destructor");
    }
    int operator *(vc );
};
vc::vc(int size1)
{
    v = new int[size2=size1];
    for(int i=0;i<this->size2;i++)
        v[i]=0;
}
vc::vc(int a[])
{

    for(int i=0;i<this->size2;i++)
    {
        v[i]=a[i];

    }
}
int vc::operator *(vc v1)
{
    int total=0;
    for(int i=0;i<size2;i++)
        total+=(v[i]*v1.v[i]);
    return total;
}

int main()
{
    int a[3]={1,2,3};
    int b[3]= {5,6,7};
    vc v1(3),v2(3);
     v1=a;
     v2=b;
    int total = v1*v2;
    cout << total;
    return 0;
}

首先,此代码无法正常工作。它应该显示 38 作为输出。当我开始调试这段代码时,我发现在这一行之后3被赋值。但是在执行下一行时,控制权被传递给第二个构造函数并显示一个垃圾值。此外,析构函数在行之后调用,下一行之后也会发生同样的情况。size2vc v1(3),v2(3);size2v1=a

最终输出:

calling destructor
calling destructor
calling destructor0

为什么析构函数被调用 3 次?这段代码错了吗?

4

5 回答 5

8

当你调用时v1*v2,你传递v2给方法

int vc::operator *(vc v1)

这会创建v2. 因此,您有一个额外的vc实例。

你第一个疑惑的答案,

但是在执行下一行时,控制权被传递给了第二个构造函数,并且 size2 显示了一个垃圾值,并且它没有执行三次。

是因为

v1 = a;

vc通过调用创建一个临时对象vc::vc(int a[])并将其分配给v1. 但是,这个构造函数没有初始化size2。所以你得到一个垃圾值。

一种更简洁的方法是传递一个数组及其大小:

vc::vc(int a[], int size1) {                                                                                       
    v = new int[size2=size1];                                                                                      
    for(int i=0; i<size2; i++)                                                                                     
        v[i] = a[i];                                                                                                 
}

int main()
{
    int a[3]={1,2,3};
    int b[3]= {5,6,7};
    vc v1(a, 3), v2(b, 3); 
    int total = v1*v2;
    cout << total;
    return 0;
}

然后total将是 38 岁。

于 2013-08-09T15:17:14.233 回答
5
v1=a;

那条线实际上不仅仅是一个任务。这是一个临时对象的构造,并将其分配给您已经创建的对象。分配给编译器可以看到的类的唯一方法int[]是使用vc(int a[])构造函数创建一个临时对象vc- 但该构造函数没有初始化 size2,这就是您看到的问题(“但是在执行下一行时,控制权被传递给第二个构造函数,并且 size2 显示一个垃圾值。”)。

创建此临时对象后,隐式赋值运算符(由编译器自动为您创建,因为您没有指定一个)将将此对象的成员复制到您已经创建的对象中。

这称为隐式转换(参见例如此处:https ://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr384.htm )。如果要防止这种情况发生,请在构造函数上使用explicit关键字。vc(int a[])

事后思考:我最初的回答并没有真正回答最后陈述的主要问题(为什么要调用析构函数三次)。我想这里的其他人已经很好地回答了这个问题:一个vc对象是在您的内部本地创建的,operator*因为您在那里执行按值传递的参数。

现在你可能会问为什么析构函数实际上没有被调用 5 次(对于隐式创建的对象也是 2 次)。我想这是因为编译器以某种方式优化了新对象的实际创建(可能是返回值优化,如果我错了,请有人纠正我!)。

于 2013-08-09T15:24:06.860 回答
3

v1*v2创建第三个临时对象,因此也调用它的析构函数以及您创建的两个对象。

于 2013-08-09T15:16:31.050 回答
2

您应该通过引用而不是值来传递额外的析构函数的原因。

   int vc::operator *(vc &v1)

而且,从 nyarlathotep 的回答当场。除了添加显式关键字,这将防止错误。

你只是错过了这个

void vc::operator=(int* a)
{
    for(int i=0;i<this->size2;i++)
{
    v[i]=a[i];

}
}
于 2013-08-09T15:23:55.983 回答
1

正如其他人所说,析构函数被调用了三次,因为当operator*(vc v1)被调用时,会构造一个临时的本地副本,因为它v1通过value接受参数。

nyarlathotep给出的答案是正确的。innosam的建议几乎是理想的。

我想补充一些其他更具体的观点。

有两点需要注意:

  • 当传递您只想在被调用函数内部引用的对象实例(而不是复制或更改)时,就像这个operator*方法一样,总是通过引用到常量传递:operator*(const vc& v1)
  • v1 = a vc(int a[])仅由于将整数数组隐式转换为 nyarlathotep 提到a的实例化,调用构造函数。vc您可以通过向此构造函数添加关键字来避免这种情况,该关键字explicit指示编译器不允许此类转换。

保持示例main不变,产生您正在寻找的答案的最少代码更改次数(38)如下:

  • 更改operator*为采用 type 的参数const vc&
  • 定义自定义赋值运算符:vc& operator=(int src[]). 它的实现可以与构造函数完全相同vc(int a[])

您会注意到构造函数vc(int a[])将不再被这两个更改调用。

此外,虽然我知道这只是书中的一个例子(并且与 nyarlathotep 相呼应,希望这是一个该做什么的例子),但这个课程还有其他问题,我想指出几个他们:

  • 应该将内存清理(即delete[])添加到析构函数中。
  • 由于该类动态地为成员变量分配内存,因此默认的复制构造函数和默认的赋值运算符是不够的,因为它们只执行成员变量的浅拷贝,而不是深拷贝。鉴于您提供的代码(我上面提到的两个更改),不会调用此行为,但请记住这一点以备将来使用。
于 2013-08-09T16:50:36.517 回答