c++ --> 构造函数与析构函数

一、构造函数的介绍
 

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

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

 #include <iostream>

     using namespace std;

     class Point
     {
         public:
             Point()     //声明并定义构造函数
             {
                 cout<<"自定义的构造函数被调用...\n";
                 xPos = 100;         //利用构造函数对数据成员 xPos, yPos进行初始化
                 yPos = 100;
             }
             void printPoint()
             {
                 cout<<"xPos = " << xPos <<endl;
                 cout<<"yPos = " << yPos <<endl;
             }

         private:
             int xPos;
             int yPos;
     };

     int main()
     {
         Point M;    //创建对象M
         M.printPoint();

         return 0;
     }

编译运行的结果:

 自定义的构造函数被调用...
         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, 而不是一个随机值。
        
   需要提示一下的是, 构造函数的定义也可放在类外进行。

三、有参数的构造函数

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

#include <iostream>

    using namespace std;

    class Point
    {
        public:
            Point(int x = 0, int y = 0)     //带有默认参数的构造函数
            {
                cout<<"自定义的构造函数被调用...\n";
                xPos = x;         //利用传入的参数值对成员属性进行初始化
                yPos = y;
            }
            void printPoint()
            {
                cout<<"xPos = " << xPos <<endl;
                cout<<"yPos = " << yPos <<endl;
            }

        private:
            int xPos;
            int yPos;
    };

    int main()
    {
        Point M(10, 20);    //创建对象M并初始化xPos,yPos为10和20
        M.printPoint();

        Point N(200);       //创建对象N并初始化xPos为200, yPos使用参数y的默认值0
        N.printPoint();

        Point P;            //创建对象P使用构造函数的默认参数
        P.printPoint();

        return 0;
    }

编译运行的结果:

自定义的构造函数被调用...
        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; 是相同的。
    
    与在构造函数体内进行初始化不同的是, 使用初始化表进行初始化是在构造函数被调用以前就完成的。每个成员在初始化表中只能出现一次, 并且初始化的顺序不是取决于数据成员在初始化表中出现的顺序, 而是取决于在类中声明的顺序。
    
    此外, 一些通过构造函数无法进行初始化的数据类型可以使用初始化表进行初始化, 如: 常量成员和引用成员, 这部分内容将在后面进行详细说明。使用初始化表对对象成员进行初始化的完整示例:

#include <iostream>

    using namespace std;

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

            void printPoint()
            {
                cout<<"xPos = " << xPos <<endl;
                cout<<"yPos = " << yPos <<endl;
            }

        private:
            int xPos;
            int yPos;
    };

    int main()
    {
        Point M(10, 20);    //创建对象M并初始化xPos,yPos为10和20
        M.printPoint();

        return 0;
    }

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

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

#include <iostream>
     #include <cstring>

     using namespace std;

     class Book
     {
         public:
             Book( const char *name )      //构造函数
             {
                 bookName = new char[strlen(name)+1];
                 strcpy(bookName, name);
             }
             ~Book()                 //析构函数
             {
                 cout<<"析构函数被调用...\n";
                 delete []bookName;  //释放通过new申请的空间
             }
             void showName() { cout<<"Book name: "<< bookName <<endl; }

         private:
             char *bookName;
     };

     int main()
     {
         Book CPP("C++ Primer");
         CPP.showName();

         return 0;

     }

编译运行的结果:

        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-10-29 01:42:41

c++ --> 构造函数与析构函数的相关文章

C++ 构造函数和析构函数

构造函数: 作用: 1)分配空间:分配非静态数据成员的存储空间 2)初始化成员:初始化非静态数据成员 分配空间: 1)含有指针变量,需要程序员显式申请空间(使用new申请) 2)非指针变量:由系统自动分配空间 初始化成员: 1)使用赋值语句初始化:一般的变量 2)使用表达式表初始化:一般的变量 +  Const成员,引用成员,对象成员 调用时机:在定义对象的时候,系统自动调用构造函数 1)对象被创建时,自动调用构造函数 Coord p1(1); Coord p2=1;  //此时也会调用构造函数

python中的构造函数和构造函数和析构函数的作用

构造函数和构造函数和析构函数都属于python中的特殊方法 其中的"__del__"就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间

.NET 基础 一步步 一幕幕[面向对象之构造函数、析构函数]

构造函数.析构函数 构造函数: 语法: //无参的构造函数 [访问修饰符] 函数名() :函数名必须与类名相同. //有参的构造函数 [访问修饰符] 函数名(参数列表):函数名必须与类名相同. 作用:帮助我们初始化对象(给对象的每个属性依次的赋值) 构造函数是一个特殊的方法: 1).构造函数没有返回值,连void也不能写. 2).构造函数的名称必须跟类名一样. 3).构造函数有无参和有参之分. 创建对象的时候会执行构造函数,构造函数是可以有重载的. *** 类当中会有一个默认的无参数的构造函数,

python中的构造函数和析构函数

python中的特殊方法,其中两个,构造函数和析构函数的作用: 比说“__init__”这个构造函数,具有初始化的作用,也就是当该类被实例化的时候就会执行该函数.那么我们就可以把要先初始化的属性放到这个函数里面.如下程序: 其中的“__del__”就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间. 待深入……………………

构造函数和析构函数中得异常处理

一. 构造函数 总结如下: 1. 构造函数中抛出异常,会导致析构函数不能被调用,但对象本身已申请到的内存资源会被系统释放(已申请到资源的内部成员变量会被系统依次逆序调用其析构函数). 2. 因为析构函数不能被调用,所以可能会造成内存泄露或系统资源未被释放. 3. 构造函数中可以抛出异常,但必须保证在构造函数抛出异常之前,把系统资源释放掉,防止内存泄露.(如何保证???使用auto_ptr???) 试验代码: 1 //ExceptionConstructor.h 2 #pragma once 3

对C++中派生类的构造函数和析构函数的认识

一:构造函数 形式:派生类名::派生类名:基类名1(参数1),基类名2(参数2),--基类名n(参数n),数据成员1(参数1),数据成员2(参数2),--数据成员n(参数n){ 各种操作的说明 } 执行过程:先执行基类的构造函数,再进行数据成员的赋值,最后执行函数体. 其中基类名和数据成员的顺序是由在派生类的定义中声明的顺序决定执行的顺序的,因此它们的顺序是任意的,但为了可读性,还是最好按顺序写. 如果基类只有默认构造函数,则基类名和参数表可以不用写出来. 二:复制构造函数 派生类的构造函数的形

拷贝构造,深度拷贝,关于delete和default相关的操作,explicit,类赋初值,构造函数和析构函数,成员函数和内联函数,关于内存存储,默认参数,静态函数和普通函数,const函数,友元

 1.拷贝构造 //拷贝构造的规则,有两种方式实现初始化. //1.一个是通过在后面:a(x),b(y)的方式实现初始化. //2.第二种初始化的方式是直接在构造方法里面实现初始化. 案例如下: #include<iostream> //如果声明已经定义,边不会生成 class classA { private: int a; int b; public: //拷贝构造的规则,有两种方式实现初始化 //1.一个是通过在后面:a(x),b(y)的方式实现初始化 //2.第二种初始化的方式是直

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

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

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

http://blog.csdn.net/xw13106209/article/details/6899370 1.参考文献 参考1: C++继承中构造函数.析构函数调用顺序及虚函数的动态绑定 参考2: 构造函数.拷贝构造函数和析构函数的的调用时刻及调用顺序 参考3: C++构造函数与析构函数的调用顺序 2.构造函数.析构函数与拷贝构造函数介绍 2.1构造函数 构造函数不能有返回值 缺省构造函数时,系统将自动调用该缺省构造函数初始化对象,缺省构造函数会将所有数据成员都初始化为零或空 创建一个对象

Effective C++ Item 09-绝不在构造函数和析构函数中调用virtual函数

Item 09-绝不在构造函数和析构函数中调用virtual函数(Never call virtual functions during construction or destruction) Why? 由于base class构造函数的执行更早于derived class构造函数,当base class构造函数执行derived class的成员变量尚未初始化.如果期间调用的virtual函数下降至derived class阶层,要知道derived class的函数几乎必然取用local成