• C++初阶 | [四] 类和对象(下)


    摘要:初始化列表,explicit关键字,匿名对象,static成员,友元,内部类,编译器优化

    类是对某一类实体(对象)来进行描述的,描述该对象具有哪些属性、哪些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。


    1. 初始化列表

    构造函数函数体内是对成员变量进行赋值,而不是初始化!

    初始化列表:成员变量定义的地方

    为什么规定初始化列表是所有成员变量定义的地方?

    对于以下三类特殊的成员变量类型,初始化列表的存在是必要的:

    • const 成员变量—— const 变量必须在定义时初始化
    • 引用 —— 引用必须初始化
    • 自己没有默认构造函数自定义类型变量

    warning:Initialization is not assignment. Initialization happens when a variable is given a value when it is created. Assignement obliterates an object's current value and replaces that value with a new one.

     Four different ways  to define an int variable named units_sold and initialize it to 0:

    • int units_sold = 0;
    • int units_sold = {0}; //list initialization (C++11)
    • int units_solc{ 0 }; //list initialization (C++11)
    • int units_sold(0);

    ——《C++ Primer》

    Use

    1. 冒号开始,逗号分隔,初始化值/表达式 用括号括起来。示例如下。
    2. 成员变量走初始化列表的顺序为成员变量声明的顺序.(如下图,先用v2初始化v1,而v2还未初始化所存储的为随机值)
    3. C++规定初始化列表是每个成员变量定义的地方,每个成员变量会走初始化列表(所以成员变量的初始化尽量使用初始化列表),定义的时候不给初始值——即在初始化列表没有显式写的成员变量——未被初始化。
    4. 在初始化列表没写的成员变量,对于内置类型将不做处理——初始化值为随机值(但因编译器不同而异),对于自定义类型默认调用它自己的构造函数

    sum.能用初始化列表就用初始化列表,但同时有些场景需要初始化列表和构造函数体混用。(根据具体的需求而异)

    补充:C++11 支持在成员变量声明的时候给缺省值,这个缺省值是给初始化列表用的,如果初始化列表没有显式给初始值,就用缺省值作为初始化值。


    2. explicit 关键字

    • C++98 支持单参数隐式类型转化

    • C++11 进一步支持多参数隐式类型转化

     在构造函数前加 explicit 使得不能进行隐式类型转换:(示例如下)

    1. class B
    2. {
    3. public:
    4. explicit B(const int b1, const int b2)
    5. {
    6. _b1 = b1;
    7. _b2 = b2;
    8. }
    9. explicit B(const B& b)
    10. {
    11. _b1 = b._b1;
    12. _b2 = b._b2;
    13. }
    14. private:
    15. int _b1;
    16. int _b2;
    17. };

    3. 匿名对象

    • 匿名对象:生命周期仅在改匿名对象定义的这一行。e.g.A(7);
    • 有名对象:生命周期在该作用域。e.g.A aa(7);

    用途:

    1. 传参(如果单/多参数隐式类型转化被禁)
    2. 定义对象就是为了调用成员函数 → 就可以用匿名对象去调用
    3. ……(匿名对象的用途是十分广泛的)

    const引用会延长匿名对象的生命周期

    const引用其实使得匿名对象变成了有名对象:引用给了匿名对象别名,这个“别名”的生命周期即为被引用的匿名对象的生命周期。e.g.const A& ref = A();


    4. static 成员

    如果我们有如下两个需求:

    • 统计累计创建了多少个对象
    • 统计正在使用的对象还有多少个

    统计累计创建的对象:首先,我们可以选择创建全局变量来统计。同时,因为创建对象会自动调用构造函数,所以我们在构造函数体内对这个用于统计的变量进行++操作,这样每次创建一个对象,就会调用并执行构造函数,每次执行都会累计对这个全局变量++。

    统计正在使用的对象:对象销毁会自动调用析构函数,创建的对象 - 已经析构的对象 = 正在使用的对象。

    1. int e = 0;//统计累计创建的对象
    2. int now_e = 0;//统计仍在使用的对象
    3. class A
    4. {
    5. public:
    6. A(const int a = 0)
    7. {
    8. ++e;
    9. ++now_e;
    10. _a = a;
    11. }
    12. ~A()
    13. {
    14. --now_e;
    15. }
    16. private:
    17. int _a;
    18. };

    问题:不够封装,全局变量可能会被随意修改,将造成需求之外的影响。 → “封装” → 将变量放入类中  →  私有变量无法访问 → static 成员

    static 成员变量

    static 成员变量的性质:

    1. 属于所有对象——即整个类,而不是某一个对象。
    2. 不能给缺省值。
    3. 不走初始化列表,初始化列表是某个对象的初始化,而 static 成员变量不属于某个对象。
    4. 位于静态区。

    static 成员变量的声明与定义:类内声明,类外定义(静态成员变量一定要在类外进行初始化)

    1. class A
    2. {
    3. private:
    4. static int m;//static成员变量声明
    5. };//这样写A相当于空类
    6. int A::m = 0;//static成员变量定义

    访问 static 成员变量的两种情况下的方式:

    • 如果 static 成员变量是 public 的:①创建对象访问;②通过指针访问(这里的指针只是为了突破类域,而不会解引用,之前在类和对象上的this指针关于空指针的内容有详细解释);③直接访问。
      1. class A
      2. {
      3. public:
      4. static int m;//static成员变量声明
      5. };//这样写A相当于空类
      6. int A::m = 0;//static成员变量定义
      7. int main()
      8. {
      9. A aa;
      10. aa.m;//①创建对象访问;
      11. A* p = nullptr;
      12. p->m;//②通过指针访问
      13. A::m = 1;//③直接访问
      14. return 0;
      15. }
    • 如果 static 成员变量是 非public 的:提供函数接口访问,这样可以使得 static 成员变量是只读不可修改的(引用返回可修改,这个根据需求自己控制),不同于public的情况。
      1. class A
      2. {
      3. public:
      4. int GetM()
      5. {
      6. return m;
      7. }
      8. private:
      9. static int m;//static成员变量声明
      10. };
      11. int A::m = 0;//static成员变量定义
      12. int main()
      13. {
      14. A aa;
      15. cout << aa.GetM() << endl;
      16. return 0;
      17. }

    static 成员函数

    static 成员函数的性质:

    1. 没有 this 指针,不能访问非静态成员变量
    2. 访问方式同 static 成员变量——突破类域即可访问 

    5. 友元

    • 友元函数

    特性:

    1. 可以访问类的 private 和protected 的成员,但友元函数不是类的成员函数。
    2. 不能用 const 修饰(因为不是成员函数更没有 this 指针,const 修饰的是 this 指针)。
    3. 可在类定义的任何地方声明,不受访问限定符限制。
    4. 一个函数可以是多个类的友元函数。
    5. 友元函数的调用同普通函数
    1. class Date
    2. {
    3. friend ostream& operator<<(ostream& out, Date d);//友元函数声明
    4. private:
    5. int _year;
    6. int _month;
    7. int _day;
    8. };
    9. ostream& operator<<(ostream& out, Date d)//函数定义
    10. {
    11. out << d._year << "/" << d._month << "/" << d._day << endl;
    12. return out;
    13. }

    • 友元类

    友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。(如下代码,B类在A类友元声明之后,B类中的所有成员函数相当于在A类中都进行了友元声明——可以访问A类的非公有成员。注意:A类不可以访问B类的非公有成员。)

    友元类的性质:

    ①单向性(如下,B可以访问A,但A不可以访问B);

    ②友元关系不传递(B是A的友元,A是C的友元,不能得出B是C的友元);

    ③不继承。

    1. class A
    2. {
    3. friend class B;
    4. private:
    5. int _a;
    6. };
    7. class B
    8. {
    9. void Print(A _aa)
    10. {
    11. cout<<_aa._a;
    12. }
    13. //A aa;//注意:这个只是声明,不开空间
    14. };

    6. 内部类

    内部类:在类中再定义一个类。内部类受外部类的类域和访问限定符限制,此外内部类与外部类是两个独立的类。

    使用场景:内部类可以应用于当你想定义一个类而仅在某个类中使用时。

    注意:如上图,C不可以访问D的非公有成员, D在C类域之内又封装了一层,并且根据友元类的单向性也是如此。


    7. 拷贝对象时编译器的一些优化

    此部分内容仅作了解,在之前旧版本的博客有较为详细的讲述,详细内容可参见本文:C++ -4- 类和对象(下)


    END

  • 相关阅读:
    02 【版本控制命令】
    如何通过图片提取文献图里的数据点
    修改变量的值(变量与常量)
    评价——秩和比综合评价
    【5.Vue 父子组件监听数据】
    day18 代码回想录 二叉树05 找树左下角的值&路径总和&从中序与后序遍历序列构造二叉树
    CSS垂直居中的方法
    新电脑的前端开发环境的安装配置
    winapi获取鼠标指向当前元素
    uniapp-时间格式和距离格式的转换
  • 原文地址:https://blog.csdn.net/fantastic_13_7/article/details/134474574