Cpp进阶语法详解

C++进阶语法

继承

  • 继承是为了解决,多个不同类 但都具有共同属性 而出现重复编写 这些属性 所导致的 冗余而设计的语法
    • 基类用于放公共属性,派生类放自己单独的属性
  • 类的访问限定符
    1. public(公有):任何作用域代码 都可以访问 此关键字修饰的类成员,允许该类和其子孙类域下直接访问即可,非子孙类域需子孙类域::/祖先类域::/祖先对象./子孙对象.的方式访问
    2. private(私有):只有类域本身下的代码 才能访问 此关键字修饰的类成员
    3. protected(保护):只有类域本身和其子孙类域下的代码 才能访问并且可以直接访问 此关键字修饰的类成员
  • 子孙类对象在实例化开辟成员变量空间时,会单独给该子孙类对象 开辟出该子孙的 成员变量以及祖先的成员变量 对应的空间

继承中 基类和派生类 关系逻辑图

python 复制代码
 _____________________    ____________________    ____________________
|class B1(A of Child)|   |class B2(A of Child)|  |class B3(A of Child)|
|                    |   |                    |  |                    |
|____________________|   |____________________|  |____________________|
 ____|_________________________|_______________________|___________
|class A                                                           |
|                                                                  |
|__________________________________________________________________|
 ____|_______________     _____|______________    _____|______________
|class B4(A of Child)|   |class B5(A of Child)|  |class B6(A of Child)|
|                    |   |                    |  |                    |
|____________________|   |____________________|  |____________________|

继承代码演示

访问祖先类的相对权限,祖先的非子孙是按照自己的访问限定符来修饰自己的代码,但是对于自己子孙则按照Min(继承方式,祖先类访问限定符)的方式来修饰自己的代码

cpp 复制代码
class Animal
{
public:
    Animal()
    {
        std::cout<<"Animal"<<std::endl;
    }
};
class Animal:public/*继承方式*/ Cat
{
public:
    Cat()
    {
        std::cout<<"Cat"<<std::endl;
    }
};
class Animal:public/*继承方式*/ Dog
{
public:
    Dog()
    {
        std::cout<<"Dog"<<std::endl;
    }
};

基类和派生类对象赋值转换

  • 派生类对象可以给基类对象赋值,但是基类对象不可以给派生类对象赋值
    • 这里派生类对象给基类对象赋值 不涉及隐式类型转换所以不会产生临时对象,涉及新的概念 叫is-a 和 切割,因为子类是is-a的所以切割出 基类的部分 赋值给 基类对象
      • 子类对象里基类的部分 给基类对象 赋值时,是会走 该基类对应的 拷贝构造 和 赋值重载的
      • 派生类对象给基类对象赋值时 不涉及 隐式类型转换的动作也叫做 赋值兼容
    • is-a定义:每个子类对象都是一个特殊的父类对象,派生类只看其继承部分就相当于其是一个特殊的基类
    • 切割定义:切割是 基类和派生类对象赋值转换 中的定义,根据is-a 去将 派生类对象 的 派生类继承的部分 和 派生类单独的部分 分割开来的使用
  • 祖先类指针指向子孙类对象地址,祖先指针只会指向 子孙对象里 祖先部分的开头地址,该指针也只会访问 子孙类对象里祖先部分的数据

基类和派生类对象赋值转换 逻辑图

cpp 复制代码
  Person            Student
 ___________        ___________
|           |      |           |
|char* _name|<-----|char* _name|
|___________|      |___________|
|           |      |           |
|int _age   |<-----|int _age   |
|___________|      |___________|
                   |           |
                   |int _number|
                   |___________|

隐藏

  • 隐藏:派生类域如果和基类域都定义和声明了同名成员,直接手动方式 如子类对象./子类域::/直接子类域下 去调用或者使用时,也不指定哪个类域,那么编译器默认就调用子类的

隐藏现象演示

cpp 复制代码
class A
{
public:
    void Print()
    {
        std::cout<<_num<<std::endl;
    }
public:
    int _num=1;
};
class B
{
public:
    int _num=2;
};

int main()
{
    B b;
    b.Print();
    return 0;
}

隐藏解决方式:出现父子类域同名类域,指定父类成员 使用和调用 代码演示

cpp 复制代码
class A
{
public:
    int _num=1;
};
class B
{
public:
    void Print()
    {
        std::cout<<A::_num<<std::endl;
    }
public:
    int _num=2;
};

int main()
{
    B b;
    b.Print();
    return 0;
}

函数名构成隐藏的例子

这里函数名(语言层函数名)相同,构成隐藏的例子

cpp 复制代码
class A
{
public:
    void Print()
    {
        std::cout<<A::_num<<std::endl;
    }
public:
    int _num=1;
};
class B
{
public:
    void Print(int i)
    {
        std::cout<<A::_num+i<<std::endl;
    }
public:
    int _num=2;
};

派生类的默认成员函数

  • 构造函数:派生类对象执行的自己的构造函数前,会先执行权限允许的基类的构造函数,对派生类对象的基类部分 进行初始化 再执行 派生类对应的构造函数
    • 注意:初始化列表是构造函数的一部分,派生类的初始化列表无法直接初始化 其基类部分的成员变量,需在初始化列表中指定调用基类构造去初始化 派生类里基类部分 的成员变量
    • 派生类对象执行基类构造,要么默认执行基类的默认构造,要么就手动去初始化列表指定执行对应的构造

手动让派生类对象的 基类部分 去执行指定的 基类构造

cpp 复制代码
class A
{
public:
    A()
    {
        std::cout<<"A默认构造"<<std::endl;
    }
    A(int i)
    {
        std::cout<<"指定的A构造"<<std::endl;
    }
};
class B:public A
{
public:
    B():A(1)
    {
        std::cout<<"B默认构造"<<std::endl;
    }
};
class C:public A
{
public:
    C()
    {
        std::cout<<"B默认构造"<<std::endl;
    }
};
int main()
{
    B b;
    C c;
    return 0;
}
  • 拷贝构造函数:派生类对象执行的自己的拷贝构造函数前,会先执行权限允许的基类的拷贝构造函数,对派生类对象的基类部分 进行初始化 再执行 派生类对应的拷贝构造构造函数
    • 注意:拷贝构造也是构造,也具有初始化列表
    • 派生类对象执行基类构造,要么默认执行编译器自生成的基类拷贝构造,要么就手动去初始化列表 指定执行对应 手写的基类的拷贝构造

手动让派生类对象的 基类部分 去执行指定的 基类拷贝构造

cpp 复制代码
class A
{
public:
    A(const A& a)
    {
        std::cout<<"指定的A拷贝构造"<<std::endl;
    }
};
class B:public A
{
public:
    B(const B& b):A(b)/*指定拷贝构造*/
    {
        std::cout<<"B拷贝构造"<<std::endl;
    }
};

int main()
{
    B b;
    B bb(b);
    return 0;
}
  • 赋值重载:派生类对象执行的自己的赋值重载函数前,会先执行权限允许的基类的赋值重载函数,对派生类对象的基类部分 进行赋值 再执行 派生类对应的赋值重载函数
  • 派生类对象执行基类赋值重载,要么默认执行编译器自生成的基类赋值重载,要么就手动去赋值重载函数体内 指定执行对应 手写的基类的拷贝构造
    • 函数体内手动指定需要基类域::函数重载(...)来指定调用,因为派生类和基类的赋值重载会构成隐藏,因此调用时不指定基类域 就会默认调用 子类的赋值重载 从而导致死循环崩溃

手动让派生类对象的 基类部分 去执行指定的 基类赋值重载

cpp 复制代码
class A
{
public:
    A& operator=(const A& a)
    {
        std::cout<<"A赋值重载"<<std::endl;
    }
};
class B:public A
{
public:
    B& operator=(const B& a)
    {
        A::operator=(a);    /*指定赋值重载*/
        std::cout<<"B赋值重载"<<std::endl;
    }
};

int main()
{
    B b;
    B bb;
    bb=b;
    return 0;
}
  • 析构函数:派生类对象执行的自己的析构函数后,会再自动执行权限允许的基类的析构函数,对派生类对象的基类部分 进行析构
  • 派生类对象执行自己派生类析构函数后,会直接自动执行基类默认生成或手写的析构函数,就无需手动去 派生类析构函数体内 指定执行对应 手写的基类的析构函数,如果手动指定调用基类 析构则会多调用一次 基类析构函数
    • 函数体内手动指定需要基类域::析构函数名(...)来指定调用,因为派生类和基类的析构函数会构成隐藏,因此调用时不指定基类域 就会默认调用 子类的析构函数 从而导致死循环崩溃
    • 在语言层析构函数名都叫destructor,虽然语言层上层的用户层析构函数名为~类名()

手动让派生类对象的 基类部分 去执行指定的 基类析构函数

cpp 复制代码
class A
{
public:
    ~A()
    {
        std::cout<<"A析构函数"<<std::endl;
    }
};
class B:public A
{
public:
    ~B()
    {
        A::~A();
        std::cout<<"B析构函数"<<std::endl;
    }
};

int main()
{
    B b;
    return 0;
}

继承和友元

  • 基类的友元不是派生类的友元,友元关系不可被继承

继承域静态成员

  • 基类的static静态成员,这个静态成员在基类和子孙类中 就独一份,所有子孙或者基类的对象 只能用这同一个静态成员,并且只供 基类和其子孙类 的对象等 共用

基类静态成员和子孙类的 逻辑关系

cpp 复制代码
 _______________________________________________
|class Father    _____________________          |
|               |                     |         |
|_______________|                     |_________|
|class Child    |                     |         |
|               |Father static member |         |
|_______________|                     |_________|
|class Child    |                     |         |
|               |_____________________|         |
|_______________________________________________|

多继承与单继承

  • 单继承:就是指 一个子类只能有一个直接父类 的关系,那么这段关系就叫做单继承
  • 多继承:就是指 一个子类存在多个直接父类 的关系,那么这段关系就叫做 多继承
  • 拓展:java 不像 cpp,java 只有单继承

单继承语法 代码演示

cpp 复制代码
class A
{
public:
    A()
    {
        std::cout<<"A"<<std::endl;
    }
protected:
    int _a;
};
class B:public A
{
public:
    B()
    {
        std::cout<<"B"<<std::endl;
    }
private:
    int _b;
};

多继承语法 代码演示

cpp 复制代码
class A
{
public:
    A()
    {
        std::cout<<"A"<<std::endl;
    }
protected:
    int _a;
};

class B
{
public:
    B()
    {
        std::cout<<"B"<<std::endl;
    }
protected:
    int _b;
};

class C:public A,public B   //继承A和B
{
public:
    C()
    {
        std::cout<<"C"<<std::endl;
    }
private:
    int _c;
};

菱形继承

  • 菱形继承:将当前类 的祖先类 全部画出,派生类和基类 相连,构成环路的 这些类组成的关系 就叫做菱形继承

菱形继承代码演示

cpp 复制代码
class A
{
public:
    A()
    {
        std::cout<<"A"<<std::endl;
    }
protected:
    int _a;
};

class B:public A
{
public:
    B()
    {
        std::cout<<"B"<<std::endl;
    }
protected:
    int _b;
};

class C:public A
{
public:
    C()
    {
        std::cout<<"C"<<std::endl;
    }
private:
    int _c;
};

class D:public B,public C
{
public:
    D()
    {
        std::cout<<"D"<<std::endl;
    }
private:
    int _d;
};
  • 菱形继承存在的问题:类在实例化时,会出现重复性质的 类成员对象
    • 注意:建议编写时尽量避免菱形继承的现象

菱形继承代码

D 类对象的所有成员变量所包含哪些成员变量:_b+_c+2_a+_d

cpp 复制代码
class A
{
public:
    A()
    {
        std::cout<<"A"<<std::endl;
    }
public:
    int _a;
};

class B:public A
{
public:
    B()
    {
        std::cout<<"B"<<std::endl;
    }
public:
    int _b;
};
 ___________  :B对象
|   int _a  |_part of A
|___________|
|   int _b  |
|___________|
class C:public A
{
public:
    C()
    {
        std::cout<<"C"<<std::endl;
    }
public:
    int _c;
};
 ___________  :C对象
|   int _a  |_part of A
|___________|
|   int _c  |
|___________|

class D:public B,public C
{
public:
    D()
    {
        std::cout<<"D"<<std::endl;
    }
public:
    int _d;
};

int main()
{
    D d;
    std::cout<<d._a<<std::endl;     !!!此时会报错,因为不知道访问哪个_a
    std::cout<<d.B::_a<<std::endl;  //此时访问的是 根据基类B的基类 所开辟的空间
    std::cout<<d.C::_a<<std::endl;  //此时访问的是 根据基类C的基类 所开辟的空间
}
           ___________  :D对象
part of A_|   int _a  |_
          |___________| |_B类部分
          |   int _b  |_|
          |___________|
part of A_|   int _a  |_
          |___________| |_C类部分
          |   int _c  |_|
          |___________|
          |   int _d  |
          |___________|

菱形继承包含公式 逻辑图

cpp 复制代码
//例1
    A
  /   \
 B     C
  \   /
    D
D = (A通往D路线数)*(A的成员对象)+(B通往D路线数)*(B成员变量)+(C通往D路线数)*(C成员变量)+D成员变量
  = 2_a+_b+_c+_d
//例2
            A
          /   \
         B     C
       /   \    \
      D     E    F
       \    |   /
        \   |  /
         \  | /
            G
G = (A通往G路线数)*(A的成员对象)+(B通往G路线数)*(B成员变量)+(C通往G路线数)*(C成员变量)+(D通往G路线)*(D对象)+(E通往G路线)*(E对象)+(F通往G路线)*(F对象)+G对象
  = 3_a+2_b+_c+_d+_e+_f+_g
//例3
    A
  /   \
 B     C     D
  \    |    /
   \   |   /
    \  |  /
       E
E = (A通往E路线数)*(A的成员对象)+(B通往E路线数)*(B成员变量)+(C通往E路线数)*(C成员变量)+(D通往E路线数)*(D成员变量)+E对象
  = 2_a+_b+_c+_d+_e

虚继承

  • 为了解决菱形继承带来的数据二义性和空间冗余,此时 Cpp 设计中就设计出虚继承这个语法,对出现问题的类对象里 对基类的基类成员 去重,减少数据冗余和避免数据二义性
    • 虚继承 用 virtual 关键字来解决,在每个环路的第二大辈分对应 的类中 执行虚继承
  • 执行虚继承的类 其类对象 实例化后,对应的 成员变量空间 也会改变
  • 被虚拟继承的类就是虚基类

虚继承代码演示

虚继承原理 如原理图可知,之所以虚继承采用这种存储方式解决二义性等问题,主要是为了方便 底层满足的切割操作

cpp 复制代码
class A                 //虚基类
{
public:
    A()
    {
        std::cout<<"A"<<std::endl;
    }
protected:
    int _a;
};

class B:virtual public A
{
public:
    B()
    {
        std::cout<<"B"<<std::endl;
    }
protected:
    int _b;
};

class C:virtual public A
{
public:
    C()
    {
        std::cout<<"C"<<std::endl;
    }
private:
    int _c;
};
//C类对象内存逻辑图,C类采用虚继承因此内部的内存逻辑图也会跟着改变,ptr指向的表也叫做虚基表,虚基表专门存放偏移量
//C类内存逻辑图之所以会改变是为了方便底层切割动作统一,这样内存格式一样 那么C和其子孙类 底层既可以采用统一的切割方式
 ____________  :C对象     ___________
|  int* ptr--|---------->| 8(offset) |~~~~~~&ptr1+8
|____________|           |___________|           |
|   int _b   |                                   |
|____________|                                   |
|   int _a   |<__________________________________|
|____________|

class D:public B,public C
{
public:
    D()
    {
        std::cout<<"D"<<std::endl;
    }
private:
    int _d;
};
int main()
{
    D d;
    std::cout<<d._a<<std::endl;                         !!!此时会报错,因为不知道访问哪个_a
    std::cout<<d.B::_a<<std::endl<<d.C::_a<<std::endl;  //此时只有一个_a,所以这两次访问的都是一个_a
}
//D类对象内存逻辑图,ptr1和ptr2指向的表也叫做虚基表,虚基表专门存放偏移量
 ____________  :D对象     ___________
|  int* ptr1-|---------->| 20(offset)|~~~~~~&ptr1+20
|____________|           |___________|           |
|   int _b   |                                   |
|____________|            ___________            |
|  int* ptr2-|---------->| 12(offset)|~&ptr2+12  |
|____________|           |___________|    |      |
|   int _c   |                            |      |
|____________|                            |      |
|   int _d   |                            |      |
|____________|                            |      |
|   int _a   |<___________________________|      |
|____________|<__________________________________|

继承总结

  • 避免菱形继承,因为用起来太复杂了,还有 能组合就组合,尽量少的继承
  • 组合就是将存有公共属性的类,作为不同类的类成员对象,本质和继承解决的 问题都一样

组合和继承的代码演示

cpp 复制代码
class A
{
public:
    int _a;
};

class B             //组合
{
private:
    A a;
    int _b;
};

class C:public A    //继承
{
private:
    int _c;
};
                    //C和B类都有A这种公共属性
  • 耦合是用来形容类与类之间的关系,内聚是用来形容类内之间成员的关系
相关推荐
此生决int1 小时前
深入理解C++系列(04)——类和对象(下)
开发语言·c++
2601_9545267512 小时前
【工业传感与算法实战】温漂补偿与零点抗漂破局:基于二阶多项式拟合的 C/C++ 边缘校准算法,深度拆解“压力变送器什么牌子好”的技术硬指标
c语言·c++·算法
code_pgf12 小时前
`unordered_map` 详解
c++
一拳一个呆瓜14 小时前
【STL】iostream 编程:检测提取操作产生的错误
c++·stl
稚南城才子,乌衣巷风流14 小时前
判断素数与拓展应用
c++
众少成多积小致巨14 小时前
C++ 规范参考(下)
c++
c2385614 小时前
把 C++ 内存分配拆透:new 与 malloc 的三层血缘
开发语言·c++·算法
txzrxz15 小时前
最短路问题——Dijkstra 算法
数据结构·c++·算法·最短路·优先队列
noipp16 小时前
推荐题目:洛谷 P6231 [JSOI2013] 公交系统
c语言·数据结构·c++·算法·游戏·洛谷·luogu