• [C++]C++类和对象(下)、


    目录

    1、再谈构造函数

    1.1、构造函数的函数体中进行赋值

    1.2、初始化列表

    1.3、explicit关键字

    2、友元

    2.1、友元函数

    2.2、友元类

    3、内部类

    3.1、概念及特性

    4、例题

    4.1、例题1

    4.2、例题2

    4.3、例题3 

    4.4、例题4 

    4.5、例题5

    5、再次理解封装

    6、再次理解面向对象


    1、再谈构造函数

    1.1、构造函数的函数体中进行赋值

        在创建对象时,编译器通过自动调用构造函数,给对象中各个内置类型的类成员变量一个合适的初始值、
    1. class Date
    2. {
    3. public:
    4. Date(int year, int month, int day)
    5. {
    6. _year = year;
    7. _month = month;
    8. _day = day;
    9. }
    10. private:
    11. int _year;
    12. int _month;
    13. int _day;
    14. };
          虽然上述构造函数(非默认)调用之后,对象中的各个内置类型的类成员变量都已经有了一个初始值,但是不能将该非默认构造函数的函数体中的赋值过程作为类对象中的各个内置类型的类成员变量的初始化,构造函数(默认和非默认)的函数体中的语句(赋值过程)只能称作为赋初值,而不能称作初始化,因为初始化只能初始化一次,是在初始化列表中进行的,而构造函数(默认和非默认)的函数体内可以进行多次赋值, 初始化列表可以认为就是类体中非静态类成员变量进行定义的地方,可以认为在初始化列表中,对类体中的所有的非静态类成员变量均进行了定义,在定义的地方进行的赋值操作称为初始化,并且初始化只能进行一次、

    1.2、初始化列表

         初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据列表,每个"非静态的类成员变量"后面跟一个放在括号中的初始值或表达式、

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. class A
    5. {
    6. public:
    7. 1
    8. //A(int x)
    9. //{
    10. // _x=x;
    11. //}
    12. //2、
    13. A(int x)
    14. :_x(x)
    15. {}
    16. private:
    17. int _x;
    18. };
    19. //5、
    20. int value = 10; //全局变量、
    21. class Date
    22. {
    23. public:
    24. 1,在构造函数的函数体内进行初始化、
    25. //Date(int year = 1, int month = 1, int day = 1)
    26. //{
    27. // _year = year;
    28. // _month = month;
    29. // _day = day;
    30. //}
    31. 2,在初始化列表中进行初始化、
    32. //Date(int year = 1, int month = 1, int day = 1)
    33. // :_year(year)
    34. // , _month(month)
    35. // , _day(day)
    36. //{}
    37. 3,上述两种初始化方式可以混用(仅限于非静态类成员变量的部分情况)、
    38. //Date(int year = 1, int month = 1, int day = 1)
    39. // :_year(year)
    40. // , _month(month)
    41. //{
    42. // _day = day;
    43. //}
    44. 4
    45. //Date(int year, int month, int day, int n, int value)
    46. // : _n(n)
    47. // , _value(value)//此时,整型变量value是局部变量,而非静态类成员变量_value是局部变量value的别名,而局部变量value出了该构造函数就会被销毁,所以继续进行优化、
    48. //{
    49. // _year = year;
    50. // _month = month;
    51. // _day = day;
    52. //}
    53. 5
    54. //Date(int year, int month, int day, int n)
    55. // : _n(n)
    56. // , _value(value)//此时,将整型局部变量value变为整型全局变量,具体见 Date类体外部 的上面、
    57. //{
    58. // _year = year;
    59. // _month = month;
    60. // _day = day;
    61. //}
    62. 6
    63. //Date(int year, int month, int day, int n)
    64. // : _n(n)
    65. // , _value(value)
    66. // , _aa(10)
    67. //{
    68. // _year = year;
    69. // _month = month;
    70. // _day = day;
    71. //}
    72. 注意: 对于自定义类型的非静态类成员变量 _aa 而言,若他需要进行初始化时,只能在初始化列表中进行初始化,不能够在构造函数的函数体中进行初始化,
    73. 当然,我们也可以选择让自定义类型的非静态类成员变量 _aa 不进行初始化,那么这时候,在初始化列表中还有该自定义类型的非静态类成员变量 _aa 的定义
    74. 与其他非静态类成员变量不同的是,该自定义类型的非静态类成员变量_aa的定义(不传参),则可以自动调用它对应的默认构造函数,但此时,由于它对应的类体中
    75. 并不存在默认构造函数,所以编译器会报错说是,不存在合适的默认构造函数可以使用,要想成功编译的话,可以在它对应的类体中提供默认构造函数即可,其次,
    76. 如果就是不想在它对应的类体中提供默认构造函数的话,怎么做才能编译成功呢,此时,就需要让自定义类型的非静态类成员变量 _aa 进行初始化,并且只能在初始化列表中进行初始化、
    77. 7
    78. //Date(int year, int month, int day, int n, int a)
    79. // : _n(n) //1
    80. // , _value(value) //10
    81. // , _aa(a) //15
    82. //{
    83. // _year = year;
    84. // _month = month;
    85. // _day = day;
    86. //}
    87. 对于非静态类成员变量 _value和_n 而言,若要进行初始化的话,则只能在初始化列表中进行,若不进行初始化的话,那么在初始化列表中则会有这两者的定义,
    88. 但与自定义类型的非静态类成员变量_aa不同的是,这两者的定义并不会自动调用它对应的默认构造函数,此时如果不对这两者在初始化列表中进行初始化的话,
    89. 则编译器就会报错说是未对他们进行初始化,因此,这两个非静态类成员变量 _value和_n ,必须要在初始化列表中进行初始化才可以,对于除了这两种之外的
    90. 非静态类成员变量在初始化列表中可以选择进行初始化,也可以选择不进行初始化、
    91. 8
    92. //Date(int year, int month, int day, int n, int a)
    93. // : _n(n) //1
    94. // , _value(value) //10
    95. // , _aa(a) //15
    96. // //, _year(year)
    97. // //每个非静态类成员变量在初始化列表中只能出现一次,即初始化只能进行一次,不可以进行多次、
    98. // //初始化列表可以认为就是类体中非静态类成员变量进行定义的地方,可以认为在初始化列表中,对类体中的所有的非静态类成员变量均进行了定义,在定义的地方进行的赋值操作称为初始化,并且初始化只能进行一次、
    99. //{
    100. // _year = year;
    101. // _month = month;
    102. // _day = day;
    103. //}
    104. //问:那么 初始化列表 存在的价值是什么?
    105. //像非静态类成员变量 _year,_month,_day ,即可以在构造函数的函数体内进行初始化,也可以在初始化列表中进行初始化,但是对于 某些 非静态类成员变量而言,
    106. //只能在初始化列表中进行初始化,即只能在其定义的地方进行初始化,具体见下面的总结、
    107. private:
    108. //以下只是声明、
    109. //下面这些非静态类成员变量,能在初始化列表中进行初始化,即,能在定义的时候进行初始化,也可以在初始化列表中不进行初始化,即,在定义的时候不进行初始化
    110. //而是在构造函数的函数体内进行初始化、
    111. int _year;
    112. int _month;
    113. int _day;
    114. //上述三者均属于非静态类成员变量,均是声明,都在初始化列表中进行定义,但是,他们可以在初始化列表中进行初始化,也可以在构造函数的函数体中进行初始化、
    115. //下面这些非静态类成员变量,只能在初始化列表中进行初始化,即只能在定义的时候进行初始化、
    116. const int _n; //此处关键字const修饰的非静态类成员变量也是声明,在初始化列表中进行定义,但是,只能在初始化列表中进行初始化、
    117. int& _value; //此处的运算符&代表引用,也是声明,在初始化列表中进行定义,但是,只能在初始化列表中进行初始化、
    118. A _aa; //自定义类型的非静态类成员变量,此处也是声明,在初始化列表中进行定义,但是,只能在初始化列表中进行初始化、
    119. // //除了这三种非静态类型的类成员变量以外的其他的非静态类型的类成员变量,即可以在初始化列表中进行初始化,也可以在构造函数的函数体内进行赋值(初始化),准确来说,在构造函数的函数体内进行
    120. // //的不能叫做初始化,而是叫做赋值,即,除了这三种非静态类型的类成员变量以外的其他的非静态类型的类成员变量,即可以在初始化列表中进行初始化操作,也可以在初始化列表中不进行初始化,而是在
    121. // //构造函数的函数体内进行赋值操作、
    122. //int _year = 0;
    123. //当我们在类体中不显式的实现构造函数(默认和非默认)时,则编译器会在类体中自动生成并调用一个无参的默认构造函数该无参的默认构造函数会对内置类型不做处理,对自定义类型
    124. //通过自动调用它对应的构造函数(默认和非默认)去进行初始化,这里的整型数字 0 ,并不代表的是进行初始化,因为这行代码只是进行的声明,并不为非静态类成员变量 _year 开辟内存空间,这里是缺
    125. //省值(C++11),这个缺省值,就是给初始化列表使用的,此时这行代码在初始化列表中等价于: _year(0) 但是如果在构造函数(除编译器自动生成的无参的默认构造函数之外的所有构造函数)的形参列
    126. //表中拿到了局部整型变量 year 的值,不管是全缺省拿到的,还是外面传过来的,那么此时如果是在初始化列表中对非静态类成员变量_year进行初始化操作,则不会使用类体中非静态类成员变量 _year
    127. //声明的这里的缺省值,他就是一个备胎,如果是在构造函数(除编译器自动生成的无参的默认构造函数之外的所有构造函数)的函数体内对非静态类成员变量_year进行初始化操作,那么此时应该是,在执
    128. //行初始化列表中的内容时,会先使用非静态类成员变量 _year 声明的这里的缺省值,然后当执行完初始化列表中的内容后,再执行构造函数(除编译器自动生成的无参的默认构造函数之外的所有构造函数)的
    129. //函数体中的内容,此时会再次把非静态类成员变量 _year 中的在初始化列表中使用的类体中非静态类成员变量 _year 声明的这里的缺省值的值替换掉,即,把非静态类成员变量 _year 声明的这里的缺省值替换掉、
    130. //总结: 有些非静态类成员变量只能在初始化列表中进行初始化,而有些非静态类成员变量能够在构造函数(除编译器自动生成的无参的默认构造函数之外的所有构造函数)的函数体中进行初始化,也可以在初始化列表中进行初始化
    131. //在此建议,把非静态类成员变量的初始化操作 尽量 都放在 初始化列表中 进行, 这是一定正确的、
    132. };
    133. int main()
    134. {
    135. 123
    136. //Date d1(2022,5,18);
    137. //此处是自定义类型的对象d1的定义,我们知道,对于非静态类成员变量均是属于某一个自定义类型的对象,所以当某一个自定义类型的对象进行定义时
    138. //那么该自定义类型的对象对应的类体中的非静态类成员变量才进行真正的定义,那这些非静态类成员变量具体在哪里进行的定义呢?
    139. //即,初始化列表可以认为就是类体中非静态类成员变量进行定义的地方,可以认为在初始化列表中,对类体中的所有的非静态类成员变量均进行了定义,
    140. //这是编译器自动完成的,我们不需要手动的去完成,我们只需要判断一下是否需要在初始化列表中对这些非静态类成员变量进行初始化即可、
    141. 4
    142. //Date d1(2022,5,18,1,2);
    143. 5
    144. //Date d1(2022, 5, 18, 1);
    145. 6
    146. //Date d1(2022, 5, 18, 1);
    147. 7
    148. //Date d1(2022, 5, 18, 1,15);
    149. 8
    150. //Date d1(2022, 5, 18, 1,15);
    151. return 0;
    152. }
    153. //注意:
    154. //初始化列表只在构造函数(默认和非默认)中才会有,并且,当调用构造函数(默认和非默认)的时候,要先把初始化列表中的内容都执行完毕之后才会去执行该构造函数函数体中的代码
    155. //要注意,在初始化列表中,对类体中的所有的非静态类成员变量均进行了定义,我们知道,如果我们在类体中不显式的实现构造函数(默认和非默认)的话,则编译器
    156. //会在类体中自动生成一个无参的默认构造函数,还要知道,该无参的默认构造函数中的初始化列表中并没有显式的写出任何一个非静态类成员变量
    157. //初始化的操作,即编译器自生成的无参的默认构造函数的初始化列表是空的,那么,所以编译器自动生成的无参的默认构造函数对内置类型不进行
    158. //处理,而对自定义类型则是通过自动调用它对应的构造函数(默认和非默认)来进行初始化的意思就是说,在编译器自动生成的无参的默认构造函数
    159. //中的初始化列表中,对类体中的非静态类成员变量均进行了定义,并且其初始化列表为空,所以在初始化列表中并没有对内置类型进行初始化,但是
    160. //在初始化列表中却自动调用了自定义类型对应的默认构造函数,并且编译器自动生成的无参默认构造函数的函数体内也是空的,所以,在该
    161. //函数体中也没有对类体中的内置类型进行初始化操作,这就构成了我们常说的,当我们不显式的在类体中实现构造函数时(默认和非默),那么编译器
    162. //会在类体中自动生成并调用无参的默认构造函数,它对内置类型不做处理,对自定义类型则是通过自动调用它对应的默认构造函数进行初始化,
    163. //总结: 如果某一个构造函数(默认和非默认)的初始化列表为空,且在该构造函数的函数体中对类体中的内置类型进行初始化操作的话,那么一定是先对类体中的自定义类型
    164. //进行了初始化操作,后对类体中的内置类型进行了初始化操作、

      注意:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. int ret = 10; //全局变量、
    5. class A
    6. {
    7. public:
    8. A(int x)
    9. :_x(x)
    10. {}
    11. int _x;
    12. };
    13. class Date
    14. {
    15. public:
    16. Date(int year, int month, int day, int n,int a)
    17. : _year(year=1)
    18. , _month(month=2)
    19. , _day(day=3)
    20. ,_n(n = 4) //若为此操作,则优先使用 n=4 , 注意: 表达式 n=4 执行后的结果为 n ,其余5者同理、
    21. , _ret(ret=5)
    22. , _aa(a=6)
    23. {}
    24. void Print()
    25. {
    26. cout << _year << " " << _month << " " << _day << " " << _n << " " << _ret << " "<< _aa._x << endl;
    27. }
    28. private:
    29. //此处的整型数字10,20,30,均用于初始化列表,但我们知道,在类体中,内置类型声明时赋缺省值的优先级比较低,当在初始化列表中已经显式的进行了初始化操作,则就不再使用这里声明处的缺省值、
    30. int _year = 10;
    31. int _month = 20;
    32. int _day = 30;
    33. int& _ret;
    34. const int _n ;
    35. //const int _n = 7;//此时为赋缺省值,不是初始化,即使为初始化也没关系,因为关键字const修饰的变量不能被修改,但是可以进行初始化,此时在初始化列表中即使不写: ,_n(n=4)
    36. //也是可以的,因为,编译器会自动在初始化列表中加上代码: ,_n(7) 所以不会报错说是未在初始化列表中进行初始化操作、
    37. A _aa = 20;
    38. //对于部分(该自定义的类,类A的类体中的构造函数除形参列表中第一个位置上隐藏的this指针外,只有单个参数时)自定义类型的对象在类体(Date类)中声明时也可以进行赋缺省值的操作,具体见C++类和对象下的博客、
    39. //由于此处只是声明,不是定义,考虑隐式类型转换,但不考虑部分编译器在一个步骤(表达式)中连续调用构造加拷贝构造或者连续调用拷贝构造加拷贝构造的优化和关键字explicit的作用
    40. };
    41. int main()
    42. {
    43. Date d1(2022, 5, 18, 10,100);
    44. d1.Print();
    45. return 0;
    46. }

    例题:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. class A
    5. {
    6. public:
    7. A(int a)
    8. :_a1(a)
    9. , _a2(_a1)
    10. {}
    11. void Print()
    12. {
    13. cout << _a1 << " " << _a2 << endl;
    14. }
    15. private:
    16. int _a2;
    17. int _a1;
    18. };
    19. int main()
    20. {
    21. A aa(1);
    22. aa.Print(); // 1 -858993460
    23. }
    24. //A.输出: 1、1
    25. //B.程序崩溃
    26. //C.编译不通过
    27. //D.输出: 1、随机值
    28. //答:
    29. //答案选D,这是因为:
    30. //非静态类成员变量在类体中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
    31. //故,在初始化列表中,先对非静态类成员变量 _a2 进行初始化操作,再对非静态类成员变量 _a1 进行初始化操作
    32. //此时,在初始化列表中先执行代码: , _a2(_a1) ,而此时的非静态类成员变量 _a1 还未进行初始化操作,又因为
    33. //自定义类型的对象aa是局部对象,故非静态类成员变量 _a1 的值就是一个随机值,则非静态类成员变量 _a2 得到
    34. //的就是一个随机值,然后编译器再执行代码 :_a1(a) ,则非静态类成员变量 _a1 得到的就是整型局部变量a,即得
    35. //到的结果就是整型数字1,故最后打印出来的就是: 1 随机值
    36. //还要知道,当调用构造函数(默认和非默认)时,都必须先要把初始化列表中的内容都执行完毕后,再去执行该构造
    37. //函数的函数体中的代码、

    1.3、explicit关键字

    1. //#define _CRT_SECURE_NO_WARNINGS 1
    2. //#include
    3. //#include //在C++中会学习一个STL库中的string类、
    4. //using namespace std;
    5. //class Date
    6. //{
    7. //public:
    8. // /*explicit Date(int year)
    9. // :_year(year)
    10. // {
    11. // cout << "Date(int year)" << endl;
    12. // }*/
    13. //
    14. // Date(int year)
    15. // :_year(year)
    16. // {
    17. // cout << "Date(int year)" << endl;
    18. // }
    19. //
    20. // Date(const Date& d)
    21. // {
    22. // cout << "Date(const Date& d)" << endl;
    23. // }
    24. //private:
    25. // int _year;
    26. //};
    27. 因为是STL库中的string类,所以我们不需要手动的显式实现这里的string类,而是在STL库中
    28. 已经实现了这里的string类, string类属于自定义类型的类、
    29. class string
    30. {
    31. public:
    32. string(const char* str)
    33. {}
    34. };
    35. //void Func(const string& s)
    36. //{}
    37. //int main()
    38. //{
    39. // //Date d1(2022); //调用非默认的构造函数、
    40. // //Date d2 = d1; //调用拷贝构造函数、
    41. // //Date d3(d1); //调用拷贝构造函数、
    42. //
    43. // Date d4(2022); //调用非默认的构造函数、
    44. //
    45. // //隐式类型转换(构造函数(非默认,需要传一个实参)的形参列表中,除了隐藏的this指针外为单参数,才会考虑进行隐式类型转换)、
    46. // Date d5 = 2022; //(单参2022,多参数的话,就不考虑进行隐式类型转换了,比如: Date d5=2022,5,18; )、
    47. // //先使用整型数字2022去调用构造函数(非默认),构造一个自定义的Date类型的临时对象(已经定义完毕),再使用该自定义的Date类型的已经定义完毕的临时对象去调用拷贝构造函数,
    48. // //这是隐式类型转换本质上的原理,强制类型转换也是上述原理,但由于部分编译器会对这一块进行优化,比如:VS编译器,那么当执行代码: Date d5 = 2022; 时,就只调用了构造函数
    49. // //(非默认),直接使用整型数字2022对自定义类型的对象d5中的内置类型的类成员变量进行初始化操作,此时代码: Date d5 = 2022; 等价于: Date d5(2022); 不再调用拷贝构造函数、
    50. // //总结:
    51. // //1、若类体中显式实现的非默认的构造函数的函数名前面没有加关键字 explicit 的话,若编译器进行了优化,那么他是支持上述的只调用非默认构造函数,不调用拷贝构造函数、
    52. // //2、若类体中显式实现的非默认的构造函数的函数名前面加上了关键字 explicit 的话,若编译器进行了优化,那么他是不支持上述的只调用非默认构造函数,不调用拷贝构造函数,
    53. // //此时就会报错说是:不存在从 int 转换为 Date 的适当的构造函数(非默认)、
    54. //
    55. // string s1("hello"); //调用非默认的构造函数、
    56. // string s2 = "hello"; //同上,隐式类型转换、
    57. //
    58. //
    59. // //以下内容均不考虑编译器对隐式类型转换的优化,也不考虑使用关键字 explicit 、
    60. //
    61. // //Date& d6 = d4; //权限不变,可成功编译、
    62. // const Date& d6 = 2022; //隐式类型转换、
    63. // //此时自定义类型的对象d6引用的并不是整型数字2022,而引用的是由整型数字2022调用非默认构造函数从而构造的自定义类型Date类型的临时对象,此时出了这一行代码,该临时对象
    64. // //不会被销毁,其生命周期会进行延续,只有当自定义类型的对象d6出了其作用域,那么该临时对象才会被销毁,而临时对象具有常属性,此时属于权限不变,可以编译成功,若此处不加
    65. // //关键字const的话,则属于权限放大,编译错误,此处是先调用一次构造函数,构造出自定义类型Date类型的临时对象,然后对该临时对象进行取别名,并非是再调用拷贝构造函数,所以
    66. // //编译器在此处并不会进行优化,即,不会把调用构造函数和取别名的操作合二为一、
    67. //
    68. // Func(s1); //此时,对象s1和s2均属于自定义类型的对象,此时属于权限缩小,可以成功编译、
    69. // Func("hello");
    70. // //此时属于权限不变,也是隐式类型转换,Func函数的形参部分的自定义类型的局部对象s是对字符串"hello"调用构造函数(非默认),构造出来的自定义的string类型的临时对象进行的引用,而临时
    71. // //对象具有常属性,此时属于权限不变,可以编译成功,若Func函数中的形参部分不加关键字const的话,则属于权限放大,编译错误,此处也是先调用一次构造函数,构造出自定义类型string类型的临时对象,
    72. // //然后对该临时对象进行取别名,并非是再调用拷贝构造函数,所以编译器在此处并不会进行优化,即,不会把调用构造函数和取别名的操作合二为一、
    73. // return 0;
    74. //}
    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. class B
    7. {
    8. public:
    9. B(int b = 0)
    10. :_x(b)
    11. {}
    12. int _x;
    13. };
    14. class A
    15. {
    16. public:
    17. //在类A的类体中的 非静态类成员变量(对象) 声明处赋 缺省值 等价于如下所示:
    18. //A()
    19. // :_a(10)
    20. // , _b(20)
    21. // , _p((int*)malloc(4))
    22. //{}
    23. void Print()
    24. {
    25. cout << _a << endl;
    26. cout << _b._x << endl;
    27. cout << _p << endl;
    28. cout << _n << endl;
    29. }
    30. private:
    31. //在C++11中,可以在类体中的 非静态类成员变量(对象) 声明处赋 缺省值 ,如下所示:
    32. //1、
    33. int _a = 10;
    34. //2、
    35. B _b = 20;
    36. //对于部分(该自定义的类,类B的类体中的构造函数除形参列表中第一个位置上隐藏的this指针外,只有单个参数时)自定义类型的对象在类体(A类)中声明时也可以进行赋缺省值的操作,
    37. //具体见C++类和对象下的博客,由于此处只是声明,不是定义,考虑隐式类型转换,但不考虑部分编译器在一个步骤(表达式)中连续调用构造加拷贝构造或者连续调用拷贝构造加拷贝构造的优化和关键字explicit的作用
    38. //3、
    39. int* _p = (int*)malloc(4);
    40. //在C++11中, 不可以 在类体中的 静态类成员变量(对象) 声明处赋 缺省值 ,如下所示:
    41. //static int _n =10; //错误,因为这里的缺省值是用于初始化列表中进行初始化的,而静态类成员变量(对象)是在类外进行定义的,并不是在初始化列表中进行的定义、
    42. static int _n; //静态类成员变量在类内声明、
    43. };
    44. int A::_n = 10; //静态类成员变量在类外定义、
    45. int main()
    46. {
    47. A a;
    48. a.Print();
    49. return 0;
    50. }

    2、友元

    友元分为: 友元函数 友元类
    友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度,破坏了封装,对于友元函数而言,也只能在类体外的友元函数的定义中使用类体中的私有或保护的类成员变量和类成员函数,但不管怎么样,友元函数和友元类均破坏了封装,故友元不宜多用、

    2.1、友元函数

       友元函数 可以在类外 直接访问 类体中的 私有,保护或公有的 成员变量和类成员函数,它是 定义在类体外部 普通函数(全局函数) ,不属于任何类,但需要在类的内部进行声明,声明时需要在返回类型的前面加上 friend  关键字、
     说明:
    1、友元函数可在类外(类外的友元函数的定义处的函数体中)访问类的私有,保护和公有的类成员变量和类成员函数,但,友元函数不是类的类成员函数,而属于全局函数、
      2、友元函数的声明和定义处的形参列表的后面不能用关键字const进行修饰,因为友元函数属于全局函数,而不属于类成员函数,故其形参列表中的第一个位置上并没有隐藏的this指针、
    3、友元函数可以在类体中的任何地方进行声明,不受类访问限定符的限制,一般常在类体的开头进行友元函数的声明、
    4、一个函数可以是多个类的友元函数,如下所示:
    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. //前置声明、
    7. class Date; //在此处声明 Date 是一个类的名字,关于 Date类 具体的定义可以去后面找、
    8. //问:此处为什么要加一个前置声明 class Date; 呢?
    9. //答:在Time类体中的对全局友元函数Print的声明中,出现了 const Date&d , 编译器不会从该Time类体中整体去查找Date类的声明或定义,而是直接向上查找 Date类的声明或定义
    10. //这是因为 Date 是一个类名,注意这里不要和之前所说的在整个类体中查找,两者不一样,具体见类和对象的第一篇博客,其次,对于该类体中的对全局的友元函数Print的声明而言,是不会报错的
    11. //这是因为,他是声明,并非是使用,而出现的 const Date&d 是使用,并非是声明、
    12. class Time
    13. {
    14. friend void Print(const Date& d, const Time& t); //声明,Print全局函数是Time类的友元函数,在Print全局函数的函数体内可以直接访问Time类的私有,保护和公有的类成员变量和类成员函数、
    15. public:
    16. Time(int hour = 0, int minute = 0, int second = 0)
    17. {
    18. _hour = hour;
    19. _minute = minute;
    20. _second = second;
    21. }
    22. private:
    23. int _hour;
    24. int _minute;
    25. int _second;
    26. };
    27. class Date
    28. {
    29. friend void Print(const Date& d, const Time& t); //声明,Print全局函数是Date类的友元函数,在Print全局函数的函数体内可以直接访问Date类的私有,保护和公有的类成员变量和类成员函数、
    30. public:
    31. Date(int year, int month, int day)
    32. {
    33. _year = year;
    34. _month = month;
    35. _day = day;
    36. }
    37. private:
    38. int _year;
    39. int _month;
    40. int _day;
    41. };
    42. void Print(const Date& d, const Time& t)
    43. {
    44. cout << d._year << "-" << d._month << "-" << d._day << endl;
    45. cout << t._hour << "-" << t._minute << "-" << t._second << endl;
    46. }
    47. int main()
    48. {
    49. Date d1(2022, 5, 20);
    50. Time t1;
    51. Print(d1, t1);
    52. return 0;
    53. }

    5、友元函数的调用与普通函数的调用的原理相同、  

    2.2、友元类

    友元类的经典使用:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. //前置声明、
    7. //class Date; //不需要前置声明,因为此时的 friend class Date; 是进行的声明,并非进行的使用、
    8. class Time
    9. {
    10. friend class Date; //声明 日期类为时间类的友元类 ,则在日期类中的任何地方均可以直接访问Time类中的私有,保护和公有的类成员变量和类成员函数、
    11. //无论是友元类的声明还是友元函数的声明,均可以在所在类的类体中的任何位置进行声明、
    12. //注意:此时,在时间类中声明日期类为时间类的友元类,则在日期类中的任何地方均可以直接访问Time类中的私有,保护和公有的类成员变量和类成员函数,但是此时
    13. //没有在日期类的类体中的任何位置声明时间类是日期类的友元类,所以在时间类中的任何地方均不可以直接访问日期类中的私有和保护的类成员变量和类成员函数、
    14. //友元类(日期类)中的所有的类成员函数都可以看做是另一个类(时间类)的友元函数,都可以直接访问另一个类(时间类)中的私有,保护和公有的类成员变量和类成员函数、
    15. public:
    16. Time(int hour, int minute, int second)
    17. : _hour(hour)
    18. , _minute(minute)
    19. , _second(second)
    20. {}
    21. private:
    22. int _hour;
    23. int _minute;
    24. int _second;
    25. };
    26. class Date
    27. {
    28. public:
    29. Date(int year = 1900, int month = 1, int day = 1)
    30. : _year(year)
    31. , _month(month)
    32. , _day(day)
    33. , _t(1,2,3)
    34. {}
    35. void SetTimeOfDate(int hour, int minute, int second)
    36. {
    37. //直接访问时间类中的私有,保护和公有的类成员变量和类成员函数、
    38. _t._hour = hour;
    39. _t._minute = minute;
    40. _t._second = second;
    41. }
    42. private:
    43. int _year;
    44. int _month;
    45. int _day;
    46. Time _t;
    47. };
    48. int main()
    49. {
    50. Date d1;
    51. return 0;
    52. }
    1、友元关系是单向的,不具有交换性、
          比如上述的 Time 类和 Date 类,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类中直接访问  Time 类的私有,保护和公有的类成员变量和类成员函数,但想在Time 类中访问 Date 类中私有或保护的类成员变量和类成员函数则是不行的、
    2、友元关系不能传递、
          如果 B A 的友元, C B 的友元,则不能说明 C是 A 的友元、
    1. class A
    2. {
    3. friend class B;
    4. };
    5. class B
    6. {
    7. friend class C;
    8. };
    9. class C
    10. {};
    11. //此时在类C的类体中可以访问到类B的私有,保护和公有的类成员变量和类成员函数,但是不能在类C的类体中访问到类A的私有和保护的类成员变量和类成员函数、

    3、内部类

    3.1、概念及特性

    概念: 如果一个类B定义在另一个类A的内部,这个内部的类B就叫做内部类,注意,此时这个内部类B是一个独立的类,它不属于外部类A,更不能通过外部类A的对象去调用内部类B,外部类A对内部类B没有任何优越的访问权限、
    注意: 内部类B就是外部类A的友元类,注意友元类的定义,内部类B中的任何一处均可以通过外部类A实例化出来的对象参数来访问外部类A中私有,保护和公有的类成员变量和类成员函数,但是外部类A不是内部类B的友元类、
    特性:
    1、 内部类可以定义在外部类中的  public protected private 都是可以的,若一个类B定义在另一个类A的内部,这个内部的类B就叫做内部类,此时可以控制内部类B的属性,但是如果不把类B放在类A的内部,那么此时类B的属性就相当于是公有,封装性较差、
    2、注意:内部类中任何位置均可以直接访问外部类中的  static 、枚举成员,且不需要外部类的对象或者 类名、
    3、当计算 sizeof( 外部类的类名或外部类的对象 ) 时 ,和内部类没有任何关系、
    4、使用内部类的类名进行实例化其对应的自定义类型的对象时,要注意加上外部类的类名和域作用限定符,这是以内部类定义在外部类的公有属性下为前提的、
    1. class A
    2. {
    3. public:
    4.     class B
    5.     {
    6.         
    7.     }
    8. }
    9. int main()
    10. {
    11.     A::B b1; //类B是类A的内部类、
    12.     return 0;
    13. }
    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. class A
    7. {
    8. public:
    9. class B //内部类,类B的属性到底为私有,保护还是公有,具体根据自己的需求去设定即可,类B天生就是类A的友元类(单向),
    10. { //所以在类B的类体中的任意位置均可以直接访问类A的私有,保护和公有的类成员变量和类成员函数,类A不是类B的友元类,此时这种内部类关系和把类B定义在类A外面的全局区域中
    11. public: //唯一不同的就是这里具有的天生的友元类的性质,除此之外,别无其他不同,此时类A中的内部类,类B受类A中的访问限定符的限制、
    12. void foo(const A& a)
    13. {
    14. cout << _k << endl;//OK
    15. cout << a._h << endl;//OK
    16. }
    17. private:
    18. int _b;
    19. };
    20. private:
    21. static int _k;
    22. int _h;
    23. };
    24. int A::_k = 1;
    25. int main()
    26. {
    27. A::B b;
    28. b.foo(A());
    29. cout << sizeof(A) << endl;//4,静态类成员变量属于整个类,即属于由该类实例化出来的所有的对象,不属于某一个对象,所以在计算sizoef(A)时,不把静态类成员变量算进去
    30. //除此之外,内部类B也不算入,这是因为,在类A的类体中,并不存在使用自定义的类B实例化出来的自定义类型的对象,此时计算自定义的 类A 所占内存空间的大小,本质上计算的
    31. //是由该自定义类型实例化出来的自定义类型的对象所占内存空间的大小、
    32. return 0;
    33. }

    4、例题

    4.1、例题1

    1、 1+2+3+...+n ,要求不能使用乘除法、 for while if else switch case 等关键字及条件判断语句(A? B:C)    求1+2+3+...+n_牛客题霸_牛客网

    方法一:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. class Sum
    7. {
    8. public:
    9. Sum()
    10. {
    11. _ret += _n;
    12. _n++;
    13. }
    14. //尽量不使用友元、
    15. //方法一:
    16. //int GetRet()
    17. //{
    18. // return _ret;
    19. //}
    20. //方法二:
    21. static int GetRet()
    22. {
    23. return _ret;
    24. }
    25. private:
    26. static int _n;
    27. static int _ret;
    28. };
    29. int Sum::_n=1;
    30. int Sum::_ret=0;
    31. class Solution
    32. {
    33. public:
    34. int Sum_Solution(int n)
    35. {
    36. //方法:编译器自动调用n次构造函数来完成任务:
    37. //1.变长数组、
    38. Sum a[n]; //牛客和力扣上都支持变长数组、
    39. //使用自定义类型的类名Sum实例化一个自定义类型的对象的过程中,编译器会自动调用一次构造函数,
    40. //若使用自定义类型的类名Sum实例化出一个数组,该数组有n个元素,在该过程中,编译器会自动调用n次构造函数、
    41. //方法一:
    42. //return a[0].GetRet();
    43. //方法二:
    44. return Sum::GetRet();
    45. 2
    46. //Sum*p =new Sum[n];
    47. }
    48. };
    49. //由于静态类成员变量属于类,不属于某一个对象,而是属于由该类实例化出来的所有的对象,所以在main函数中即使
    50. //还未通过该类名实例化出来某一个对象,那么类中的静态类成员变量也已经存在了,和全局变量,全局静态变量,局部静态变量
    51. //一样,在main函数之前就已经为其在静态区上开辟了内存空间,只不过是他们的作用域不同而已,全局静态,局部静态,全局变量,
    52. //类体中的静态,他们的生命周期都是整个工程,只不过是作用域不同而已,类体中的静态的作用域是在类体内,或者是在
    53. //指定类域后的位置可以进行使用、
    54. //全局静态变量和全局变量的区别:链接属性不同,在包含时,静态全局变量只能在当前文件中使用,而全局变量
    55. //在所有地方均可使用,全局函数和全局静态函数也是如此,全局变量和全局函数可能会出现连接冲突的问题、

    注意:

    1. /由于静态类成员变量属于类,不属于某一个对象,而是属于由该类实例化出来的所有的对象,所以在main函数中即使
    2. //还未通过该类名实例化出来某一个对象,那么类中的静态类成员变量也已经存在了,和全局变量,全局静态变量,局部静态变量
    3. //一样,在main函数之前就已经为其在静态区上开辟了内存空间,只不过是他们的作用域不同而已,全局静态,局部静态,全局变量,
    4. //类体中的静态,他们的生命周期都是整个工程,只不过是作用域不同而已,类体中的静态的作用域是在类体内,或者是在
    5. //指定类域后的位置可以进行使用、
    6. //全局静态变量和全局变量的区别:链接属性不同,在包含时,静态全局变量只能在当前文件中使用,而全局变量
    7. //在所有地方均可使用,全局函数和全局静态函数也是如此,全局变量和全局函数可能会出现连接冲突的问题、

    方法二:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. class Solution
    7. {
    8. public:
    9. int Sum_Solution(int n)
    10. {
    11. //方法:编译器自动调用n次构造函数来完成任务:
    12. Sum a[n]; //牛客和力扣上都支持变长数组、
    13. //使用自定义类型的类名Sum实例化一个自定义类型的对象的过程中,编译器会自动调用一次构造函数,
    14. //若使用自定义类型的类名Sum实例化出一个数组,该数组有n个元素,在该过程中,编译器会自动调用n次构造函数、
    15. return _ret;
    16. }
    17. private:
    18. class Sum //内部类,则此时,Sum类为Solution类的友元类、
    19. {
    20. public:
    21. Sum()
    22. {
    23. _ret += _n;
    24. _n++;
    25. }
    26. };
    27. static int _n;
    28. static int _ret;
    29. };
    30. int Solution::_n = 1;
    31. int Solution::_ret = 0;

    4.2、例题2

    计算日期到天数的转换  计算日期到天数转换_牛客题霸_牛客网
    1. #include
    2. using namespace std;
    3. int main()
    4. {
    5. //默认均按照平年来计算、
    6. //SumMonthDayArray1_N[i]代表存放的是前i个月的天数之和、
    7. int SumMonthDayArray1_N[13] = {0,31,59,90,120,151,181,212,243,273,304,334,365};
    8. int year,month,day;
    9. cin>>year>>month>>day;
    10. int n=SumMonthDayArray1_N[month-1]+day; //前n-1个月的天数之和加上当前月的天数、
    11. //判断是否是闰年,如果是闰年并且前面跨过了2月,则就加一天、
    12. if(month > 2 && ((year%4==0 && year%100 !=0) ||year%400 ==0))
    13. {
    14. n+=1;
    15. }
    16. cout<
    17. return 0;
    18. }

    4.3、例题3 

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. //默认均按照平年来计算、
    7. //SumMonthDayArray1_N[i]代表存放的是前i个月的天数之和、
    8. int SumMonthDayArray1_N[13] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
    9. int year=0, day=0;
    10. cin >> year >> day;
    11. for (int i = 0; i < 13; i++)
    12. {
    13. if (day <= SumMonthDayArray1_N[i])
    14. {
    15. day -= SumMonthDayArray1_N[i-1]; //计算出要计算的当前月中所剩余的天数、
    16. if (i>2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)) //判断是否超过2月并判断是否是闰年、
    17. {
    18. day -= 1; //如果是闰年且超过了2月,本年中2月份就会多一天,那么所要计算的当前月中所剩的天数就少一天、
    19. }
    20. printf("%d-%02d-%02d\n", year, i, day);
    21. //%d2:代表向右对齐两位,%d-2:代表向左对齐两位,%d02:代表向右对齐两位,不够的填充0补齐,%d20:代表的是向右对齐20位,并不是向左对齐两位,不够的填充0补齐、
    22. break;
    23. }
    24. }
    25. return 0;
    26. }

    4.4、例题4 

    累加天数 日期累加_牛客题霸_牛客网

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using namespace std;
    4. int GetMonthDay(int year, int month)
    5. {
    6. //默认均按照平年来计算、
    7. //MonthDay[i]代表存放的是第i个月对应的天数、
    8. int MonthDay[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    9. //注意:当判断闰年时且还有其他的判断时,要在判断闰年的整体外面加上大括号,如方法2,其次,对于判断闰年时,里面的两部分
    10. //加不加括号都是可以的,加上的话就比如方法1,是可以的,不加的话也是可以的,比如方法2,但此时一定要把判断闰年的整体的外面加上大括号
    11. //如方法2,若只有判断闰年而不存在其他的判断的话,则该整体的大括号加不加都可以,里面的两部分加不加括号都是可以的、
    12. 方法1
    13. //if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//判断是否是闰年的2月份、
    14. // return 29;
    15. //方法2、
    16. if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))//判断是否是闰年的2月份、
    17. return 29;
    18. return MonthDay[month];
    19. }
    20. int main()
    21. {
    22. int n = 0;
    23. cin >> n;
    24. int year = 0, month = 0, day = 0, ret = 0;
    25. for (int i = 0; i
    26. {
    27. cin >> year >> month >> day >> ret;
    28. day += ret;
    29. while (day > GetMonthDay(year, month))
    30. {
    31. day -= GetMonthDay(year, month);
    32. month++;
    33. if (month == 13)
    34. {
    35. year++;
    36. month = 1;
    37. }
    38. }
    39. printf("%4d-%02d-%02d\n", year, month, day);
    40. }
    41. return 0;
    42. }

    4.5、例题5

    方法一:
    1. //方法1:
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include
    4. using namespace std;
    5. int GetMonthDay(int year, int month)
    6. {
    7. //默认均按照平年来计算、
    8. //MonthDay[i]代表存放的是第i个月对应的天数、
    9. int MonthDay[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    10. //注意:当判断闰年时且还有其他的判断时,要在判断闰年的整体外面加上大括号,如方法2,其次,对于判断闰年时,里面的两部分
    11. //加不加括号都是可以的,加上的话就比如方法1,是可以的,不加的话也是可以的,比如方法2,但此时一定要把判断闰年的整体的外面加上大括号
    12. //如方法2,若只有判断闰年而不存在其他的判断的话,则该整体的大括号加不加都可以,里面的两部分加不加括号都是可以的、
    13. 方法1
    14. //if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//判断是否是闰年的2月份、
    15. // return 29;
    16. //方法2、
    17. if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))//判断是否是闰年的2月份、
    18. return 29;
    19. return MonthDay[month];
    20. }
    21. int main()
    22. {
    23. int year1, year2, month1, month2, day1, day2;
    24. //&year读取4位,&month读取2位,&day读取2位, %d 不读取空白字符、
    25. while (scanf("%04d%02d%02d%04d%02d%02d", &year1, &month1, &day1, &year2, &month2, &day2)!=EOF)
    26. {
    27. int n = 1;//记录日期差值(绝对值)、
    28. if (year1
    29. || year1 == year2 && month1
    30. || year1 == year2 && month1 == month2 && day1
    31. {
    32. while (!(year1 == year2 && month1 == month2 && day1 == day2))
    33. {
    34. day1 += 1;
    35. n++;
    36. if (day1 > GetMonthDay(year1, month1))
    37. {
    38. month1++;
    39. day1 = 1;
    40. if (month1 == 13)
    41. {
    42. year1++;
    43. month1 = 1;
    44. }
    45. }
    46. }
    47. }
    48. else
    49. {
    50. while (!(year1 == year2 && month1 == month2 && day1 == day2))
    51. {
    52. day2 += 1;
    53. n++;
    54. if (day2 > GetMonthDay(year2, month2))
    55. {
    56. month2++;
    57. day2 = 1;
    58. if (month2 == 13)
    59. {
    60. year2++;
    61. month2 = 1;
    62. }
    63. }
    64. }
    65. }
    66. cout << n << endl;
    67. }
    68. return 0;
    69. }

    方法二:

    1. //方法2:
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include
    4. #include
    5. using namespace std;
    6. int daytab[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    7. int isLeapYear(int year)
    8. {
    9. if ((year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
    10. return 1;//闰年、
    11. else
    12. return 0;//平年、
    13. }
    14. int nomYear(int year)
    15. {
    16. if (isLeapYear(year))
    17. return 366;//闰年、
    18. else
    19. return 365;//平年、
    20. }
    21. //以下算法计算给定的年月日距离0000年00月01日的天数、
    22. int sumYearDay(int year, int month, int day)
    23. {
    24. int sum = 0;
    25. for (int i = 0; i
    26. sum = sum + nomYear(i);
    27. }
    28. for (int i = 1; i
    29. sum = sum + daytab[i];
    30. }
    31. sum += day;
    32. return sum;
    33. }
    34. int main()
    35. {
    36. int y1, m1, d1, y2, m2, d2;
    37. while (scanf("%04d%02d%02d", &y1, &m1, &d1) != EOF)
    38. {
    39. scanf("%04d%02d%02d", &y2, &m2, &d2);
    40. printf("%d\n", abs(sumYearDay(y1, m1, d1) - sumYearDay(y2, m2, d2)) + 1);
    41. }
    42. return 0;
    43. }

    方法三:

    1. //方法3:
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include
    4. #include
    5. using namespace std;
    6. int DayTab[2][13] =
    7. {
    8. { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    9. { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    10. };
    11. bool IsLeapYear(int year)
    12. {
    13. return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
    14. }
    15. int YearTab[2] = { 365, 366 };
    16. int main()
    17. {
    18. int year1, month1, day1;
    19. int year2, month2, day2, minyear, maxyear;
    20. int number, num1, num2;
    21. while (scanf("%04d%02d%02d\n%04d%02d%02d", &year1, &month1, &day1, &year2, &month2, &day2) != EOF) {
    22. number = 0;
    23. minyear = year1 < year2 ? year1 : year2;
    24. maxyear = (minyear == year1) ? year2 : year1;
    25. while (minyear < maxyear) {
    26. number += YearTab[IsLeapYear(minyear)];
    27. ++minyear;
    28. }
    29. num1 = 0;
    30. for (int i = 0; i < month1; ++i) {
    31. num1 += DayTab[IsLeapYear(year1)][i];
    32. }
    33. num1 += day1;
    34. num2 = 0;
    35. for (int i = 0; i < month2; ++i) {
    36. num2 += DayTab[IsLeapYear(year2)][i];
    37. }
    38. num2 += day2;
    39. number += (year1 < year2 ? (num2 - num1 + 1) : (num1 - num2 + 1));
    40. cout << number << endl;
    41. }
    42. return 0;
    43. }

    注意:

           在一个步骤(表达式)中,连续调用构造函数加拷贝构造函数,或者,连续调用拷贝构造函数加拷贝构造函数,激进的编译器会进行优化,将二步合二为一,新一点的编译器都会进行优化,比如:VS2013,VS2019、

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. using std::cout;
    4. using std::cin;
    5. using std::endl;
    6. class Solution
    7. {
    8. public:
    9. Solution()
    10. {
    11. cout << "Solution()" << endl;
    12. }
    13. Solution(const Solution& w)
    14. {
    15. cout << "Solution(const Weight& w)" << endl;
    16. }
    17. ~Solution()
    18. {
    19. cout << "~Solution()" << endl;
    20. }
    21. int Sum_Solution(int n)
    22. {
    23. //方法:编译器自动调用n次构造函数来完成任务:
    24. //Sum a[n]; //VS系列的编译器均不支持变长数组、
    25. Sum*p = new Sum[n];
    26. return _ret;
    27. }
    28. private:
    29. class Sum //内部类,则此时,Sum类为Solution类的友元类、
    30. {
    31. public:
    32. Sum()
    33. {
    34. _ret += _n;
    35. _n++;
    36. }
    37. };
    38. static int _n;
    39. static int _ret;
    40. };
    41. int Solution::_n = 1;
    42. int Solution::_ret = 0;
    43. class Weight
    44. {
    45. public:
    46. Weight()
    47. {
    48. cout << "Weight()" << endl;
    49. }
    50. Weight(const Weight& w)
    51. {
    52. cout << "Weight(const Weight& w)" << endl;
    53. }
    54. ~Weight()
    55. {
    56. cout << "~Weight()" << endl;
    57. }
    58. Weight& operator=(const Weight& w)
    59. {
    60. cout << "Weight& operator=(const Weight& w)" << endl;
    61. return *this;
    62. }
    63. };
    64. Weight Func(Weight u) //自定义类型的对象 传值传参 会自动调用拷贝构造函数、
    65. {
    66. Weight v(u);
    67. Weight w = v;
    68. return w; //自定义类型的对象 传值返回 会自动调用拷贝构造函数、
    69. }
    70. class A
    71. {
    72. public:
    73. A(int x)
    74. :_x(x)
    75. {
    76. cout << "A(int x)" << endl;
    77. }
    78. ~A()
    79. {
    80. cout << "~A()" << endl;
    81. }
    82. private:
    83. int _x;
    84. };
    85. int main()
    86. {
    87. 1
    88. //Weight wt;
    89. //Func(wt); //当自定义类型的对象 传值返回 会自动调用拷贝构造函数构造出一个临时对象,但不要忘记这个临时对象在销毁时会自动调用一次析构函数、
    90. 注意: 自动调用构造函数的次数与自动调用拷贝构造函数的次数 之和 应该等于自动调用析构函数的次数、
    91. 2
    92. //Weight wt;
    93. //Weight();//定义了一个匿名对象,类似于临时对象,若不使用它的话,其生命周期只有这一行,出了这一行就销毁了,但如果使用关键字const去使用它的话,它
    94. 的生命周期则会延续,此时这就属于未使用它,定义一个匿名对象也就相当于定义了一个自定义类型Weight的对象,则此时会自动调用一次构造函数,又因此时
    95. 并未使用该匿名对象,所以出了该行代码,匿名对象就会被销毁,此过程中还会自动调用一次析构函数、
    96. //Func(wt);
    97. 3
    98. //Solution().Sum_Solution(10); //匿名对象的使用,此时,先执行:Solution(),会自动调用一次构造函数,然后进入Sum_Solution函数内部,此时调用很多次Sum类的构造函数,
    99. 并不是Solution类的构造函数,当从Sum_Solution函数出来返回到此,出了该行代码,该匿名对象就会销毁,再自动调用一次析构函数,这里使用匿名对象并不是上述
    100. 总结的那种使用方法,所以该匿名对象的生命周期不会被延续、
    101. 4
    102. //Func(Weight()); //匿名对象的使用、
    103. 按理说,先执行: Weight() 自动调用一次构造函数,然后构造出来的匿名对象进行 传值传参 ,又由于自定义类型的对象传值传参会自动调用拷贝构造函数,但是此时,这两步合成一步,只调用一次构造函数,
    104. 这是因为编译器进行了优化,如果一个步骤中(表达式中),连续调用构造函数加拷贝构造函数,则会合二为一,只调用一次构造函数,C++标准并没有具体规定是否要进行优化,具体是否优化则和编译器有关,激进
    105. 一些的编译器则会进行优化,比如:VS系列的编译器(特别早的编译器可能不进行优化),其实,新一点的编译器都会进行优化,包括VS2013,VS2019、
    106. 5
    107. //A a1(1); //调用构造函数、
    108. 隐式类型转换、
    109. //A a2 = 2; //本质上: 构造:A(2) -> 拷贝构造: A a2 = A(2)或A a2(A(2)); 匿名对象类似于临时变量,都具有常性、
    110. 本质上应该是先调用构造函数,再调用拷贝构造函数,但是这是在一个步骤中连续调用构造函数加拷贝构造函数,编译器会进行优化,只调用一次构造函数、
    111. 6
    112. //A(2);//匿名对象的使用、
    113. 7
    114. //Weight wt;
    115. //Weight ret =Func(wt);
    116. 本质上,当Func函数的函数体中,自定义类型的对象传值返回时,会自动调用一次拷贝构造函数从而构造出一个自定义Weight类型的临时对象,然后再执行代码: Weight ret =Func(wt);时,使用该自定义Weight类型的临时对象(已经存在)
    117. 去初始化正在定义的自定义Weight类型的对象ret,此时还要再调用一次拷贝构造函数,此时在一个步骤(表达式)中连续调用拷贝构造函数加拷贝构造函数,则编译器会进行优化,将二步合二为一,只调用一次拷贝构造函数、
    118. 注意:在一个步骤(表达式)中,连续调用构造函数加拷贝构造函数,或者,连续调用拷贝构造函数加拷贝构造函数,激进的编译器会进行优化,将二步合二为一,新一点的编译器都会进行优化,比如:VS2013,VS2019、
    119. //8、
    120. Weight wt;
    121. Weight ret;
    122. ret= Func(wt);
    123. //当Func函数的函数体中,自定义类型的对象传值返回时,会自动调用一次拷贝构造函数从而构造出一个自定义Weight类型的临时对象,然后再执行代码: ret= Func(wt); 使用该自定义Weight类型的临时对象(已经存在),去赋值一个已经存在
    124. //的自定义Weight类型的对象ret,此时调用的是赋值运算符重载函数,此时在一个步骤(表达式)中连续调用拷贝构造函数加赋值运算符重载函数,则编译器不会进行优化,不会将二步合二为一、
    125. return 0;
    126. }

    5、再次理解封装

    C++ 是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态、
    C++ 通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起,通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化、

    6、再次理解面向对象

    可以看出面向对象其实是在模拟抽象映射现实世界、

  • 相关阅读:
    技术速递|Java on Azure Tooling 5月更新 - Java 对 Azure 容器应用程序的入门指南支持
    LeetCode中等题之分数加减运算
    UMLChina建模知识竞赛第4赛季第17轮
    Java架构师系统架构设计原则
    如何扫码分享文件?二维码扫描预览文件的方法
    网络爬虫是什么?怎么学python爬虫
    高阶导数简介
    【Android 四大组件之Service】一文吃透Service 服务
    Ajax跨域请求的两种实现方式
    Java集合框架详解(二)——泛型
  • 原文地址:https://blog.csdn.net/lcc11223/article/details/125929444