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

学习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();
时间: 2024-11-08 04:53:32

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

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

1. 本人学习鸡啄米课程的笔记记录,用来记录学习的历程和进度 2. 构造函数 我们在声明一个变量时,如果对它进行了初始化,那么在为此变量分配内存空间时还会向内存单元中写入变量的初始化.声明对象有相似的过程,程序执行时遇到对象声明语句时会向操作系统申请一定的内存空间来存放这个对象,但是它能像一般变量那样初始化时写入指定的初始值吗?类的对象太复杂了,要实现这一点不太容易,这就需要构造函数来实现. 构造函数的作用就是在对象被创建时利用特定的初始值构造对象,把对象置于某一个初始状态,它在对象被创建的时候

C++学习之构造函数和析构函数

1 /* 2 3 4 实验启示1:构造子类对象时,先调用父类构造函数,后调用子类构造函数 5 实验启示2:析构子类对象时,先调用子类析构函数,后调用父类析构函数 6 实验启示3:在这个对象生命周期结束时,析构函数会自动调用 7 8 9 */ 10 11 #include<iostream> 12 13 int flag; 14 15 class Animal 16 { 17 18 public: 19 20 int weight; 21 int length; 22 double hungry

c++学习笔记5,多重继承中派生类的构造函数与析构函数的调用顺序(二)

现在来测试一下在多重继承,虚继承,MI继承中虚继承中构造函数的调用情况. 先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; }

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

在C++的类中,都会有一个或多个构造函数.一个析构函数.一个赋值运算操作符.即使我们自己定义的类中,没有显示定义它们,编译器也会声明一个默认构造函数.一个析构函数和一个赋值运算操作符.例如: 1 //声明一个空类 2 class Empty{}; 3 4 //但是这个空类和下面这个类是等同的 5 class Empty 6 { 7 Empty(){.....}; //默认构造函数 8 Empty( const Empty & rhs ){......} //复制构造函数 9 ~Empty(){.

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

10.3.1 声明和定义构造函数构造函数原型:// constructor prototype with some default argumentsStock(const string &co, long n = 0, double pr = 0.0);构造函数定义:// constructor definitionStock::Stock(const string & co, long n, double pr){    company = co;    if (n < 0)  

构造函数与析构函数(其中有两点值得学习)

// 构造函数与析构函数.cpp : 定义控制台应用程序的入口点.//要学习的两点://要想表现出析构函数,必须在对象外面加大括号!!//  while (cin.get() != '\n')   num++;#include "stdafx.h"#include<iostream>using namespace std;class Count{public:    Count();    ~Count();    void process();private:    in

C++ Primer 学习笔记_18_类与数据抽象(4)_构造函数、析构函数、explicit关键字、赋值与初始化、类成员的显式初始化

引言: 构造函数确保每个对象在创建时自动调用,以确保每个对象的数据成员都有合适的初始值. 一.构造函数.默认构造函数 1.构造函数 --构造函数是特殊的成员函数 --构造函数是为了保证对象的每个数据成员都被正确初始化 --函数名和类名完全相同 --不能定义构造函数的类型(返回类型),也不能使用void --通常情况下构造函数应声明为公有函数,一般被隐式地调用. --构造函数被声明为私有有特殊的用途,比如单例模式. (1).构造函数可以被重载 一般而言,不同的构造函数允许用户指定不同的方式来初始化

C++文件头,命名空间,new和delete,内联函数,引用,函数重载,构造函数和析构函数,深拷贝和浅拷贝,explict,this指针

 目  录 1       开始学习C++.............................................................................................................. 4 1.1       C++的头文件.................................................................................................

构造函数与析构函数2

// 构造函数与析构函数2.cpp : 定义控制台应用程序的入口点.//学习动态内存单元的申请 #include "stdafx.h"#include<iostream>using namespace std;class Student{public:    Student();    Student(int pid, char*pname, float s);    void modify(float s);    void display();    ~Student(