一、析构函数
函数名与类名相同,在函数名前面添加~,函数没有返回值,没有参数,当对象销毁的时候,系统会自动调用(析构函数可以释放成员空间)。
作用:释放类中的数据成员(例如:堆空间,映射空间,打开的文件......)。
析构函数的特点:
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;
}
分析: