C++基础学习第十一课(多继承和多态)

第十一课

1,多继承定义

派生类只有一个基类,称为单继承.

C++支持多继承,即一个派生类可以有两个或多个基类;

2,多继承声明
class 派生类名:访问控制 基类名1,访问控制 基类名2...
{
    数据成员和成员函数声明;
};
注意点:
1,类C可以根据访问控制同时继承类A和类B的成员,并添加自己的成员;
2,如果类A和类B有同名的成员,需要使用域运算符::显示调用;
3,多继承的二义性

1,如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性;

2,为了解决多继承时的命名冲突和冗余数据问题,C++提出了虚继承,使得在派生类中只保留一份间接基类的成员,使用关键字Virtual;

案例:
class A
{
    protected:
    int a;
};
class B:virtual public A
{
    protected:
    int b;
};
4,多态

1,发现问题

当子类定义了与父类中原型相同的函数,子类会覆盖父类同名成员函数(子类重定义父类同名成员);

案例:
class A
{
    public:
    void Voice()
    {
        cout<<"A V"<<endl;
    }
};
class B:public A
{
    public:
    void Voice()
    {
        cout<<"B V"<<endl;
    }
};
void MV(A* v)
{
    v->Voice();
}
int main()
{
    A *v=new A;
    MV(v);//调用父类的Voice;
    v=new B;
    MV(v);//还是调用的父类Voice;
    return 0;
}
得到问题:
父类指针无论是指向自己,还是指向子类,都是调用父类的成员函数;
我们所希望的:
1,根据父类指针指向的对象的实际类型,调用不同类的成员函数;
2,父类指针指向父类,调用父类成员函数;
3,父类指针指向子类,调用子类成员函数;
5,解决问题-多态的实现:

在父类重写的函数的前面加上virtual关键字(子类可写可不写,默认会自己写);

案例:
class A
{
public:
	virtual void Voice()//声明为虚函数
	{
		cout << "A V" << endl;
	}
};
class B :public A
{
public:
	void Voice()
	{
		cout << "B V" << endl;
	}
};
void MV(A* v)
{
	v->Voice();
}
int main()
{
	A* v = new A;
	MV(v);//调用父类的Voice;
	v = new B;
	MV(v);//变成调用的子类Voice;
	return 0;
}
6,多态成立的三要素:

1,要有继承;

2,要有虚函数重写;

3,要有父类指针(或引用),指向子类对象;


#### 7,案例分析多态的好处

```C++
class Shape
{
public:
	Shape(int x,int y,int w,int h):_x(x),_y(y),_w(w),_h(h){}
	virtual int GetArea()
	{
		cout << "我他喵的是形状,哪来的面积?" << endl;
		return 0;
	}
	~Shape(){}
protected:
	int _x;
	int _y;
	int _w;
	int _h;
};
class Rect :public Shape
{
public:
	//子类使用父类的构造函数 关键字:using A::A
	//如果子类的构造函数和父类差不多,那么就可以使用这个语句,把父类的构造函数继承过来
	using Shape::Shape;
	int GetArea()
	{
		cout << "RECT:" << endl;
		return (_w * _h);
	}
};
class Circle:public Shape
{
public:
	using Shape::Shape;
	int GetArea()
	{
		cout << "CIRCLE:" << endl;
		return (3.14 * _r * _r);
	}
private:
	int _r=8;

};
void show(Shape& s)
{
	cout <<"Area:"<< s.GetArea() << endl;
}
void show(Shape* s)
{
	cout << "Area:" << s->GetArea() << endl;
}
int main()
{
	/*Rect r(5, 4,7,8);
	cout<<r.GetArea();*/
	Shape* base = nullptr;
	base = new Rect(2, 3, 4, 5);
	show(base);
	delete base;
	base = new Circle(2, 3, 4, 5);
	show(base);
	delete base;
    //使用多态后,可以十分方便地添加功能而不影响其他部分
	return 0;
}
10,虚析构函数

注意事项:

1,构造函数不能是虚函数,建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数;

2,析构函数必须是虚函数,在有继承关系和父类指针指向子类对象时,如果析构函数不是虚函数,则对象析构时,可能存在内存泄漏的问题;

类型一:

本类指针指向本类对象:

`不管析构函数是否是虚函数(即是否加virtual关键字),delete的基类和子类都会被释放;

类型二:

父类指针指向子类对象:

`若析构函数是虚函数(加上virtual),delete时基类和子类都会被释放;

`若析构函数不是虚函数(没加virtual),delete时只释放基类,不释放子类;

11,final和override

1,final可以用来修饰父类的虚函数,表示在子类中禁止对该虚函数进行重写;

2,override可以用来修饰子类的虚函数,表示是虚函数重写;

3,用在类名后面,表示无法继承该类;

12,动态联编和静态联编

联编是指一个程序模块,代码之间互相关联的过程;

静态联编:是程序的匹配,连接在编译阶段实现,也称为早期联编;

`重载函数的使用就是静态联编;

动态联编:是程序的匹配,连接在运行阶段实现,也称为迟邦定.(将函数体和函数调用关联起来,就叫绑定);

`分支语句就是动态联编,多态也是多态联编;

13,多态中的动态联编的实现

若我们声明了类中的成员函数为虚函数,那么C++编译器就会为类生成一个虚函数表,通过这个表即可实现动态联编;

14,虚函数表(虚函数指针数组)

`虚函数表是顺序存放在虚函数地址的,虚函数表是顺序表(数组),依次存放着类里面的虚函数;

`虚函数表是由编译器自动生成与维护的,相同类的不同对象的虚函数表是一样的;

`当我们在类中定义了virtual函数,C++编译器就会给对象添加一个vptr指针,这个指针存的就是虚函数表首地址;

15,vptr指针

1,使用sizeof可以得到该指针存在的事实;并且可以发现它指向的数组里存着所有虚函数的地址;

操作分析:(设置对象为p)

1,vptr指针是类的第一个成员,所以需要对对象取地址,即&p,得到vptr指针的首地址;

2,指针是四个字节,所以把&p强转为int*,可把对象分为每个元素都是四个字节的数组,然后获得第一个元素,即vptr指针( *(int *)&p)或者 *(int *)&p)[0];

3,因为vptr指针存储的是虚函数表的首地址,所以把vptr指针转成int*指针,然后就可以访问每个成员了(这里的成员都是函数指针): *((int )( * (int)&p)+i);

4,最后把得到的结果转为函数指针即可调用;

案例:
class Base//空类大小为1字节,若有对象则替换掉
{
public:
	virtual void imm()
	{
		cout << "fjjdf" << endl;
	}
	virtual void oiuyiy()
	{
		cout << "turti" << endl;
	}

};
int main()
{
	cout << sizeof(Base) << endl;
	Base f;
	&f;//对象的地址
	((int*)&f);//强转,把对象的内存空间,看成一个int类型的数组;
	*((int*)&f + 0);//相当于((int*)&f)[0];//拿到了第一个指针,即vptr
	typedef void (*FunType)();//函数指针类型
	*((int*)&f);//把vptr里面的地址取出来,这个是虚函数表的地址
	FunType fun = (FunType) * ((int*)*((int*)&f)+0);//获取第一个虚函数
	FunType fun2 = (FunType) * ((int*)*((int*)&f) + 1);//获取第二个虚函数
	fun();//输出fjjdf
	fun2();//输出turti
	return 0;
}
16.纯虚函数和抽象类

1,纯虚函数:是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本(如果不定义,那么派生类也是抽象类);

2,纯虚函数为各派生类提供一个公共界面(接口的封装和设计,软件的模块功能划分);

定义:
    virtual 类型 函数名(参数列表) =0;//纯虚函数

1,抽象类:一个具有纯虚函数的基类称为抽象类;

`特点:

不能被实例化(定义对象);

//若需定义对象:需重写其他子类的成员函数;

可以定义指针或引用;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值