学习C++ -> 构造函数与析构函数

一、构造函数的介绍
    1. 构造函数的作用
        构造函数主要用来在创建对象时完成对对象属性的一些初始化等操作, 当创建对象时, 对象会自动调用它的构造函数。一般来说, 构造函数有以下三个方面的作用:
            ■ 给创建的对象建立一个标识符;
            ■ 为对象数据成员开辟内存空间;
            ■ 完成对象数据成员的初始化。
        
    2. 默认构造函数
        当用户没有显式的去定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为 "默认构造函数", 默认构造函数不能完成对象数据成员的初始化, 只能给对象创建一标识符, 并为对象中的数据成员开辟一定的内存空间。
        
    3. 构造函数的特点
        无论是用户自定义的构造函数还是默认构造函数都主要有以下特点:
            ①. 在对象被创建时自动执行;
            ②. 构造函数的函数名与类名相同;
            ③. 没有返回值类型、也没有返回值;
            ④. 构造函数不能被显式调用。

        #给Python程序员的注释: C++中的构造函数类似于Python中的 __init__ 方法.

二、构造函数的显式定义
    由于在大多数情况下我们希望在对象创建时就完成一些对成员属性的初始化等工作, 而默认构造函数无法满足我们的要求, 所以我们需要显式定义一个构造函数来覆盖掉默认构造函数以便来完成必要的初始化工作, 当用户自定义构造函数后编译器就不会再为对象生成默认构造函数。
    
    在构造函数的特点中我们看到, 构造函数的名称必须与类名相同, 并且没有返回值类型和返回值, 看一个构造函数的定义:

 1     #include <iostream>
2
3 using namespace std;
4
5 class Point
6 {
7 public:
8 Point() //声明并定义构造函数
9 {
10 cout<<"自定义的构造函数被调用...\n";
11 xPos = 100; //利用构造函数对数据成员 xPos, yPos进行初始化
12 yPos = 100;
13 }
14 void printPoint()
15 {
16 cout<<"xPos = " << xPos <<endl;
17 cout<<"yPos = " << yPos <<endl;
18 }
19
20 private:
21 int xPos;
22 int yPos;
23 };
24
25 int main()
26 {
27 Point M; //创建对象M
28 M.printPoint();
29
30 return 0;
31 }

编译运行的结果:

        自定义的构造函数被调用...
xPos = 100
yPos = 100 Process returned 0 (0x0) execution time : 0.453 s
Press any key to continue.

    代码说明:
        在Point类的 public 成员中我们定义了一个构造函数 Point() , 可以看到这个Point构造函数并不像 printPoint 函数有个void类型的返回值, 这正是构造函数的一特点。在构造函数中, 我们输出了一句提示信息, "自定义的构造函数被调用...", 并且将对象中的数据成员xPos和yPos初始化为100。
        
        在 main 函数中, 使用 Point 类创建了一个对象 M, 并调用M对象的方法 printPoint 输出M的属性信息, 根据输出结果看到, 自定义的构造函数被调用了, 所以 xPos和yPos 的值此时都是100, 而不是一个随机值。
        
        需要提示一下的是, 构造函数的定义也可放在类外进行。

三、有参数的构造函数
    在上个示例中实在构造函数的函数体内直接对数据成员进行赋值以达到初始化的目的, 但是有时候在创建时每个对象的属性有可能是不同的, 这种直接赋值的方式显然不合适。不过构造函数是支持向函数中传入参数的, 所以可以使用带参数的构造函数来解决该问题。

 1     #include <iostream>
2
3 using namespace std;
4
5 class Point
6 {
7 public:
8 Point(int x = 0, int y = 0) //带有默认参数的构造函数
9 {
10 cout<<"自定义的构造函数被调用...\n";
11 xPos = x; //利用传入的参数值对成员属性进行初始化
12 yPos = y;
13 }
14 void printPoint()
15 {
16 cout<<"xPos = " << xPos <<endl;
17 cout<<"yPos = " << yPos <<endl;
18 }
19
20 private:
21 int xPos;
22 int yPos;
23 };
24
25 int main()
26 {
27 Point M(10, 20); //创建对象M并初始化xPos,yPos为10和20
28 M.printPoint();
29
30 Point N(200); //创建对象N并初始化xPos为200, yPos使用参数y的默认值0
31 N.printPoint();
32
33 Point P; //创建对象P使用构造函数的默认参数
34 P.printPoint();
35
36 return 0;
37 }

编译运行的结果:

        自定义的构造函数被调用...
xPos = 10
yPos = 20
自定义的构造函数被调用...
xPos = 200
yPos = 0
自定义的构造函数被调用...
xPos = 0
yPos = 0 Process returned 0 (0x0) execution time : 0.297 s
Press any key to continue.

    代码说明:
        在这个示例中的构造函数 Point(int x = 0, int y = 0) 使用了参数列表并且对参数进行了默认参数设置为0。在 main 函数中共创建了三个对象 M, N, P。
            M对象不使用默认参数将M的坐标属性初始化10和20;
            N对象使用一个默认参数y, xPos属性初始化为200;
            P对象完全使用默认参数将xPos和yPos初始化为0。

三、构造函数的重载
    构造函数也毕竟是函数, 与普通函数相同, 构造函数也支持重载, 需要注意的是, 在进行构造函数的重载时要注意重载和参数默认的关系要处理好, 避免产生代码的二义性导致编译出错, 例如以下具有二义性的重载:

        Point(int x = 0, int y = 0)     //默认参数的构造函数
{
xPos = x;
yPos = y;
} Point() //重载一个无参构造函数
{
xPos = 0;
yPos = 0;
}

在上面的重载中, 当尝试用 Point 类重载一个无参数传入的对象 M 时, Point M; 这时编译器就报一条 error: call of overloaded 'Point()' is ambiguous 的错误信息来告诉我们说 Point 函数具有二义性, 这是因为Point(int x = 0, int y = 0) 全部使用了默认参数, 即使我们不传入参数也不会出现错误, 但是在重载时又重载了一个不需要传入参数了构造函数 Point(), 这样就造成了当创建对象都不传入参数时编译器就不知道到底该使用哪个构造函数了, 就造成了二义性。

四、初始化表达式
    对象中的一些数据成员除了在构造函数体中进行初始化外还可以通过调用初始化表来进行完成, 要使用初始化表来对数据成员进行初始化时使用 : 号进行调出, 示例如下:

        Point(int x = 0, int y = 0):xPos(x), yPos(y)  //使用初始化表
{
cout<<"调用初始化表对数据成员进行初始化!\n";
}

在 Point 构造函数头的后面, 通过单个冒号 : 引出的就是初始化表, 初始化的内容为 Point 类中int型的 xPos 成员和 yPos成员, 其效果和 xPos = x; yPos = y; 是相同的。
    
    与在构造函数体内进行初始化不同的是, 使用初始化表进行初始化是在构造函数被调用以前就完成的。每个成员在初始化表中只能出现一次, 并且初始化的顺序不是取决于数据成员在初始化表中出现的顺序, 而是取决于在类中声明的顺序。
    
    此外, 一些通过构造函数无法进行初始化的数据类型可以使用初始化表进行初始化, 如: 常量成员和引用成员, 这部分内容将在后面进行详细说明。使用初始化表对对象成员进行初始化的完整示例:

五、析构函数
    与构造函数相反, 析构函数是在对象被撤销时被自动调用, 用于对成员撤销时的一些清理工作, 例如在前面提到的手动释放使用 new 或 malloc 进行申请的内存空间。析构函数具有以下特点:
        ■ 析构函数函数名与类名相同, 紧贴在名称前面用波浪号 ~ 与构造函数进行区分, 例如: ~Point();
        ■ 构造函数没有返回类型, 也不能指定参数, 因此析构函数只能有一个, 不能被重载;
        ■ 当对象被撤销时析构函数被自动调用, 与构造函数不同的是, 析构函数可以被显式的调用, 以释放对象中动态申请的内存。

    #给Python程序员的注释: C++中的析构函数类似于Python中的 __del__ 方法.

当用户没有显式定义析构函数时, 编译器同样会为对象生成一个默认的析构函数, 但默认生成的析构函数只能释放类的普通数据成员所占用的空间, 无法释放通过 new 或 malloc 进行申请的空间, 因此有时我们需要自己显式的定义析构函数对这些申请的空间进行释放, 避免造成内存泄露。

 1 #include <iostream>
2 #include <cstring>
3
4 using namespace std;
5
6 class Book
7 {
8 public:
9 Book( const char *name ) //构造函数
10 {
11 bookName = new char[strlen(name)+1];
12 strcpy(bookName, name);
13 }
14 ~Book() //析构函数
15 {
16 cout<<"析构函数被调用...\n";
17 delete []bookName; //释放通过new申请的空间
18 }
19 void showName() { cout<<"Book name: "<< bookName <<endl; }
20
21 private:
22 char *bookName;
23 };
24
25 int main()
26 {
27 Book CPP("C++ Primer");
28 CPP.showName();
29
30 return 0;
31
32 }

编译运行的结果:

        Book name: C++ Primer
析构函数被调用... Process returned 0 (0x0) execution time : 0.266 s
Press any key to continue.

    代码说明:
        代码中创建了一个 Book 类, 类的数据成员只有一个字符指针型的 bookName, 在创建对象时系统会为该指针变量分配它所需内存, 但是此时该指针并没有被初始化所以不会再为其分配其他多余的内存单元。在构造函数中, 我们使用 new 申请了一块 strlen(name)+1 大小的空间, 也就是比传入进来的字符串长度多1的空间, 目的是让字符指针 bookName 指向它, 这样才能正常保存传入的字符串。
        
        在 main 函数中使用 Book 类创建了一个对象 CPP, 初始化 bookName 属性为 "C++ Primer"。从运行结果可以看到, 析构函数被调用了, 这时使用 new 所申请的空间就会被正常释放。
        
        自然状态下对象何时将被销毁取决于对象的生存周期, 例如全局对象是在程序运行结束时被销毁, 自动对象是在离开其作用域时被销毁。 
        
    如果需要显式调用析构函数来释放对象中动态申请的空间只需要使用 对象名.析构函数名(); 即可, 例如上例中要显式调用析构函数来释放 bookName 所指向的空间只要:

        CPP.~Book();

学习C++ -> 构造函数与析构函数的更多相关文章

  1. C++学习之构造函数和析构函数及指针

    C++的构造函数在创建对象时调用,分配内存空间,多少个对象(对象数组)就调用几次构造函数:析构函数在调用结束时调用(可以添加一些最后的处理)以释放内存给其它来用.对于同类型同生命期的对象,先创建的对象 ...

  2. C++学习笔记-构造函数和析构函数

    构造函数和析构函数是C++的重要组成部分,了解构造函数和析构函数有助于深入了解C++ 构造函数 构造函数产生的原因 在C++中,有时候需要在对象创建的时候初始化数据,如果采用普通函数的话,每次初始化都 ...

  3. 鸡啄米:C++编程之十四学习之构造函数和析构函数

    1. 本人学习鸡啄米课程的笔记记录,用来记录学习的历程和进度 2. 构造函数 我们在声明一个变量时,如果对它进行了初始化,那么在为此变量分配内存空间时还会向内存单元中写入变量的初始化.声明对象有相似的 ...

  4. C++学习之类的构造函数、析构函数

    在C++的类中,都会有一个或多个构造函数.一个析构函数.一个赋值运算操作符.即使我们自己定义的类中,没有显示定义它们,编译器也会声明一个默认构造函数.一个析构函数和一个赋值运算操作符.例如: //声明 ...

  5. C++学习之路—继承与派生(二):派生类的构造函数与析构函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 由于基类的构造函数和析构函数是不能被继承的,所以 ...

  6. 《C++ Primer Plus》10.3 类的构造函数和析构函数 学习笔记

    10.3.1 声明和定义构造函数构造函数原型:// constructor prototype with some default argumentsStock(const string &c ...

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

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

  8. C++学习笔记(6)----基类和派生类的构造函数和析构函数的执行顺序

    基类和派生类:构造函数和析构函数的执行顺序 在Visual Studio中,新建控制台工程,构造类如下: #include<iostream> using namespace std; c ...

  9. python学习笔记(二十一)构造函数和析构函数

    python中的特殊方法,其中两个,构造函数和析构函数的作用: 比说“__init__”这个构造函数,具有初始化的作用,也就是当该类被实例化的时候就会执行该函数.那么我们就可以把要先初始化的属性放到这 ...

随机推荐

  1. 初始Socket编程(python)

    通信双方要有一个服务端和一个客户端,所以要分开去写代码. 所以我创建了两个py程序,第一个是服务端:iServer.py 和客户端 iClient.py 服务端: #coding:utf-8from ...

  2. servlet自动获取前端页面提交数据

    servlet自动获取前端页面jsp提交数据 以下是本人在学习过程中,因前端页面提交参数过多,后台servlet封装实体类过于麻烦而写的一个工具类,应用于jsp/servlet数据提交后,基于MVC+ ...

  3. 在CentOS6上编译安装实现LAMP(php-modules)+phpMyAdmin安装过程全记录

    php与apache协作有三种模式:CGI.modules.FastCGI. 在CGI模式下,用户请求php文件时,apache会启动响应进程,调用php处理器处理请求,然后将结果返回给客户端.用户响 ...

  4. LCT学习笔记

    最近自学了一下LCT(Link-Cut-Tree),参考了Saramanda及Yang_Zhe等众多大神的论文博客,对LCT有了一个初步的认识,LCT是一种动态树,可以处理动态问题的算法.对于树分治中 ...

  5. centos7基础学习第一天

    Linux是一个操作系统: 智能手机,Android和ios.Windows: 网站.游戏.QQ.微信等都是运行在Linux系统之上的应用:客户端.服务器端交互的: Linux的起源: Linux之前 ...

  6. Java+Velocity模板引擎集成插件到Eclipse及使用例子

    一.因为我用的是当前最新的Eclipse4.5,Eclipse中安装集成VelocityEclipse插件之前需要先安装其支持插件:Eclipse 2.0 Style Plugin Support 1 ...

  7. java反射获取字段的属性值,以及为字段赋值等方法

    1.获取某个类的属性值 /*利用getter方法获取值(首字母大写) CjJssetDTO obj: */ String filedName = "Cj"+(i+1); Class ...

  8. ABAP 开启制定路径下的文件或网址URL

    REPORT ZTEST001. CALL FUNCTION 'CALL_INTERNET_ADRESS' EXPORTING PI_ADRESS = * PI_TECHKEY = EXCEPTION ...

  9. Windows-universal-samples-master示例 XamlCommanding

    Windows-universal-samples-master XamlCommanding 运行默认如果是 ARM会出现没有引用System,只要在调试选择CPU为PC的就好 默认 选择PC平台 ...

  10. VS2017生成解决方案报错,提示对路径的访问被拒绝

    目前我用的vs2017的版本是15.3.5.生成解决方案有时会提示如下: 开始以为是权限的问题,找到相应的目录设置everyone权限,再次生成还是不行.重启VS试了下,还是不行. 最后无奈重启下电脑 ...