构造函数与析构函数

在c++中有2个特殊的函数:构造函数和析构函数,它们分别对类对象进行初始化和清理工作。

1. 构造函数

构造函数,与类名相同,当创建类对象时会自动调用该函数。如果创建类对象时没有手动创建构造函数,系统会自动创建一个默认的构造函数,这个默认的构造函数函数体是空的,无任何功能。

构造函数是作为类的成员函数,它可以访问类中所有的数据成员,可以是内联函数,可以带参数及默认形参值,还可以重载。

构造函数主要是在创建对象时初始化对象,它与其他成员函数的区别:

  • 构造函数必须与类同名,一般函数不能与类同名。
  • 构造函数无返回值,也不需要使用void来修饰,而其他普通函数如果没有返回值则要用void来修饰(在java语言中如果构造函数/方法如果用void来修饰则会变成普通函数/方法)。
  • 构造函数不能直接被调用,在创建类对象时编译器会自动的调用构造函数,普通成员函数在程序执行到它是被调用。
  1. #include<iostream>
  2. usingnamespace std;
  3. classPerson
  4. {
  5. int idNum;
  6. int age;
  7. char name[20];
  8. public:
  9. Person(int idNum=0,int age=1)
  10. {
  11. this->idNum=(idNum>=0&&idNum<=100)?idNum:0;
  12. this->age=(age>=0&&age<=150)?age:0;
  13. }
  14. void showInfo()
  15. {
  16. cout<<"idNum:"<<idNum<<endl;
  17. cout<<"age:"<<age<<endl;
  18. }
  19. };
  20. int main()
  21. {
  22. Person p(9,30);
  23. p.showInfo();
  24. return0;
  25. }

2. 析构函数

析构函数,在类对象消失前自动调用的函数,它的形式如下:

  1. ~funName()
  2. {
  3. operation;
  4. }

在析构函名与类名相同,相对于构造函数,析构函数作用刚刚相反,即是一个“逆构造函数”,在它前面有个~符号。

析构函数具有如下特点:

  • 析构函数没有任何参数,不能被重载,但可以是一个虚函数,一个类只有一个析构函数。
  • 析构函数没有返回值,也不用修饰符修饰。
  • 析构函数前面有一个~符号来区别构造函数。
  • 析构函数一般有用户自动定义,在类对象消失前调用,如果用户没有定义析构函数,系统会自动生成一个内容为空的析构函数。
  1. #include<iostream>
  2. usingnamespace std;
  3. classPerson
  4. {
  5. private:
  6. int age;
  7. intNumber;
  8. public:
  9. Person(int age=0,intNumber=0)
  10. {
  11. this->age=(age>=0&&age<=150)?age:0;
  12. this->Number=(Number>=0&&Number<=99)?Number:0;
  13. cout<<"Constructor;";
  14. cout<<"age:"<<age;
  15. cout<<";Nunber:"<<Number<<endl;
  16. }
  17. ~Person()
  18. {
  19. cout<<"destructor;"<<"age:"<<age<<";Number:"<<Number<<endl;
  20. }
  21. };
  22. int main()
  23. {
  24. Person p3(5,9);
  25. Person p4(8,7);
  26. return0;
  27. }
  28. Person p1(6,11);
  29. Person p2(9,10);

运行结果:

  1. Constructor;age:6;Nunber:11
  2. Constructor;age:9;Nunber:10
  3. Constructor;age:5;Nunber:9
  4. Constructor;age:8;Nunber:7
  5. destructor;age:8;Number:7
  6. destructor;age:5;Number:9
  7. destructor;age:9;Number:10
  8. destructor;age:6;Number:11

从运行结果来看,构造函数的调用顺序是

Created with Raphaël 2.1.4Startp1::Person()p2::Person()p3::Person()p4::Person()End
  1. st=>start:Start
  2. e=>end:End
  3. op1=>operation: p1::Person()
  4. op2=>operation: p2::Person()
  5. op3=>operation: p3::Person()
  6. op4=>operation: p4::Person()
  7. st->op1->op2->op3->op4->e

而析构函数的调用顺序是

Created with Raphaël 2.1.4Startp4::~Person()p3::~Person()p2::~Person()p1::~Person()End
  1. st=>start:Start
  2. e=>end:End
  3. op1=>operation: p4::~Person()
  4. op2=>operation: p3::~Person()
  5. op3=>operation: p2::~Person()
  6. op4=>operation: p1::~Person()
  7. st->op1->op2->op3->op4->e

这是因为p1和p2对象属于全局对象,p3和p4属于局部对象,在程序运行时会先创建全局对象栈,然后才是局部对象栈。当对象消失时,先是局部栈中栈顶的p4对象先弹出来,然后是p3对象,再之后是全局栈中的对象。

3. 拷贝函数

拷贝构造函数又叫拷贝函数,它是一种特殊的构造函数,它作用是在建立新对象时将已经存在对象的数据成员拷贝给新对象。既然拷贝函数也是一个构造函数,那么它的函数名也是和类同名的,其形参是本类对象的引用

同样,它作为构造函数的一种,当用户没有自定义拷贝函数,系统会自动的生成一个默认的拷贝函数来进行对象之间的位拷贝,默认拷贝函数的功能是把初始值对象的每一个数据成员的值依次复制新的对象中。

用户自己定义一个拷贝函数的一般形式为:

  1. 类名(类名&对象名)
  2. {
  3. ...
  4. }

下面3种情况相当于用一个已经存在的对象去初始化新建的对象。

  1. 当用类的一个对象去初始化该类的另一个对象时(显示调用)。
  2. 如果函数的形参是类对象,调用函数时,将对像作为函数实参传递给函数的形参时(隐含调用)。
  3. 如果函数的返回值是类的对象,函数执行完成,将返回值返回(隐含调用)。
  1. #include<iostream>
  2. usingnamespace std;
  3. classTime{
  4. private:
  5. int Y,M,D;
  6. public:
  7. Time(int y=0,int m=0,int d=0)
  8. {
  9. Y=y,M=m,D=d;
  10. cout<<"constructor:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
  11. }
  12. ~Time()
  13. {
  14. cout<<"destructor:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
  15. }
  16. Time(Time& t)
  17. {
  18. cout<<"copy constructor before call:year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
  19. Y=t.Y;
  20. M=t.M;
  21. D=t.D;
  22. }
  23. void showTime()
  24. {
  25. cout<<"year is:"<<Y<<":Month is :"<<M<<":day is :"<<D<<endl;
  26. }
  27. };
  28. Time fun(Time t)
  29. {
  30. return t;
  31. }
  32. int main()
  33. {
  34. Time t1(1991,10,7);
  35. Time t2(1991,11,6);
  36. Time t3(t1);
  37. fun(t2);
  38. Time t4;
  39. t4=t2;
  40. return0;
  41. }

运行结果:

  1. constructor:year is:1991;Monthis:10;day is:7
  2. constructor:year is:1991;Monthis:11;day is:6
  3. copy constructor before call:year is:967104272;Monthis:32767;day is:4197060
  4. copy constructor before call:year is:1;Monthis:0;day is:4197645
  5. copy constructor before call:year is:967104568;Monthis:32767;day is:4197552
  6. destructor:year is:1991;Monthis:11;day is:6
  7. destructor:year is:1991;Monthis:11;day is:6
  8. constructor:year is:0;Monthis:0;day is:0
  9. destructor:year is:1991;Monthis:11;day is:6
  10. destructor:year is:1991;Monthis:10;day is:7
  11. destructor:year is:1991;Monthis:11;day is:6
  12. destructor: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行。

  1. 拷贝函数作用是用一个已经存在的类对象去初始化一个新的类对象,在对象之间进行赋值操作,拷贝函数不会被调用(实际上就上面3种情况会调用拷贝函数)。
  2. 创建对象时,构造函数与拷贝函数有且只有一个被调用。
  3. 当对象作为函数的返回值时,需要调用拷贝函数,这时系统会在堆中动态创建一个临时对象,将函数返回的对象拷贝给该临时对象,并把该临时对象的地址存储在寄存器中,最后由临时对象完成函数返回值的传递。

4. 总结

最近在学习c++的时候,一直没搞懂 c++中 各个特殊函数的调用情况,然后查看了下以前的课本[c++语言程序设计教程],并做了以上学习笔记方便后面查阅,希望能够对c++ 中这3个特殊函数有个大概了解(毕竟在读书的时候根本没看过,那时学习马马虎虎只是为了过考试而已),而且觉得像这些语言细节特性对后面深入学习和工作还是非常有用的。

c++中构造函数与析构函数的更多相关文章

  1. (转载)C++中, 构造函数和析构函数能不能被显示调用?

    (转载)http://blog.csdn.net/zhangxinrun/article/details/6056321 代码: view plaincopy to clipboardprint?#i ...

  2. C++学习笔记(7)----类的数组中构造函数和析构函数的调用顺序

    C++类的数组中构造函数和析构函数的调用顺序(2) 对于如下的代码: #include<iostream> using namespace std; class CBase { priva ...

  3. 显示调用C++中构造函数和析构函数(有什么弊端)

    1.C++中, 构造函数和析构函数可以被显示调用. 显示调用默认构造函数的语法: a.A::A();(不能写成a.A();) , 显示调用非默认构造函数的语法: a.A::A(7);(不能写成a.A( ...

  4. C++C++中构造函数与析构函数的调用顺序

    http://blog.csdn.net/xw13106209/article/details/6899370 1.参考文献 参考1: C++继承中构造函数.析构函数调用顺序及虚函数的动态绑定 参考2 ...

  5. C++中构造函数和析构函数的调用顺序

    一般而言,析构函数调用的顺序和构造函数调用顺序相反,但是,对象的存储类别可以改变调用析构函数的顺序.举例说明: CreateAndDestroy类的定义 CreateAndDestroy类的成员函数的 ...

  6. C#中构造函数和析构函数的用法

    构造函数与析构函数是一个类中看似较为简单的两类函数,但在实际运用过程中总会出现一些意想不到的运行错误.本文将较系统的介绍构造函数与析构函数的原理及在C#中的运用,以及在使用过程中需要注意的若干事项.一 ...

  7. C/C++中构造函数和析构函数能否被继承

    http://bbs.csdn.net/topics/390160673 标准方面做了要求的.Even though destructors are not inherited 构造函数和析构函数是不 ...

  8. (转)PHP中构造函数和析构函数解析

    --http://www.jb51.net/article/56047.htm 构造函数 void __construct ([ mixed $args [, $... ]] ) PHP 5 允行开发 ...

  9. c++中构造函数 、析构函数的作用域详解

    我们知道,在c++中,析构函数是在函数作用域尾部执行析构函数,从而释放对象,但是有一种情况下,析构函数作用域发生变化,请看下面的例子,定义了一个Stock类,Stock类存放在stock.h中,主调用 ...

随机推荐

  1. ES 6 新特性笔记

    let 与 var 的区别 功能 let var 块级作用域 ️ 变量提升 ️ 重复声明(相同作用域内) ️ var 没有块级作用域的解决方法 使用函数替代块级作用域,以保证变量的正常使用,如: .. ...

  2. shell判断新字符串列表是否在老字符串列表中

    for sn in `cat 12.30-new`;do if ! [[ `cat 12.30-old` =~ $sn ]];then echo $sn; fi; done

  3. python requests库的简单运用

    python requests的简单运用 使用pycharm获取requests包 ctrl+alt+s Project:pythonProject pythoninterpreter 点+号搜索 使 ...

  4. SpringBoot 处理跨域请求问题

    增加一个配置类 import org.springframework.context.annotation.Bean; import org.springframework.context.annot ...

  5. c++之面试(5)

    问题描述 为什么用自增作为主键? 解释 做为主键时,uuid和自增相比较,自增更适合.原因: 1 uuid是无序的, 插入数据时,页的位置会发生变化,页分裂,速度慢. 2 uuid占的空间大,并且in ...

  6. C++ 获取函数耗时

    C++ 记录耗时 #include <sys/timeb.h> #include <stdio.h> long long getSystemTime() { struct ti ...

  7. 【九度OJ】题目1087:约数的个数 解题报告

    [九度OJ]题目1087:约数的个数 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1087 题目描述: 输入n个整数,依次 ...

  8. 【LeetCode】268. Missing Number 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 求和 异或 日期 题目地址:https://leet ...

  9. B. Arpa's weak amphitheater and Mehrdad's valuable Hoses

    B. Arpa's weak amphitheater and Mehrdad's valuable Hoses time limit per test 1 second memory limit p ...

  10. RMQ(ST(Sparse Table))(转载)

    1. 概述 RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A ...