系列文章目录
【C++基础】面向对象方法(一)
【C++基础】面向对象方法(二)
一、析构函数
在对象的生存期结束时,有时也需要执行一些操作。这部分工作可以放在析构函数中。
析构函数是一个特殊的由用户定义的公有成员函数,析构函数具有如下特征:
-
析构函数名为:~<类名>
-
析构函数无任何函数返回类型说明
-
析构函数无参数,所以不能被重载
-
如果在类声明中没有给出析构函数,系统会自动给出一个默认的析构的函数:
~<类名>(){} -
当对象的生命周期结束或用delete释放动态对象时,系统自动调用析构函数完成对象撤销前的处理。
析构函数示例:定义一个整数数组IntArray类。要求:可以要求根据需要确定数组的规模,默认数组的规模为10个元素,能够显示数据的规模信息。
类设计思路:
- 将整数数组类定义为动态数组,即数组的规模可动态变化。因此,用两个数据成员来描述一个整数数组的属性:"int *m_ptr"用来记录数组的开始地址,"m_size"用来描述数组的规模。
- 定义一个成员函数"infoOfArray()"显示数组的信息。
- 根据"根据需要确定数组的规模,默认数组的规模为10个元素"的要求,可为整形数组类IntArray设计两个构造函数。
- 类的设计者要清楚的知道,无论是哪一个构造函数,都在初始化对象时额外地申请了数组空间,而这部分数组空间不会随着对象地撤销而自动被收回,就会发生"内存泄漏"。因此,需要将收回这些额外空间的工作放在析构函数"~IntArray()"中。
具体的C++实现代码如下:
/*定义一个整数数组IntArray类,要求:可以根据需要确定数组的规模,默认数组的规模为10个元素,能够显示数组的规模信息*/
#include<iostream>
using namespace std;
class IntArray
{
private:
int m_size;
int* m_ptr;
public:
IntArray(int sz)//有参构造函数
{
m_size = sz;
m_ptr = new int[sz];//动态分配数组的内存空间
}
IntArray()//无参构造函数
{
m_size = 10;
m_ptr = new int[m_size];//动态分配数组的内存空间
}
void infoOfArray()//显示数组信息
{
cout << "The size of this array is:" << m_size << endl;
}
~IntArray()//析构函数
{
delete[]m_ptr;//收回额外空间
}
};
二、拷贝构造函数
拷贝构造函数的作用是用一个已经存在的对象来初始化一个正在创建的新对象。拷贝构造函数有如下特征:
- 拷贝构造函数名与类名相同,形参只有一个,是对象的引用(不能重载拷贝构造函数)。拷贝构造函数的原形为:<类名>(<类名>&对象名);
- 拷贝构造函数无任何函数返回类型说明。
- 如果在类声明中没有给出拷贝构造函数,系统会自动给出一个默认的拷贝构造函数,该拷贝构造函数只进行对象数据成员间的对外拷贝,即所谓的"浅拷贝"。
- 在某些情况下,用户必须在类定义中给出一个显式的拷贝构造函数,以实现用户指定的用一个对象初始化另一个对象的功能,即所谓的"深拷贝"。
在以下3种情况下,系统会自动调用拷贝构造函数
- 当使用下面的声明语句用一个已存在的对象初始化一个新对象时,系统会自动调用拷贝构造函数:
<类名><新对象名>(<已存在对象名>);
或
<类名><新对象名>=<已存在对象名>; - 对象作为实参,在函数调用开始进行实参和形参结合时。
- 如果函数的返回值是类的对象,在函数调用完成返回时,系统自动调用拷贝构造函数,用return后面的已知对象来初始化一个临时新对象。
例:对于前文的例子中的IntArray类,如果在主函数中需要用一个已知的IntArray类对象来初始化一个新的IntArray类对象,直接用系统提供的默认拷贝函数,看看会有什么问题以及如何解决。
部分代码如下(示例):
int main()
{
IntArray x(20);
IntArray y(x);//用对象x初始化新建对象y
x.infoOfArray();
y.infoOfArray();
return 0;
}
上述代码是存在明显问题的,原因在于同一块地址空间会被析构函数重复释放两次,而发生这个问题的根本原因在于"浅拷贝"。
解决方法:需要类设计者定义自己的拷贝构造函数,使得由一个对象初始化后的新对象也具有自己独立的动态数组空间。
部分代码如下:
class IntArray
{
public:
...
IntArray(IntArray &x)
{
m_size=x.size;
m_ptr=new int(m_size);
}
...
};
完整可运行代码如下所示:
#include<iostream>
using namespace std;
class IntArray
{
private:
int m_size;
int* m_ptr;
public:
IntArray(int sz)//有参构造函数
{
m_size = sz;
m_ptr = new int[sz];//动态分配数组的内存空间
}
IntArray()//无参构造函数
{
m_size = 10;
m_ptr = new int[m_size];//动态分配数组的内存空间
}
IntArray(IntArray& x)//拷贝构造函数,如果没有自定义拷贝构造,主函数那部分代码就会报错
{
m_size = x.m_size;
m_ptr = new int(m_size);
}
void infoOfArray()//显示数组信息
{
cout << "The size of this array is:" << m_size << endl;
}
~IntArray()//析构函数
{
delete[]m_ptr;//收回额外空间
}
};
int main()
{
IntArray x(20);
IntArray y(x);
//IntArray y(x);用对象x初始化新建对象y,这里出现了错误,原因是浅拷贝导致的,解决方案是需要类设计者定义自己的拷贝构造函数
x.infoOfArray();
y.infoOfArray();
//y.infoOfArray();
return 0;
}
三、类声明与类定义的分离
C++允许将类的声明和实现分离。类的声明描述了类的结构,包括类的所有数据成员、函数成员和友元。类的实现定义了成员函数的具体功能。类的声明和实现放在两个不同的文件中,这两个文件可以具有相同的文件名、不同的扩展名。类声明文件的扩展名为".h",类实现文件的扩展名为".cpp"
例1:IntArray类的声明和实现的分离。
IntArray类的声明头文件如下:
//IntArray.h
class IntArray
{
public:
IntArray(int); //有参构造函数
void infoOfArray(); //显示数组信息
private:
int m_size;
int* m_vector;
};
在类的实现文件中,成员函数的定义形式:
<函数类型><类名>::<函数名>(<形参数表>)
{
函数体
}
其中,"::"是作用域运算符,表示所定义函数属于哪个类。
IntArray类的实现文件如下:
#include"IntArray.h"
#include<iostream>
using namespace std;
IntArray::IntArray(int sz) //有参构造函数
{
m_size = sz;
m_vector = new int[sz]; //动态分配空间
}
void IntArray::infoOfArray()//显示数组信息
{
cout << "The size of this array is:" << m_size << endl;
}
例2:类的声明和实现分离的程序设计
//testIntArray.cpp
#include<iostream>
#include"IntArray.h"
using namespace std;
int main()
{
IntArray x(20);
x.infoOfArray();
return 0;
}
总结
以上就是今天记录的内容,本文仅仅简单介绍了析构函数、拷贝构造函数及类的声明和实现的分离。