C++继承和多态
继承
什么是继承
当我们想为一个类添加新的属性,比如:将
圆
这个类添加属性高
,变成了圆柱
。
我们可以将圆
的代码复制到圆柱
中。
这样就产生了问题:如果想修改圆
的代码,我们还要找到圆柱
的代码进行修改。非常麻烦。
使用继承可以解决这个问题。
继承的好处:
- 代码重用
- 方便维护
已存在的类称为基类(父类),新建的类称为派生类(子类)。
如何继承
class A{
public:
int a;
};
class B:public A{ //以public方式继承了A
int b;
};
int main(int argc, const char * argv[]) {
B b1;
b1.a=1;
cout<<b1.a<<endl;
}
解释:
B以public的方式继承了A。(继承方式下面会讲到)
在main函数中可以看到:B已经拥有了A的属性。
圆柱继承圆:
#include <iostream>
using namespace std;
class Circle{
public:
float r;
};
class Cylinder:public Circle{
public:
float height;
};
int main(int argc, const char * argv[]) {
Cylinder cylinder;
cylinder.r=10;
cout<<cylinder.r<<endl;
}
继承方式
继承方式分为3种:公有继承、保护继承和私有继承。
通过不同的继承方式,继承过来的成员访问权限是不一样的。
得出结论:
- 原访问权限与继承方式比较:取较严格一方。
多继承
一个类可以同时继承多个类。(即有多个父类)
class A{
public:
int a;
};
class B{
int b;
};
class C:public A,public B{ //同时继承A和B
};
int main(int argc, const char * argv[]) {
C c1;
c1.a=1; //可正常访问A中的成员
cout<<c1.a<<endl;
}
多继承中的继承格式:
class C:public A,public B{}; //同时继承A和B
多继承中的命名冲突
因为有多个父类,如果多个父类之间的成员名是一样的,那么如何调用它们?
直接调用会报错,因为程序不知道该谁的成员。
class A1{
public:
int a; //数据成员a
};
class A2{
public:
int a; //数据成员a
};
class C:public A1,public A2{
};
int main(int argc, const char * argv[]) {
C c1;
c1.A1::a=1; //这样就不冲突了
cout<<c1.A1::a<<endl;
}
解释:
通过这种方式,就明确了调用的是A1的a,而不是A2的a。
格式:
对象名.父类名::冲突的成员名
多继承中构造函数的调用顺序
构造函数是在创建对象时自动被调用的。
如果拥有多个父类,那么这些构造函数是如何调用的呢?
class A1{
public:
A1(){
cout<<"A1的构造函数被调用"<<endl;
}
};
class A2{
public:
A2(){
cout<<"A2的构造函数被调用"<<endl;
}
};
class C:public A1,public A2{
public:
C(){
cout<<"C的构造函数被调用"<<endl;
}
};
int main(int argc, const char * argv[]) {
C c1;
}
结果:
A1的构造函数被调用
A2的构造函数被调用
C的构造函数被调用
得出结论:
先调用父类的构造函数,再调用子类的构造函数。
如果有多个父类,则按照继承的顺序依次调用。
这里添加一个知识点:
析构函数的调用顺序:与继承顺序相反。
继承时如何调用构造函数
父类中的构造函数需要参数,怎么把参数传给父类呢?
class Circle{
public:
float r;
Circle(float r){
this->r=r;
}
};
class Cylinder:public Circle{
public:
float height;
};
可以在子类中添加构造函数
利用子类间接得把参数传给父类。
#include <iostream>
using namespace std;
class Circle{
public:
float r;
Circle(float r){
this->r=r;
}
};
class Cylinder:public Circle{
public:
float height;
Cylinder(float r):Circle(r){ //调用父类构造函数Circle(r),其中变量r由主函数中的Cylinder(5)传入。
}
};
int main(int argc, const char * argv[]) {
Cylinder cylinder=Cylinder(5);
cout<<cylinder.r<<endl; //输出5
}
组合类
类可以作为类的数据成员,称为组合类。
class A{
public:
int a;
};
class B{
public:
A x; //定义对象x作为数据成员
};
int main(int argc, const char * argv[]) {
B b;
b.x.a=1; //访问类中的对象
cout<<b.x.a<<endl;
}
多重继承
子类依然可以作为父类,被其他类继承
class A{
public:
int a;
};
class B:public A{
};
class C:public B{
};
int main(int argc, const char * argv[]) {
C c;
c.a=100;
cout<<c.a<<endl;
}
多重继承中的二义性:菱形继承
B1和B2都继承了A。那么,C继承了A后就会产生二义性。
因为在C中有两个A。
class A{
public:
int a;
};
class B1:public A{
};
class B2:public A{
};
class C:public B1,public B2{
};
int main(int argc, const char * argv[]) {
C c;
c.B1::a=100; //这样就没有二义性了
cout<<c.B1::a<<endl;
}
可以得出结论:
- 只要遇到二义性,都可以通过
c.B1::a
这种方式消除。
多态
字面意思:
- 多种状态。可利用虚函数来定义接口,通过实现接口来体现不同的状态。
官方说法:
- 接口的多种不同的实现方式即为多态。
- 多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术。
- 我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。
多态-将子类对象赋值给父类指针
class People{
public:
char* name;
};
class Student:public People{
public:
int id;
};
int main(int argc, const char * argv[]) {
People *p;
p = new Student(); //p是People类型,但却可以用Student类型赋值
p->name=“张三”; //可以调用People的成员函数和数据成员
cout<<p->name<<endl;
}
这样直接体现的意思是:Student也是一个人,包含People所有的属性,所以可以赋值给People。
多态-函数重写
子类继承父类之后,可以定义与父类同名同参数的函数,此时将重写父类的函数。
class People{
public:
void who_am_i(){
cout<<"我是People"<<endl;
}
};
class Student:public People{ //继承了People
public:
void who_am_i(){ //定义同名同参数函数
cout<<"我是Student"<<endl;
}
};
int main(int argc, const char * argv[]) {
People p;
Student s;
p.who_am_i(); //输出"我是People"
s.who_am_i(); //输出"我是Student”
//s.People::who_am_i(); //这样可以调用父类的函数(输出"我是People")
}
多态-虚函数
我们先来看一个例子:
class People{
public:
void who_am_i(){
cout<<"我是People"<<endl;
}
};
class Student:public People{
public:
void who_am_i(){
cout<<"我是Student"<<endl;
}
};
int main(int argc, const char * argv[]) {
People *p1=new People;
People *p2=new Student;
p1->who_am_i();
p2->who_am_i();
}
结果:都会输出"我是People"
因为在编译过程中,People的函数都被静态编译到了People上。
导致即使是Student,也会运行父类People的who_am_i()。
使用虚函数可以实现动态编译。
- 虚函数是在基类中使用关键字 virtual 声明的函数。
class People{
public:
virtual void who_am_i(){
cout<<"我是People"<<endl;
}
};
class Student:public People{
public:
void who_am_i(){
cout<<"我是Student"<<endl;
}
};
int main(int argc, const char * argv[]) {
People *p1=new People;
People *p2=new Student;
p1->who_am_i(); //输出“我是People”
p2->who_am_i(); //输出“我是Student”
}
解释:
这个例子和上个例子唯一的改变是:父类People的成员函数前多了virtual关键字。
结果完全不一样,会输出“我是People”和“我是Student”。
原因就是动态编译。即如果子类重写了父类函数,就动态编译到子类的函数中,所以会输出“我是Student”。
多态-纯虚函数
纯虚函数是一种特殊的虚函数,它只有函数头,没有函数体。
class People{
public:
virtual void who_am_i()=0; //纯虚函数
};
class Student:public People{
public:
void who_am_i(){
cout<<“我是Student”<<endl; //重写纯虚函数
}
};
int main(int argc, const char * argv[]) {
People *p2=new Student;
p2->who_am_i();
}
纯虚函数的作用:
可以为子类重写此函数提供一种规范。(接口)
比如规定了函数名,函数参数名,函数参数类型,函数参数个数。
纯虚函数的特点:
因为纯虚函数没有函数体,所以拥有纯虚函数的类不能实例化为对象。
拥有纯虚函数的类称之为抽象类。
虚基类和虚继承
回顾一下菱形继承:
#include <iostream>
using namespace std;
class A{
public:
void print(){
cout<<"hello"<<endl;
}
};
class B1:public A{
};
class B2:public A{
};
class C:public B1,public B2{
};
int main(int argc, const char * argv[]) {
C c;
c.print(); //无法编译通过,因为存在二义性。
}
菱形继承会出现二义性。我们可以用虚继承来解决这个问题。
比如:在上一个例子中,在C中只将A只保留一份,这样就不会出现二义性了。
这个共享的类叫做虚基类。
B1和B2继承A叫做虚继承。
class A{
public:
void print(){
cout<<"hello"<<endl;
}
};
class B1:virtual public A{ //虚继承
};
class B2:virtual public A{ //虚继承
};
class C:public B1,public B2{
};
int main(int argc, const char * argv[]) {
C c;
c.print();
}