一:虚函数
1.用virtual关键字声明的函数叫虚函数
虚函数标明在基类声明的虚函数是虚拟的,并不是实际存在的函数,在派生类中才会正式定义此函数
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数
eg:class A{ public : void display(){}}; class B:public A{ public : void display(){}};
A a;
B b;
A *ptr;
ptr=&a;
ptr->display();//调用的是类A中的display
ptr=&b;
ptr->display();//调用的还是类A中的display
当然这不是我们想要的,我们想要的是定义一个指向A类的指针,可以调用本身的函数还可以调用其众多派生类的函数
(不能调用派生类函数是因为,基类指针指向的是基类对象,如果将他指向派生类对象,则自动进行指针类型转换,将派生类的对象的指针先转换为基类的指针,所以基类指针指向的派生类中基类的部分,自然就不能调用派生类中的函数了。)
只要将A类中的display()改为虚函数就可以做到了(在类中添加virtual,在类外定义函数(给函数增加实体)的时候就不用加了)
派生类中不需要加virtual,因为当一个派生类重写基类中的虚函数时,派生类中的此函数自动变为了虚函数,但是为了代码的清晰,一般在派生类也都会加上virtual
这就是:上面红字中:通过基类指针或引用来访问基类和派生类中的同名函数的意义
(其实用虚函数的时候,指针发生类型变换之类的都是一样的,并且这时候基类指针指向的同样是派生类中基类的部分,只不过派生类中重写虚函数的时候,实际上是把派生类中继承基类的这个函数覆盖掉了,就是说派生类中继承来的这个函数替换成了自己重写的这个函数了,其实这就叫覆盖。
因为你继承类的时候,派生类拷贝了基类所有的非private的内容,又添加了自己的内容,并不是跟基类共用那部分内容,是拷贝)
当然可通过直接用b.display的方式调用,但是如果A类有很多的派生类,这样就非常的麻烦了
这就是C++的多态:利用同一个语句ptr->display();调用了不同的函数,并且Ptr只是基类的指针,看基类的指针是指向的基类还是派生类来决定调用哪个函数
二:多态
1.在编译时如果发现虚函数,就会采用迟绑定技术,编译时并不确定具体调用的函数,而是运行时依据对象的类型来调用的那一个函数,这种能力叫做多态性
简单说:在基类前加virtual关键字,在派生类中重写此函数,运行时根据对象的实际类型(这个对象的实际类型是通过基类指针的指向来确定的)来调用相应的函数
静态多态性:像函数的重载,用同一函数名但是会调用不同的函数,此时的多态是编译前就知道了需要用到哪个函数
动态多态性:类似1中讲到的虚函数作用的例子,实际上就是动态多态性:同一类族(基类跟他众多的继承类)中不同类的对象,对同一函数调用作出的不同响应
2.为什么一直强调的是基类指针
因为正是因为基类指针才实现的多态,如果不用虚函数的情况下,重写基类中的函数,用基类指针调用的是基类的函数,但是用派生类的指针调用的就是派生类重写的函数了,这样相当于是用了两个指针完成的不同调用,这不叫多态,多态就是因为都是基类指针,只是指向不同从而实现的调用不同。
三:纯虚函数
成因:有些时候虚函数其实对基类来说是无用的,也就是说基类中并不需要这个函数,只是为了派生类需要而为其留个函数名,因为不留的话无法实现多态了这个时候就可以用纯 虚函数
使用:在类中声明的时候 virtual 函数类型 函数名 (参数) =0 ;(=0并不是说返回值为0,只是告诉编译器这是个纯虚函数)
特点:纯虚函数没有函数体,只有函数的名字不具备函数的功能,所以不能调用;只是声明一个虚函数供派生类使用的,只有在派生类中提供了此函数的定义(派生类中实现覆盖,并添加函数体)后才可以被调用。
注:如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,此派生类依然为抽象类。
四:抽象类
定义:含有纯虚函数的类就叫抽象类
特点:抽象类不能创建对象,或者说不能被实例化,但是可以定义指向抽象类数据的指针变量,当派生类完全实现了所有的纯虚函数后,就可以用这种指针指向派生类的对象,然后通过该指针调用虚函数,实现多态性