谈谈C++多态的基本使用和总结

谈谈C++多态的基本使用和总结,第1张

前言

文章目录
  • 前言
  • 1. 多态的理解
  • 2. 构成多态的条件
  • 3. 虚函数
  • 4. 虚函数的重写(覆盖)
  • 5.多态调用虚函数的规则
  • 6. 析构函数的重写(基类与派生类析构函数的名字不同)
  • 7. 设计一个不能被继承的基类
  • 8. final关键字和override 关键字
  • 9. 抽象类
  • 10. 接口继承和实现继承区别

1. 多态的理解

简单的说:多态就是根据你传入的不同的对象,去做同一样的事情,会有不同的结果和行为的表现产生;

在C++中,传入不同的对象意思:给父类传入不同的子类;

去做同一样的事意思是:父类指针或者引用调用了同一样的虚函数;

产生不同的结果意思是:调用同样的虚函数,但是结果却不一样;


2. 构成多态的条件

继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数;
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写;


3. 虚函数

虚函数:即被virtual修饰的类非静态成员函数称为虚函数。


(静态成员不可以作为虚函数,全局函数也不行)
虚函数的重写;

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
4. 虚函数的重写(覆盖)

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。


class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后
基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*/
/*void BuyTicket() { cout << "买票-半价" << endl; }*/ 
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }

5.多态调用虚函数的规则

构成多态,跟父类的指针或者引用的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 – 跟对象有关;
不构成多态,调用就是父类指针或者引用类型的函数 – 跟类型有关;


#include
using namespace std;
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void test1(Person& p)
{
	p.BuyTicket();
}
void test2(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	//父类引用调用虚函数,子类重写虚函数,构成多态;那么调用函数就传入的对象有关
	//这里传入的对象ps的类型为 Person 所以调用Person的虚函数;
	Person& p1 = ps;
	p1.BuyTicket();
	//父类引用调用虚函数,且子类重写父类虚函数,构成多态,那么调用函数就和传入的对象有关;
	//这里传入的对象是 Student,所以调用的是Student的虚函数;
	Person& p2 = st;
	p2.BuyTicket();

	//指针的分析和引用一致
	Person* p3 = &ps; //构成多态
	p3->BuyTicket();

	Person* p4 = &st; //构成多态
	p4->BuyTicket();

	getchar();
	return 0;
}

符合预期:


测试:假如不是父类指针或者引用调用虚函数会发生什么;

去除条件1,不是父类指针或引用而是父类对象去调用虚函数,是否会发生多态?

  1. 假如 子类对象赋值给父类对象会不会发生多态?肯定不会?所以父类对象调用虚函数,只看父类的类型;
	//子类对象赋值给父类对象,没有父类指针或引用指向子类对象,这不会构成多态,所以调用虚函数
	//只看p5的类型;
	Person p5 = st;
	p5.BuyTicket(); //调用的是父类的虚函数,不构成多态
  1. 即使有父类的指针或引用调用了虚函数,假如子类没有重写虚函数,依旧不会发生多态,没有多态那么,父类指针或引用去调用虚函数时候,都是看父类的类型的;
    (ps:我们可以修改父类和子类虚函数,只要不构成重写,就可以验证我上面的结论)

6. 析构函数的重写(基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。


虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。



class Person {
public:
virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成
//多态,才能保证p1和p2指向的对象正确的调用析构函数。


int main() { Person* p1 = new Person; Person* p2 = new Student; delete p1; // 编译器默认转化了调用虚构函数的形式 p1->destructor() delete p2; // 编译器默认转化了调用虚构函数的形式 p2->destructor(), //由于构成多态,所以会正确的嗲用传入对象是子类的虚析构函数 return 0; }

构成多态,正确的调用了子类的析构函数;
这也是唯一的场景:是析构函数,需要写成虚析构的形式;


没有发生多态时候,不管你的父类还是子类是否为虚析构,都可以正确的调用析构函数


非虚析构函数,依旧可以正确释放


上面两种原因很简单:继承体系中,子类对象指向析构时候,本身就会现执行子类再执行父类,和你的析构函数是否为虚函数没关系;


所以说析构函数是否需要写成虚析构函数,首先得发生多态行为,才需要父类析构写成虚析构,这也才可以保证delete 父类指针时候,能够去调用子类的析构函数;


本质我们 delete 父类指针,就是为了析构子类对象的,因为父类指针指向了子类对象,我们希望的就是做这样的事;delete父类指针,为了析构子类对象,那么就需要发生多态,这个行为才会产生;有多态,就必须,子类重写父类的虚析构函数;


7. 设计一个不能被继承的基类

在C++98时候,有一种方式可以设计不被继承:那就是设计基类的构造函数为私有的;

#include
using namespace std;


class Base{
private:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
private:
	int _b;
};
class Derive :Base{
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}

这种方式:当我屏蔽了:Derive son这句代码,也就是,不创建子类对象时候,上面的代码编译是可以通过的;

但是当我写上了Derive son也就是创建子类对象时候,那么就会编译报错:原因就是基类的构造函数为私有,派生类创建对象时候,也是执行Derive son代码时候,需要先调用父类的构造函数,但是由于父类的构造函数时私有的,不可以被调用,所以就会编译报错了;

设计基类的构造函数是私有的:这也就解决了一个类不可以被继承的情况;


8. final关键字和override 关键字

C++11通过新增一个关键字final:解决了基类不可以被继承问题, 也就是不需要设计基类的构造函数为私有的;
具体 *** 作就是:在声明基类的后面加多一个关键字 final就可以达到效果;

class Base final{//final关键字修饰,使得该类不可以被继承
public:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
private:
	int _b;
};
class Derive :public Base{
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}


final关键还可以修饰基类的虚函数,目的就是为了使得基类的虚函数不被子类重写;

class Base {
public:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
	virtual void fun() final{ //使得该虚函数不能够被重写
		cout << "Base::fun()被调用" << endl;
	}
private:
	int _b;
};

class Derive :public Base{
	public
	virtual void fun(){ //由于基类这个函数fun被final修饰,所以派生类不可以重写
		cout << "Derive::fun()被调用" << endl;
	}
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}


c++11还有一个关键字override,它是修饰派生类的虚函数,目的为的是:派生类必须重写基类虚函数,如果基类没有该虚函数,那么就会报错,或者派生类重写错乱基类虚函数,那么就会报错;

反正就是在基类中找不到你要重写的虚函数,那么就会直接报错;

所以说:override 有检查机制,帮你检查你的派生类是否正确的重写了基类的虚函数;


class Base {
public:
	Base(){
		cout << "Base::Base()的构造函数调用" << endl;
	}
	virtual void fun() { 
		cout << "Base::fun()被调用" << endl;
	}
private:
	int _b;
};

class Derive :public Base{
public:
	virtual void fun1() override{ 
		cout << "Derive::fun()被调用" << endl;
	}
private:
	int _d;
};
int main(){

	Derive son;

	return 0;
}


9. 抽象类

3.1 概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。



包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。



派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。



纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。



由于抽象类不能实例化对象,所以虚函数实现也是没有任何意义的,所以不会有人实现纯虚函数;
纯虚函数是可以实现的,但是就是没有意义罢了;


class Car
{
public:
	virtual void Drive() = 0; //纯虚函数
};
class Benz :public Car
{
public:
		virtual void Drive()
	{
	cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
		virtual void Drive()
	{
		cout << "BMW- *** 控" << endl;
	}
};
void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
}

有纯虚函数,只能用父类指针或引用指向子类对象了,不可能再指向父类对象,因为父类对象是不可以被创建的;

10. 接口继承和实现继承区别

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。



虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。


所以如果不实现多态,不要把函数定义成虚函数。



欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/607823.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-14
下一篇 2022-04-14

发表评论

登录后才能评论

评论列表(0条)

保存