C++多态的理解
多态概念
多态:C++中多态是面向对象编程的三大类型之一(封装,继承),它允许不同类的对象通过相同的接口调用不同的实现。多态=“一个接口,多种实现”
多态分类
| 类型 | 描述 | 实现方式 | 绑定时间 |
| 编译多态 | 在编译期间决定调用哪个函数 | 函数重载,运算符重载,模板 | 静态绑定 |
| 运行多态 | 在运行期间决定调用哪个函数 | 虚函数(virtual) | 动态绑定 |
多态定义和实现
多态是继承关系下的运用对象,去调用同一函数,产生不同对象的行为,例如买票,有的是全价买票,学生半价买票,军人优先买票
实现多态的必要条件
1:被调用的函数必须是虚函数,并且完成了虚函数的重写或覆盖
2:必须是基类的指针或引用调用虚函数
虚函数
类的成员函数前面加上virtual关键字修饰,那么这个成员函数就被称为虚函数。
注:不是成员函数不能用virtual修饰
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
虚函数的重写和覆盖
重写:在派生类中有一个与基类完全相同的虚函数(返回值类型,函数名字,参数列表完全相同),称为派生类的虚函数重写了基类的虚函数
注:在重写虚函数时,即使派生类不加virtual关键字也构成虚函数重写(因为继承后基类的虚函数被继承了下来,在派生类中依旧保持虚函数的属性)
基类函数一旦是 virtual,派生类同名同参的函数自动带虚属性,写不写 virtual 只影响可读性,不影响多态行为。
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
return;
}
};
class Student :public Person
{
public:
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
return;
}
};
void Func(Person* ptr)
{
ptr->BuyTicket();
}
int main()
{
Person p;
Student s;
Func(&p);
Func(&s);
return 0;
}
析构函数的重写
如果基类的析构函数为虚函数,那么派生类的析构函数只需要定义,无论是否加virtual都与基类的析构函数构成了重写,虽然派生类的析构函数名称与基类不同,不符合重写的规则,但是实际上编译器已经对析构函数做了特殊处理,使其函数名称都统一处理成destructor,所以只要基类的析构函数加上virtual构成虚函数,派生类的析构函数都构成重写
class Person
{
public:
virtual~Person()//如果不构成重写,那么就会导致Student存在内存的泄露
{
cout << "~Person()" << endl;
}
};
class Student :public Person
{
public:
~Student()
{
delete[] _ptr;
cout << "~Student()" << endl;
}
protected:
int* _ptr = new int[10];
};
int main()
{
Person* p1 = new Student;
delete[]p1;
Person* p2 = new Person;
delete[]p2;
return 0;
}
override和final关键字
override:可以帮助我们检查是否构成重新
class Car
{
public:
virtual void Drive()
{ }
};
class Benz :public Car
{
public:
void Drive() override//如果基类没写virtual就报错
{
cout << "舒适" << endl;
}
};
int main()
{
Benz bz;
bz.Drive();
return 0;
}
final:如果不想让派生类重写函数就用该关键字修饰
class Car
{
public:
virtual void Drive() final//加上final该成员函数就不能被重写
{ }
};
class Benz :public Car
{
public:
void Drive() override//如果基类没写virtual就报错
{
cout << "舒适" << endl;
}
};
int main()
{
Benz bz;
bz.Drive();
return 0;
}
重载/重写/隐藏的对⽐

纯虚函数和抽象类
纯虚函数:在虚函数的后面加上=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()
{
//编译报错:error C2259 : “Car”:⽆法实例化抽象类
Car car;
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
return 0;
}
多态的原理
运行时多态是靠虚函数(virtual)
运行时多态的核心理念就是:虚函数表(vtable)+虚指针(vptr)
虚函数表(vtable)
1:每个包含虚函数的类,编译器都会为其生成一个虚函数表
2:表中存储的都是该类所有虚函数的函数指针
虚指针(vptr)
1:每一个函数对象内部都有一个指针,指向该类的虚函数表
2:当该函数对象被构造时,vptr被初始化成对应类的虚函数表指针的对应位置
动态绑定
通过基类的指针或引用调用虚函数时,运行时会通过虚指针(vptr)找到对应的虚函数表(vtable),再调用实际派生类的函数
class Base
{
public:
virtual void Drive()
{
cout << "Base Spack" << endl;
}
};
class Derived :public Base
{
public:
void Drive() override
{
cout << "Derived Sapck" << endl;
}
};
int main()
{
Base* ptr = new Derived();
ptr->Drive();//Derived Sapck
return 0;
}
Base是静态类型,编译器在编译时会去查找,而new则是动态类型,在运行时会去查找,则ptr是调用Derived,在运行时调用谁就是谁的
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
};
class Derive :public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
void Func1(Base* p)
{
p->Func1();
p->Func3();
}
int main()
{
Base b;
Derive d;
Func1(&b);
Func1(&d);
return 0;
}
两个类构成多态都各自生成一个虚函数表,通过调用Func1函数,形成虚函数表,根据虚指针,调用了成员函数Func1和Func3,但是Func3不构成重写不是虚函数就不在虚函数表中。
在Func1(&d)中Derive也生成了一个虚函数表,与基类的虚函数表内存位置不同,但是因为继承,实现方式是类似的,通过虚指针的指向就可以形成指向谁调用谁的结果。
如果指定类作用域就不存在这样的概念,但是在Derive中调用Func2就和基类的Func2的内存位置相同,是直接切割了派生类中父类的成员函数,给到了派生类
更多推荐


所有评论(0)