c++多态1---基础知识和概念

1.多态概念理解

多态的表面意思可以理解为多种形态,完成某个行为时,不同的对象会用不同的方法去完成从而产生出不同的状态。
多态就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法,比如:模板技术,RTTI技术,虚 函数技术,要么是试图做到在编译时决议(重载),要么试图做到运行时(多态)决议。

比如下面这个例子,Person、Bird、Dog都属于Animal类中的一部分。每个动物Eat()、Foot()、Sleep()的操作都有些不同。其中void Active(Animal *pa)可以调用这三个函数。传入的对象不同,每个动物Eat()、Foot()、Sleep()的操作也会不同。

代码里将传入不同的对象到Active(Animal *pa)调用不同类的Eat()、Foot()、Sleep()的操作实现多态。
这里Animal 类的方法都加了关键字virtual 将该函数边为虚函数,否则最终每个类调动的都将是Animal 类的方法。
将子类对象传入到Active(Animal *pa)中赋值给基类对象的指针pa,由于基类中的虚函数被派生类中的虚函数的重写,通过pa调动Eat()、Foot()、Sleep()则调动子类的该方法。

class Animal
{
public:
	virtual void Eat(){cout<<"Animal::Eat()"<<endl;}
	virtual void Foot(){cout<<"Animal::Foot()"<<endl;}
	virtual void Sleep(){cout<<"Animal::Sleep()"<<endl;}
};

class Person : public Animal
{
public:
	virtual void Eat(){cout<<"Person::Eat()"<<endl;}
	virtual void Foot(){cout<<"Person::Foot()"<<endl;}
	virtual void Sleep(){cout<<"Person::Sleep()"<<endl;}
};

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

class Dog : public Animal
{
public:
	void Eat(){cout<<"Dog::Eat()"<<endl;}
	void Foot(){cout<<"Dog::Foot()"<<endl;}
	void Sleep(){cout<<"Dog::Sleep()"<<endl;}
};
//多态  完成不同的功能
void Active(Animal *pa)
{
	pa->Eat();
	pa->Foot();
	pa->Sleep();
}

void main()
{
	Person p;
	Bird b;
	Dog d;
	Active(&p);
	Active(&b);
	Active(&d);
}

在这里插入图片描述

2.多态构成条件

在继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数。(理解:用子类对象、指针或者引用给父类赋值构造,父类可以调用虚函数重写的子类方法,实现多态)
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

3.虚函数&虚函数重写

虚函数:即被virtual修饰的类成员函数称为虚函数。C++ 中的虚函数的作用主要是实现了多态的机制。
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
虚函数的重写的两个例外

  1. 协变(基类与派生类虚函数返回值类型不同)
    派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

eg:

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

int main()
	{
		Person ps;
		Student st;
		Func(ps);
		Func(st);
		return 0;
	}
  1. 析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理。
    在多态中常见的一个操作是把父类的析构函数设置为虚函数,这样在释放(delete)父类对象时则通过虚函数机制调动子类的析构函数,释放掉子类对象额外申请的空间,防止内存泄漏。(设为虚函数之后,编译器会做特殊处理,会在子类的析构函数中插入父类的析构函数,最终实现了先调用子类析构函数再调用父类析构函数析构整个对象)----参考文章:为什么父类析构函数必须为虚函数

4.关键字:override 和 final (C++11)

C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
1. final:修饰虚函数,终止了该函数在子类中的重写,即该虚函数不能再被继承。

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

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

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

5 重载、覆盖(重写)、隐藏(重定义)概念和对比

重载:在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

重写(覆盖):派生类中与基类具有 同返回值类型、同名和同参数的虚函数,构成虚函数覆盖,也叫虚函数重写。需要注意这里有两个特殊情况,即协变返回类型和析构函数的重写。

隐藏(重定义):派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问),成员函数名相同则构成隐藏,和参数与返回值无关

在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值