3_构造函数与析构函数

构造函数与析构函数

什么是构造函数

struct Sclass
{
    int a;
    int b;
    int c;
    int d;
    Sclass()//构造函数
    {
            printf("观察这个函数\n");
    }

    int Plus()
    {
        return a+b+c+d;
    }
}

大家注意看 Sclass 函数, 它没有返回类型, 也没有 void, 我们知道普通函数如果没有返回类型的话, 前面会有一个 void, 但是这个函数没有

int main(int argc, char* argv[])
{
    Sclass s;   //创建一个对象 s
    return 0;
}

我们运行一下, 程序打印输出了 "观察这个函数"

也就是说这个函数执行了, 我们根本就没有调用它, 它也执行了.

这个函数我们称它为构造函数, 它必须与类名也就是结构体名字一样, 它不能有返回值, void 也不能写. 然后构造函数, 我没有调用它, 它就自己执行了.

我们看我创建对象处的汇编代码就知道, 编译器自动给我 call 了构造函数.

那么这个函数它主要用来做一些初始化的工作, 举个例子.

//我们当前类里面有四个成员 a b c d.
int main(int argc, char* argv[])
{
// 按照以前的方式我们是这样给它们赋值
    Sclass s = (1, 2, 3, 4);

    return 0;
}

但是现在不一样了, 我们可以在构造函数里面直接给它进行赋值 :

struct Sclass
{
    int a;
    int b;
    int c;
    int d;
    Sclass(int a, int b, int c, int d)//给它传四个参数进来, 刚才说了这个函数前面不能有返回值, 但是它的参数, 你可以根据需要想怎么写就怎么写
    {
        this->a = a;
        this->b = b;
        this->c = c;
        this->d = d;

        printf("观察这个函数\n");
    }

    int Plus()
    {
        return a+b+c+d;
    }
}

但是我们现在编译一下, 编译器告诉我们 : no appropriate default constructor available, 也就是它找不到一个没有参数的构造函数了.

我们看了这块的反汇编, 意思是编译器会默认找一个没有参数的函数, 但是我们加了参数, 编译器再找的时候就找不到那个函数了. 现在怎么解决这个问题呢 ?

int main(int argc, char* argv[])
{
    Sclass s(1, 2, 3, 4);   //你就在这里这么写, 告诉编译器不要找那个没有参数的构造函数了, 而是去找带 4 个 int 类型的构造函数
    return 0;
}

但是这么写还是有个问题, 如果我创建对象的时候不想就这么赋值, 怎么办 ?

很简单, 我们再定义一个没有参数的构造函数就好

struct Sclass
{
    int a;
    int b;
    int c;
    int d;
    Sclass(int a, int b, int c, int d)
    {
        this->a = a;
        this->b = b;
        this->c = c;
        this->d = d;

        printf("有参构造函数\n");
    }

    Sclass() //新定义的一个没参数的构造函数
    {
            printf("无参构造函数\n");
    }

    int Plus()
    {
        return a+b+c+d;
    }
}

这样就好了, 并且到时候我们可以根据需求, 来告诉编译器我们是想用有参数的还是用无参数的构造函数.

我们简单总结下构造函数 :

  1. 与类同名并且没有返回值

  2. 创建对象的时候函数执行, 所以主要用来初始化操作

  3. 可以有多个(最好有一个不带参数的), 这个称为重载, 其它函数也可以重载

  4. 编译器不要求必须提供构造函数

什么是析构函数

struct Person
{
    int age;
    int level;
    Person()
    {
        printf("无参构造函数执行了...\n");
    }
    Person(int age, int level)
    {
        printf("有参构造函数执行了...\n");
        this->age = age;
        this->level = level;
    }
    ~Person()
    {
        printf("析构函数执行了...\n");
    }
    void Print()
    {
        printf("%d-%d", age, level);
    }
}

它和构造函数很像, 唯一的区别就是在前面要写上 ~ 符号.

它不能带任何参数, 前面也不能写 void.

它是在对象用完了, 要被销毁的时候使用. 那么对象什么时候被销毁呢, 你要看你在哪里创建的对象, 比如这里我们在 main() 函数中创建对象, 那么 main() 函数执行完的时候, 就要销毁这个对象了.

我们看汇编代码, 你 main() 在 return 0 之前要调用这个析构函数.

简单总结一下 :

  1. 只能有一个析构函数, 不能重载

  2. 不能带任何参数

  3. 不能带返回值

  4. 主要用于清理工作

  5. 编译器不要求必须提供析构函数

发现没有, 不管是构造函数还是析构函数, 根本就不是我们调用的, 而是编译器替我们调用的.

析构函数到底什么时候执行

1.当对象在堆栈中分配

比如说我当前对象是在 main 函数里面创建

int main(int argc, char* argv[])
{
    Person p(1,2);

    return 0;
}

那么这个析构函数就是在 main 函数执行完, return 之前的时候执行

2.当对象在全局区分配

那我们把这个对象放在全局的时候, 析构函数什么时候执行呢 ?


Person p(1,2);

int main(int argc, char* argv[])
{
    return 0;
}

这个时候, 当前进程在退出之前会调用这个析构函数

析构函数主要用于清理工作

什么叫清理工作, 因为析构函数执行的时候, 意味着当前的对象已经用完了, 那么对象已经申请了一些空间, 这些空间就需要被释放.

我们看看下面的代码

struct Person
{
    int age;
    int level;
    char* arr;

    Person(int age, int level)
    {
        this->age = age;
        this->level = level;
        arr = (char*)malloc(1024);
    }

    ~Person()
    {
        printf("析构函数执行了...\n");
        free(arr);
    }

    // 其它函数
}

我创建了一个对象, 对象用 malloc 函数申请了一段空间. malloc 函数分配的空间是在堆中分配的, 在堆中分配的空间, 我们用完之后一定要明确告诉操作系统我们不用了, 这个时候我们通常使用 free 函数来告诉操作系统可以回收这个堆中分配的空间.

我们可以把释放堆内存的这个 free 代码写到析构函数里面.

再比方说你打开了很多文件, 你就可以把关闭文件的操作写到析构函数里面.