一.多态的概念
简单点来说,多态就是对于一个任务,不同的对象去完成会产生不同的效果。
举个栗子
对于买票这个行为,,学生去买就是半价,普通人去买就是全价,产生了不同的效果。
虚函数是什么呢?
我们指被virtual修饰的函数为虚函数
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
虚函数的重写(覆盖):派生类中有跟基类一模一样的虚函数(函数名,返回值,参数列表)
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
这样使用*/
};
诶!我们这里还有两个例外哦:
什么叫做协变呢,就是派生类和基类的虚函数返回值不同,例如:
class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
};
可以看见基类的虚函数返回的是基类的指针,派生类的虚函数返回的是派生类的指针。
析构函数的重写即基类和派生类的析构函数名字不同。
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student :public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
}
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
这种情况也是构成重写的,是因为编译器对这种情况做了特殊处理,将析构函数的函数名转换为了destructor。
在C++11中,引入了override和final
(1)override:放在派生类虚函数的后面,检测该虚函数是否重写了基类的虚函数,如果没有就报错
class Car
{
public:
virtual void Drive() override
{
}
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
int main()
{
return 0;
}
正确方式如下:
class Car
{
public:
virtual void Drive()
{
}
};
class Benz :public Car
{
public:
virtual void Drive() override
{
cout << "Benz-舒适" << endl;
}
};
int main()
{
return 0;
}
(2)final:放在虚函数的后面使该虚函数不能被重写
class Car
{
public:
virtual void Drive() final
{
}
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
在讲抽象类之前先讲讲纯虚函数,我们在虚函数的后面加上=0,这样的虚函数就叫纯虚函数。
下面的Drive就是纯虚函数。
class Car
{
public:
virtual void Drive() = 0;
};
包含纯虚函数的类就是我们的抽象类了,抽象类是不能实例化出对象的,而且他的派生类也不能,只有派生类重写了纯虚函数,那么派生类才能实例化对象。
class Car
{
public:
virtual void Drive() = 0;
};
//间接强制了子类重写虚函数,因为不重写的话,子类依旧是抽象类,就实例化不出对象
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BWM :public Car
{
public:
virtual void Drive()
{
cout << "BWM-操控" << endl;
}
};
int main()
{
//抽象类,实例不出对象
//Car c1;
Car* p = new Benz;
p->Drive();
p = new BWM;
p->Drive();
Benz b;
b.Drive();
return 0;
}
对于下面的一段代码:
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
Base bb;
cout << sizeof(Base);
return 0;
}
大家猜猜结果是多少?
哈哈其实是8哦!
为啥呢?
人狠话不多,直接上调试:
我们可以看见除了成员_b以外,还有一个指针_vfptr,这个指针就是我们的主角的引路人-》虚函数表指针,它指向一个虚函数表。
这就是虚函数表,简称虚表,里面的第一行就是函数Func1()的地址。
咱们再来看一下在继承中虚函数表是什么样的:
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
char _ch = 'a';
};
class Drive :public Base
{
public:
virtual void Func1()
{
cout << "Drive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base bb;
Drive dd;
return 0;
}
调试一下:
我们可以发现以下规律:
那我们的虚函数与虚表是存在哪的呢?
对于虚函数来说,本质也是个函数,所以存在于代码段中。那虚表呢?
class Base
{
public:
Base()
:_b(2)
{}
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b=1;
};
class Drive :public Base
{
public:
virtual void Func1()
{
cout << "Drive::Func1()" << endl;
}
virtual void Func3()
{
cout << "Drive::Func3()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Base b1;
Base b2;
Drive d;
int i = 0;
static int j = 1;
int* p1 = new int;
const char* p2 = "xxxxxx";
printf("栈:%p\n", &i);
printf("静态区:%p\n", &j);
printf("堆:%p\n", p1);
printf("常量区:%p\n", p2);
Base* p3 = &b;
Drive* p4 = &d;
printf("Base虚表地址:%p\n", *(int*)p3);
printf("Drive虚表地址:%p\n", *(int*)p4);
return 0;
}
结果如下:
可以看到虚表是在常量区的。
上面噼里啪啦说了这么一大堆,那多态的原理到底是什么呢?
当p指向Mike对象时,p->BuyTicket()在虚表中调用的是Person::BuyTicket()
而当p指向Johnson对象时,p->BuyTicket()在虚表中调用的是Student::BuyTicket()
这就实现了不同的对象去完成同一个任务时,呈现出不同的效果。
通过对汇编代码的分析发现,满足多态的函数调用,是在运行后到对象中去找的,而不满足多态的函数调用,是在编译时就确认好的。
总结
好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。
祝大家越来越好,不用关注我(疯狂暗示)