原创 DeRoy 编程学习基地 2020-02-29

构造析构_C语言

点击蓝字 关注我们


构造析构




目录



构造函数构造函数的使用特点构造函数的重载析构函数析构函数的使用特点关于delete[]构造析构顺序初始化列表



正文



构造函数

为成员变量赋初值,分配资源,设置对象的初始状态

可以理解为类的初始化函数

构造函数的使用

#include<iostream>
using namespace std;
class STU
{

    char* m_name;
    int m_age;
public:
    //STU(){} 缺省构造函数 无参 一旦你实现了任意一个构造函数将不再提供缺省构造
    STU()
    {
        m_name = new char[20]{ "学生姓名" };//分配内存并初始化
        m_age = 18;
    }
    void introduce()
    
{
        cout << "name:" << m_name << endl << "age:" << m_age << endl;
    }
};
int main()
{
    STU stu;//自动调用构造函数
    stu.introduce();
    return 0;
}

构造函数一般用于为对象分配内存空间

特点

  • 函数名与类名相同,没有返回类型

  • 对象创建时自动调用且只调用一次

  • 如果没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作

  • 一般访问属性为public,除非我们不允许外部创建对象

构造函数的重载

和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数

#include<iostream>
using namespace std;
class STU
{

    char* m_name;
    int m_age;
public:
    STU()
    {
        m_name = new char[20]{ "学生姓名" };//分配内存并初始化
        m_age = 18;
    }
    //构造函数的重载
    STU(const char* name, int age)
    {
        m_name = new char[20];//分配了内存就要释放
        strcpy(m_name, name);
        m_age = age;
    }
    void introduce()
    
{
        cout << "name:" << m_name << endl << "age:" << m_age << endl;
    }
};
int main()
{
    STU stu;              //自动调用构造函数
    stu.introduce();
    STU stu1("程序"20)//自动匹配并调用构造函数的重载
    stu1.introduce();
    return 0;
}
//打印结果
name:学生姓名
age:18
name:程序
age:20

析构函数

创建对象时系统会自动调用构造函数进行初始化工作,对应的,销毁对象时系统也会自动调用一个函数来进行清理工作

析构函数的使用

//在构造代码下面追加析构函数
~STU()
{
    delete[] m_name;    //构造申请内存,析构释放内存
}

销毁对象时系统自动调用析构函数

特点

  • 构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号

  • 对象销毁时自动调用且只调用一次

  • 如果用户没有定义,编译器会自动生成一个默认的空的析构函数

  • 析构函数没有参数,不能被重载,因此一个类只能有一个析构函数

关于delete[]

为什么释放多个内存要加[]

为了测试这一情况,定义一个类

class test
{

public:
    test()
    {
        cout << "构造" << endl;
    }
    ~test()
    {
        cout << "析构" << endl;
    }
};

在主函数中申请多个对象后加[]释放

int main()
{
    test *pTest = new test[4];
    delete[] pTest;
    return 0;
}
输出结果:
构造
构造
构造
构造
析构
析构
析构
析构

不加[]释放

int main()
{
    test *pTest = new test[4];
    delete pTest;
    return 0;
}
输出结果:
构造
构造
构造
构造
析构

可以看出不加[]只释放一个对象的内存,并且还会报错

其实呢,在申请多个内存是返回的内存地址并不是申请的内存首地址而是向右移4个字节之后的地址,我们可以打印出隐藏的4个字节内容

int main()
{
    test *pTest = new test[4];
    cout << *((int*)(pTest - 4)) << endl;
    delete[] pTest;
    return 0;
}
打印结果:
构造
构造
构造
构造
4
析构
析构
析构
析构

这样你会发现隐藏的4个字节存储了你申请的对象数量当delete加[]时,会先访问这4个字节的数据,然后再释放内存

构造析构顺序

在构造析构顺序之前先看一下

  • 对象创建过程(以堆区为例)

  1. 为整个对象分配内存

  2. 构造基类部分(如果存在基类)

  3. 构造成员变量

  4. 执行构造函数代码

  • 对象的销毁过程

  1. 执行析构函数代码

  2. 析构成员变量

  3. 析构基类部分

  4. 释放整个对象占用内存

这样我们先创建三个类(A,B,C),其中B类中有A对象,C类中有B对象

#include<iostream>
using namespace std;
class A
{

public:
    A(){
        cout << "A构造" << endl;
    }
    ~A() {
        cout << "A析构" << endl;
    }
};
class B
{

public:
    B() {
        cout << "B构造" << endl;
    }
    ~B() {
        cout << "B析构" << endl;
    }
    A a;
};
class C
{

public:
    C() {
        cout << "C构造" << endl;
    }
    ~C() {
        cout << "C析构" << endl;
    }
    B b;
};

int main()
{
    C* c=new C; //创建对象
    delete c;   //销毁对象
    return 0;
}
//打印结果
A构造
B构造
C构造
C析构
B析构
A析构

初始化列表

简化成员变量初始化,仅仅只是为了书写方便,没有效率上的提升

class STU
{

    string m_name;  //姓名
    int m_age;      //年龄
    int m_height;   //身高
public:
    STU():m_name("学生姓名"),m_age(18),m_height(180) { }
    STU(string name, int age,int height)
        :m_name(name)
        ,m_age(age)
        ,m_height(height) { }
    void introduce()
    
{
        cout << "Name:" << m_name << "\tAge:" << m_age << "\tHeight:" << m_height << endl;
    }
};

int main()
{
    STU stu;
    stu.introduce();
    STU stu1("程序"20,180);
    stu1.introduce();
    return 0;
}

在初始化列表中需要注意的是参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关,这个会导致什么问题呢

//将构造函数的重载修改成
STU(string name, int age,int height)
    :m_name(name)
    , m_height(height)
    ,m_age(m_height){ }
输出结果:
Name:学生姓名   Age:18  Height:180
Name:程序       Age:-858993460  Height:180

这里只是该变了初始化列表的顺序,然后用m_height成员变量去初始化m_age

造成这样后果的原因是初始化列表先初始化m_age,再初始化m_height