c++中构造函数与析构函数
构造函数与析构函数
在c++中有2个特殊的函数:构造函数和析构函数,它们分别对类对象进行初始化和清理工作。
1. 构造函数
构造函数,与类名相同,当创建类对象时会自动调用该函数。如果创建类对象时没有手动创建构造函数,系统会自动创建一个默认的构造函数,这个默认的构造函数函数体是空的,无任何功能。 
构造函数是作为类的成员函数,它可以访问类中所有的数据成员,可以是内联函数,可以带参数及默认形参值,还可以重载。 
构造函数主要是在创建对象时初始化对象,它与其他成员函数的区别:
- 构造函数必须与类同名,一般函数不能与类同名。
 - 构造函数无返回值,也不需要使用void来修饰,而其他普通函数如果没有返回值则要用void来修饰(在java语言中如果构造函数/方法如果用void来修饰则会变成普通函数/方法)。
 - 构造函数不能直接被调用,在创建类对象时编译器会自动的调用构造函数,普通成员函数在程序执行到它是被调用。
 
#include<iostream>usingnamespace std;classPerson{int idNum;int age;char name[20];public:Person(int idNum=0,int age=1){this->idNum=(idNum>=0&&idNum<=100)?idNum:0;this->age=(age>=0&&age<=150)?age:0;}void showInfo(){cout<<"idNum:"<<idNum<<endl;cout<<"age:"<<age<<endl;}};int main(){Person p(9,30);p.showInfo();return0;}
2. 析构函数
析构函数,在类对象消失前自动调用的函数,它的形式如下:
~funName(){operation;}
在析构函名与类名相同,相对于构造函数,析构函数作用刚刚相反,即是一个“逆构造函数”,在它前面有个~符号。 
析构函数具有如下特点:
- 析构函数没有任何参数,不能被重载,但可以是一个虚函数,一个类只有一个析构函数。
 - 析构函数没有返回值,也不用修饰符修饰。
 - 析构函数前面有一个
~符号来区别构造函数。 - 析构函数一般有用户自动定义,在类对象消失前调用,如果用户没有定义析构函数,系统会自动生成一个内容为空的析构函数。
 
#include<iostream>usingnamespace std;classPerson{private:int age;intNumber;public:Person(int age=0,intNumber=0){this->age=(age>=0&&age<=150)?age:0;this->Number=(Number>=0&&Number<=99)?Number:0;cout<<"Constructor;";cout<<"age:"<<age;cout<<";Nunber:"<<Number<<endl;}~Person(){cout<<"destructor;"<<"age:"<<age<<";Number:"<<Number<<endl;}};int main(){Person p3(5,9);Person p4(8,7);return0;}Person p1(6,11);Person p2(9,10);
运行结果:
Constructor;age:6;Nunber:11Constructor;age:9;Nunber:10Constructor;age:5;Nunber:9Constructor;age:8;Nunber:7destructor;age:8;Number:7destructor;age:5;Number:9destructor;age:9;Number:10destructor;age:6;Number:11
从运行结果来看,构造函数的调用顺序是
st=>start:Starte=>end:Endop1=>operation: p1::Person()op2=>operation: p2::Person()op3=>operation: p3::Person()op4=>operation: p4::Person()st->op1->op2->op3->op4->e
而析构函数的调用顺序是
st=>start:Starte=>end:Endop1=>operation: p4::~Person()op2=>operation: p3::~Person()op3=>operation: p2::~Person()op4=>operation: p1::~Person()st->op1->op2->op3->op4->e
这是因为p1和p2对象属于全局对象,p3和p4属于局部对象,在程序运行时会先创建全局对象栈,然后才是局部对象栈。当对象消失时,先是局部栈中栈顶的p4对象先弹出来,然后是p3对象,再之后是全局栈中的对象。
3. 拷贝函数
拷贝构造函数又叫拷贝函数,它是一种特殊的构造函数,它作用是在建立新对象时将已经存在对象的数据成员拷贝给新对象。既然拷贝函数也是一个构造函数,那么它的函数名也是和类同名的,其形参是本类对象的引用。 
同样,它作为构造函数的一种,当用户没有自定义拷贝函数,系统会自动的生成一个默认的拷贝函数来进行对象之间的位拷贝,默认拷贝函数的功能是把初始值对象的每一个数据成员的值依次复制新的对象中。 
用户自己定义一个拷贝函数的一般形式为:
类名(类名&对象名){...}
下面3种情况相当于用一个已经存在的对象去初始化新建的对象。
- 当用类的一个对象去初始化该类的另一个对象时(显示调用)。
 - 如果函数的形参是类对象,调用函数时,将对像作为函数实参传递给函数的形参时(隐含调用)。
 - 如果函数的返回值是类的对象,函数执行完成,将返回值返回(隐含调用)。
 
#include<iostream>usingnamespace std;classTime{private:int Y,M,D;public:Time(int y=0,int m=0,int d=0){Y=y,M=m,D=d;cout<<"constructor:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;}~Time(){cout<<"destructor:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;}Time(Time& t){cout<<"copy constructor before call:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;Y=t.Y;M=t.M;D=t.D;}void showTime(){cout<<"year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;}};Time fun(Time t){return t;}int main(){Time t1(1991,10,7);Time t2(1991,11,6);Time t3(t1);fun(t2);Time t4;t4=t2;return0;}
运行结果:
constructor:year is:1991;Monthis:10;day is:7constructor:year is:1991;Monthis:11;day is:6copy constructor before call:year is:967104272;Monthis:32767;day is:4197060copy constructor before call:year is:1;Monthis:0;day is:4197645copy constructor before call:year is:967104568;Monthis:32767;day is:4197552destructor:year is:1991;Monthis:11;day is:6destructor:year is:1991;Monthis:11;day is:6constructor:year is:0;Monthis:0;day is:0destructor:year is:1991;Monthis:11;day is:6destructor:year is:1991;Monthis:10;day is:7destructor:year is:1991;Monthis:11;day is:6destructor:year is:1991;Monthis:10;day is:7
分析:
- 从结果上看,首先创建了2个Time对象t1、t2,调用构造函数运行结果为1、2行。
 - 然后是运行语句
Time t3(t1);,这里会显示调用拷贝函数,所以会打印出第3行结果。因为拷贝函数相当于拷贝函数=构造函数+赋值操作,在构造对象后类成员还没有进行赋值就打印出来,所以从结果数据看是一些随机数。 - 之后运行
fun(t2);语句,首先在这个函数中会创建一个临时形参对象,然后相当于执行了Time 临时形参对象(实参对象);的语句来调用拷贝函数,在函数return时,又会创建一个临时返回对象,并执行Time 临时返回对象(临时形参对象);的语句来调用拷贝函数,之后该函数生命周期结束,对创建的2个临时对象调用析构函数(先是调用的临时返回对象的析构函数,然后是临时形参对象的析构函数–个人推测),所以看到第3~7行结果。 - 再然后执行
Time t4;语句,即创建t4对象并调用构造函数。t4=t2;语句是对已经存在对象进行赋值,所以既不会调用构造函数也不会调用拷贝函数,在内存中只是使用t2对象的值去覆盖t4对象对应的值。 - 之后是整个main函数生命周期结束调用 它 t1~t4 对象的析构函数,遵循的原则是后创建的先析构,依次是t4、t3、t2、t1,所以看到执行的结果是第9~12行。
 
注
- 拷贝函数作用是用一个已经存在的类对象去初始化一个新的类对象,在对象之间进行赋值操作,拷贝函数不会被调用(实际上就上面3种情况会调用拷贝函数)。
 - 创建对象时,构造函数与拷贝函数有且只有一个被调用。
 - 当对象作为函数的返回值时,需要调用拷贝函数,这时系统会在堆中动态创建一个临时对象,将函数返回的对象拷贝给该临时对象,并把该临时对象的地址存储在寄存器中,最后由临时对象完成函数返回值的传递。
 
4. 总结
最近在学习c++的时候,一直没搞懂 c++中 各个特殊函数的调用情况,然后查看了下以前的课本[c++语言程序设计教程],并做了以上学习笔记方便后面查阅,希望能够对c++ 中这3个特殊函数有个大概了解(毕竟在读书的时候根本没看过,那时学习马马虎虎只是为了过考试而已),而且觉得像这些语言细节特性对后面深入学习和工作还是非常有用的。
c++中构造函数与析构函数的更多相关文章
- (转载)C++中, 构造函数和析构函数能不能被显示调用?
		
(转载)http://blog.csdn.net/zhangxinrun/article/details/6056321 代码: view plaincopy to clipboardprint?#i ...
 - C++学习笔记(7)----类的数组中构造函数和析构函数的调用顺序
		
C++类的数组中构造函数和析构函数的调用顺序(2) 对于如下的代码: #include<iostream> using namespace std; class CBase { priva ...
 - 显示调用C++中构造函数和析构函数(有什么弊端)
		
1.C++中, 构造函数和析构函数可以被显示调用. 显示调用默认构造函数的语法: a.A::A();(不能写成a.A();) , 显示调用非默认构造函数的语法: a.A::A(7);(不能写成a.A( ...
 - C++C++中构造函数与析构函数的调用顺序
		
http://blog.csdn.net/xw13106209/article/details/6899370 1.参考文献 参考1: C++继承中构造函数.析构函数调用顺序及虚函数的动态绑定 参考2 ...
 - C++中构造函数和析构函数的调用顺序
		
一般而言,析构函数调用的顺序和构造函数调用顺序相反,但是,对象的存储类别可以改变调用析构函数的顺序.举例说明: CreateAndDestroy类的定义 CreateAndDestroy类的成员函数的 ...
 - C#中构造函数和析构函数的用法
		
构造函数与析构函数是一个类中看似较为简单的两类函数,但在实际运用过程中总会出现一些意想不到的运行错误.本文将较系统的介绍构造函数与析构函数的原理及在C#中的运用,以及在使用过程中需要注意的若干事项.一 ...
 - C/C++中构造函数和析构函数能否被继承
		
http://bbs.csdn.net/topics/390160673 标准方面做了要求的.Even though destructors are not inherited 构造函数和析构函数是不 ...
 - (转)PHP中构造函数和析构函数解析
		
--http://www.jb51.net/article/56047.htm 构造函数 void __construct ([ mixed $args [, $... ]] ) PHP 5 允行开发 ...
 - c++中构造函数 、析构函数的作用域详解
		
我们知道,在c++中,析构函数是在函数作用域尾部执行析构函数,从而释放对象,但是有一种情况下,析构函数作用域发生变化,请看下面的例子,定义了一个Stock类,Stock类存放在stock.h中,主调用 ...
 
随机推荐
- CF1440A Buy the String 题解
			
Content 有 \(t\) 组询问,每组询问给出一个长度为 \(n\) 的 \(0/1\) 串,你可以花 \(h\) 的代价把 \(0\) 修改成 \(1\) 或者把 \(1\) 修改成 \(0\ ...
 - 前端er必须掌握的数据可视化技术
			
又是一月结束,打工人准时准点的汇报工作如期和大家见面啦.提到汇报,必不可少的一部分就是数据的汇总.分析. 作为一名合格的社会人,我们每天都在工作.生活.学习中和数字打交道.小到量化的工作内容,大到具体 ...
 - .NET 6 优先队列 PriorityQueue 实现分析
			
在最近发布的 .NET 6 中,包含了一个新的数据结构,优先队列 PriorityQueue, 实际上这个数据结构在隔壁 Java中已经存在了很多年了, 那优先队列是怎么实现的呢? 让我们来一探究竟吧 ...
 - ORM-数据库命令操作包装实例对象学习
			
http://www.cnblogs.com/alex3714/articles/5978329.html python 之路,Day11 - sqlalchemy ORM 本节内容 ORM介绍 ...
 - 缓存系统redis操作、mongdb、memeche
			
mongdb :默认数据持久化,存在内存的同时也向硬盘写数据. redis:可配置数据持久化,默认数据在内存中 memeche:only support 内存模式 redis操作 https://ww ...
 - 当是class com.cosl.po.Pc$$EnhancerByCGLIB$$38c58f03时,反射属性都他妈不好用了
			
当是class com.cosl.po.Pc$$EnhancerByCGLIB$$38c58f03时,反射属性都他妈不好用了 搞不懂为什么?
 - C# 使用TimeSpan秒数转化为时分秒的写法
			
1.TimeSpan的生成方法 // 参数: // ticks: // A time period expressed in 100-nanosecond units. public TimeSpan ...
 - 解决VMware开机黑屏问题
			
最近搞虚拟机.一直遇见了许多问题.发现某一天.VMware开机后.其中一个虚拟机直接卡住黑屏了.关的时候就一直显示.xxx繁忙.无法关闭.网上搜到了解决方法.这边记录一下. 1.关闭所有服务 2.修复 ...
 - ios越狱使用AltDeploy签名
			
1.总结 截至目前: 越狱这么多年,用过非自己签名的越狱app, 也用过自己签名的越狱app. A .非自己签名, 别人签名好了越狱app, 下载安装,即可.笔者前期都是用的这样的方式. B.自己编译 ...
 - 【linux】环境变量生命周期的操作方式
			
目录 前言 1. 修改环境变量 1.1 手动指定 1.2 临时生效 1.3 永久生效 链接 前言 参考: 李柱明博客 本文主要记录 linux 环境变量配置的生命周期. 如,修改环境变量 PATH 是 ...