多态
- 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态;
- 多态是面向对象的非常重要的一个特性;
- 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果;
- 在运行时,可以识别出真正的对象类型,调用对应子类中的函数;
#include <iostream>
using namespace::std;
class Animal {
public:
void run(){
cout << "Animal run()" << endl;
}
};
class Dog : public Animal{
public:
void run(){
cout << "Dog run()" << endl;
}
};
class Cat : public Animal{
public:
void run(){
cout << "Cat run()" << endl;
}
};
class Pig : public Animal{
public:
void run(){
cout << "Pig run()" << endl;
}
};
void liu(Animal *a){
a->run();
}
int main(int argc, const char * argv[]) {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
- 由于用
Animal *a
来接收形参,那么执行run方法是,调用的都是Animal类中的方法,并没有实现根据不同的对象类型,调用对应的函数方法;
虚函数
-
C++中多态时通过虚函数来实现的;
-
虚函数:被
virtual
关键字修饰的函数,称为虚函数; -
只要在父类中声明为虚函数,子类中的重写的函数也自动变成虚函数,也就是说子类中的函数可以省略
virtual
关键字; -
多态实现的要素:
- 子类重写父类的成员函数;
- 父类指针指向子类对象;
- 利用父类指针调用子类的成员函数,且成员函数为虚函数;从基类开始虚,从中间类开始虚,也不能实现多态的功能;
#include <iostream>
using namespace::std;
class Animal {
public:
virtual void run(){
cout << "Animal run()" << endl;
}
};
class Dog : public Animal{
public:
void run(){
cout << "Dog run()" << endl;
}
};
class Cat : public Animal{
public:
void run(){
cout << "Cat run()" << endl;
}
};
class Pig : public Animal{
public:
void run(){
cout << "Pig run()" << endl;
}
};
void liu(Animal *a){
a->run();
}
int main(int argc, const char * argv[]) {
liu(new Dog());
liu(new Cat());
liu(new Pig());
return 0;
}
- 上述的代码实现了多态的功能,即不同的子类对象调用属于自己的函数方法;
虚表
- 虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫做虚函数表;
#include <iostream>
using namespace::std;
class Animal {
public:
int m_age;
virtual void speak(){
cout << "Animal speak()" << endl;
}
virtual void run(){
cout << "Animal run()" << endl;
}
};
class Cat : public Animal{
public:
int m_life;
void speak(){
cout << "Cat speak()" << endl;
}
void run(){
cout << "Cat run()" << endl;
}
};
int main(int argc, const char * argv[]) {
Animal *cat = new Cat();
cat->m_age = 3;
cat->speak();
cat->run();
cout << cat << endl;
return 0;
}
- 当前测试环境是在MacOS,即x64环境,则指针占用8个字节;
- 当Animal没有虚函数时,cat实例对象有m_age,m_life有两个成员变量,所以cat实例对象占用8个字节;
- 当Animal有虚函数时,即有
virtual
关键字修饰函数时,cat实例对象会占用16个字节,前面8个字节存储的是Cat类的虚函数表的地址; - cat实例对象的内部布局如下所示:
- 可通过汇编分析证明上面的描述:
-
movl $0x3, 0x8(%rax)
:rax存储的是cat实例对象的内存地址,由于虚函数地址占了前8个字节,所以m_age的偏移量为8; -
movq (%rax), %rcx
:取出cat实例对象的内存地址中前8个字节内容即虚函数的地址,存入rcx寄存器; -
callq *(%rcx)
:取出虚函数地址的前8个字节内容,即speak函数内存地址,然后调用speak函数; - 同理
callq *0x8(%rcx)
,从虚函数地址的第8个字节开始的后8个字节的内容,即run函数内存地址,然后调用run函数;
总结:
- 所有cat实例对象,不管在栈区,全局区,堆区都共用一份虚表,也就是说每个类都有自己独立的一份虚表;
- cat实例对象的前8个字节存储了虚表的内存地址,虚表中存储着cat类的所有虚函数;
- 若cat类存储虚函数,且cat子类没有实现run方法,那么虚表中会存储父类Animal的run方法,所以会调用Animal的run方法,这也就是说调用目标方法,如果子类没有实现,就调用父类的实现;
子类调用父类的成员函数
#include <iostream>
using namespace::std;
class Animal {
public:
int m_age;
virtual void speak(){
cout << "Animal speak()" << endl;
}
virtual void run(){
cout << "Animal run()" << endl;
}
};
class Cat : public Animal{
public:
int m_life;
void speak(){
Animal::speak();
cout << "Cat speak()" << endl;
}
void run(){
Animal::run();
cout << "Cat run()" << endl;
}
};
int main(int argc, const char * argv[]) {
Animal *cat = new Cat();
cat->m_age = 3;
cat->speak();
cat->run();
cout << cat << endl;
cout << sizeof(Animal) << endl;
return 0;
}
- 直接在子类的函数中,调用父类的函数;
虚析构函数
- 含有虚函数的类,应该将析构函数声明为虚函数即虚析构函数;
- delete父类指针时,才会调用子类的析构函数,保证析构的完整性,否则会造成内存泄漏;
#include <iostream>
using namespace::std;
class Animal {
public:
int m_age;
virtual void speak(){
cout << "Animal speak()" << endl;
}
virtual void run(){
cout << "Animal run()" << endl;
}
virtual ~Animal(){
cout << "~Animal()" << endl;
}
};
class Cat : public Animal{
public:
int m_life;
void speak(){
Animal::speak();
cout << "Cat speak()" << endl;
}
void run(){
Animal::run();
cout << "Cat run()" << endl;
}
~Cat(){
cout << "~Cat()" << endl;
}
};
int main(int argc, const char * argv[]) {
Animal *cat = new Cat();
cat->m_age = 3;
cat->speak();
cat->run();
delete cat;
cout << cat << endl;
cout << sizeof(Animal) << endl;
return 0;
}
-
virtual ~Animal()
:父类Animal的析构函数声明为虚函数,否则Animal *cat = new Cat()
,不会调用Cat类的析构函数,造成内存泄漏;
纯虚函数
- 纯虚函数:没有函数体且初始化为0的虚函数,用来定义借口规范;
class Animal {
public:
virtual void speak() = 0;
virtual void run() = 0;
};
抽象类
-
含有纯虚函数的类
即为抽象类,不可以实例化创建对象;例如上述的Animal类,在现实世界中是没有animal动物实例对象的,它是一个抽象的概念; - 抽象类也可以包含非纯虚函数;
- 如果父类是抽象类,
子类没有全部实现纯虚函数
,那么这个子类依然是抽象类,不能实例化创建对象;
网友评论