关于构造函数,拷贝构造函数与析构函数的自动调用的问题分析

来源:互联网 发布:js post async 编辑:程序博客网 时间:2024/05/17 22:29

/*
-----------------------------------------------------------------------------
modified by quanspace
2012年4月2日15:18:29
-----------------------------------------------------------------------------
*/
# include <iostream>
using namespace std;
//点类的界面部分   class interface
class Point
{
public:
Point(int = 0, int = 0); //成员函数 构造函数的声明
Point(const Point&); //把引用形参声明为 const参数 以保证所引用的对象不被修改
void displayxy(); 
~Point(); //析构函数声明
private:
int X, Y;
};

//点类的实现部分  class implementation
Point::Point(int x, int y)  //构造函数
{
X = x;
Y = y;
cout<<"Constructor is called!";
displayxy();
}

Point::Point(const Point& p) //拷贝构造函数
{
X = p.X;
Y = p.Y;
cout<<"Copy Constructor is called!";
displayxy();
}
Point::~Point()
{
cout<<"Destructor is called!";
displayxy();
}


void Point::displayxy()   //显示点的坐标
{
cout<<"("<<X<<","<<Y<<")"<<endl;
}
 //定义一个返回值与形参都为点类类型的函数
Point func(Point p) 
{
int x = 10*2;
int y = 10*2;
Point pp(x,y);  //定义一个局部的对象
return pp;
}

int main()
{
Point p1(3,4);  //初始化对象p1
Point p2 = p1; //用类的一个对象p1去初始化类的另一个对象
p2 = func(p1); //函数的形参与返回值都是类的对象
return 0;
}
        我认为想要完全看懂这个程序,必须先了解什么是函数的显示调用与隐式(动态)调用的概念.
显示调用:直接使用函数,只要事先声明该函数的所在库就可以了,此时编译后关于该函数的信息会在输入表里.PE文件被载入到内存的时候WINDOWS会把所需要的函数地址填充到内存的指定地方.也就是说必须是用户自己事先要说明.
隐式调用: 也就是动态的调用函数,不需要人为说明,编译器自动的调用,是随机的.至于具体的关于如何实现隐式调用,这里涉及编译原理.我也搞不懂.总之.我们现在为了看懂上述函数,只要明白一点,那就是constructor, copy constructor , destructor 在此程序中都是隐式调用. 所以函数中加入一个输出语句和display函数以便于我们分析.


        不难看出,上面这个程序中首先是定义了一个简单的点类以及一个名为func的子函数. 现在我们从主函数开始分析. 

        当compiler执行语句 Point p1(3, 4); 时, 将自动调用构造函数(constructor) Point, 从而创建一个点类的对象p1并初始化为(3,4). 我们看到此构造函数中还有一个输出语句display函数,当其此时被调用时将输出:

 Constructor is called! (3,4).

        调用完Point(int, int)后,compiler又返回main()执行语句 Point p2 = p1; 此时将创建一个点类对象p2,. compiler此时将自动调用另一特殊函数 拷贝构造函数(Copy constructor)  Point (const Point &), 输出 :

Copy constructor is called! (3,4).

       
        我们知道如果一个带参函数被调用,必须由主函数提供一个argument给parameter, 即p1传给p. 那么问题又来了,主函数中的变量(对象)p1该如何传给子函数的参量p呢?
这里就要说说 函数的参数传递问题. 在参数传递中有两种调用机制: 传值调用和引用调用.   传值调用只是使用了 argument的值,在被调函数中对parameter的操作不会改变主函数中argument的值. 而引用则是传argument的地址,对parameter的操作将改变主函数中变量的属性.

        现在回来分析,不难看出对于func来说是是属于值传递.我们不希望p1跟p一样,在函数func调用完后一起死亡(被释放), 我们要让对象p1一直活到return 0; 那么,如何将argument p1的值赋给parameter p呢?  可以给p一个p1的副本就行了. 所以此刻compiler就会自动调用Point (const Point &), 输出:

 Copy constructor is called! (3,4)

传参完成后,执行func函数体, x,y 变为20, 创建局部变量pp并初始化 , 自动调用constructor 输出:

 Constructor is called! (20,20).

        接下来执行语句return pp;( 函数返回一个对象值时,会产生临时对象,函数中的返回值会以值拷贝的形式拷贝到被调函数栈中的一个临时对象。)将函数的返回值pp赋给p2.当函数执行完return pp; 局部对象将被释放(调用析构函数).问题就来了,pp都被"析构"了,怎么将p2改变了? 此刻compiler一定创建了pp的副本.调用 Point(const Point& p) 将其成员数据赋给对象p2. 输出:

 Copy constructor is called!(20,20). 
         到此p2 = func(p1); 就执行完毕了.  
 
    当compiler执行到return 0; 后就该destructor 大显身手啦! 析构函数像个大杀手废掉一切过期的对象, 关于在主函数,子函数中释放空间具体调用顺序又是怎样的呢? 一般教材上指出 析构函数与构造函数的调用顺序正好相反.   想要进一步了解就必须知道程序运行时刻(run-time environment)的存储分配策略和内存的组成即区域划分.这儿涉及到编译原理和操作系统等后续课程的知识,具体我也搞不太懂. 大致是在函数被调用时,把为其建立的活动记录下来,推入运行栈中,该栈包括局部数据域,为局部变量分配存储空间,当函数执行完毕后返回调用程序时,该被调用的函数的活动记录也将被推上去,为局部变量非配的存储区域不复存在.   

       如果构造是从外到里, 就像析构从里到外 . 构造由全局对象到局部对象, 析构则相反.   析构先从子函数 func开始. 先释放pp副本 , 在释放形参p ,然后是 局部对象pp ,接着是第二创建的p2,最后是第一个创建的对象 p1. 

 现在我们看程序的输出结果:    


     







原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 恶心头疼想吐怎么回事 老是恶心想吐是怎么回事 老是头疼恶心想吐怎么回事 老是恶心想吐怎么回事 胃胀恶心想吐怎么回事 突然头疼恶心想吐是怎么回事 犯恶心想吐是怎么回事 天天恶心想吐怎么回事 恶心想吐拉肚子怎么回事 头晕耳鸣恶心想吐是怎么回事 头疼恶心吐怎么回事 恶心想吐拉肚子 咳嗽恶心想吐怎么回事 偏头疼恶心想吐是怎么回事 经常头疼恶心想吐是怎么回事 突然头痛恶心想吐是怎么回事 胃胀痛恶心是怎么回事 头痛 恶心 想吐 老是恶心是怎么回事 头疼加恶心是怎么回事 头晕头疼恶心想吐是怎么回事 头疼头晕恶心想吐是怎么回事 胃难受恶心想吐怎么回事 经常头晕恶心想吐是怎么回事 孕妇头晕恶心想吐是怎么回事 突然恶心想吐是怎么回事 恶心吐怎么回事 恶心想吐头晕 胃部不舒服恶心想吐 晚上恶心想吐是怎么回事 总恶心是怎么回事 恶心想吐头晕怎么回事 胃难受恶心怎么回事 来月经恶心想吐怎么回事 胃老是恶心怎么回事 老是头疼恶心怎么回事 为什么恶心想吐 经期恶心想吐是怎么回事 老是恶心想吐 头疼还恶心想吐咋回事 胃痛有点恶心想吐