首页 > 编程语言 >C++继承

C++继承

时间:2023-08-30 19:33:24浏览次数:31  
标签:函数 继承 子类 成员 C++ 父类 指针

一、什么是继承

  1. 当遇到问题时,先查看现有的类是否能解决一部分问题,如果有则继承该类,并在此基础上扩展以此解决问题,从而缩短解决问题的时间(代码复用)

  2. 当遇到一个大而复杂的问题时,可以把大问题拆分成若干个不同的小问题,然后为每个小问题设计一个类来解决,最后通过继承的方式把这些类汇总到一个类中,从而解决大问题,以此降低问题的难度,可以同时让多个程序员共同解决大问题

  3. 派生类继承基类  子类继承父类

二、继承的语法

  1. 继承表

    class Son : 继承表 [继承方式 父类名1,继承方式 父类名2,...]
    {
        成员变量;
    public:
        成员函数;
    };
    
  2. 继承方式
    public
    private
    protected

三、继承的特点

  1. C++中的继承可以有多个父类

  2. 子类会继承父类的所有内容,是否能用另说

  3. 子类对象可以向它的父类类型转换(缩小),但父类对象不能向子类类型转换(放大)
    父类指针或引用可以指向子类对象,子类指针或引用不能指向父类对象(前提:必须以public继承父类)

    Base* b = new Son; √
    Base& b = son; √
    
    Son* s = new Base; ×
    Son& s = base; ×
    
  4. 子类会隐藏父类的同名成员(成员变量、成员函数),不构成函数重载,因为作用域不同
    同名成员被隐藏后,在子类中直接访问到的是子类的同名成员
    但是可以通过 父类名::同名成员名 的方式指定访问父类同名成员

  5. 在执行子类的构造函数的初始化列表时,会按照继承表的顺序来执行父类的构造函数,默认执行的是父类的无参构造,但是也可以在子类的初始化列表中显式的调用父类的有参构造,然后再执行类类型成员的构造函数,最后执行子类的构造函数

    Son(int num){}          //调用Base的无参构造
    Son(int num):Base(val){}  //调用Base的有参构造
    
  6. 在子类的析构函数执行结束后,再调用类类型成员的析构函数,最后按照继承表的逆序调用父类的析构函数

  7. 当子类执行拷贝构造时,默认只会调用父类的无参构造,但是这是有问题的,因此需要在子类的拷贝构造函数的初始化列表中显式地调用父类的拷贝构造

    Son(const Son& that):Base(that){}   //父类引用可以指向子类对象
    
  8. 当子类执行赋值函数时,默认下不会调用父类的赋值函数,如果需要调用父类的赋值函数,可以在子类的赋值函数中通过域限定符显式地调用父类的赋值函数

    Son& operator=(const Son& that)
    {
        Base::operator=(that);  //  调用Base的赋值操作函数
    }
    

四、继承方式与访问控制属性

  1. 访问控制属性对成员的访问范围限制:

    访问控制属性 访问范围
    public 可以在任意位置访问
    protected 只能在类内和子类中访问
    private 只能在内类访问
  2. 继承方式的影响

    ①父类的成员是否能在直接子类中访问,取决于父类中的访问控制属性,不受子类的继承方式影响
    ②子类的继承方式能决定父类成员被子类继承后,在子类中变成什么样的访问控制属性,详情见表格
    ③只有以public方式继承父类,父类的指针或引用才能指向子类对象,这也是多态的基础(如果不是public方式继承父类,那么父类中的成员的访问属性在子类中会改变,具体情况见下表,那么此时父类的指针或者 引用再去指向子类对象时,访问属性就会发生改变,不再是原来的父类

    父类中的属性 public继承 protected继承 private继承
    public public protected private
    protected protected protected private
    private private private private

五、多重继承和钻石(菱形)继承

  1. 多重继承
    多重继承是一个类的父类也有父类,一层一层的继承父类,会按照继承表的顺序在子类中排列父类的内容,当使用父类指针指向子类对象,编译器会自动计算出该父类的内容在子类中的位置,并让父类指针指向该位置,所以可能会出现使用同一个子类指针给不同的父类指针赋值后,地址编号不同的情况

  2. 钻石(菱形)继承
    假设有一个类A,类B和类C都分别继承了类A,又有类D继承了类B、类C,一个类的父类有共同祖先类时,形成了钻石(菱形)继承
    问题:

    1、类B、类C中各自都有类A的内容
    2、类D会继承类B、类C的所有内容,就导致类D中继承了两份类A的内容
    3、当类D对象去访问类A的内容时,编译器不能确定访问的是哪份类A的内容,有歧义,会报错

  3. 虚继承
    使用virtual关键字去修饰继承表时,此时变成虚继承,此时子类中会多出一个成员变量:虚指针,该指针会指向父类的内容,并且当该子类被继承时该虚指针会一起被继承,如果此时形成钻石继承时,孙子类中就会有多个指向相同位置的虚指针,此时编译器会比较这些虚指针指向的内容是否一致,如果是则只继承一份
    总结:通过虚继承可以解决钻石继承中的访问共同祖先类内容会有歧义的问题

六、虚函数、虚函数表、虚表指针、覆盖

  1. 虚函数
    在成员函数前面加 virtual 后,该函数就成为虚函数,此时该类就会像虚继承一样多了一个虚表指针(虚函数表指针、虚指针)

  2. 虚函数表
    虚表指针指向的是属于该类一张表格的首地址,该表格中记录了该类中所有虚函数的首地址
    如果类中没有其他成员变量,通过 ((void(*)(void))((int)b))(); 可以直接通过虚函数表以及虚表指针来访问虚函数表中第一个虚函数void func(void)

  3. 覆盖(重写)是构成多态的基础
    当使用 virtual修饰父类的成员函数时,此时父类中就会多一个虚表指针以及一张虚函数表,子类继承父类时,会把父类的虚表指针以及虚函数表一起继承过来,然后编译器会去比较父子类中同名的成员函数的格式,如果格式完全相同的虚函数,就会把子类中虚函数表中原来同名父类

  4. 构成覆盖的条件
    ①子类以public方式继承父类
    ②父类中被覆盖的函数必须是虚函数
    ③子类中必须有与父类虚函数同名的成员函数且该函数的返回值、参数列表、常属性都必须相同
    ④返回值类型相同,或者子类同名成员函数的返回值类型可以向父类虚函数的返回值类型做隐式转换,且有继承关系

【常考面试题】

重载、覆盖、隐藏、重写的区别?

隐藏:

  1. 父子类中,如果同名且格式不同,无论是否有virtual修饰都构成隐藏

  2. 父子类中,如果同名且格式相同,如果没有virtual修饰则构成隐藏

  3. 隐藏可以隐藏同名成员变量、成员函数,但是覆盖只能覆盖满足条件的成员虚函数

  4. 在父子类中,同名成员函数要么构成隐藏、要么构成覆盖

  5. 除去父子类外,其他的不同作用域下同名标识符也构成隐藏

标签:函数,继承,子类,成员,C++,父类,指针
From: https://www.cnblogs.com/wangqiuji/p/17668100.html

相关文章

  • C++ 数组排序 查找。数值排序、冒泡排序以及顺序查找的方法
    #include<iostream>#include<cstring>#include<algorithm>#include<ctime>#defineMAX8usingnamespacestd; intmain() {   inta[MAX]={1,5,9,6,3,1,4,6};  for(inti=0;i<MAX;i++)   cout<<a[i]<<"";    ......
  • c++数组基本用法
    在C++中,数组是一种最基本的数据结构,用于存储一组相同类型的元素。以下是有关C++数组的一些重要信息:1.**声明和初始化数组:**```cpp//声明一个整数数组,指定大小为5intmyArray[5];//初始化数组的同时赋值intanotherArray[3]={10,20,30};```2.**访问数组元素:**数组中的元......
  • C++基础
    1变量和常量C++规定在创建一个变量或者常量时,必须要制定出相应的数据类型,否则无法给变量分配内存常量不可修改,一旦修改便会报错(通常在变量定义前加关键字const)宏常量不需要制定数据类型,因为其不占内存不同数据类型占用空间不同,取值范围也不同。一旦定义的变量或常量......
  • leetcode & c++多线程刷题日志
    1.按序打印按序打印解法互斥锁classFoo{mutexmtx1,mtx2;public:Foo(){mtx1.lock(),mtx2.lock();}voidfirst(function<void()>printFirst){printFirst();mtx1.unlock();}voidsecond(function<voi......
  • UE如何制作C++函数事件蓝图
    一.蓝图类中的函数在新建的actor中的C++ .h文件中,声明UFUNCTION(BlueprintCallable,Category="XXXX"),然后在.cpp中写函数的内容即可。编译后用蓝图继承C++类,可以进行函数的调用了二.建立函数库任意蓝图可以调用建立BlueprintFunctionLibrary的C++类,参考一中进行函数......
  • 标准C++ -- day07
    一、虚函数、虚函数表、虚表指针、覆盖1、虚函数在成员函数前面加virtual后,该函数就称为虚函数,此时该类就会像虚进程一样多了一个虚表指针(虚函数表指针,虚指针)classBase{public:voidfunc(void){cout<<"Basefunc"<<endl;}}cout<<size......
  • C++虚函数 覆盖(重写)
    1、虚函数  在成员函数前面加virtual后,该函数就称为虚函数,此时该类就会像虚继承一样多了一个虚表指针(虚函数表指针、虚指针)2、虚函数表  虚表指针指向的是属于该类的一张表格的首地址,该表格中记录了该类中所有虚函数的首地址    如果类中没有其他成员变......
  • 继承与接口
    文章目录一、重载、重写(覆盖)与隐藏的区别二、私有继承、公有继承、保护继承三、多重继承与虚继承1、多重继承2、类型转换与多个基类3、多重继承demo:4、虚继承5、虚继承demo:四、纯虚函数和抽象类1、面试题--->纯虚函数的实现原理,为什么抽象基类不能被实例化?2、面试题--->如何阻止一......
  • 【Effective C++】定制new和delete
    文章目录一、了解new-handler的行为1、new和malloc的对比2、set_new_handler的使用3、new-handler设计要求4、提供自己的set_new_handler和operatornew5、请记住二、了解new和delete的合理替换时机1、替换编译器提供的operatornew或operatordelete2、请记住三、编写new和delete......
  • C++算法
    运行前进行卡夫曼滤波(减小机器检测波动的影响)延迟上机算法速率法原理1、判断最新数据点和前面几个点的差值是否大于设定值2、判断两点间的斜率k是否大于设定值3、判断拟合曲线的符合度是否在规定范围内技术实现///\brief直线拟合-一元回归,拟......