1. 多态概念:
![](https://i-blog.csdnimg.cn/blog_migrate/037487467e03b359ebfc3956d2da13ee.png)
2. 多态的定义及实现
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。
class Person
{
public:
virtual void BuyTicket()
{
cout << "全价买票" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "半价买票" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student sd;
Func(ps);
Func(sd);
return 0;
}
//运行结果:
//全价买票
//半价买票
2.1 构成多态的条件:
a. 虚函数重写(返回类型相同,参数列表完全相同,函数名相同)
b. 父类的指针或者引用去调用虚函数
2.2 虚函数重写的俩个特例
a. 子类可以不加虚函数,一样构成虚函数重写,因为重写是接口继承,重写是实现。
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
//运行结果如下:
买票-全价
买票-半价
b. 协变,基类与派生类虚函数的返回值不同,但必须基类返回值类型是基类类型的返回值或者引用,派生类返回值类型是派生类类型的返回值或者引用,而且基类和派生类返回值类型,可以不是自身类型,但返回类必须构成父子关系,才能构成协变。
//协变1
class Person {
public:
virtual Person* f() { return nullptr; }
};
class Student : public Person {
public:
virtual Student* f() { return nullptr; }
};
//协变2
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 A
{
public:
//virtual ~A()
~A()
{
cout << "~A" << endl;
}
};
class B : public A
{
public:
//virtual ~B()
//析构函数函数名会统一被处理成destructor
//构成重写
~B()
{
cout << "~B" << endl;
}
};
int main()
{
A* a = new A;
A* b = new B;
delete a;
delete b;
return 0;
}
//不加virtual 运行结果:~A ~A
//加virtual 运行结果:~A ~B ~A
解释:不加virtual不构成多态,编译链接时确定地址,运行时call函数地址,没有虚表,调用的是A对象的析构函数,加virtual构成多态,运行时去虚函数表里面找,call函数地址,调用的时重写的B对象的析构函数。
学习了多态概念,请看题
class A
{
public:
virtual void func(int val = 1)
{
cout << "A->" << val << endl;
}
virtual void test()
{
func();
}
};
class B : public A
{
public:
virtual void func(int val = 0)
{
cout << "B->" << val << endl;
}
};
int main()
{
B* p = new B;
p->test();
return 0;
}
//A:A->0 B:B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
解析:
2.3 C++11 override 和 final
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒适" << endl;}
};
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
2.4 重载、覆盖(重写)、隐藏(重定义)的对比
3. 抽象类
class Car
{
virtual void Drive() = 0;
};
class BMW : public Car
{
virtual void Drive()
{
cout << "BMW经济型" << endl;
}
};
class Benz : public Car
{
virtual void Drive()
{
cout << "Benz豪华舒适" << endl;
}
};
int main()
{
//Car c;不能初始化对象,因为是抽象类
BMW b;//可以初始化对象,因为重写了Drive
//可以看出如果作为Car的派生类,就必须重写Drive
return 0;
}
4. 多态原理
4.1 虚函数表
class Base
{
public:
virtual void Func()
{
cout<<"Func"<<endl;
}
int _a;
};
int main()
{
Base b;
cout<<sizeof(b)<<endl;
return 0;
}
//运行结果如下:
//8
![](https://i-blog.csdnimg.cn/blog_migrate/a3687b5f5392a19fd11479878aef23ac.png)
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual void Func() { cout << "Person::Func()" << endl;}
void Drive() { cout << "Person::Drive()" << endl; }
};
class Student : public Person {
public:
void BuyTicket() { cout << "买票-半价" << endl; }
};
int main()
{
Person p;
return 0;
}
如果我们再派生类Student中加入一个虚函数,那么这个虚函数地址是否也会放入虚表中呢?
//typedef 函数指针数组类型为VFptr
typedef void(*VFptr)();
void PrintVFTable(VFptr* ptr)
{
for (int i = 0; ptr[i] != nullptr; i++)
{
//打印虚表中的函数地址
printf("VFptr[%d]->%p ", i, ptr[i]);
//调用虚函数
ptr[i]();
}
cout << endl;
}
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual void Func() { cout << "Person::Func()" << endl;}
void Drive() { cout << "Person::Drive()" << endl; }
};
class Student : public Person {
public:
void BuyTicket() { cout << "买票-半价" << endl; }
virtual void Add() { cout << "Student::Add()" << endl; }
};
int main()
{
Student s;
PrintVFTable((VFptr*) * (int*)&s);
return 0;
}
打印结果如下:
我们可以知道子类的虚函数地址也是放入继承父类的虚表中 。
4.2 多态原理:
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
为什么初始化Mike对象就能掉用Mike的BuyTicket()函数,初始化Johnson对象就能掉用Mike的BuyTicket()函数,明明传递给Func()函数的都是父类的指针或者引用,不应该都是调用Mike的BuyTicket()函数嘛?
原理如下:
5.多继承的虚函数表
//typedef 函数指针数组类型为VFptr
typedef void(*VFptr)();
void PrintVFTable(VFptr* ptr)
{
for (int i = 0; ptr[i] != nullptr; i++)
{
//打印虚表中的函数地址
printf("VFptr[%d]->%p ", i, ptr[i]);
//调用虚函数
ptr[i]();
}
cout << endl;
}
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1 = 1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2 = 2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d = 3;
};
多继承会同时继承多个父类的虚表,上述代码中写了Derive继承了俩个子类,所以有俩个虚表
但是有一个疑问的点是,d对象中自身的func3虚函数地址是放在Base1虚表还是 放在Base2虚表中呢?
//typedef 函数指针数组类型为VFptr
typedef void(*VFptr)();
void PrintVFTable(VFptr* ptr)
{
for (int i = 0; ptr[i] != nullptr; i++)
{
//打印虚表中的函数地址
printf("VFptr[%d]->%p ", i, ptr[i]);
//调用虚函数
ptr[i]();
}
cout << endl;
}
像单继承类似打印可得:func3虚函数地址存放于先继承Base1的虚表中。
像上述图片运行结果显示可知:
Derive中的func1()虽然在子类中被重写,但是在Base1()和Base2()虚表中俩个func1()的地址是不一样的,为什么呢?
面试题:
![](https://i-blog.csdnimg.cn/blog_migrate/94b6c78a8971606bc8850b2ac150b7b6.png)
10. C++菱形继承的问题?虚继承的原理?答:参考另外一篇博客继承,注意这里不要把虚函数表和虚基 表搞混了。
11. 什么是抽象类?抽象类的作用?答:参考:抽象类,抽象类强制重写了虚函数,另外抽 象类体现出了接口继承关系。