目录
- 面向对象三大特征:
- 封装继承多态
定义:一个类自动拥有了来自另外一个类的属性和方法,我们管这种属性叫做继承。
层次关系:上层派生下层,下层继承上层
书写格式:
- class 子类:继承权限 基类
- {};
如果A是基类,B是派生类,那么B将继承A的数据和函数。
- class A
- {
- public:
- void Fun1(void);
- void Fun2(void);
- };
- class B:public A //继承是共有的,如果没有写权限,默认是私有继承
- {
- public:
- void Fun3();
- void Fun4();
- };
- main()
- {
- B b;
- b.Fun1();//B从A继承了函数Fun1
- b.Fun2();//B从A继承了函数Fun2
- b.Fun3();
- b.Fun4();
- }
- 除了构造和析构函数之外,无论几代继承,其余的全盘被继承
- 被继承不代表能被访问,访问的两种形式:1.外界访问(主函数..)2.成员函数访问
1.子类能够继承所有数据成员
- 下图为例,B继承了A(没有写权限,默认是私有继承),因为A中的数据类型大小为12,而子类B继承了所有的数据成员(m_i,m_j,m_k),因此,下面的代码输出结果为12。
2.子类能够继承所有成员函数
- 如下代码所示,A中有成员函数print(),让B继承A,外部成员函数main内可见,B的对象b可以访问A的成员函数print。
3.公有和保护的可以被访问,私有的可以被继承,不能被访问。
- 如下代码中,原A类中的数据成员m_i :公有,m_j:保护,m_k:私有,而被B共有继承后,B中的数据成员m_i 仍未:公有,m_j:保护,m_k:私有。原本基类的数据成员类型被继承下来后类型不变。
4.保护继承和私有继承中:公有和保护可以被继承,但所有的数据成员外界都不能访问。但是自身的成员函数,内部无论是公有、保护还是私有都可以访问。
- 如下代码所示,C保护继承A,把所有public和protected的成员函数和数据成员都私有化了,因为在主函数内无法访问m_i,m_j,m_k。
总结:
| 子类权限(右👉) 基类的权限(下👇) | public | protected | private |
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
- 如果类A和类B毫不相干,不可以为了使B的功能更多些而让B继承A的功能和属性。不要觉得“不吃白不吃”,让一个好端端的健壮青年无缘无故吃人参补身体。
- 若在逻辑上B是A的“一种(a kind of)”,则允许B继承A的功能和属性。例如男人man是人human的一种,男孩boy是男人man的一种。那么类男人man可以从类人human派生,类男孩boy可以从类男人man派生。
- 继承的概念在程序世界和现实世界并不相同。例如从生物界角度讲,鸵鸟是鸟的一种,按理说鸵鸟类应该可以从鸟类中派生,但是鸵鸟不能飞,那鸟类中的fly函数就不对。
所以,更严格的继承规则应该是:若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性
组合:若在逻辑上A是B的“一部分”,则不允许B从A派生,而是要用A和其他东西组合出B。例如眼eye、鼻mose、口mouse、耳ear是头Head的一部分,所以类Head由类眼eye、鼻mose、口mouse、耳ear组合而成,不是派生而成。
class A { public: private: int m_i; }; class B:public A { public: B(){cout<<"B"< private: int m_j; };- 1.全盘接收
- 2.改写(public->不用改写)
- 3.B的数据成员(放在A的数据成员下面)
要给m_i开辟空间:需要调用基类的构造函数对从基类中继承的数据成员m_i开辟空间。
那这样的话为什么不把构造函数继承下来呢?因为子类当前没有对基类数据成员的构造权。
4.数据成员构造析构顺序
- 构造的顺序:
1.先按照继承顺序调用基类的构造
2按照继承顺序调用组合的构造
3.调用自己的构造
基类->子类
构造函数的调用顺序是某类在类继承表中出现的顺序,而不是它们在成员初始化表中的顺序。如class D : public A, public B, public C ,即为DABC的顺序
如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
- 析构函数的调用顺序:子类->基类
三、使用
1.如何在子类中对从基类中继承来的数据成员进行初始化?
子类的数据成员是从父类中继承来的,子类没有对基类数据成员的构造权,因此,需要在基类中进行初始化。
以下面student和person类的代码为例。在主函数内构造了Student类的对象s1并对其进行初始化,Student类是Person类的子类,继承了m_num,m_name,m_sex三个数据成员以及Show()的成员函数。想要在主函数内输出对象s1的初始化信息,需要调用构造函数对对象s1进行初始化,而默认构造函数是无参的,因此必须显示的调基类构造函数进行传参。利用冒号语法(初始化列表方式)进行初始化调用。
class Person { public: Person(int num, const char* name, char sex) :m_num(num),m_sex(sex) { m_name = new char[strlen(name) + 1]; strcpy_s(m_name, strlen(name) + 1, name); } void Show() { cout << m_num << " " << m_name << " " << m_sex << " "; } private: int m_num; char* m_name; char m_sex; }; class Student :public Person { public://冒号语法进行显示调用传参 Student(int num, const char* name, char sex, float score) :Person(num,name,sex),m_score(score) { } void print() { Show(); cout << m_score << endl; } private: float m_score;//学生特征 }; void main() { Student s1(1001, "wangpangpang", 'f', 89); s1.print(); }
2.无参和有参构造函数调用的差别?
1.定义无参对象时,基类的构造函数必须也是默认无参的。
- 如下代码所示:当定义无参对象s时,要调用构造函数对对象初始化开辟空间,先调用基类构造函数,然后调用子类默认的无参构造函数。但是基类中声明的三个数据成员继承给子类,基类构造函数是没有对其传参的,s就会报错。
- 修改方法就是把基类person的构造函数修改为无参的。无参时要调用无参的构造函数;有参时对象就直接调用用冒泡语法进行初始化的有参构造函数。
2.无参对象调用无参构造函数时,有指针作为数据成员,需要给指针开辟空间
- 如下代码所示,对指针m_name赋初值为占位符‘\0’,就可以避免指针对非合法空间的开辟。
开辟空间new后要析构delete
- 没有对指针char* name开辟合法内存单元,就像如下C代码所示的错误,对非法空间进行了访问。
3.赋值运算符的使用
例:
- 如下代码中s=s2;旧对象给新对象赋值,调用赋值运算符。
- 需要在子类和基类中补充对赋值运算符的操作:
- 基类将自己的数据成员m_name,m_sex,m_age赋值操作:
- 子类调用基类赋值运算符,对自身m_num,m_score进行赋值运算操作:
4.继承下的静态数据成员
基类和子类都对static的数据成员进行共享
- 如下代码中,定义person类,派生出子类teacher、worker。静态全局变量m_count(静态全局变量在类内声明,类外初始化)。此时,对count输出结果为2 。
四、隐藏规则
规则内容:
- 如果派生类的函数与基类的函数同名,但参数不同。此时,无论有无virtual关键字,基类的函数都将被隐藏(注意别和重载混淆)。
- 如果派生类的函数和基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别和覆盖混淆,覆盖是对其进行改写)
- 同名不同参,被隐藏
- 同名同参,基类无virtual函数
如下代码中,A的成员函数print(),B是A的子类,成员函数名也为print(),此时B虽然继承了A::print(),也有自身的B::print(),但是如果此时主函数内对b对象输出b.print()输出的结果为"B::print",因为同名,B内对A::print隐藏了。
对sizeof(B)进行输出的结果为8,因为虽然A的成员函数m_i与B的成员函数m_i同名,但是两者实质并不同,就像B自己有printf但还是继承了A::print,只是因为同名,对其隐藏起来了,但是还是继承下来,可以通过b.A::print()进行访问。因此对于成员函数,B内有A的m_i和B的m_i。输出结果就是4+4=8。不加前缀的话,用的是B自己的m_i。A::m_i->用A的m_i。
- 总结:
- 满足隐藏规则时,对基类函数进行隐藏,调用访问时访问的是子类的。
- 子类仍然对基类函数进行基类,只是隐藏起来。