• 第十三章 类和对象——对象的初始化和清理


    • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全

    • C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。

    一、构造函数和析构函数

    对象的初始化和清理也是两个非常重要的安全问题

    一个对象或者变量没有初始状态,对其使用后果是未知

    同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

    c++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

    对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

    编译器提供的构造函数和析构函数是空实现。

    • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

    • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

    对象的生命周期是指对象存在的时间段,对象生命周期结束,会调用析构函数。

    构造函数语法:类名(){}

    1. 构造函数,没有返回值也不写void

    2. 函数名称与类名相同

    3. 构造函数可以有参数,因此可以发生重载

    4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

    析构函数语法: ~类名(){}

    1. 析构函数,没有返回值也不写void

    2. 函数名称与类名相同,在名称前加上符号 ~

    3. 析构函数不可以有参数,因此不可以发生重载

    4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

    1.  class Person
    2.  {
    3.  public:
    4.   //构造函数
    5.   Person()
    6.   {
    7.   cout << "Person的构造函数调用" << endl;
    8.   }
    9.   //析构函数
    10.   ~Person()
    11.   {
    12.   cout << "Person的析构函数调用" << endl;
    13.   }
    14.  ​
    15.  };
    16.  ​
    17.  void test01()
    18.  {
    19.   Person p;
    20.  }
    21.  ​
    22.  int main() {
    23.  
    24.   test01();
    25.  ​
    26.   system("pause");
    27.  ​
    28.   return 0;
    29.  }

    二、构造函数的分类及调用

    两种分类方式:

    按参数分为: 有参构造和无参构造

    按类型分为: 普通构造和拷贝构造

    三种调用方式:

    括号法

    显示法

    隐式转换法

    示例:

    1.  //1、构造函数分类
    2.  // 按照参数分类分为 有参和无参构造   无参又称为默认构造函数
    3.  // 按照类型分类分为 普通构造和拷贝构造
    4.  ​
    5.  class Person {
    6.  public:
    7.   //无参(默认)构造函数
    8.   Person() {
    9.   cout << "无参构造函数!" << endl;
    10.   }
    11.   //有参构造函数
    12.   Person(int a) {
    13.   age = a;
    14.   cout << "有参构造函数!" << endl;
    15.   }
    16.   //拷贝构造函数
    17.   Person(const Person& p) {
    18.   age = p.age;
    19.   cout << "拷贝构造函数!" << endl;
    20.   }
    21.   //析构函数
    22.   ~Person() {
    23.   cout << "析构函数!" << endl;
    24.   }
    25.  public:
    26.   int age;
    27.  };
    28.  ​
    29.  //2、构造函数的调用
    30.  //调用无参构造函数
    31.  void test01() {
    32.   Person p; //调用无参构造函数
    33.  }
    34.  ​
    35.  //调用有参的构造函数
    36.  void test02() {
    37.  ​
    38.   //2.1 括号法,常用
    39.   Person p1(10);
    40.   //注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
    41.   //Person p2();
    42.  ​
    43.   //2.2 显式法
    44.   Person p2 = Person(10);
    45.   Person p3 = Person(p2);
    46.   //Person(10)单独写就是匿名对象 当前行结束之后,系统会立刻回收掉匿名对象
    47.  
    48.      //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
    49.   Person(p3); //编译器会认为 Person(p3) == Person p3 ,编译器认为是对象声明
    50.      
    51.   //2.3 隐式转换法
    52.   Person p4 = 10; // Person p4 = Person(10);
    53.   Person p5 = p4; // Person p5 = Person(p4);
    54.  ​
    55.  
    56.  }
    57.  ​
    58.  int main() {
    59.  ​
    60.   test01();
    61.   //test02();
    62.  ​
    63.   system("pause");
    64.  ​
    65.   return 0;
    66.  }

    三、拷贝构造函数调用时机

    C++中拷贝构造函数调用时机通常有三种情况

    • 使用一个已经创建完毕的对象来初始化一个新对象

    • 值传递的方式给函数参数传值

    • 以值方式返回局部对象

    示例:

    1.  class Person {
    2.  public:
    3.   Person() {
    4.   cout << "无参构造函数!" << endl;
    5.   mAge = 0;
    6.   }
    7.   Person(int age) {
    8.   cout << "有参构造函数!" << endl;
    9.   mAge = age;
    10.   }
    11.   Person(const Person& p) {
    12.   cout << "拷贝构造函数!" << endl;
    13.   mAge = p.mAge;
    14.   }
    15.   //析构函数在释放内存之前调用
    16.   ~Person() {
    17.   cout << "析构函数!" << endl;
    18.   }
    19.  public:
    20.   int mAge;
    21.  };
    22.  ​
    23.  //1. 使用一个已经创建完毕的对象来初始化一个新对象
    24.  void test01() {
    25.  ​
    26.   Person man(100); //p对象已经创建完毕
    27.   Person newman(man); //调用拷贝构造函数
    28.   Person newman2 = man; //拷贝构造
    29.  ​
    30.   //Person newman3;
    31.   //newman3 = man; //不是调用拷贝构造函数,赋值操作
    32.  }
    33.  ​
    34.  //2. 值传递的方式给函数参数传值
    35.  //相当于Person p1 = p;
    36.  void doWork(Person p1) {}
    37.  void test02() {
    38.   Person p; //无参构造函数
    39.   doWork(p);
    40.  }
    41.  ​
    42.  //3. 以值方式返回局部对象
    43.  Person doWork2()
    44.  {
    45.   Person p1;
    46.   cout << (int *)&p1 << endl;
    47.   return p1;
    48.  }
    49.  ​
    50.  void test03()
    51.  {
    52.   Person p = doWork2();
    53.   cout << (int *)&p << endl;
    54.  }
    55.  ​
    56.  ​
    57.  int main() {
    58.  ​
    59.   //test01();
    60.   //test02();
    61.   test03();
    62.  ​
    63.   system("pause");
    64.  ​
    65.   return 0;
    66.  }

    四、构造函数调用规则

    默认情况下,c++编译器至少给一个类添加3个函数

    1.默认构造函数(无参,函数体为空)

    2.默认析构函数(无参,函数体为空)

    3.默认拷贝构造函数,对属性进行值拷贝

    构造函数调用规则如下:

    • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造

    • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

    示例:

    1.  class Person {
    2.  public:
    3.   //无参(默认)构造函数
    4.   Person() {
    5.   cout << "无参构造函数!" << endl;
    6.   }
    7.   //有参构造函数
    8.   Person(int a) {
    9.   age = a;
    10.   cout << "有参构造函数!" << endl;
    11.   }
    12.   //拷贝构造函数
    13.   Person(const Person& p) {
    14.   age = p.age;
    15.   cout << "拷贝构造函数!" << endl;
    16.   }
    17.   //析构函数
    18.   ~Person() {
    19.   cout << "析构函数!" << endl;
    20.   }
    21.  public:
    22.   int age;
    23.  };
    24.  ​
    25.  void test01()
    26.  {
    27.   Person p1(18);
    28.   //如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
    29.   Person p2(p1);
    30.  ​
    31.   cout << "p2的年龄为: " << p2.age << endl;
    32.  }
    33.  ​
    34.  void test02()
    35.  {
    36.   //如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
    37.   Person p1; //此时如果用户自己没有提供默认构造,会出错
    38.   Person p2(10); //用户提供的有参
    39.   Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
    40.  ​
    41.   //如果用户提供拷贝构造,编译器不会提供其他构造函数
    42.   Person p4; //此时如果用户自己没有提供默认构造,会出错
    43.   Person p5(10); //此时如果用户自己没有提供有参,会出错
    44.   Person p6(p5); //用户自己提供拷贝构造
    45.  }
    46.  ​
    47.  int main() {
    48.  ​
    49.   test01();
    50.  ​
    51.   system("pause");
    52.  ​
    53.   return 0;
    54.  }

    五、深拷贝与浅拷贝

    深浅拷贝是面试经典问题,也是常见的一个坑

    浅拷贝:简单的赋值拷贝操作

    深拷贝:在堆区重新申请空间,进行拷贝操作

    示例:

    1.  class Person {
    2.  public:
    3.   //无参(默认)构造函数
    4.   Person() {
    5.   cout << "无参构造函数!" << endl;
    6.   }
    7.   //有参构造函数
    8.   Person(int age ,int height) {
    9.  
    10.   cout << "有参构造函数!" << endl;
    11.  ​
    12.   m_age = age;
    13.   m_height = new int(height);
    14.  
    15.   }
    16.   //拷贝构造函数  
    17.   Person(const Person& p) {
    18.   cout << "拷贝构造函数!" << endl;
    19.   //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
    20.   m_age = p.m_age;
    21.   m_height = new int(*p.m_height);
    22.  
    23.   }
    24.  ​
    25.   //析构函数
    26.   ~Person() {
    27.   cout << "析构函数!" << endl;
    28.   if (m_height != NULL)
    29.   {
    30.   delete m_height;
    31.   }
    32.   }
    33.  public:
    34.   int m_age;
    35.   int* m_height;
    36.  };
    37.  ​
    38.  void test01()
    39.  {
    40.   Person p1(18, 180);
    41.  ​
    42.   Person p2(p1);
    43.  ​
    44.   cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
    45.  ​
    46.   cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
    47.  }
    48.  ​
    49.  int main() {
    50.  ​
    51.   test01();
    52.  ​
    53.   system("pause");
    54.  ​
    55.   return 0;
    56.  }

    总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

    六、初始化列表

    作用:

    C++提供了初始化列表语法,用来初始化属性

    语法:构造函数():属性1(值1),属性2(值2)... {}

    示例:

    1.  class Person {
    2.  public:
    3.  ​
    4.   传统方式初始化
    5.   //Person(int a, int b, int c) {
    6.   // m_A = a;
    7.   // m_B = b;
    8.   // m_C = c;
    9.   //}
    10.  ​
    11.   //初始化列表方式初始化
    12.   Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
    13.   void PrintPerson() {
    14.   cout << "mA:" << m_A << endl;
    15.   cout << "mB:" << m_B << endl;
    16.   cout << "mC:" << m_C << endl;
    17.   }
    18.  private:
    19.   int m_A;
    20.   int m_B;
    21.   int m_C;
    22.  };
    23.  ​
    24.  int main() {
    25.  ​
    26.   Person p(1, 2, 3);
    27.   p.PrintPerson();
    28.  ​
    29.  ​
    30.   system("pause");
    31.  ​
    32.   return 0;
    33.  }

    七、类对象作为类成员

    C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员

    例如:

     class A {}
     class B
     {
         A a;
     }
    

    B类中有对象A作为成员,A为对象成员

    那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?

    示例:

    1.  class Phone
    2.  {
    3.  public:
    4.   Phone(string name)
    5.   {
    6.   m_PhoneName = name;
    7.   cout << "Phone构造" << endl;
    8.   }
    9.  ​
    10.   ~Phone()
    11.   {
    12.   cout << "Phone析构" << endl;
    13.   }
    14.  ​
    15.   string m_PhoneName;
    16.  ​
    17.  };
    18.  ​
    19.  ​
    20.  class Person
    21.  {
    22.  public:
    23.  ​
    24.   //初始化列表可以告诉编译器调用哪一个构造函数
    25.   Person(string name, string pName) :m_Name(name), m_Phone(pName)
    26.   {
    27.   cout << "Person构造" << endl;
    28.   }
    29.  ​
    30.   ~Person()
    31.   {
    32.   cout << "Person析构" << endl;
    33.   }
    34.  ​
    35.   void playGame()
    36.   {
    37.   cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
    38.   }
    39.  ​
    40.   string m_Name;
    41.   Phone m_Phone;
    42.  ​
    43.  };
    44.  void test01()
    45.  {
    46.   //当类中成员是其他类对象时,我们称该成员为 对象成员
    47.   //构造的顺序是 :先调用对象成员的构造,再调用本类构造
    48.   //析构顺序与构造相反
    49.   Person p("张三" , "苹果X");
    50.   p.playGame();
    51.  ​
    52.  }
    53.  ​
    54.  ​
    55.  int main() {
    56.  ​
    57.   test01();
    58.  ​
    59.   system("pause");
    60.  ​
    61.   return 0;
    62.  }

    八、静态成员

    静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

    静态成员分为:

    • 静态成员变量

      • 所有对象共享同一份数据

      • 在编译阶段分配内存

      • 类内声明,类外初始化

    • 静态成员函数

      • 所有对象共享同一个函数

      • 静态成员函数只能访问静态成员变量

    示例1 :静态成员变量

    1.  class Person
    2.  {
    3.  
    4.  public:
    5.  ​
    6.   static int m_A; //静态成员变量
    7.  ​
    8.   //静态成员变量特点:
    9.   //1 在编译阶段分配内存
    10.   //2 类内声明,类外初始化
    11.   //3 所有对象共享同一份数据
    12.  ​
    13.  private:
    14.   static int m_B; //静态成员变量也是有访问权限的
    15.  };
    16.  int Person::m_A = 10;
    17.  int Person::m_B = 10;
    18.  ​
    19.  void test01()
    20.  {
    21.   //静态成员变量两种访问方式
    22.  ​
    23.   //1、通过对象
    24.   Person p1;
    25.   p1.m_A = 100;
    26.   cout << "p1.m_A = " << p1.m_A << endl;
    27.  ​
    28.   Person p2;
    29.   p2.m_A = 200;
    30.   cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
    31.   cout << "p2.m_A = " << p2.m_A << endl;
    32.  ​
    33.   //2、通过类名
    34.   cout << "m_A = " << Person::m_A << endl;
    35.  ​
    36.  ​
    37.   //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
    38.  }
    39.  ​
    40.  int main() {
    41.  ​
    42.   test01();
    43.  ​
    44.   system("pause");
    45.  ​
    46.   return 0;
    47.  }

    示例2:静态成员函数

    1.  class Person
    2.  {
    3.  ​
    4.  public:
    5.  ​
    6.   //静态成员函数特点:
    7.   //1 程序共享一个函数
    8.   //2 静态成员函数只能访问静态成员变量
    9.  
    10.   static void func()
    11.   {
    12.   cout << "func调用" << endl;
    13.   m_A = 100;
    14.   //m_B = 100; //错误,不可以访问非静态成员变量
    15.   }
    16.  ​
    17.   static int m_A; //静态成员变量
    18.   int m_B; //
    19.  private:
    20.  ​
    21.   //静态成员函数也是有访问权限的
    22.   static void func2()
    23.   {
    24.   cout << "func2调用" << endl;
    25.   }
    26.  };
    27.  int Person::m_A = 10;
    28.  ​
    29.  ​
    30.  void test01()
    31.  {
    32.   //静态成员变量两种访问方式
    33.  ​
    34.   //1、通过对象
    35.   Person p1;
    36.   p1.func();
    37.  ​
    38.   //2、通过类名
    39.   Person::func();
    40.  ​
    41.  ​
    42.   //Person::func2(); //私有权限访问不到
    43.  }
    44.  ​
    45.  int main() {
    46.  ​
    47.   test01();
    48.  ​
    49.   system("pause");
    50.  ​
    51.   return 0;
    52.  }

  • 相关阅读:
    负载均衡的原理及其算法详解
    【CodeTON Round 2 (Div. 1 + Div. 2, Rated, Prizes!)(A~D)】
    ElasticSearch:集群安装
    多路转接IO模型(select概念及使用方法)
    华为云云耀云服务器L实例评测|redis漏洞回顾 & MySQL数据安全解决 搭建主从集群MySQL & 相关设置
    【Vue2.x源码系列01】响应式原理
    环境配置 | 图文VS2022配置OpenCV,Dlib
    做SaaS产品会遇到哪些坑?
    【无标题】
    Docker的容器管理
  • 原文地址:https://blog.csdn.net/qq_26126363/article/details/133443257