C++六:析构函数

一、析构函数

        函数名与类名相同,在函数名前面添加~,函数没有返回值,没有参数,当对象销毁的时候,系统会自动调用(析构函数可以释放成员空间)。

        作用:释放类中的数据成员(例如:堆空间,映射空间,打开的文件......)。

析构函数的特点:

1、函数名 与 类名 同名 ,且在函数前添加 ~    如:~类名

2、函数没有返回值

3、函数没有参数

4、在对象销毁时自动调用

为什么需要析构函数?

#include <iostream>

using namespace std;

class base
{
    public:
        //构造函数
        base()
        {
            cout << "Call constructor..." << endl;
            p = new char[1024];    //分配堆空间
        }
    private:
        char *p;
};

void test()
{
    //创建一个 base 的对象
    base a;    //生命周期 只在 test函数 中有效
}

int main()
{
    //调用test函数
    test();

    while(1);

    return 0;
}

分析:

二、析构函数的定义语法

class 类名

{

        public:

        ~类名()        //->析构函数

        {

        }

}

demo:

class base

{

        public:

        ~base()

        {

                cout << "析构函数" << endl;

        }

};

#include <iostream>

using namespace std;

class base
{
    public:
        //构造函数
        base()
        {
            cout << "Call constructor..." << endl;
            p = new char[1024];    //分配堆空间
        }

        //析构函数
        ~base()
        {
            cout << "Release heap space..." << endl;
            delete p;
        }
    private:
        char *p;
};

void test()
{
    //创建一个 base 的对象
    base a;    //生命周期 只在 test函数 中有效
}

int main()
{
    while(1)
    {
        //调用test函数
        test(); 
    }

    return 0;
}

编译运行:

        可以看到在调用结束后,会自动调用析构函数去释放堆空间。

三、析构函数的引用例子

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

class base
{
    public:
        //构造函数
        base()
        {
            //打开一个文件
            fd = open("my.txt",O_RDWR);
            if(fd < 0)
            {
                cout << "File opening failed...." << endl;
                exit(0);
            }
            cout << "Open file success: " << fd << endl;
        }

        //析构函数
        ~base()
        {
            cout << "Close file..." << endl;
            close(fd);
        }
    private:
        int fd;
};

int main()
{
    while(1)
    {
        //创建 base 对象
        base a;    //局部对象,只在{}内有效

        //base *p = new base;    //堆空间对象,base空间不被释放,就不调用析构
        //delete p;    //释放 base 的空间,自动调用析构函数
    }

    return 0;
}

编译运行:

练习

        设计一个文件类  class file,构造函数中,默认打开一个my.txt文件,设计一个cat接口,读取文件中的所有数据。并设计析构函数,关闭文件。

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

class file
{
    public:
        //带参数的构造函数
        file(const char *fileName="my.txt")
        {
            //打开一个文件
            fd = open(fileName,O_RDWR);
            if(fd < 0)
            {
                cout << "File opening failed...." << endl;
                exit(0);
            }
            cout << "Open file success: " << fd << endl;
        }

        //析构函数
        ~file()
        {
            cout << "Close file..." << endl;
            close(fd);
        }

        //读取文件所有数据接口
        void cat()
        {
            char readBuf[1024] = {0};
            while(1)
            {
                if(read(fd,readBuf,sizeof(readBuf)) <= 0)
                {
                    cout << "File read complete..." << endl;
                    break;
                }
            }
            cout << readBuf << endl;
        }
    private:
        int fd;
};

int main()
{
    //创建一个file类对象f
    file f;
    f.cat();

    file b("32test.cpp");
    b.cat();

    return 0;
}

编译运行:

四、析构函数可以重载吗?

        重载的依据是,根据参数列表的不同。但是,析构函数的特点之一就是没有参数,所以不能重载!!!

五、类的大小计算

类的大小计算与结构体的大小计算一致!!!

复习结构体的大小计算:

struct node
{
    char a;
    int b;
    short c;
};

上述结构体的大小为多少字节?

        12个字节。

struct node
{
    char a;    //1,其实分配了4个字节,使用了1个字节,剩下3个
    int b;     //4,而上述剩下的3个字节并不能存放int类型,则需要重新分配4字节,使用4字节,剩下0字节
    short c;   //2,分配4字节,使用2个字节,剩下2个字节
    //所以可以看出,当前结构体浪费了 5个字节 的空间
};

//大小为12个字节,为什么不是7个字节??
//当前结构体最大的数据类型时 int ,所以就是按照 4字节对齐,每次分配 4个字节

因为,结构体空间分配是根据字节对齐原则:根据最大的数据类型进行对齐。

练习:

重新设计上述结构体,让它分配的空间最小。

struct node
{
    int b;     //4,分配4字节,使用4字节,剩下0字节
    short c;   //2,分配4字节,使用2个字节,剩下2个字节
    char a;    //1,上面分配的4字节空间,还剩下2个字节,可以存放char,不需要再分配4字节空间
    //所以可以看出,当前结构体浪费了 1个字节 的空间
};//大小为 8个字节 ,节省了4个字节的空间

类的大小

#include <iostream>

using namespace std;

struct node
{
    char a;
    int b;
    short c;
};

struct node1
{
    int b;
    short c;
    char a;
};

class base
{
    char a;
    int b;
    short c;
};

class base1
{
    int b;
    short c;
    char a;
};

int main()
{
    //计算结构体及类的大小
    cout << "node size: " << sizeof(node) << endl;
    cout << "base size: " << sizeof(base) << endl;
    cout << "node1 size: " << sizeof(node1) << endl;
    cout << "base1 size: " << sizeof(base1) << endl;

    return 0;
}

编译运行:

结论:类的空间大小分配 与 结构体是一模一样的。

        所以在定义类的数据成员时,应该把数据类型 从小到大排序 定义。这样定义出来的类,所需要的空间最小。

class base
{
    char a;        //小
    short c;       //中

    int b;           //大
};        // 8

六、类的大小分配与属性无关

#include <iostream>

using namespace std;

class base
{
    public:            //公有区
        char a;
    protected:         //保护区
        short b;
    private:           //私有区
        int c;
};

class base1
{
    private:           //私有区
        char a;
    protected:         //保护区
        short b;
    public:            //公有区
        int c;
};

int main()
{
    cout << "base: " << sizeof(base) << endl;
    cout << "base1: " << sizeof(base1) << endl;

    return 0;
}

编译运行:

结论:上述,不管属性如何修改,类的大小都是8字节。说明,类的大小与属性无关,只与数据成员的位置有关。这是因为类的空间分配,是根据数据成员 从上往下 依次分配空间的!!!

可以用一个程序,打印它们的地址来看看类是如何分配空间的:

#include <iostream>

using namespace std;

class base
{
    public:
        char a;    //占用地地址 最先分配 0xbfa50754
        int c;     //                   0xbfa50758
        short b;   //占用高地址 最后分配 0xbfa5075c    从上往下依次分配!!!
};

int main()
{
    //定义一个对象
    base a;    //&a 0xbfa50754  &a 就是 &a.a 的地址,因为最开始的就是 a.a
    
    cout << "&a: " << &a << endl;
    cout << "&a.a: " << (void *)&a.a << endl; //&a.a 0xbfa50754
    cout << "&a.c: " << &a.c << endl;         //&a.c 0xbfa50758
    cout << "&a.b: " << &a.b << endl;         //&a.b 0xbfa5075c

    return 0;
}

编译运行:

七、类的大小与成员函数无关

#include <iostream>

using namespace std;

//类的大小与成员函数无关
class base
{
    public:
        void func()
        {
            int a;    //整型数据
            //当函数调用时,才创建,结束时,自动销毁。与类中的成员无关!
        }
    private:
        int date;    //整型数据 类中的数据成员,一直存在类中
};

class base1
{
    public:
        void func()
        {
            //int a;    //整型数据
        }
    private:
        int date;    //整型数据
};

class base2
{
    public:
        void func()
        {
            int a;    //整型数据
        }
    private:
        //int date;    //整型数据
};

class base3
{
    public:
        void func()
        {
            int a;    //整型数据
            int b;
            int c;
        }
    private:
        int date;    //整型数据
};

int main()
{
    //计算类的大小
    cout << "base: " << sizeof(base) << endl;      //4
    cout << "base1: " << sizeof(base1) << endl;    //4
    cout << "base2: " << sizeof(base2) << endl;    //1
    cout << "base3: " << sizeof(base3) << endl;    //4

    return 0;
}

编译运行:

为什么类的大小与成员函数无关?

        因为在函数定义的数据都属于局部变量,在函数未被调用前,这些数据是不会被分配空间的。函数调用结束时,会自动销毁。

八、this指针

        概念:在每一个类的内部都含有一个this指针,指向当前类的首地址。

        作用:用来区分 类内成员 与 类外成员。

#include <iostream>

using namespace std;

class base
{
    public:
        base()
        {
            cout << "this: " << this << endl;    //输出this指针指向的地址
            //0xbfbb814f
        }
};

int main()
{
    //创建一个base类的对象
    base a;
    cout << "&a: " << &a << endl;    //0xbfbb814f

    return  0;
}

编译运行:

#include <iostream>

using namespace std;

class base
{
    public:
        base()
        {
            cout << "this: " << this << endl;    //输出this指针指向的地址
        }

        base(int data)    //类外成员
        {
            //data = data;    //通过构造函数进行初始化,优先使用类外成员
            this->data = data;    //当前类中的data = 形参data(类外)
        }

        void show()
        {
            cout << "data: " << data << endl;
        }
    private:
        int data;    //类内
};

int main()
{
    //创建一个base类的对象
    base a;
    cout << "&a: " << &a << endl;

    base b(100);
    b.show();

    return  0;
}

分析:

练习

完成下述类的赋值:

class base

{

        public:

                base(int a,char b,short c,float d,const char *buf)

                {

                }

        private:

                int a;

                char b;

                short c;

                float d;

                char buf[100];

}

#include <iostream>
#include <string.h>

using namespace std;

class base
{
    public:
        base(int a,char b,short c,float d,const char *buf)
        {
            this->a = a;
            this->b = b;
            this->c = c;
            this->d = d;
            strcpy(this->buf,buf);
        }
        
        void show()
        {
            cout << "a: " << a << endl;
            cout << "b: " << b << endl;
            cout << "c: " << c << endl;
            cout << "d: " << d << endl;
            cout << "buf: " << buf << endl;
        }
    private:
        int a;
        char b;
        short c;
        float d;
        char buf[100];
}

int main()
{
    base a(10086,'W',20,3.14f,"hello");
    a.show();
}

        可见,this指针用于区分 类内成员 与 类外成员。同样,我们也可以用参数列表来区分 类内 与 类外成员:

九、类外定义类的成员函数

#include <iostream>

using namespace std;

class base
{
    public:
        //构造函数
        base();
        //析构函数
        ~base();
        //显示接口函数
        void show();
};

//在类外编写成员函数,需要将这个函数与这个类关联起来,使用 域操作符 关联
base::base()
{
    cout << "base()" << endl;
}

base::~base()
{
    cout << "~base()" << endl;
}

void base::show()
{
    cout << "base show" << endl;
}

int main()
{
    //定义一个 base 的对象
    base a;
    a.show();

    return 0;
}

编译运行:

提示:在实际开发中,一般在xxx.h中对类的所有接口进行声明,xxx.cpp中实现这些类内函数,main.cpp中调用该类的接口。

1、类成员函数分文件编写

1、main.cpp

#include <iostream>
//添加写好的类头文件
#include "base.h"

using namespace std;

int main()
{
    base a;
    a.show();

    base b(10);
    b.show();

    return 0;
}

2、base.h

#ifndef BASE_H
#define BASE_H

#include <iostream>

using namespace std;

//声明类接口
class base
{
    public:
        base();    //声明构造函数
        base(int date);    //声明带参的构造函数
        ~base();   //声明析构函数
        void show();    //声明函数方法
    private:
        int date;    //声明数据成员
};

#endif

3、base.cpp

#include "base.h"

//实现构造函数
base::base()
{
    cout << "base()" << endl;
    this->date = 0;
}

//实现带参构造函数
base::base(int date)
{
    cout << "base(int date)" << endl;
    this->date = date;
}

//实现析构函数
base::~base()
{
    cout << "~base()" << endl;
} 

//实现函数显示接口
void base::show()
{
    cout << "base show" << endl;
    cout << "date: " << date << endl;
}

编译运行:

2、练习

        设计一个计算器类,实现 加,减,乘,除。在设计该类的构造与析构,实现 声明在 xxx.h 实现在 xxx.cpp中。

1、calculate.h

#ifndef CALCULATE_H
#define CALCULATE_H

#include <iostream>

using namespace std;

//声明类接口
class calculate
{
    public:
        calculate();    //声明构造函数
        calculate(int a,int b);    //声明带参的构造函数
        ~calculate();   //声明析构函数
        void show();    //声明函数方法
        //设计接口
        int add();        //加法
        int Lessen();     //减法
        int Mul();        //乘法
        float Except();   //除法
    private:
        int a;    //声明数据成员
        int b;    //声明数据成员
};

#endif

2、calculate.cpp

#include "calculate.h"

calculate::calculate()
{
    a = 0;
    b = 0;
}

calculate::calculate(int a,int b)
{
    this->a = a;
    this->b = b;
}

calculate::~calculate()
{
    cout << "~calculate()" << endl;
}

void calculate::show()
{
    cout << "a: " << a << endl;
    cout << "b: " << b << endl;
}

int calculate::add()
{
    return a+b;
}

int calculate::Lessen()
{
    return a-b;
}

int calculate::Mul()
{
    return a*b;
}

float calculate::Except()
{
    return (float)a/b;
}

3、main.cpp

#include <iostream>
#include "calculate.h"

using namespace std;

int main()
{
    //定义一个计算器类
    calculate a(10,20);
    
    a.show();
    cout << "add: " << a.add() << endl;
    cout << "Lessen: " << a.Lessen() << endl;
    cout << "Mul: " << a.Mul() << endl;
    cout << "Except: " << a.Except() << endl;

    return 0;
}

编译运行:

十、拷贝构造函数(重点)

        当一个对象 对 另外一个对象 赋值 时,就会调用拷贝构造函数。(通俗的说,就是我们在定义一个对象时,拿一个对象去初始化一个对象。)

#include <iostream>

using namespace std;

class base
{
    private:
        int date;
    public:
        base()
        {
            date = 0;
        }

        base(int date):date(date)
        {
            
        }

        void show()
        {
            cout << "date: " << date << endl;
        }
};

int main()
{
    //定义一个对象 a
    base a(10086);

    //通过对象a 对 对象b 进行赋值,系统会调用 自动生成的拷贝构造函数
    base b = a;

    a.show();
    b.show();

    return 0;
}

分析:

浅拷贝:系统自动生成的拷贝构造函数。

1、浅拷贝会出现的问题

#include <iostream>

using namespace std;

class base
{
    private:
        char *p;
    public:
        base()
        {
            p = new char[1024];    //指向一个堆空间(需要析构函数释放堆空间)
        }

        ~base()
        {
            delete p;    //释放堆空间
        }
};

int main()
{
    //定义一个对象 a
    base a;

    //通过对象a 对 对象b 进行赋值,系统会调用 自动生成的拷贝构造函数
    base b = a;

    return 0;
}

这样子运行,程序就会死掉,有问题。分析如下:

        所以,当遇到这种情况(原来的对象里面指向了一块空间、原来的对象映射了一块空间、原来的对象打开了一个文件)时,就不能使用系统自动生成的拷贝构造函数(浅拷贝),这时候,我们需要重写拷贝构造函数(深拷贝)。

2、重写拷贝构造函数(深拷贝)

深拷贝:用户自定义的拷贝构造函数。

浅拷贝:系统自动生成的拷贝构造函数。

当一个构造函数传递的参数是当前类的引用时,那么该构造函数就是,拷贝函数。

语法:

class 类名

{

        public:

                类名(类名 &引用名)       //用户自定义的拷贝构造函数(深拷贝)

                {

                }

}

例子:

class base

{

        public:

        base(base &tmp)

        {

                cout << "哈哈哈,我是拷贝构造函数" << endl;

        }

}

//提示:当用户自定义拷贝构造函数后,系统就不会自动生成拷贝构造函数。

#include <iostream>

using namespace std;

class base
{
    private:
        int date;
    public:
        base()
        {
            date = 0;
        }

        base(int date):date(date)
        {
            
        }

        //自定义拷贝构造函数
        base(base &tmp)
        {
            cout << "hahahaha,base(base &tmp)" << endl;
            //手动赋值
            this->date = tmp.date;
        }

        void show()
        {
            cout << "date: " << date << endl;
        }
};

int main()
{
    //定义一个对象 a
    base a(10086);

    //通过对象a 对 对象b 进行赋值,调用拷贝构造函数
    base b = a;

    a.show();
    b.show();

    return 0;
}

3、深拷贝,实现堆空间拷贝

#include <iostream>

using namespace std;

class base
{
    private:
        char *p;
    public:
        base()
        {
            p = new char[1024];    //指向一个堆空间(需要析构函数释放堆空间)
            cout << "堆空间首地址:" << (void *)p << endl;
        }

        //重写拷贝构造函数,实现堆空间的拷贝 深拷贝
        base(base &t)
        {
            cout << "调用重写后的拷贝构造函数" << endl;
            //重新分配一块 1024 个字节的堆空间
            this->p = new char[1024];
            cout << "堆空间首地址:" << (void *)p << endl;
        }

        ~base()
        {
            delete p;    //释放堆空间
        }
};

int main()
{
    //定义一个对象 a
    base a;

    //通过对象a 对 对象b 进行赋值,系统会调用 自动生成的拷贝构造函数
    base b = a;

    return 0;
}

 分析:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值