详解构造函数和析构函数【建议收藏】

举报
Linux猿 发表于 2021/09/30 15:13:44 2021/09/30
【摘要】 详解构造函数和析构函数【建议收藏】

🎈 作者:Linux猿

🎈 简介:CSDN博客专家🏆,华为云享专家🏆,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊!

🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬


这篇文章对C++构造函数和析构函数做一下总结。

一、构造函数:构造函数用于为类对象进行初始化,如果没有显示定义构造函数,会生成一个默认的构造函数。

1.1 形式:

class 类名{
    类名(形式参数)
        构造体
};

1.2 规则

    (1)在对象创建时自动调用,完成初始化相关工作,不能显示调用。

    (2)无返回值,与类名相同。

    (3)可以重载,可默认参数,不能为虚构造函数。

    (4)默认构造函数没有参数的空构造体,构造函数如果有实现,默认不复存在。

1.3 初始化列表

class Cont{
public:
    Cont(int _a, int _c, string _str):c(_c),a(_a),str(_str){
        cout<<"Cont(int _a, int _c, int _str)"<<endl;
    }
    void display(){
        cout<<"a = "<<a<<endl;
        cout<<"c = "<<c<<endl;
        cout<<"str = "<<str<<endl;
    }
private:
    int a;
    int c;
    string str;
};
int main(int argc, char *argv[])
{
    //Cont temp;      //false
    Cont temp(1, 2, "ABC");
    temp.display();
    return 0;
}

必须使用初始化列表的情况:

(a)非静态const 成员或引用类型的成员

    用初始化列表进行初始化,参数初始化的顺序和初始化列表的顺序无关,与它们在类中声明的顺序有关。非静态const数据成员必须用初始化列表来初始化。

使用初始化列表效率高的原因(这里针对的是非内置类型):

(1)在构造函数体内赋值:先调用此成员变量的构造函数,在调用它的赋值构造函数。

(2)使用初始化列表:只调用一次成员变量的构造函数。

2.拷贝构造函数

2.1 定义

由已经存在的对象创建新对象,也就是说新对象不由构造函数来构造,而是由拷贝构造函数来构造。

2.2 形式

class 类名{
    类名(const 类名& another)
        拷贝构造体
};

2.3 规则

    1. 系统提供默认的拷贝构造器,一经实现,不复存在。

    2. 系统提供的是等位拷贝,也就是所谓的浅浅的拷贝。

    3. 要实现深拷贝必须自己定义。

    4. 参数的传递用采用引用传递,如果采用值传递,会造成递归调用。

2.4 拷贝构造发生的时机

(1)以一个对象去建立另一个对象

    Cont a(1, 2);
    Cont b(a);      //拷贝构造函数
    Cont c = a;     //拷贝构造函数

(2)作为函数的参数

class Cont{……};
void display(Cont Obj)

(3)作为函数的返回值

class Cont{……};
Cont display(){
    
    return Obj;
}

2.5 深拷贝和浅拷贝

    拷贝构造函数又分为深拷贝和浅拷贝。系统提供的拷贝构造函数是等位拷贝,即浅拷贝。如果类中包含的数据元素全部在栈上,浅拷贝可以满足需求。但如果堆上的数据,则会发生多次析构行为,要防止这种错误就需要自己定义拷贝构造函数,即深拷贝,如果用户自己定义,则系统不再提供浅拷贝。

一下四中情况不需要按位拷贝:

(1)class内部成员变量声明有显示的拷贝构造函数。

(2)class的基类有显示的拷贝构造函数。

(3)class中有虚基类,因为编译器要为对象设置虚函数表,所以无法按位拷贝。

(4)class继承虚基类。

2.6 C++默认为类创建的成员函数

包括默认构造函数、拷贝构造函数、析构函数、拷贝赋值函数。

//个人编写
class Cont{};
 
//编译器实现
class Cont{
public:
    Cont(){};                    //默认构造函数
    Cont(const Cont& another){}  //拷贝构造函数
    ~Cont(){}                    //析构函数
    Cont& operator=(const Cont& another){}  //拷贝赋值操作符
};

二、析构函数

1.1 定义

    用来实现与构造函数相反的操作:释放对象使用的资源,并销毁非static成员。

1.2 形式

class 类名{
    ~类名()
       析构体
};

1.3 规则

对象销毁时,自动调用。完成销毁的善后工作。

无返回值,与类名相同,无参数,不可以重载,不可以有默认参数。

系统提供默认析构器,一经实现,不复存在。

析构函数的作用不是要删除对象,而是在对象销毁前完成一些清理工作。

1.4 虚析构函数

    虚析构函数一般是指子类继承基类,基类的析构函数一般申请为虚析构函数。这是为什么呢?是在子类中有指针成员变量时,为了防止内存泄漏而写成虚函数。也就是说虚析构函数使得在删除指向子类对象的基类指针时,可以调用子类的析构函数来释放子类中堆内存的目的,从而防止内存泄漏。

举个栗子:

class Base{
    public:
        Base(){
            cout<<"Base()"<<endl;
        }
        virtual ~Base(){
            cout<<"~Base()"<<endl;
        }
        virtual void Solve(){
            cout<<"Base::Solve()"<<endl;
        }
};
class Node : public Base{
    public:
        Node(){
            cout<<"Node()"<<endl;
            p = new int;
        }
        ~Node(){
            cout<<"~Node()"<<endl;
        }
        void Solve(){
            cout<<"Node::Solve()"<<endl;
            delete p;
            p = NULL;
        }
    private:
        int *p;
};
int main()
{
    Base *b = new Node;
    b->Solve();
    delete b;
    return 0;
}

输出结果:

构造函数和析构函数.png

   上面的栗子中,如果基类的析构函数不申请为虚函数的话,delete b 的时候就不会调用子类的析构函数,这样子类在堆上申请的内存就无法释放,从而造成内存泄漏。这里需要注意的是:基类的虚函数被声明为虚析构函数,但是子类的析构函数仍然无法覆盖基类的析构函数,这是由于基类的析构函数是子类无法继承的。

参考文献:

[1] https://www.cnblogs.com/yetuweiba/p/4231870.html

[2] http://www.cnblogs.com/yetuweiba/p/3390853.html

[3] https://blog.csdn.net/baiyq369/article/details/54926983/

[3] http://www.runoob.com/cplusplus/cpp-constructor-destructor.html

[4] https://www.cnblogs.com/raichen/p/4752025.html

[5] https://www.cnblogs.com/MrListening/p/5567762.html


🎈 有任何疑问欢迎交流!

🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。