c/c++多态与继承知识点细讲
c++是基于面向对象的编程语言,面向对象的三大特性为:封装、继承和多态。本文对继承与多态的知识点进行了总结归纳,这部分内容对于学习c++编程语言是非常重要的,文章加入我的个人理解,希望能给大家带来帮助,如果有问题欢迎大家指出。
本文的所有代码运行环境为【windows 10】vs2013
知识框架:
1.继承的概念
什么是继承?为什么有继承?
通过继承将有共同部分的、相互联系的类构成一种层次关系,共同部分组成的类一般在最顶端称为基类(父类),其他类直接或间接地继承基类,通过继承而来的这些类称为派生类(子类)。这样就可以实现复用,子类只需要继承父类就会拥有父类的所有东西。
2。访问控制与继承关系:public、protected、private
继承关系相当于给从基类继承过来的所有成员外部加了继承关系的限定符。
一个类使用protected来声明那些他想与派生类分享但不想被其他公共部分访问使用的成员。
3.继承与转换--赋值兼容规则 (public继承的前提下)
在public继承的前提下,满足一下赋值兼容规则:
(1)子类对象可以赋值给父类对象,反之则不行。(学生类是人类的子类,你可以把学生说是人,但你不能说人一定就是学生)
(2)父类对象的指针/引用可以指向/引用子类对象,反之不行。(子类除了拥有父类的成员,还拥有自己特有的。若将父类对象的地址赋给子类的指针,相当于扩大了指针的权限,解引用就有可能访问到非法空间,所以不可以)
4.成员函数的重定义/隐藏
当子类与父类成员(成员变量和成员函数)同名时,子类成员就会隐藏父类成员.(这里只需要同名即可,与成员函数参数和返回值都无关)
5.单继承和多继承
单继承:就是只有一个直接父类。
多继承:有两个或两个以上的直接父类。
菱形继承/钻石继承:
通过单继承和多继承合成了菱形继承,这种继承是存在一定问题的:
(1)数据冗余。(动物有一个成员是嘴巴,人和鱼各自都继承了嘴巴这个成员,而美人鱼继承自人和鱼,他就会有两个嘴巴,然而他只需要一个就行了。)
(2)访问的二义性。(美人鱼有了两个嘴巴,它到底用哪个嘴巴呢?这里就产生了二义性)
6.虚继承
为了解决上面菱形继承带来的两个问题,我们引入了虚继承。
- <span style="font-size:14px;"><span style="font-size:14px;">class A
- {
- public:
- A()
- :_a(1)
- {}
- protected:
- int _a;
- };
- class B : virtual public A //也可以写为class B : public virtual B
- {
- public:
- B()
- :_b(2)
- {}
- private:
- int _b;
- };
- class C : virtual public A
- {
- public:
- C()
- :_c(3)
- {}
- private:
- int _c;
- };
- class D : public B, public C
- {
- public:
- D()
- :_d(4)
- {}
- private:
- int _d;
- };</span></span>
继承时在继承关系前加上virtual关键字,就会变为虚继承。这样_a成员在D类中就只保存了一份。这是怎么实现的呢?
虚继承解决了菱形继承所带来的问题,但它也降低了性能。
7.虚函数与多态
虚函数:在类的成员函数之前加virtual关键字。
虚函数重写/覆盖:当派生类与父类的虚函数完全相同时(函数名,参数,返回值都相同,协变<返回值为类类型的指针,满足多态才有协变>和虚析构函数<函数名为类名>除外),子类的这个函数重写/覆盖了父类的函数。
多态:
之前已经介绍过函数重载也就是静态多态了,这里我们就只说动态多态了。
- <span style="font-size:14px;"><span style="font-size:14px;">include <iostream>
- using namespace std;
- class Book
- {
- public:
- void ShowPrice()
- {
- cout << "全价" << endl;
- }
- };
- class BarginBook :public Book
- {
- public:
- void ShowPrice()
- {
- cout << "半价" << endl;
- }
- };
- int main()
- {
- BarginBook barginbook;
- Book* pbook = &barginbook;
- pbook->ShowPrice();
- Book book;
- pbook = &book;
- pbook->ShowPrice();
- system("pause");
- return 0;
- }</span></span>
运行结果:
全价
全价
若将ShowPrice函数改为虚函数,即给父类和子类的ShowPrice函数前家virtual关键字,则程序运行结果就变为:
半价
全价
这里,因为ShowPrice函数为虚函数,它与基类的虚函数完全相同,所以重写了父类的虚函数。这种方式就实现了动态多态,根据基类指针指向不同的类对象,来调用不同的虚函数。
动态多态的实现条件:
1.子类虚函数重写父类的虚函数(两个类中的虚函数必须完全相同)。
2.使用父类的指针/引用来调用父类或子类的虚函数。
六个默认成员函数中,为什么将析构函数写为虚函数?
- <pre class="cpp" name="code"><span style="font-size:14px;">class Base
- {
- public:
- Base()
- {
- cout << "Base()" << endl;
- }
- ~Base()
- {
- cout << "~Base()" << endl;
- }
- };
- class Derive:public Base
- {
- public:
- Derive()
- :_pi(new int(1))
- {}
- ~Derive()
- {</span>
- <span style="font-size:14px;"> delete _pi;
- cout << "Derive()" << endl;
- }
- protected:
- int* _pi;
- };
- int main()
- {
- {
- Derive d;
- Base* pb = &d;
- }
- system("pause");
- return 0;
- }</span>
上面的代码执行结果是什么呢?
程序调用了Base和Derive的构造函数,却只执行了Base的析构函数,这样会导致Derive中开辟的内存没有释放,产生内存泄漏。将Base类和Derive类的析构函数定义为虚函数就会以子类的虚构函数重写父类的虚构函数,调用子类的虚构函数是会自动调用父类的虚构函数,所以就解决了这样的问题。
如果我们删除的是一个指向派生类对象的基类指针,则需要虚析构函数。
8.友元与继承
友元不能继承。父类的友元不能继承给子类,就像爸爸的朋友不是你的朋友一样,但是并不是不能做朋友,只要你在子类里面再声明一次友元就可以了。
9.静态成员与继承
- <span style="font-size:14px;">class Derive:public Base
- {
- public:
- Derive()
- {
- _count++;
- }
- void Show()
- {
- cout << _count << endl;
- }
- };
- int main()
- {
- Base b;
- Derive d;
- b.Show();
- d.Show();
- system("pause");
- return 0;
- }</span>
运行结果是:
3
3
通过上面的程序我们就能看出来,静态成员在整个继承体系中只有一份,这个静态成员属于整个继承体系,只要没有访问限定,整个体系中的类都可以访问他。
到这里,继承与多态的基础知识就差不多了,但是还有比较深入的知识,在继承与多态(二)中进行总结归纳。谢谢阅读,希望能给大家带来帮助。
转自:https://blog.csdn.net/xhfight/article/details/52281547