这是一个,在写析构函数的时候想系统理一下知识点,然后乱入了多态访问控制(在另一篇笔记里懒得发了),最后又乱入了基类派生类对象互相搅和的故事……
借鉴了两篇优秀博文
https://blog.csdn.net/github_35160620/article/details/52602332
https://blog.csdn.net/starlee/article/details/619827
构造函数 析构函数 的调用顺序
构造函数
基类构造函数、派生类对象成员构造函数、派生类本身的构造函数
析构函数
派生类本身的析构函数、派生类对象成员析构函数、基类析构函数(与构造顺序正好相反)
不同对象何时析构
局部对象,在退出程序块时析构
静态对象,在定义所在文件结束时析构
全局对象,在程序结束时析构
继承对象,先析构派生类,再析构父类
对象成员,先析构类对象,再析构对象成员
析构函数
通常情况下,系统自动生成的析构函数已足够使用。但若有需要自己编写析构函数,则一定需要同时编写拷贝构造函数和重载赋值运算符,这便是三法则。下面分情况讨论:
指针成员
//常规写法
class NoName{
public:
NoName():pstring(new std::string), i(0), d(0){
cout << "构造函数被调用了!" << endl;
}
~NoName(){
cout << "析构函数被调用了!" << endl;
}
private:
std::string * pstring;
int i;
double d;
};
- 指针成员构造时使用new,拥有额外的资源,则需要手动释放指针指向的内存。
NoName::~NoName(){
delete pstring;
cout << "析构函数被调用了!" << endl;
}
- 若使用了系统合成的拷贝构造函数,拷贝过去的也只是指针(浅拷贝),则两个对象的指针成员指向同一块内存,析构两个对象时,这块内存会被释放两次,因此需要手动编写拷贝构造函数。
//编写拷贝构造函数
NoName::NoName(const NoName & other){
pstring = new std::string; //开辟新的内存
*pstring = *(other.pstring);
i = other.i;
d = other.d;
}
- 赋值操作符同理,若不重载=,对于指针成员,c++自己的=只对指针进行简单的值拷贝,指向同一段内存,析构时则出现问题
NoName& NoName::operator=(const NoName &rhs){
pstring = new std::string; //开辟新的内存
*pstring = *(other.pstring);
i = other.i;
d = other.d;
return *this;
}
虚析构函数
牢记准则:基类的析构函数需要写成虚函数
class Base {
public:
Base() {};
virtual ~Base() {};
}
class Derived:public Base {
public:
Derived() {};
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
}
int main() {
Base *b = new Derived;
delete b;
}
若Base的析构函数没有被定义为virtual,对于基类指针指向一个派生类对象的情况,派生类的析构函数不会被调用,则会造成内存泄漏。
- 并不需要把所有的析构函数定义为virtual,因为当类含有虚函数时,编译器会给类添加一个虚函数表,用来存放虚函数指针,会增加类的存储空间。
基类与派生类对象之间的赋值兼容关系
- 可以将基类指针指向其公有派生类的对象
- 不可以将基类指针指向其私有派生类的对象
- 不可以将派生类指针指向基类对象
基类指针指向派生类对象的使用规则
class Base{ //声明基类
public:
int i;
...
};
class Derived:public Base{ //声明基类Base的公有派生类Derived
...
};
在Base的对象可以使用的任何地方,都可以用Derived的对象来代替,但只能使用从基类继承来的成员
- 派生类对象可以向基类对象赋值,即用派生类对象中从基类继承来的数据成员,逐个赋值给基类对象的数据成员
Base b;
Derived d;
b=d;
- 派生类对象可以初始化基类对象的引用
Derived d;
Base &br=d; //Base的对象的引用br,并用Derived的对象对其进行初始化
- 派生类对象地址可以赋值给指向基类对象的指针
Derived d;
Base *bp=&d;
- 如果函数的形参是基类对象或基类对象的引用,在调用函数时可以将派生类对象作为实参
void fun(Base &bb)
{
cout << bb.i << endl; //输出该引用所代表的对象的数据成员i
}
Derived d;
fun(d);