【C++干货铺】继承后的多态 | 抽象类

=========================================================================

个人主页点击直达:小白不是程序媛

C++系列专栏:C++干货铺

代码仓库:Gitee

=========================================================================

目录

多态的概念

多态的定义和实现

多态的定义条件

虚函数

虚函数的重写

特殊情况

协变(基类和派生类的虚函数返回值不同)

析构函数的重写

C++11 override 和 final关键字

重载、重写(覆盖)、重定义(隐藏)的对比

抽象类


多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 

举个栗子:很多朋友在外地上大学,回家或者去学校可能都会买火车票或者其他交通工具的票。但是作为一个大学生我们是有学生认证的大学生身份,买票有打折优惠;但是对于其他不是学生的公民来说就是普通人的身份是享受不到这个优惠的,必须全价买票;又或者对于军人来说,他们有军人的身份,军人优先买票。

因此对于买票这个行为,当普通人买票时,是全价买票学生买票时,是半价买票军人
买票
时,是优先买票


多态的定义和实现

多态的定义条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了
Person。Person对象买票全价,Student对象买票半价。

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

  • 必须通过基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 

虚函数

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

class Person {
public:
    virtual void Buy() { cout << "买票-全价" << endl;}
};

虚函数的重写

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

注意:重写只是对函数体的内容重写而不是对整个函数重写,使用的还是基类的接口,只不过是函数体的内容变了。

class Person
{
public :
	virtual void Buy()
	{
		cout << "普通人——全价买票" << endl;
	}
};
class Student :public Person
{
public:
	virtual void Buy()
	{
		cout << "学生——半价买票" << endl;
	}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

特殊情况

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。

class Person
{
public :
	virtual void Buy()
	{
		cout << "普通人——全价买票" << endl;
	}
};
class Student :public Person
{
public:
	virtual void Buy()
	{
		cout << "学生——半价票" << endl;
	}
	//派生类的虚函数前面可以不加virtual 但是这样并不规范,不建议这样使用
	//void Buy()
	//{
	//	cout << "学生——半价票" << endl;
	//}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

虚函数的两个例外:

协变(基类和派生类的虚函数返回值不同)

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

class Person
{
public:
	virtual Person* Buy()
	{
		cout << "普通人——全价买票" << endl;
		return nullptr;
	}
};
class Student :public Person
{
public:
	virtual Student* Buy()
	{
		cout << "学生——半价票" << endl;
		return nullptr;
	}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

返回值可以为其他类的指针

class A
{
public:
};
class B : public A
{
public:
};
class Person
{
public:
	virtual A* Buy()
	{
		cout << "普通人——全价买票" << endl;
		return nullptr;
	}
};
class Student :public Person
{
public:
	virtual B* Buy()
	{
		cout << "学生——半价票" << endl;
		return nullptr;
	}
};
void Func(Person& p)
{
	p.Buy();
}
int main()
{
	Person p1;
	Student s1;
	Func(p1);
	Func(s1);
	return 0;
}

析构函数的重写

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor。

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

C++11 override 和 final关键字

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来debug会得不偿失,因此:C++11提供了overridefinal两个关键字,可以帮
助用户检测是否重写

final:修饰虚函数,表示该虚函数不能再被重写

class Person
{
public :
	virtual void Func()final
	{
		cout << "被final修饰" << endl;
	}
};
class Student : public Person
{
public:
	virtual void Func()
	{

	}
};

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

//override
// 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Person
{
public:
	virtual void Func() {};
};
class Student : public Person
{
public:
	void Func ()override {};
};

重载、重写(覆盖)、重定义(隐藏)的对比


抽象类

抽象类的概念

虚函数的后面写上 =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;
	}
};
int main()
{
	Benz b;
	b.Drive();
	BMW m;
	m.Drive();
	return 0;
}


今天对继承之后的多态的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法。您三连的支持就是我前进的动力,感谢大家的支持!! !

猜你喜欢

转载自blog.csdn.net/qq_55119554/article/details/134984224