今天小编和大家一起学习C++中多态的全部主要内容,希望今天大家和小编一起学习之后,会对多态有一个初步的了解和使用,好啦,话不多说,开始学习!~~~
一、多态的概念及满足条件
概念:指的就是不同的对象对于同一种情况会表现出不同的状态,以编程的角度来看,就是不同类型的对象调用相同的函数会产生不同的结果(输出不同)。举个例子,不同身份的人买票的价格不同,学生买票 ->学生票, 普通人 ->原价票, 军人买票 -> 优先买票。
那我们该如何用编程的角度实现上面的买票部分的代码呢,这就需要知到多态满足的条件之后来实现。
满足条件:1、被调用的函数必须是虚函数,并且在派生类里面必须对父类的虚函数进行重写。
2、使用过程中必须通过父类(基类)指针或者引用来调用虚函数。
在这里我们先说下虚函数的是什么:虚函数是被 virtual 所修饰的类成员函数,如下代码:
class Person
{
public:
virtual void BuyTickts()
{
cout << "原价购买" << endl;
}
private:
};
接下来我们说下对父类的虚函数进行重写:派生类中有一个和父类中完全相同的虚函数(即派生类的函数名,函数参数,函数返回值都相同),这就称为对父类的虚函数进行重写。
重点:虚函数的调用过程:1、父类的虚函数的函数名,函数参数,返回值部分
2、子类虚函数的函数内容
以上就是多态的满足条件,为此再结合一幅图加深来理解:
接下来我们就实现一下买票的多态情况,代码如下:
class Person
{
public:
// 虚函数满足三个条件 (virtual)
// 函数返回值,函数名字,函数参数都要相同
virtual void BuyTickts()
{
cout << "原价购买" << endl;
}
private:
};
// 重点:虚函数的调用是 调用父类虚函数的返回值和函数名和函数参数,包括子类虚函数的函数内容
class Student : public Person
{
virtual void BuyTickts()
{
cout << "半价购买" << endl;
}
private:
};
class Solider : public Person
{
virtual void BuyTickts()
{
cout << "优先购买" << endl;
}
private:
};
void test(Person& s)
{
s.BuyTickts();
}
// 测试在不同继承关系的类对象,去调用同一函数,产生了不同的行为
void test1()
{
Student st;
Solider so;
test(st);
test(so);
}
这样就可以做到调用谁的虚函数,就输出谁的虚函数的内容,也就是多态。
在这里虚函数还有两个特殊情况:
1、当重写虚函数的时候派生类函数的返回类型和基类的不相同,此时被称为协变,但是此时的返回值类型只能为基类的虚函数返回基类的指针或者引用,派生类返回值为派生类的指针或者引用。(了解内容)
2、析构函数的重写,在这里虽然类中的析构函数名字不相同,并且析构函数前面没有加 virtual 按理说不满足虚函数重写的规则,但是编译器会默认将继承中的派生类和基类中的析构函数名字都处理为 destructor() 函数,并且无论析构函数是否加 virtual 都构成虚函数重写(相当于编译器默认加了 virtual) ,这样析构函数就构成多态,然后就可以做到指向谁就调用谁的析构函数,如下代码:
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; // 调用 Person 的析构函数
delete p2; // 调用 Student 的析构函数
return 0;
}
二、C++11引入的两个关键字,重载,隐藏,复写的对比
1、final -> 虚函数不可再被复写
在不需要复写的虚函数后面加上 这个 final 的关键字,后面如果还对这个虚函数进行重写的话就会报错。具体如何使用如下:
class Car
{
public:
virtual void Drive() final // 表示这个 Drive() 的虚函数不可以被复写
{}
};
class Benz :public Car
{
public:
virtual void Drive() // 这里还对这个 Drive() 虚函数进行重写,就会报错
{cout << "Benz-舒适" << endl;}
};
2、override -> 检查 派生类虚函数 是否重写了基类某个虚函数,如果没有重写编译报错。
判断派生类中的虚函数是否是重写的基类中的某个虚函数,在派生类虚函数后面加上关键字 override 表示对派生类中的虚函数是否重写的基类中的虚函数进行检查,如果不是,就直接报错。具体使用方法如下:
class Car{
public:
virtual void Drive()
{}
};
class Benz :public Car {
public:
virtual void Drive() override // 检查 派生类虚函数 是否重写了基类某个虚函数
{cout << "Benz-舒适" << endl;}
};
3、重载、隐藏、复写的对比图
三、抽象类
1、概念:包含纯虚函数的类称为抽象类,并且抽象类无法实例化出对象,派生类继承抽象类之后,也无法实例化出对象,只有派生类对这个纯虚函数进行重写之后才可以实例化出对象。
这里就又需要提到一个概念,也就是纯虚函数,语法很简单,直接看下面代码:
// 纯虚函数
// 语法:在基类的虚函数 后面 + '= 0' 即可,这样就是纯虚函数
// 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象
class Car
{
public:
virtual void Drive() = 0; // 语法就是在基类虚函数后面加 = 0 即可,纯虚函数不能实例化处对象
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
这里 Car 的类里面包含纯虚函数,所以作为抽象类,它无法实例化出自己的对象,对于 Benz 这个类因为它重写了基类中的纯虚函数,所以它可以实例化出自己的对象。
注意:
四、多态的原理
1、虚函数和虚函数表的关系
对于存在虚函数的类而言,这个类中会多一个指针大小的内容,被称为虚函数表指针,具体怎么发现的请看一段代码及其运行结果:
继承父类后应该大小为8个字节,为什么多了4的字节,在32位机器上,由于 Derive 类中多了一个虚函数表指针,所以多了四个字节。
那虚函数,虚函数表指针,虚函数表在类中的储存情况是什么样子的,这里以 Derive类 为例下面画图告诉大家:
好了,这就是今天多态的全部内容,希望大家看完之后都有所收获!~~~