什么多态(重写)
多态是在不同继承关系的类对象中,去调用同一函数,产生不同的行为。这种行为成为重写
using namespace std;
class Person {
public:
virtual void BuyTicket() const
{
cout << "买票:成人票全价" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() const
{
cout << "买票:学生票半价" << endl;
}
};
void func(const Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
func(ps);
func(st);
return 0;
}
多态实现的条件
1)首先多态的实现是建立在继承关系的基础上。
2)被调用的函数必须是虚函数并且完成了虚函数的重写。
3)调用函数的对象的类型必须是父类指针或者引用。
虚函数
- 调用函数是重写的虚函数:虚函数重写的条件为,返回类型相同,函数名字相同,参数列表形同。但是还有一些例外也是可以实现虚函数的,例如派生类重写的虚函数钱可以不加
virtual
,和协变都是例外**- 基类是指针或引用
协变
协变的返回值可以不同,但是要求返回值必须是父子类关系(先父后子)指针或引用,不能结合使用
using namespace std;
class Person {
public:
virtual Person* BuyTicket() const
{
cout << "买票:成人票全价" << endl;
return 0;
}
};
class Student : public Person {
public:
virtual Student* BuyTicket() const
{
cout << "买票:学生票半价" << endl;
return 0;
}
};
//
void func(const Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
func(ps);
func(st);
return 0;
}
析构函数
析构函数是可以为虚函数的,因为如果析构函数不是虚函数那么有些情况下就会发生内存泄漏。
析构函数前加virtual也是构成了虚函数的重写,且类析构函数都被处理成destructor这个统一的名字
class Person {
public:
virtual void BuyTicket() const
{
cout << "买票:成人票全价" << endl;
}
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() const
{
cout << "买票:学生票半价" << endl;
}
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
//
void func(const Person& p)
{
p.BuyTicket();
}
int main()
{
//Person p;
//Student s;
Person* p = new Person;
p->BuyTicket();
delete p;
p = new Student;
p->BuyTicket();
delete p; // p->destructor() + operator delete(p)
// 这里我们期望p->destructor()是一个多态调用,而不是普通调用
return 0;
}
如果析构函数前面不加virtual,那么就会发生内存泄漏
怎样做可以不让重写
C++11提供了
override
和final
两个关键字,可以帮助我们检测是否重写。
final 直接不让重写
override 帮助派生检查是否完成重写,如果没有会报错
重载、覆盖(重写)、隐藏(重定义)的对比
多态实现条件的原因
为社么不能子类指针或者引用调用?为什么不可以是对象?
指针和引用不存在拷贝问题,都是直接切片指向,所以,如果换成父类给子类传值,那么切片这一块的逻辑就过不去。
也不可以是对象,如果是对象,子类赋值给父类的时候,会进行切片,但是不会拷贝虚表,如果拷贝虚表,那么父类对象虚表中是父类虚函数还是子函数就不确定了。
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual void Func1()
{
cout << "Person::Func1()" << endl;
}
virtual void Func2()
{
cout << "Person::Func2()" << endl;
}
//protected:
int _a = 0;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
virtual void Func3()
{
//_b++;
cout << "Student::Func3()" << endl;
}
protected:
int _b = 1;
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
st._a = 10;
ps = st;
Person* ptr = &st;
Person& ref = st;
}
派生类的虚表是怎么形成的
在完成继承后,先将基类中的虚表内容拷贝一份到派生类虚表中。
如果派生类中重写了基类中的某个函数,用派生类自己的虚函数覆盖虚表中基类的虚函数。
派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
证实虚表存储与常量区
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual void Func1()
{
cout << "Person::Func1()" << endl;
}
virtual void Func2()
{
cout << "Person::Func2()" << endl;
}
//protected:
int _a = 0;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
virtual void Func3()
{
//_b++;
cout << "Student::Func3()" << endl;
}
protected:
int _b = 1;
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
//栈区
int a = 0;
printf("栈区:%p\n", &a);
//堆区
int* b = new int;
printf("堆区:%p\n", b);
//静态区
static int c = 0;
printf("静态区:%p\n", &c);
//常量区
const char* s = "hello world";
printf("常量区:%p\n", s);
//判断
printf("判断:%p\n", *((int*)&ps));
printf("判断:%p\n", *((int*)&st));
return 0;
}
多继承虚函数
多继承类的大小计算
每个类中都分别有一个变量,被继承的类中都分别有一个存放虚表的指针,所以最后大小为20.
//函数执政数组
typedef void(*FUNC_PTR) ();
// 打印函数指针数组
// void PrintVFT(FUNC_PTR table[])
void PrintVFT(FUNC_PTR* table)
{
for (size_t i = 0; table[i] != nullptr; i++)
{
printf("[%d]:%p->", i, table[i]);
FUNC_PTR f = table[i];
f();
}
printf("\n");
}
class Base1
{
public:
virtual void func1() { cout << "Base1:func1" << endl; }
virtual void func2() { cout << "Base1:func2" << endl; }
private:
int b1;
};
class Base2
{
public:
virtual void func1() { cout << "Base2:func1" << endl; }
virtual void func2() { cout << "Base2:func2" << endl; }
private:
int b2;
};
class Derive :public Base1, public Base2
{
public:
virtual void func1() { cout << "Derive:func1" << endl; }
virtual void func3() { cout << "Derive:func3" << endl; }
public:
int d1;
};
int main()
{
Derive d;
cout << sizeof(d) << endl;
int vft1 = *((int*)&d);
Base2* ptr = &d;
int vft2 = *((int*)ptr);
PrintVFT((FUNC_PTR*)vft1);
PrintVFT((FUNC_PTR*)vft2);
return 0;
}
在多继承多态里新创建的多态函数会放在第一个继承的类里,但是我们可以从输出结果看出,我们重写的func1的地址不一样但是调用结果一样,这是为什么呢?
原因是编译器在内部进行了指针的修正,最后修正后的指针都是指向同一个目标的
而单继承调用重写函数由于父类地址与子类地址重合,传入的this 样,所以直接可以调用重写函数,多继承由于会出现ptr2调用时传入的地址与子类地址不一样,所以会先调用指针调整函数
多态菱形继承
我们发现跟我们的菱形继承是没有区别的
多态菱形虚继承
当我们进行多态菱形虚继承时,如果用父类D调用虚函数时,由于继承了B和C的虚函数,导致在调用的时候,编译器不知道该调用哪个,如果想要解决,需要在D类重写虚函数
class A
{
public:
virtual void func1()
{
cout << "A::func1" << endl;
}
public:
int _a;
};
class B :virtual public A
//class B : virtual public A
{
public:
virtual void func1()
{
cout << "B::func1" << endl;
}
public:
int _b;
};
class C :virtual public A
//class C : virtual public A
{
public:
virtual void func1()
{
cout << "C::func1" << endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
virtual void func1()
{
cout << "D::func1" << endl;
}
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
d.func1();
return 0;
}
在D类重写虚函数
如果我们在B类C类添加自己的虚函数,那么虚函数表会建立在哪里
由图可见,分别在各自的类中建立了虚表,因为B和C都继承了A且都弄成了虚继承,把共有的变量提取出来了,如果现在虚表也共用,那么就会容易混,所以就需要各建立各的
如果在D中在建立虚函数,那么会在D类中创建虚表吗?
因为D继承了B和C又因为它是最小的子继承,所以它会选择放入她的第一个父类中。
抽象类(包含纯函数类)
特点:抽象类不可以实例化对象
只要包含或继承纯虚类的类都不可以实例化对象,除非继承类重写纯虚类
抽象类和override的区别:
抽象类是间接强制派生类重写虚函数,而override是检查派生类是否完成了重写
重写虚函数
class Car
{
public:
virtual void Drive() = 0;
};
class baoma : public Car
{
virtual void Drive() {
cout << "我是宝马" << endl;
}
};
int main()
{
Car* c;
baoma b;
c = &b;
c->Drive();
}
纯虚函数
纯虚函数就是在虚函数的后边加一个 = 0。赋值 0;
class Car
{
public:
virtual void Drive() = 0;
};