C++ 多态使用详解

目录

一、多态的概念

二、多态的实现

1、多态的具体含义

2、如何才能构成多态?

1、什么是虚函数?

2、什么是重写?

3、具体代码

4、多态场景的一个选择题

三、多态的其他问题

1、协变

2、析构函数的重写

3、override和final关键字

1、override:

2、final

4、重载、覆盖和隐藏的辨析

四、纯虚函数和抽象类

 

一、多态的概念

多态分为编译时多态(静态多态)和运行时多态(动态多态)

静态多态就是函数模板一类的,通过传不同的参数,函数做出不同的结果;这种都是在编译时完成的;

动态多态就是现在要学习的多态,这种多态具体就是传不同的对象给函数,做出不同的行为;这是在运行时的多态行为;

二、多态的实现

1、多态的具体含义

比如人类和学生类分别定义了两个对象,这两个对象调用一个相同函数产生不同的效果;

2、如何才能构成多态?

首先,多态是基于继承关系存在这一前提形成的,也就是说是在基类和父类之间的行为;

其次,多态调用函数必须是通过基类的指针或者引用去调用的(基类和派生类之间的转换),并且还不能是基类类型,必须是指针或者引用;接着,多态调用的函数必须是虚函数,并且派生类对基类中的虚函数进行了重写(覆盖),才能达到不同对象产生不同行为的效果;

1、什么是虚函数?

虚函数就是函数前面加上virtual,但是这个函数必须是成员函数。

2、什么是重写?

基类中有一个虚函数,它的派生类中有一个相同名称,相同参数列表,相同返回值的函数。那么就称对虚函数进行了重写,派生类中就是重写的。派生类中对应函数可以不加virtual

3、具体代码

class Person
{
public:
	virtual void Func()//虚函数
	{
		cout << "全价购票" << endl;
	}
};
class Student :public Person
{
public:
	virtual void Func()//重写(或者叫覆盖)
	{
		cout << "半价购票" << endl;
	}
};

void func(Person& ptr)//必须是父类对象的指针或者引用
{
	ptr.Func();
}
void test1()
{
	Person p;
	Student s;
	func(p);
	func(s);
}

4、多态场景的一个选择题

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};
class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

运行结果:

解释:首先B是A的一个派生类,并且有个一个合法的虚函数及其重写函数(func),p是B对象的指针,调用test,这里要注意的第一个点:继承不是将父类中的行为或者属性拷贝到派生类,拷贝只是形象的说法,这里的B对象调用的test()函数第一个参数任然是指向A类对象的指针,所以 就传参这一点来说,这里是符合多态的(必须是父类的指针或者引用去调用虚函数);接着在test里面调用func,构成多态,那么调用的就是B中的func,理论上说应该输出B->0,但不是,这里要注意的第二个点:重写父类中的虚函数只是对函数的内容进行重写,其他的其实都没有改变,也就是说B中的func的缺省值任然为1;所以这里输出是1.


三、多态的其他问题

1、协变

协变就在多态中,父类虚函数和子类对应的重写的虚函数的返回值可以不同,基类的虚函数的返回类型是基类的指针或者引用,派生类的返回类型是派生类的指针或者引用;这里的实际意义不大。

###代码演示

class A
{};
class B:public A
{};
class Person
{
public:
	virtual A* Func()
	{
		cout << "全价购票" << endl;
		return nullptr;
	}
};
class Student :public Person
{
public:
	virtual B* Func()
	{
		cout << "半价购票" << endl;
		return nullptr;
	}
};
void func(Person& ptr)
{
	ptr.Func();
}
void test1()
{
	Person p;
	Student s;
	func(p);
	func(s);
}

当然返回的指针类型可以是自己类类型的指针或者引用,但是虚函数及其重写的函数的返回类型必须是继承的父子类。

 

2、析构函数的重写

只要是基类的析构函数前面加上virtual,那么派生类中的析构函数就一定是基类中虚构函数的重写,为什么呢?

首先,在编译之后,类的析构函数的名称统一被置为destructor,那么基类和派生类中的析构函数名称就相同了;其次派生类中的重写可以不加virtual。

为什么会设置成这样?(面试可能会问)假设有一个多态关系并且没有这样设计,A是基类,B是派生类,它们分别实例化了一个对象,但是都是用基类A的指针去接受的,在析构时,A实例化的对象没问题,调用A的析构函数,但是B实例化的对象此时通过A指针调用的还是A的析构函数,那么要是B中有申请资源的并且不是A中继承过来的属性,此时会造成内存泄漏,这样就不行;所以这样设计,保证即使是通过基类的指针也能完全析构派生对象类中该析构的内容;

###代码示例:

class A
{
public:
	virtual ~A()
	{
		cout << "~A" << endl;
	}
};

class B :public A
{
public:
	~B()
	{
		cout << "~B" << endl;
		delete _p;
	}
protected:
	int* _p = new int[10];
};
void test2()
{
	A* p1 = new A;
	A* p2 = new B;//多态的调用必须是基类指针或者引用,这里的例子恰好满足

	delete p1;
	delete p2;
}

析构构成多态,就算是通过A类指针也能精准析构B对象;这里打印的后面含有一个A的析构,是因为在继承中得知,派生类对象先析构自己再调用基类自己的析构函数析构基类部分。

 

3、override和final关键字

1、override:

在实现多态时,我们知道动态多态是在运行时确定的,所以有些错误只有在运行时才能看到,所以为了在编译阶段看到多态哪里出错了就在派生类的重写函数参数列表后面加上 override,这样就能看到是哪里出错了;

###代码示例:

class Car
{
public:
	virtual void Drive()
	{}
};

class Benz : public Car
{
public:
	void Dirve()
	{
		cout << "Benz-舒适" << endl;
	}
};

void test1()
{
	Car c1;
	Benz b1;
	c1.Drive();
	b1.Dirve();
}

这里派生类和基类中的成员函数名不一样,我们是想要在派生类中重写Drive的,但是这里函数名不同也不报错;为了在编译时就看到重写是哪里错了就这样写 保证正确形成重写

class Car
{
public:
	virtual void Drive()
	{}
};

class Benz : public Car
{
public:
	void Drive()override
	{
		cout << "Benz-舒适" << endl;
	}
};

void test1()
{
	Car c1;
	Benz b1;
	c1.Drive();
	b1.Drive();
}

2、final

final在继承中的类名后面加上表示这个类是最终类,不能被继承;

在这里,在父类成员函数参数列表后面加上final表示这个成员函数不能被重写

 

4、重载、覆盖和隐藏的辨析


四、纯虚函数和抽象类

纯虚函数:在虚函数函数名后面加上 =0 之后这个虚函数就是纯虚函数;纯虚函数必须先得是成员函数,

抽象类:拥有纯虚函数的类;抽象类不能实例化出对象;

抽象类的派生类也是抽象类,也不能实例化出对象;除非这个派生类对基类中的纯虚函数重写,使之在派生类中不再是纯虚函数,那么这个派生类就不再是抽象类了,可以实例化出对象;

可以说,纯虚函数和抽象类强制了派生类进行重写。

###代码示例:

class Family_Guy
{
public:
	virtual void Speak()=0
	{}
};

class Peter :public Family_Guy
{
public:
	void Speak()
	{
		cout << "Alright" << endl;
	}
};
class Quagmire :public Family_Guy
{
public:
	void Speak()
	{
		cout << "Giggity" << endl;
	}
};
void test2()
{
	//抽象类不能实例化出对象
	//Family_Guy mum;
	//抽象类的派生类没有重写纯虚函数也不能实例化出对象
	//Peter p1;

	Peter p1;
	Quagmire q1;
	p1.Speak();
	q1.Speak();
}

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值