【C++】类和对象(3)

[复制链接]
发表于 2025-9-9 03:22:07 | 显示全部楼层 |阅读模式
【C++】类和对象(3)



一、再探构造函数

1.1初始化列表的基本语法:

  • 之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化另有⼀种方式,就是初始化列表,初始化列表的利用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"反面跟⼀个放在括号中的初始值或表达式。
1.2初始化列表的特性:

  • 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。
  • 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置举行初始化,否则会编译报错。
  • C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有表现在初始化列表初始化的成员利用的。
  • 只管利用初始化列表初始化,由于那些你不在初始化列表初始化的成员也会走初始化列表
  • 初始化列表中按照成员变量在类中声明顺序举行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。

1.1初始化列表的基本语法:


  • 之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化另有⼀种方式,就是初始化列表,初始化列表的利用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"反面跟⼀个放在括号中的初始值或表达式。
    1. //初始化列表:
    2. class Date
    3. {
    4. public:
    5.     //函数体内赋值:
    6.     //Date(int year, int month, int day)
    7.     //{
    8.     //    _year = year;
    9.     //    _month = month;
    10.     //    _day = day;
    11.     //}
    12.     //初始化列表:
    13.     Date(int year, int month, int day)
    14.             :_year(year)
    15.         ,_month(month)
    16.             ,_day(day)
    17.     {}
    18. private:
    19.     int _year;
    20.     int _month;
    21.     int _day;
    22. };
    复制代码

1.2初始化列表的特性


  • 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。
    1. //初始化列表:
    2. class Date
    3. {
    4. public:
    5.     //函数体内赋值:
    6.     //Date(int year, int month, int day)
    7.     //{
    8.     //    _year = year;
    9.     //    _month = month;
    10.     //    _day = day;
    11.     //}
    12.     //初始化列表:
    13.     Date(int& rx, int year, int month, int day)
    14.             :_year(year)
    15.         ,_month(month)
    16.             ,_day(day)
    17.             ,_n(n)
    18.             ,_rx(rx)
    19.     {}
    20. private:
    21.     int _year;
    22.     int _month;
    23.     int _day;
    24.    
    25.     const int _n;//const限定类型必须在初始化列表初始化
    26.     int& _rn;//引用类型对象
    27. };
    复制代码
  • 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置举行初始化,否则会编译报错。
    1. #include <iostream>
    2. using namespace std;
    3. class Time
    4. {
    5. public:
    6.         //显示写构造函数,但并非默认构造函数,编译器也不会再生成默认构造函数
    7.         Time(int hour)
    8.                 :_hour(hour)
    9.         {
    10.                 cout << "Time()" << endl;
    11.         }
    12. private:
    13.         int _hour;
    14. };
    15. class Date
    16. {
    17. public:
    18.     //初始化列表:
    19.     Date(int& rx, int year = 2025, int month = 4, int day = 23)
    20.         :_year(year)
    21.         , _month(month)
    22.         , _day(day)
    23.         , _n(1)
    24.         , _rx(rx)
    25.         ,_t(1)
    26.     {}
    27.     void Print()
    28.     {
    29.         cout << _year << '/' << _month << '/' << _day << endl;
    30.     }
    31. private:
    32.     int _year;
    33.     int _month;
    34.     int _day;
    35.     //必须在初始化列表里初始化:
    36.     //常量限定类型的对象
    37.     //引用对象
    38.     //没有合适的默认构造函数可用的自定义类型对象
    39.     const int _n;//const 类型必须在初始化列表初始化
    40.     int& _rx; //引用 类型必须在初始化列表初始化
    41.     Time _t;//对于自定义类型,有默认构造会自动调用,没有默认构造可以通过初始化列表初始化
    42. };
    复制代码
  • C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有表现在初始化列表初始化的成员利用的。
    1. class Date
    2. {
    3. public:
    4.     Date(int year, int month)
    5.             :_year(year)
    6.         , _month(month)
    7.         //在初始化列表里:
    8.         //即使没有显示地初始化成员变量,初始化列表也会走一遍成员变量的缺省声明
    9.     {}
    10.     void Print() const
    11.         {
    12.             cout << _year << '/' << _month << '/' << _day << endl;
    13.             cout << _ptr << endl;
    14.         }
    15. private:
    16.     //C++11
    17.     //这里不是初始化,这里给的是缺省值,这个缺省值是给初始化列表的
    18.     //如果初始化列表没有显示初始化成员变量,默认就会用这个缺省值初始化
    19.     //声明,缺省值->初始化列表
    20.     int _year = 1;
    21.     int _month = 1;
    22.     int _day = 1;
    23.    
    24.     Time _t = 1;
    25.     const int _n = 1;
    26.    
    27.     //缺省值:可以是表达式
    28.     int* ptr = (int*)malloc(12);
    29. };
    复制代码

    • 初始化列表里没有表现地初始化成员变量,没有初始化的变量会按照成员变量声明里的缺省值初始化

    • 若不表现地写构造函数,编译器生成的默认构造也会按照成员变量里声明的缺省值来初始化


  • 只管利用初始化列表初始化,由于那些你不在初始化列表初始化的成员也会走初始化列表
    1. class Date
    2. {
    3. public:
    4.     Date(int year, int month)
    5.         :_year()
    6.         ,_month()
    7.     {}
    8. private:
    9.     int _year = 1;
    10.     int _month = 1;
    11.     int _day = 1;
    12. };
    复制代码

    • 如果你没有给缺省值,对于没有表现在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。
    • 对于没有表现在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。



  • 初始化列表中按照成员变量在类中声明顺序举行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。


1.3 总结

无论是否表现写初始化列表,每个构造函数都有初始化列表;
无论是否在初始化列表表现初始化,每个成员变量都要走初始化列表初始化;


二、类型转换


  • C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
    1. #incldue <iostream>
    2. using namespace std;
    3. class A
    4. {
    5. public:
    6.     //默认构造函数
    7.         A(int a = 1)
    8.                 :_a1(a)
    9.         {
    10.                 cout << "A()" << endl;
    11.         }
    12.     //拷贝构造函数
    13.         A(const A& aa)
    14.                 :_a1(aa._a1)
    15.         {
    16.                 cout << "A(const A& aa)" << endl;
    17.         }
    18.         void Print()
    19.         {
    20.                 cout << _a1 << endl;
    21.         }
    22.         //析构函数
    23.         ~A()
    24.         {
    25.                 cout << "~A()" << endl;
    26.         }
    27. private:
    28.         int _a1;
    29. };
    30. int main()
    31. {
    32.         A aa1(1);
    33.         aa1.Print();
    34.         //隐式类型转换
    35.         //2 构造一个A的临时对象,在用这个临时对象拷贝构造aa2
    36.         // 编译器遇到连续构造+拷贝构造 -> 优化为直接构造
    37.         A aa2 = 2;
    38.         aa2.Print();
    39.         return 0;
    40. }
    复制代码

  • 构造函数前⾯加explicit就不再⽀持隐式类型转换。

  • 类类型的对象之间也可以隐式转换,需要相应的构造函数⽀持。

  • C++11支持多个成员变量的类类型,可以由内置类型隐式类型转化


三、static成员

3.1 static成员的特性:


  • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外举行初始化。
  • 静态成员变量为类的所有对象所共享,不属于某个具体的实例化对象,不存在实例化对象中,存放在静态区。
  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
  • 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,由于没有this指针。
  • ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过类名::静态成员 大概 对象.静态成员 来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制
  • 静态成员变量不能在声明位置给缺省值初始化,由于缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。

3.2 static成员的特性详解:


  • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外举行初始化。
    1. #include <iostream>
    2. class A
    3. {
    4. public:
    5. private:
    6.     //类里面声明
    7.     static int _scount;
    8. };
    9. //类外初始化
    10. int A::_scount = 0;
    复制代码
  • 静态成员变量为该类的所有对象所共享,不属于某个具体的实例化对象,空间不存在实例化对象中,存放在静态区。

  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
    1. #include <iostream>
    2. class A
    3. {
    4. public:
    5.         static int getACount()
    6.     {
    7.         return _scount;
    8.     }
    9. private:
    10.     //类里面声明
    11.     static int _scount;
    12. };
    复制代码
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,由于没有this指针。

  • 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

  • 突破类域就可以访问静态成员,可以通过类名::静态成员 大概 对象.静态成员来访问静态成员变量和静态成员函数。

  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制

  • 静态成员变量不能在声明位置给缺省值初始化,由于缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

3.3 static成员的作用:

记录生命周期还未竣事的该自定义类变量个数

3.4 关于static成员的试题

1.面试题

  1. class Sum
  2. {
  3. public:
  4.     Sum()
  5.     {
  6.         _ret += _i;
  7.         ++_i;
  8.     }
  9.     static int GetSumCount()
  10.     {
  11.         return _ret;
  12.     }
  13. private:
  14.     static int _i;
  15.     static int _ret;
  16. };
  17. int Sum::_i = 1;
  18. int Sum::_ret = 0;
  19. class Solution
  20. {
  21. public:
  22.     int Sum_Solution(int n) {
  23.         // Sum a[n];//变长数组C99
  24.         Sum* p = new Sum[n];
  25.         return Sum::GetSumCount();
  26.     }
  27. };
复制代码
2.面试题
  1. //设已经有A,B,C,D 4个类的定义,
  2. // 程序中A,B,C,D构造函数调⽤顺序为?()
  3. // 程序中A,B,C,D析构函数调⽤顺序为?()
  4. //A:D B A C
  5. //B:B A D C
  6. //C:C D B A
  7. //D:A B D C
  8. //E:C A B D
  9. //F:C D A B
  10. C c;
  11. int main()
  12. {
  13.         A a;
  14.         B b;
  15.         static D d;
  16.         return 0;
  17. }
复制代码


四、友元friend

4.1友元的特性:


  • 友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明大概类声明的前面加friend,而且把友元声明放到⼀个类的里面。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • ⼀个函数可以是多个类的友元函数
  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
  • 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能通报,如果A是B的友元, B是C的友元,但是A不是C的友元。

4.2友元特性详解:


  • 友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明大概类声明的前面加friend,而且把友元声明放到⼀个类的里面。
    1. void Func2(const A& aa)
    2. {
    3.     cout << aa.a1 << endl;
    4. }
    5. class A
    6. {
    7.     //友元声明
    8.     friend class B;//友元类声明
    9.     friend void Func2(const A& aa);//友元函数声明
    10. private:
    11.    int _a1 = 1;
    12.    int _a2 = 2;
    13. };
    14. class B
    15. {
    16. public:
    17.     void Func1(const A& aa)
    18.     {
    19.         cout << aa._a1 << endl;
    20.     }
    21. };
    复制代码
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。

  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • ⼀个函数可以是多个类的友元函数

  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。

  • 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
    A把B当朋友,A对B毫无保留。但B没把A当朋友,只是把A看成工具人,对A有所防备

  • 友元类关系不能通报,如果A是B的友元, B是C的友元,但是A不是C的友元。
    朋友的朋友不是朋友

  • 偶然提供了便利。但是友元会增长耦合度,粉碎了封装,以是友元不宜多用。

五、内部类

5.1 内部类的特性:


  • 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,以是外部类定义的对象中不包罗内部类。
  • 内部类默认是外部类的友元类。
  • 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类利用,那么可以思量把A类计划为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
5.2 内部类特性详解:


  • 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,以是外部类定义的对象中不包罗内部类。
    1. class A
    2. {
    3. public:
    4.     class B//内部类
    5.     {
    6.     public:
    7.         void foo(const A& a)
    8.         {
    9.             cout << _k << endl;
    10.             cout << a._h << endl;
    11.         }
    12.     private:
    13.         int _b = 1;
    14.     };
    15. private:
    16.     static int _k;
    17.     int _h;
    18. };
    19. int A::_k = 0;
    20. int main()
    21. {
    22.         A a;
    23.         A::B b;
    24.         cout << sizeof(A) << endl;
    25.         b.foo(a);
    26.         return 0;
    27. }
    复制代码

  • 内部类默认是外部类的友元类。
    内部类可以随意访问外部类的私有和保护,但外部类不能随意访问内部类的私有和保护

  • 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类利用,那么可以思量把A类计划为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

六、 匿名对象

6.1 匿名对象


  • ⽤ 类型(实参) 定义出来的对象叫做匿名对象,相⽐之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象
  • 匿名对象生命周期只在当前一行,一般临时定义⼀个对象当前用⼀下即可,就可以定义匿名对象。
  1. class A
  2. {
  3. public:
  4.     A(int a = 1)
  5.         : _a(a)
  6.     {
  7.          cout << "A(int a = 1)" << endl;
  8.     }
  9.    
  10.     void Func1()
  11.     {
  12.         cout << _a1 << endl;
  13.     }
  14.    
  15.     ~A()
  16.     {
  17.         cout << "~A()" << endl;
  18.     }
  19. private:
  20.     int _a1;
  21. };
  22. int main()
  23. {
  24.     A aa1;//有名对象
  25.    
  26.     A(1);//匿名对象,生命周期仅在该行
  27.     A(2);
  28.    
  29.     aa1.Func1();//有名对象函数调用
  30.     A().Func1();//匿名对象函数调用
  31.    
  32.     return 0;
  33. }
复制代码


七、对象拷贝时的编译器


  • 现代编译器会为了尽大概提高步伐的效率,在不影响正确性的情况下会尽大概减少⼀些传参和传返回值的过程中可以省略的拷贝。

    • 传值传参上连续的构造和拷贝构造
      1. #include <iostream>
      2. class A
      3. {
      4. public:
      5.         A(int a = 0)//默认构造
      6.                 :_a1(a)
      7.         {
      8.                 cout << "A(int a = 1)" << endl;
      9.         }
      10.         A(const A& aa)
      11.                 :_a1(aa._a1)
      12.         {
      13.                 cout << "A(const A& aa)" << endl;
      14.         }
      15.         ~A()
      16.         {
      17.                 cout << "~A()" << endl;
      18.         }
      19. private:
      20.         int _a1;
      21. };
      22. void f1(A aa)
      23. {
      24.         cout << "**********" << endl;
      25. }
      26. int main()
      27. {
      28.         //传值传参
      29.         A aa1;
      30.         f1(aa1);
      31.         cout << endl;
      32.         //隐式类型转换,连续构造+拷贝构造 ->优化为直接构造
      33.         f1(1);
      34.         //原本的语法流程为首先1构造一个临时对象,
      35.         //临时对象再拷贝构造给临时对象
      36.         //现在优化为省去拷贝构造直接构造
      37.        
      38.     f1(A(2));
      39.         //原本:匿名对象构造出对象,让后拷贝构造个临时对象作为形参
      40.         //现在:匿名对象直接替代临时对象,直接当作形参
      41.         cout << "#################" << endl;
      42.         return 0;
      43. }
      复制代码


    • 传值返回中连续拷贝构造+拷贝构造->优化⼀个拷贝构造
      1. #include <iostream>
      2. using a
      3. class A
      4. {
      5. public:
      6.         A(int a = 0)//默认构造
      7.                 :_a1(a)
      8.         {
      9.                 cout << "A(int a = 1)" << endl;
      10.         }
      11.         A(const A& aa)
      12.                 :_a1(aa._a1)
      13.         {
      14.                 cout << "A(const A& aa)" << endl;
      15.         }
      16.         ~A()
      17.         {
      18.                 cout << "~A()" << endl;
      19.         }
      20. private:
      21.         int _a1;
      22. };
      复制代码

  • 如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷贝会举行合并优化,有些更新更"激进"的编译器还会举行跨行跨表达式的合并优化。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表