继承和多态
继承
概念
继承:是一种复用的手段,子类里拥有所有的父类里的成员,
三种继承关系的父类成员在子类下的访问关系的变化
继承关系 |
基类的public成员 |
基类的protected成员 |
基类的private成员 |
public继承 |
仍为public成员 |
为protected成员 |
不可见 |
protected继承 |
变为protected成员 |
为protected成员 |
不可见 |
private继承 |
变为private成员 |
变为private成员 |
不可见 |
- 父类的private成员是不可以被子类访问的,要想访问就可以在父类中将它定义为protected成员,在类外不可访问但在子类中可以访问
- 不管是哪种继承方式,public/protected成员都可以访问,private成员都不可访问
- 使用关键字class默认为private继承,struct为public继承
举个栗子
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
int main()
{
printf("%d", sizeof(B));//8
system("pause");
return 0;
}
继承的转换规则
- 子类可赋值给父类(切片)
- 父类不可直接赋值给子类
- 父类可赋值给子类的指针和引用(通过强转)
例如:A是B的父类
A *p1 = new B;
一定要注意的是,在定义子类的默认成员变量或函数的时候,最好不要定义和父类重名的成员,这样会在造成隐藏,就是你在访问元素的时候你不知道访问的到底是子类的成员还是父类的成员,而且一般如果在子类中访问这个同名成员的时候,会默认访问子类成员对父类成员造成屏蔽,那么怎么去访问父类成员呢?其实很简单,直接在成员前加访问限定符即可!!
子类的六个默认函数
- 总的一句话,对父类成员操作的时候,就要显示调用父类的成员函数,子类成员可直接操作
但其实析构函数是个例外,所有析构函数在底层的名字是一样的,所以在调用析构函数在释放空间的时候,不需要显式调用
到这我们可以看一个问题,就是怎么实现一个不可以被继承的类
将父类的构造函数设为私有,子类继承之后不能对父类成员进行初始化就不能用父类成员,就等于父类不能继承
单继承,多继承和菱形继承
单继承:一个子类只有一个直接的父类
多继承:一个子类有多个直接父类
多继承会引发菱形继承
菱形继承的缺点:数据冗余(因为最后一个子类里的),访问的二义性
解决方法:二义性:指定定义域
数据冗余:虚继承(vitual)在子类继承关系的前面加
我们可以用代码来解释解释
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
//B
d.B::_a = 2;
d._b = 3;
//C
d.C::_a = 4;
d._c = 3;
//D
d._d = 4;
printf("%d", sizeof(d));//20
system("pause");
此时不是虚继承,菱形继承的模型如下,还有他的内存结构图如下
内存结构
菱形虚继承的内存模型和内存结构图
class A
{
public:
int _a;
};
class B :virtual public A
{
public:
int _b;
};
class C :virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
//B
d.B::_a = 2;
d._b = 3;
//C
d.C::_a = 4;
d._c = 3;
//D
d._d = 4;
printf("%d", sizeof(d));
system("pause");
return 0;
}
内存结构
多态
多态:当用父类的指针或引用来调用重写的虚函数时,指向父类调用的虚函数就调用的是父类的,指向子类就调用的是子类的虚函数
虚函数:在函数名前加virtual,则这个成员函数称为虚函数
虚函数的重写:当子类定义了一个和父类一模一样的虚函数之后,则称这个子类的函数重写(覆盖)了这个父类的虚函数
我们用下面这个例子来描述虚函数
class Person
{
public:
virtual void BuyTicket()
{
printf("买票!\n");
}
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
printf("学生票!\n");
}
};
void Fun(Person &p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
Fun(p);
Fun(s);
system("pause");
return 0;
}
简单来说就是那个对象调这个重写的虚函数,就调的是那个对象的函数
我们在重写的时候要注意一下几点
- 子类重写虚函数的时候,要求和父类的函数名,返回值和参数都相同(协变除外)
- 只有类的成员函数才能定义为虚函数
- 静态函数不能定义为虚函数
- 构造函数不能为虚函数
- 最好不要在构造函数和析构函数中使用虚函数
- 最好将析构函数定义为虚函数,就可以显示调用了(自购安徽省怒不叫特殊这和构造函数底部的实现有关,不管是那个类的构造函数,在底层总是一样的名字)
- 纯虚函数就是在虚函数后面加=0,包含纯虚函数的类不能实例化出对象,纯虚函数只有在子类中重新定义了之后,才能实例化出对象
- 友元关系不能继承,即父类的友元不能访问子类的私有和保护成员
- 在父类中定义了static成员,那么不管派生出多少个子类,都只有一个static成员
接下来我们来说一下,重载,重写,重定义的区别和联系