总结6:构造析构 函数

    技术2022-07-12  66


    本文PDF下载站点: https://github.com/MrWang522/Private-Document.git


    1. 概念

    1.1 构造函数

    定义:与类名相同的特殊成员函数

    语法: ClassName();

    作用:完成对属性的初始化

    特点: ①. 在定义时可以有参数,也可没有参数         ②. 没有任何返回类型的声明

    调用方式: 一般情况下C++编译器会自动调用构造函数, 在一些情况下则需要手工调用构造函数

    class MyTest{ public: MyTest() {} // 构造函数: 定义对象时,自动调用该函数,完成对属性的初始化 ~MyTest() {} // 析构函数:对象声明周期结束时,自动调用该函数,释放对象占用的空间 };

    1.2 析构函数

    定义:在构造函数名前加 ‘~’ 的特殊成员函数

    语法: ~ClassName();

    作用:对象销毁时,自动被调用,用来释放对象占用的空间

    特点: ①. 声明的析构函数 没有参数 没有任何返回类型         ②. 在对象销毁时自动被调用

    调用方式: 被C++编译器自动调用

    注意(重点!!! ):先定义的对象 后析构

    2. 构造函数分类

    1. 默认构造函数 2. 无参数构造函数 3. 带参数构造函数 4. 拷贝构造函数

    class Test{ public: Test(){ // 1. 无参数构造函数 m_a = 0; m_b = 0; m_c = 0; } Test(int a){ // 2. 有参数构造函数 1个参数 m_a = a; } Test(int a, int b, int c){ // 2. 有参数构造函数 3个参数 m_a = a; m_b = b; m_c = c } Test(const Test& obj ){ // 3 . 拷贝构造函数 /* ... ... */ } private: int m_a, m_b, m_c; };

    2.1 默认构造函数

    默认 无参 构造函数: 当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空默认 拷贝 构造函数: 当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制(浅拷贝)

    2.2 无参构造函数

    Test t1; // 直接调用 无参数构造函数 --> 属性初始化值为: m_a = 0; m_b = 0; m_c = 0;

    2.3 有参构造函数

    注意:对象初始化 和 对象赋值是两个不同的概念 !!!

    /* 1. 括号法 自动调用 */ Test t2(1, 2, 3); // 直接调用三个参数的构造函数: --> 属性初始化值为: m_a = 1; m_b = 2; m_c = 3; /* 2. 等号法 自动调用 */ Test t3 = (1, 2); // 直接调用一个参数的构造函数: --> 属性初始化值为: m_a = 2; 注意:C++等号符功能加强!!!直接取最后一个参数 Test t4 = (1, 2, 3); // 直接调用三个参数的构造函数: --> 属性初始化值为: m_a = 1; m_b = 2; m_c = 3; /* 3. 手动调用 初始化操作 */ Test t5 = Test(1, 2, 3); // 产生匿名对象, 赋给t5, 但只调用一次构造函数, 原因是把匿名对象那块地址 直接命名为t5 !!!

    2.4 拷贝构造函数

    作用:用一个对象 去 初始化另外一个对象

    class Test{ public: Test(int a, int b){ // 1. 有参数构造函数 2个参数 m_a = a; m_b = b; } Test(const Test& obj ){ // 2 . 拷贝构造函数 m_a = obj.m_a; m_b = obj.m_b; } private: int m_a, m_b; }; void MyTest_03(Test mp){ // 第三种调用测试 /* ... ... */ } Test MyTest_04(){ // 第四种调用测试 Test tmp(1, 2); return tmp; // *******重点!!! 返回一个匿名对象 返回时调用 一次拷贝构造函数(构造的是匿名对象) 在进行一次析构函数(析构的是tmp) } void main(){ Test a(1, 2); /* 第一种调用:直接调用 */ Test b = a; // 1. 不会调用普通构造含糊 直接调用拷贝构造函数!!! // t1 = t2; --> 不会调用拷贝构造函数 这种写法是操作符重载 /* 第二种调用:直接调用 */ Test c(a); // 2. 与上边一样 只是写法不同 /* 第三种调用:类的形参初始化 */ MyTest_03(a); // 3. 此时会调用拷贝构造函数 该函数执行完毕后 调用形参参数的析构函数 /* 第四种调用:函数返回类的匿名对象 */ Test d = MyTest_04(); // 4. 该函数返回了一个匿名对象 使用了匿名对象时 直接把d 的名字给匿名对象(地址不会改变) a = MyTest_04(); // 因为 a 已经被定义,此时匿名对象未使用,则直接调用匿名对象的析构函数 }

    3. 构造函数的规则

    当类中定义了 有参/无参构造 函数时, C++编译器 不会提供 默认的构造函数当类中定义了 拷贝构造 函数时,C++编译器 不会提供 默认的构造函数默认拷贝构造函数成员变量简单赋值(浅拷贝)

    4. 浅/深 拷贝

    浅拷贝: 默认拷贝构造函数 可以完成 对象的数据成员值 简单地复制深拷贝: 对象的属性 有指针 指示的堆时,需要 显式定义 拷贝构造函数(自定义)

    4.1 浅拷贝

    class MyClass{ public: MyClass(const char *sp){ m_cp = (char *)malloc(strlen(sp) + 1); // 需要末尾添加 \n ,所以此处 +1 strcpy(m_cp, sp); } /* 编译器 自动提供一个默认的拷贝构造函数 */ ~MyClass(){ if(m_cp != NULL){ free(m_cp); m_cp = NULL; } } private: char *m_cp; } void Test(){ MyClass p1("home"), p3; MyClass p2 = p1; // 1. 此处进行默认的拷贝 但是 运行时候会造成 断错误, 原因如下图 p3 = p1; // 2. 操作运算符重载 使用默认的运算符重载 也会 存在这样的问题 造成段错误 }

    注意(重点!!! ):操作运算符重载 使用 默认的运算符重载 (浅拷贝) 也会段错误


    4.2 深拷贝

    class MyClass{ public: MyClass(const char *sp){ m_cp = (char *)malloc(strlen(sp) + 1); // 需要末尾添加 \n ,所以此处 +1 strcpy(m_cp, sp); } MyClass(const Name& obj1){ // 自定义拷贝构造函数完成深拷贝 m_cp = (char *)malloc(strlen(bj1.m_cp) + 1); strcpy(m_cp, obj1.m_cp); } ~MyClass(){ if(m_cp != NULL){ free(m_cp); m_cp = NULL; } } private: char *m_cp; } void Test(){ MyClass p1("home"), p3; MyClass p2 = p1; // 这样就解决了这个问题 }


    5. 构造 初始化列表

    5.1 语法规则

    构造函数初始化列表以一个冒号开始, 以逗号分隔的数据成员列表, 每个数据成员后面跟一个放在括号中的初始化式(所赋的参数)

    class CExample { public: CExample(): a(0),b(8.8) {} /* 构造函数初始化列表 */ CExample(){ a=0; b=8.8; } /* 构造函数内部赋值 */ private: int a; float b; };

    5.2 必须 使用初始化列表 情景

    情景1: 成员类型是 没有默认构造函数的类,若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。 class A { public: A(int a) {m_a = a} private: int m_a; }; class B { public: /* 编译器提供默认构造函数 */ private: A m_b; }; void Test(){ B obj; // 编译出错!!! 原因:没有机会初始化 B 类 } 情景2: const 成员 引用类型 的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。 class CExample { public: CExample(): a(0),b(8.8) {} private: int m_a; const float m_fb; };

    5.3 成员变量的初始化顺序

    成员变量的初始化顺序 与 声明的顺序 相关,与在 初始化列表中的顺序 无关(重点!!!) class CExample { public: CExample(): a(0),b(8.8) {} /* 构造函数初始化列表 与初始化列表顺序无关 */ private: float b; int a; /* 先初始化b 再初始化a 与声明顺序有关 */ };

    6. 构造函数中调用构造函数

    说明: 构造函数 中 调用构造函数 是 不可取 ( 危险 ) 的!!!

    class Test{ public: Test(int a, int b, int c){ this->a = a; this->b = b; this->c = c; } Test(int a, int b){ this->a = a; this->b = b; Test(a, b, 100); // 产生新的匿名对象, 对原有的 属性并无影响,并且执行完此函数后 匿名对象被析构 } private: int a, b, c; } void MyTest(){ Test x(1, 2); cout << "c的值为?" << endl; // C的值是 随机值!! } 运行结果: C的值是 随机值 (没有赋初值)原因: 构造 调用 构造, 产生了新的匿名对象, 对原有的 属性并无影响

    7. 匿名对象的生命周期

    class Test{ public: Test(int a, int b, int c){ this->a = a; this->b = b; this->c = c; } ~Test(){} private: int a, b, c; } void MyTest(){ Test(1, 2, 3); // 直产生匿名对象 --> 先调用一次 构造函数 紧接着在调用析构函数 Test aa = Test(1, 2, 3); // 先调用一次 构造函数 然后将aa名字赋给匿名对象地址(编译器自动优化) aa生命周期结束后 在调用析构 }
    写文不易 且行且珍惜 ☛ ☚ MrWang
    Processed: 0.012, SQL: 10