• 从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字


    一、构造函数、默认构造函数

    (1)、构造函数

    构造函数是特殊的成员函数
    创建类类型的新对象,系统自动会调用构造函数
    构造函数是为了保证对象的每个数据成员都被正确初始化

    函数名和类名完全相同
    不能定义构造函数的类型(返回类型),也不能使用void
    通常情况下构造函数应声明为公有函数,一般被隐式地调用。
    构造函数被声明为私有有特殊的用途,比如单例模式,以后详谈。
    构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)

    (2)、默认构造函数

    不带参数的构造函数
    如果程序中未声明,则系统自动产生出一个默认构造函数,是空函数

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    #ifndef _TEST_H_
    #define _TEST_H_

    class Test
    {
    public:
         // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的
         // 默认的构造函数
        Test();
        Test( int num);
         void Display();

        Test & operator=( const Test &other);

        ~Test();
    private:
         int num_;
    };
    #endif  // _TEST_H_
     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
     
    #include  "Test.h"
    #include <iostream>
    using  namespace std;

    // 不带参数的构造函数称为默认构造函数
    Test::Test()
    {
        num_ =  0;
        cout <<  "Initializing Default" << endl;
    }

    Test::Test( int num)
    {
        num_ = num;
        cout <<  "Initializing " << num_ << endl;
    }

    Test::~Test()
    {
        cout <<  "Destroy " << num_ << endl;
    }

    void Test::Display()
    {
        cout <<  "num=" << num_ << endl;
    }

    Test &Test:: operator=( const Test &other)
    {
        cout <<  "Test::operator=" << endl;
         if ( this == &other)
             return * this;

        num_ = other.num_;
         return * this;
    }


    C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    #include  "Test.h"

    int main( void)
    {
        Test t;
        t.Display();

        Test t2( 10);
        t2.Display();

        Test *t3 =  new Test( 20);     // new operator
        t3->Display();

         delete t3;

         return  0;
    }

    用上面的程序测试,输出为:


    可以看到构造函数是被自动调用的,且构造函数可以被重载调用;栈上的对象生存期到了会自动调用析构函数;而new operator 做了两件事,一个是创建了对象内存,一个是调用构造函数;堆上的内存需要delete 释放,做了两件事,一是调用析构函数,二是释放内存。


    还有一个注意点,全局对象的构造先于main函数执行,如下:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    #include  "Test.h"
    #include <iostream>
    using  namespace std;

    Test t( 10);

    int main( void)
    {
        cout <<  "Entering main ..." << endl;
        cout <<  "Exiting main ..." << endl;
         return  0;
    }

    在return 0 时全局变量的生存期也到了,故也会自动调用析构函数。


    二、析构函数

    函数名和类名相似(前面多了一个字符“~”)
    没有返回类型
    没有参数
    析构函数不能被重载
    如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:
    类名::~默认析构函数名( )
    {
    }
    默认析构函数是一个空函数

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    #include  "Test.h"

    int main( void)
    {
        Test t[ 2] = { 1020};

        Test *t2 =  new Test( 2);
         delete t2;

        Test *t3 =  new Test[ 2];
         delete[] t3;

         return  0;
    }

    注意  Test t[2] = {1020}; 中10,20是当作参数传递给每个对象的构造函数的,如果没有对应的构造函数,比如只有2个参数的构造函数,那么编译是失败的。


    实际上,构造函数和析构函数都是可以被显式调用的,只是很少这样做,可以参考这里


    三、转换构造函数

    单个参数的构造函数不一定是转换构造函数
    将其它类型转换为类类型
    类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    #include  "Test.h"

    int main( void)
    {
        Test t( 10);      // 带一个参数的构造函数,充当的是普通构造函数的功能

        t =  20;          // 将20这个整数赋值给t对象
         // 1、调用转换构造函数将20这个整数转换成类类型 (生成一个临时对象)
         // 2、将临时对象赋值给t对象(调用的是=运算符)

        Test t2;

         return  0;
    }


    可以看到初始化了一个临时对象,传递参数20,然后调用赋值运算符operator=,接着释放临时对象,最后释放的对象是已经被更改过的t 。赋值运算符的格式为:Test& Test::operator=(const Test& other);事实上如果没有自己实现,编译器也会实现一个默认的赋值运算符,做的事情跟我们现在实现的函数一样。


    四、赋值与初始化的区别

    在初始化语句中的等号不是运算符。

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    #include  "Test.h"

    int main( void)
    {
        Test t =  10;         // 等价于Test t(10); 这里的=不是运算符,表示初始化。

        t =  20;              // 赋值操作

        Test t2;
        t = t2;              // 赋值操作 t.operator=(t2);


         return  0;
    }


    第一条语句是初始化,后面是赋值操作,参照上面临时对象的创建销毁,赋值运算符的调用可以理解输出。


    五、explicit 关键字

    只提供给类的构造函数使用的关键字。
    编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象


    假设在Test 类的构造函数Test(int num); 前面加上explicit 关键字,那么Test t = 10; 或者 t = 20; 这种语句都是编译不通过的,因为不允许隐式转换。

  • 相关阅读:
    警惕 InnoDB 和 MyISAM 创建 Hash 索引陷阱
    从头认识java-18.2 主要的线程机制(5)-守护线程与非守护线程
    leetcode
    算法学习笔记(五) 递归之 高速幂、斐波那契矩阵加速
    No WebApplicationContext found: no ContextLoaderListener registered?报错解决
    poj 3041 Asteroids (最小点覆盖)
    C语言函数--E
    APDU命令的结构和处理【转】
    Linux ALSA声卡驱动之一:ALSA架构简介【转】
    Linux 系统内核空间与用户空间通信的实现与分析
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3155473.html
Copyright © 2020-2023  润新知