回 帖 发 新 帖 刷新版面

主题:[十万火急]请教有关析构函数的问题

[size=3]#include<iostream.h>
class Myclass
{
public:
    Myclass(int a,int b,int c);
    ~Myclass();
    void Getnumber();
    int Getsum(Myclass m);
private:
    int A,B,C;
    static int Sum;
};
int Myclass::Sum(0);          //静态成员函数的初始化
Myclass::Myclass(int a,int b,int c)
{
    A=a;
    B=b;
    C=c;
    Sum+=A+B+C;
    cout<<"调用构造函数一次"<<A<<endl;
}
Myclass::~Myclass()
{
    cout<<"调用析构函数一次"<<A<<endl;
}
void Myclass::Getnumber()
{
    cout<<A<<';'<<B<<';'<<C<<endl;
}
int Myclass::Getsum(Myclass m)
{
    return m.Sum;             
}
void main()
{
    Myclass M(3,4,5),N(5,6,7);
    N.Getnumber();
    cout<<M.Getsum(N)<<';'<<M.Getsum(N)<<','<<N.Getsum(M)<<endl;
}
    输出结果好象与想象的不一致呀
    总共调用了4次析构函数,按理说共定义了两个对象,就应该只调用2次析构函数,它怎么调用了5次析构函数呢??
    在执行最后以行前就执行了析构函数3次,与形参的个数一致,且顺序一致
    急急急!!![/size]

回复列表 (共8个回复)

沙发

[color=FF00FF]有进展了,只知道与拷贝构造函数有关,具体思路还未理清[/color]
#include<iostream.h>
class Myclass
{
public:
    Myclass(int a,int b,int c);
[color=FF00FF]Myclass(Myclass &p); [/color]
    ~Myclass();
    void Getnumber();
    int Getsum(Myclass m);
private:
    int A,B,C;
    static int Sum;
};
int Myclass::Sum(0);          //静态成员函数的初始化
Myclass::Myclass(int a,int b,int c)
{
    A=a;
    B=b;
    C=c;
    Sum+=A+B+C;
    cout<<"调用构造函数一次"<<A<<endl;
}
Myclass::~Myclass()
{
    cout<<"调用析构函数一次"<<A<<endl;
}
[color=FF00FF]Myclass::Myclass(Myclass &p)
{  cout<<"调用拷贝构造函数"<<A<<endl; }  [/color]
void Myclass::Getnumber()
{
    cout<<A<<';'<<B<<';'<<C<<endl;
}
int Myclass::Getsum(Myclass m)
{
    return m.Sum;             
}
void main()
{
    Myclass M(3,4,5),N(5,6,7);
    N.Getnumber();
    cout<<M.Getsum(N)<<';'<<M.Getsum(N)<<','<<N.Getsum(M)<<endl;
}
    输出结果好象与想象的不一致呀
    总共调用了4次析构函数,按理说共定义了两个对象,就应该只调用2次析构函数,它怎么调用了5次析构函数呢??
    在执行最后以行前就执行了析构函数3次,与形参的个数一致,且顺序一致
    急急急!

板凳

楼主的疑问源之于拷贝构造函数!!!!!!!!!!
但有一个对象去初始化另一个对象时会调用拷贝构造函数!!!!!
针对于楼主刚开始的那个程序,楼主可能对输出时中间的那3个析构函数感到疑问,这3个怎么回事??
其实编译器不仅调用了3个析构函数还调用了3个隐含的拷贝构造函数!!!
楼主在第二个程序中加了一个显式的拷贝构造函数,输出时楼主应该发现中间多了3个拷贝构造函数,而且是一对一的!!这些调用都是由下面的语句引起的
cout<<M.Getsum(N)<<';'<<M.Getsum(N)<<','<<N.Getsum(M)<<endl;
就以M.Getsum(N)为例吧
int Myclass::Getsum(Myclass m)
{
    return m.Sum;             
}
当用对象N去初始化对象m时就会调用拷贝构造函数,当函数调用完毕后这个对象m
就会被析构!(因为它的作用域仅在函数Getsum内)进而调用析构函数
另外两个同上楼主可以去找点有关拷贝构造函数的书看看

3 楼

为什么需要析构函数???

析构函数是用来专门清理 构造函数在堆上面的分配的内存的....

因为程序的内存分为 栈和堆.

栈是系统自动分配的,变量超出作用域就会被回收,所以经常用来定义局部变量和函数的参数传值.

堆呢,是由程序员自己分配,自己管理的一块内存,操作符为new和delete.

类在构造函数里面如果用new分配了内存,那么就必须使用delete在析构函数里面释放,以免当类变量超出作用域之后,导致内存泄露,,,最后导致程序崩溃.

楼主这么个学法,有点照瓢画葫的意思,没有理解到内涵.....

再来说说你这个白痴程序吧.


void main()
{
    Myclass M(3,4,5),N(5,6,7);
    N.Getnumber();
    cout<<M.Getsum(N)<<';'<<M.Getsum(N)<<','<<N.Getsum(M)<<endl;
}

首先你定义了两个类 M和N两个局部变量,他们都是在栈上面分配的内存
然后调用了M.Getsum两次,N.Getsum一次

当M,N超出作用域的时候,系统就会注销掉M和N,当然就会分别调用析构函数,M和N总共两次.


M.Getsum(N)这里,你是值传递,创建了一个N的临时类变量,也是在栈上面分配的,所以函数调用完毕后,这里还会调用一次.因为有两个函数,所以乘以2

N.Getsum(N)这里也是,也调用一次.

这么加起来,你这里就应该总共调用五次析构函数...........



你这本书应该是清华出的吧?完全是误认子弟.........


推荐你买本 谭浩强的C语言,仔细练习.

然后买本<C++ Premier>来读,脑中要有个清醒的认识,,,不要拘泥于程序的语法.

为什么会有C++?
为了程序的复用..

如何复用的?
请看面向对象的编程思想!!!最好买本<Head First Java>来学.
C/C++并不是为了某一种程序的设计方法而开发的,所以它适合情况的编程.
但这种自由度带来了一个问题,,,,一个新手,在还没搞清楚编程模型的情况下(大多数人都不可避免),就匆匆开始学起了语法,以为只要会了C++的语法,自己就会面向对象了.
这实际上是一个非常错误的想法.
而Java的出现很轻易的就解决了这种窘境.
它以简洁的语法结构,让你不花过多的时间拘泥于语法的问题上.而让你尽快的将它运用到面向对象编程的实践当中.
当然,Java的主要应用范围还是在网络上面.
但是作为一个掌握了面向对象思想的人,一个熟悉java语言的人,回过头来再来看C++,它的学习目的就会很明确,他能很轻易的在成堆的文字中找出关键的部分进行阅读和掌握,而不会被其淹没,搞得晕头转向.
我觉得你,应该先学Java.
学完了,学做网页,找工作也容易.
C/C++的人虽然牛x,但是牛人背后所付出的可是成倍的时间.如果你是一个大学生,在众多的课程学习中,还能把VC/C++都玩的很精通的话,无疑你这个人真的很牛x.


Java的那些语法,如果用C++,应该如何写?
Java里面的变量类型分
基本型,用=号的时候,是开辟新内存,复制值过去.
引用型,用=号赋值的时候,只是讲地址传递给了左边的变量,没有开辟新内存.
基本型在C/C++里面如何实现的呢?实际上C/C++里面所有变量的行为都是基本型.
引用型的变量在C/C++里面怎么实现的呢?把左边的变量定义为指针.



C/C++中的变量的内存是如何分配和管理的?
上面我已经讲的差不多了,但是还不够.

4 楼

补充一下,所谓的拷贝构造函数,也是为了

值传递,无法自动复制堆上面分配的内存而设置的.

真的有必要使用他吗?

如果将C/C++按照java的约束来写程序的话,,,,,,,我看没多大的必要

5 楼

关于拷贝构造函数:
C++拷贝对象执行按位拷贝,这是非常精确的拷贝,在某些情况下会导致问题
如某对象从堆里获得内存,某函数以该对象为实参被调用,然后在该函数内局部拷贝对象,退出函数时,将删除该对象的局部拷贝,因为是按位拷贝,同时也删除了该对象在堆里分配的内存,这样这次使用该内存区将导致错误,该对象释放时又会再次释放该内存区。C++为解决这种情况,引入拷贝构造函数,即在拷贝对象时由用户再分配一片内存区。这就是所谓的深拷贝。  一般的拷贝不涉及动态内存是不需要用户编写拷贝构造函数的C++会自动处理,这就是浅拷贝。
就象LZ的这种情况,是不需要自己编写的,如果用户写了,C++就会调用用户的拷贝构造函数,C++为用户做的事情全部交给用户,这里LZ的拷贝构造函数有问题
Myclass::Myclass(Myclass &p)
{  cout<<"调用拷贝构造函数"
       <<A      [color=FF0000]// 这个成员是临时对象的并没有初始化[/color]
       <<endl; } 
输出结果是:
调用构造函数一次3
调用构造函数一次5
5;6;7 
[color=FF0000]调用拷贝构造函数4200248  // 一些莫名其妙的数[/color]
调用析构函数一次4200248
调用拷贝构造函数1245056
调用析构函数一次1245056
调用拷贝构造函数1245056
调用析构函数一次1245056
30;30,30
调用析构函数一次5
调用析构函数一次3
Press any key to continue

要么不写,要么至少象下面这样:
Myclass::Myclass(Myclass &p)
{   A=p.A;
    B=p.B;
    C=p.C;
cout<<"调用拷贝构造函数"<<A<<endl; } 
输出结果:
调用构造函数一次3
调用构造函数一次5
5;6;7
调用拷贝构造函数3
调用析构函数一次3
调用拷贝构造函数5
调用析构函数一次5
调用拷贝构造函数5
调用析构函数一次5
30;30,30
调用析构函数一次5
调用析构函数一次3
Press any key to continue

不过没有涉及深拷贝,完全没必要自己去写。

6 楼

Getsum(MyClass const & m);

7 楼

按我的理解就是,如果数据成员没有涉及到指针类型的,就没有必要自己写拷贝函数了。如果涉及到指针,而且这个指针是用来指示动态分配的内存,那么拷贝函数就是必须的了。比如,
MyClass
{
char * m_szMyString;
public:
MyClass(char * szOther)
}

MyClass::MyClass(char *szOther)
{
int ilen=strlen(szOther)+1;
m_szMyString=(char *)malloc(ilen);
strcpy(m_szMyString,szOther);
}

MyClass A("Hello,I am A!");
MyClass B(A);调用拷贝函数

以上数据成员涉及到字符串的指针,并且这个指针是用来指向分配给自己的字符串。如果不自己写拷贝函数,那么当拷贝的时候,C++就会调用默认的拷贝函数,即一项一项的复制数据,m_szMyString=Other.m_szMyString;这样的话,B的m_szMyString跟A的m_szMyString指向的地址空间就会相同了,都是"Hello,I am A!".楼主应该可以想象以后会怎样了吧。

8 楼

输出结果是:
调用构造函数一次3
调用构造函数一次5
5;6;7 
调用拷贝构造函数-858993460  // 乱码,至于为什么你可以看一下江南孤峰的回帖,说得很清楚
//为什么会调用拷贝构造函数呢,看一下下面的函数
//Myclass::Getsum(Myclass m)
//调用该函数它首先要进行形实结合,即使实参初始化m
//这里的参数如果是引用的话,情况就不一样了,你可以试一下
//那么就只调用两次拷贝,两次析构了


调用析构构造函数-858993460  
//由于m的作用域只在该函数内,函数调用完毕马上析构
//下面两次的调用情况同


调用构造函数一次1245056
调用析构函数一次1245056
调用拷贝构造函数1245056
调用析构函数一次1245056
30;30,30
调用析构函数一次5
调用析构函数一次3

我来回复

您尚未登录,请登录后再回复。点此登录或注册