c++ 析构函数 指针成员 虚析构函数 基类与派生类的赋值兼容(乱七八糟……)

这是一个,在写析构函数的时候想系统理一下知识点,然后乱入了多态访问控制(在另一篇笔记里懒得发了),最后又乱入了基类派生类对象互相搅和的故事……
借鉴了两篇优秀博文
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);

你可能感兴趣的:(C++)