C++ 构造析构函数

来源:互联网 发布:用服务器ip做网站域名 编辑:程序博客网 时间:2024/05/20 20:58

构造函数的作用:
该类对象被创建时,编译系统为对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作,故:构造函数的作用:初始化对象的数据成员。当然他的可以做其它的,但一般用于初始化。


下面以程序来讲解构造函数和析构函数的知识点:

class Person{private:    char *name;    int age;    char *work;public:     /*     注意:构造函数不要有返回类型,如果它有返回值,要么编译器必须知道如何处理返回值,要么就只能由客户程序员自己来显式的调用构造函数,这样一来,安全性就被人破坏了。    */    /* 无参构造函数, 我们定义了任意一种构造函数,系统都不会再产生默认的构造函数。 */    Person(){}    /* 一般构造函数(也称重载构造函数) */    Person(char *name)    {        this->name = name;    }    /* 如果只传入两个参数,则第三个参数会使用默认值 */    Person(char *name, int age, char *work="none")    {        this->name = name;        this->age = age;        this->work = work;    }};int main(int argc, char **argv){       /* 会调用有参的构造函数,没指名形参则调用无参构造函数 */    Person per("zhangsan", 16);    /* 会默认调用无参构造函数,如果我们没有定义则系统会生成一个什么都不做的无参构造函数 */    Person per2;    return 0;}

下面先来做一个实验,我们在 C++ 里面不要用 malloc 新建内存,尽量不用动态内存,要用也用 new 和 delete ,new 和 delete 可以保证调用构造和析构函数,而 malloc 和 free 不会。一般我们在 main 函数里面实例化对象,退出后系统会自动释放堆空间,单如果在某个子函数里面实例化对象并且一直处理运行状态呢?我们要等到系统运行结束可能要很久,而你在子函数里面不停的 new 一些新的对象出来而不进行手动释放就可能产生“内存泄漏”,下面我们在类的构造函数里面申请一些内存用于存在字符串来做个测试,代码如下:

#include <iostream>#include <string.h>#include <unistd.h>using namespace std;class Person{private:    char *name;    int age;    char *work;public:     /* 构造函数 */    Person()    {        //cout <<"Pserson()"<<endl;        name = NULL;        work = NULL;    }    Person(char *name)    {        /* 使用存储空间存储 */        this->name = new char[strlen(name) + 1];        strcpy(this->name, name);        this->work = NULL;    }    /* 如果只传入两个参数,则第三个参数会使用默认值 */    Person(char *name, int age, char *work="none")    {        this->age = age;        /* 创建内存用于存在字符串 */        this->name = new char[strlen(name) + 1];        strcpy(this->name, name);        this->work = new char[strlen(work) + 1];        strcpy(this->work, work);    }}void test_fun(){       /* 初始化构造函数 */    Person *per7 = new Person("lisi", 18, "student");    /* main程序执行完后会自动释放,也可以手动释放 */    delete per7;}int main(int argc, char **argv){    /*         在 linux 下使用 free 命令查看内存的消耗         free -m (输出的信息以 M 为单位)      */    /* 这里不停的创建看下结果如何 */    for(int y=0; y<10; y++)    {        for (int i = 0; i < 1000000; i++)            test_fun();        cout << "run test_fun end"<<endl;        sleep(10);    }    return 0;   }

下图为”未执行程序前”和”执行程序后”的内存占用情况:
这里写图片描述
可以看出我们在 test_fun 这个函数里面已经做了 detele 操作,但实际上”类里面” new 的内存空间并没有释放,而是不断的递增占用内存直到系统退出程序才恢复,这就存在很大的隐患,所以这里我们需要引入一个概念,就是 C++ 的“析构函数”,析构函数也是跟类名一致,但是为了和构造函数区分开来,他在前面加了一个“~”号,析构函数是在什么情况下会被执行呢?实例化对象被销毁之前的瞬间系统会帮你自动调用析构函数(即上面代码中的 test_fun 函数执行 “delete per7;”之前会调用析构函数),所以我们可以利用这点在析构函数里面去 delect 掉我们 new 出来的内存,避免内存泄漏。在上面类的定义里面加上一个析构函数,如下:

~Person(){    if (this->name) {        delete this->name;    }    if (this->work) {        delete this->work;    }}

接下来我们再讲解一下“拷贝构造函数”,假设我们有这样一种定义形式,如下:

Person per("zhangsan", 18);Person per2(per);

将定义的 person 类赋值给另一个 person 类, 实现的作用是把前面定义的那个类中所有的参数复制到当前对象中去,但如果这里面有析构函数就会出问题,“拷贝构造函数”会进行值的拷贝,其形参是“引用”,占用同一内存,当前面一个先释放了变量的内存后,这个per2也会再次释放同一块内存,一块内存释放两次,就有问题了,所以我们就需要引入自己的拷贝构造函数,修改代码如下,实现一个自己的拷贝构造函数:

/* 自己实现拷贝构造函数 */Person(Person &per){    this->age = per.age;    this->name = new char[strlen(per.name) + 1];    strcpy(this->name, per.name);    this->work = new char[strlen(per.work) + 1];    strcpy(this->work, per.work);}

假设我们创建了一个新的类,这个新的类里面使用了我们之前定义的 Person 类,那我们如何在这个新的类里面给 Person 类的构造函数赋值而不使用默认的构造函数呢?
现在创建的新的类假设是“Student”,如下:

class Student {private:    Person father;    Person mother;    int student_id;public:    Student()    {        cout<<"Student()"<<endl;    }    /* 一般没有“:”后面的部分的话上面的 Person 类一旦定义就会调用默认的构造函数,但我们不希望他调用默认的我们就需要添加“:”然后在后面输入类变量名并且带上形参,形参的赋值可以在前面指定 */    Student(int id, char *father, char *mother, int father_age = 40, int mother_age = 39) : mother(mother, mother_age), father(father, father_age)    {        cout<<"Student(int id, char *father, char *mother, int father_age = 40, int mother_age = 39)"<<endl;    }};

构造析构的顺序:

在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序。在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
可以简记为:先构造的后析构,后构造的先析构,它相当于一个栈,先进后出。

最后归纳一下什么时候调用构造函数和析构函数:

  1. 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。

  2. 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

  3. 如果在函数中定义静态(static )局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

原创粉丝点击