拓冰建站拓冰建站
首页 / 资讯中心 / 正文

初识C++:继承

1.继承的概念及定义1.1继承的概念在最开始C的介绍中我们了解到,C的特性有:封装,继承,多态.封装的体现就是用类封装函数与变量,是对C语言的一大改进,那么继承有什么特点,能用来干什么呢?继承是一种复用的手段,它允许我们在原有类的基础上进行扩展,增加新的函数和变量,这样产生的新的类,叫做派生类/子类,而被继承,复用的类,叫做基类/父类.继承呈现了面向对象程序设计的层次结构.从以往对函数的复用上升到了对类的复用.例如:在一个学校的身份管理系统中,有不同的角色,比如学生,老师,教授,或者职工等等,他们除了学校赋予的身份,还有一些相同的基础信息,比如身份证号,籍贯…在每个角色的类中都要设计相同的变量有些冗余,所以我们把它们提取出来,成为一个新的父类,“基本信息”,把学校给予的身份构建为一个新的子类,里面是学号/工号,职位…子类继承父类,拥有其中的基本信息,实现了对类的复用1.2继承的定义1.2.1继承定义格式继承的格式是在派生类的后面声明:classperson{protected:char*_name;char*_id;int_age;};classstudent:publicperson{protected:char*_student_id;};classteacher:publicperson{protected:char*_teacher_id;};class 派生类 :继承方式 基类可以看到,继承方式的关键字和类中访问权限的关键字相同,那么继承权限和访问权限之间有什么关联吗?1.2.2基类成员访问方式变化可以认为派生类中继承的优先级是privateprotectedpublic,由基类中的访问权限和继承方式中更高优先级的来决定在派生类中的权限.由于private的优先级最高,对于基类的private成员无论是什么继承方式在派生类中一律不可见,在类外,同样不可使用也就是说不能直接访问(虽然可以通过人为写一个get函数间接得到在基类中我们尽量不使用private,如果想要在派生类中访问,类外不可访问就用protected,想要在派生类和类外都能访问就用public派生类的继承方式建议使用public,不会对基类的权限产生影响,如果使用protected或者private继承,都只能在类内部使用,可维护性并不强class的默认继承方式是private,struct默认继承方式是public,不过建议还是显示写出1.3继承类模板templateclassTclassstack:publicvectorT{public:voidpush(constTval){vectorT::push_back(val);}};voidtest2(){stackints1;s1.push(1);}类模板同样可以使用继承,比如stack既满足复用的has-a关系(栈里面有一个vector),又满足继承的is-a关系使用时需要指定模板类型,在内部使用vector的函数时也需要指定类域,因为虽然实例化stack的时候vector也完成了实例化,但是模板是按需实例化的,成员函数还没有实例化,所以找不到2.基类和派生类之间的转换前面说派生类和基类之间的关系是is-a,即派生类可以看作是一个特殊的基类,这两个类之间是存在特殊处理的,允许转换.正常来讲,把一个类对象赋值给另一个类的指针或者引用的时候,会触发类型转换,生成一个临时变量,而临时变量是带有常性的,所以引用或者指针要加上const但是继承是例外,使用基类对派生类的引用和指针不会发生类型转换,而是直接引用/指向派生类中继承的部分:派生类对象赋值给基类对象会调用拷贝构造,赋值重载等,这个过程叫切割基类对象不能赋值/转换到派生类对象3.继承中的作用域3.1隐藏在同一个作用域中可以存在多个同名函数,只要参数不同,这样就会构成重载为什么在很多容器中都有push_back,参数相同却不报错呢?因为不属于同一个作用域对于基类和派生类,二者的作用于本来就不同,所以不会构成重载,当两个类中存在同名函数或者变量时,基类中的会被屏蔽,因为派生类调用函数或者变量时,会优先调用当前(派生类)类域的变量,如果想要调用基类中的变量需要指定类域3.2练习题classA{public:voidfun(){coutfunc()endl;}};classB:publicA{public:voidfun(inti){coutfunc(int i)iendl;}};intmain(){B b;b.fun(10);b.fun();return0;};A和B类中的两个func构成什么关系A.重载B.隐藏C.没关系这道题选B,重载是针对同一作用域下的同名函数来说的,这里的基类和派生类不是同一作用域,只存在隐藏classA{public:voidfun(){coutfunc()endl;}};classB:publicA{public:voidfun(inti){coutfunc(int i)iendl;}};intmain(){B b;b.fun(10);b.fun();return0;};下⾯程序的编译运⾏结果是什么A.编译报错B.运⾏报错C.正常运⾏这道题选A,因为继承中同名函数被屏蔽,不指定类域时编译器只能看到fun(int i),此时进行无参调用的时候找不到匹配的函数,会报编译错误4.派生类的默认成员函数4.1常见的4个默认成员函数之前学习默认成员函数时的流程是:我们不写时编译器默认生成的函数行为是什么,能不能满足我们的需求,不满足需求,那我们应该怎么写?现在了解派生类中的默认成员函数依然要遵循这个流程,只不过不同的点是多了一个继承类,我们可以把它当一个自定义类型来看待.构造函数:编译器默认生成的默认构造函数对内置类型不做处理,自定义类型以及基类会调用它的默认构造函数.调用顺序是优先构造基类一般情况下我们都会写构造函数,因为要传参初始化.走初始化列表时因为找不到进行初始化的基类变量名,所以直接使用基类名称初始化即可.拷贝构造:对于内置类型完成浅拷贝,对于自定义类型以及基类调用它的拷贝构造operator调用基类的operator,由于基类和派生类都叫operator,基类中的会被屏蔽,所以需要指定类域进行调用析构函数:派生类会在析构结束后自动调用基类的析构函数(因为如果先调用基类的析构函数,之后如果派生类中再访问基类的成员就会出错),可以理解为:在构造时先构造的基类再构造派生类,后构造的先析构派生类中不能主动调用基类的析构,因为只要自己写就可能会有顺序问题,所以编译器规定在生命周期结束后自动调用.4.2实现一个不能被继承的类方法一:基类构造函数私有,派生类调用不到积累的构造函数,自然无法实例出对象方法二:final关键字修改基类5.继承和友元派生类不会继承基类的友元关系.classStudent;classPerson{public:friendvoidDisplay(constPersonp,constStudents);protected:string _name;// 姓名};classStudent:publicPerson{protected:int_stuNum;// 学号};voidDisplay(constPersonp,constStudents){coutp._nameendl;couts._stuNumendl;}intmain(){Person p;Student s;// 编译报错error C2248: “Student::_stuNum”: ⽆法访问protected 成员// 解决⽅案Display也变成Student 的友元即可Display(p,s);return0;}6.继承与静态成员基类中定义了static成员,则整个继承中无论复用了多少份基类都只会有这一份static变量7.多继承以及菱形继承问题7.1继承模型单继承:派生类只有一个基类多继承:派生类有两个或个以上的基类,规定先继承的基类在前面,后继承的基类在后面,派生类成员最后定义菱形继承:菱形继承是一种象形称呼,指的是这种继承的逻辑形状像菱形,例如上图的assistant类继承student,teacher,而这两个基类又都继承了person.这样就导致了数据冗余和二义性问题,assistant中存储了两份person,这又会导致访问_name的时候不知道要访问哪一份name,尽管通过指定类域可以解决,但是数据冗余问题无法避免7.2虚继承虚继承是用来解决菱形继承的方案,在可能会出现数据冗余的派生类使用virtual修饰,被virtual修饰的派生类会忽略掉基类的构造函数.// 使⽤虚继承Person类classStudent:virtualpublicPerson{protected:int_num;//学号};// 使⽤虚继承Person类classTeacher:virtualpublicPerson{protected:int_id;// 职⼯编号};被virtual修饰后共同使用一份基类,解决了数据冗余问题7.3练习题classBase1{public:int_b1;};classBase2{public:int_b2;};classDerive:publicBase1,publicBase2{public:int_d;};intmain(){Derive d;Base1*p1d;Base2*p2d;Derive*p3d;return0;}下⾯说法正确的是()Ap1p2p3 Bp1p2p3 Cp1p3!p2 Dp1!p2!p3答案选C,因为d构造时会先构造第一个继承,再调用第二个继承,此时第一个继承和d本身有相同的地址7.4io库中的菱形虚拟继承templateclassCharT,classTraitsstd::char_traitsCharTclassbasic_ostream:virtualpublicstd::basic_iosCharT,Traits{};templateclassCharT,classTraitsstd::char_traitsCharTclassbasic_istream:virtualpublicstd::basic_iosCharT,Traits{};8.继承和组合public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。组合是⼀种has-a的关系。假设B组合了A每个B对象中都有⼀个A对象。继承⼀定程度破坏了基类的封装基类的改变对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强耦合度⾼。组合类之间没有很强的依赖关系耦合度低。优先使⽤对象组合有助于你保持每个类被封装。优先使⽤组合⽽不是继承。实际尽量多去⽤组合组合的耦合度低代码维护性好。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门