多态感觉真挺难的,这篇就水一水了
引言
多态:多种形态 静态的多态:函数重载,同一个函数看起来调用同一个函数有不同的行为 动态的多态: 静态:是在编译的时候实现的
动态的多态
一个父类的引用或指针去调用同一个函数,如果传递不同的对象,会调用不同的函数 动态:运行时实现的
举个例子: 买票这个行为,对于不同的人价格不同,普通人去买价格低,而有钱人买价格高
扫红包:也是一种多态行为,根据行为去分类,鼓励新用户用,给的红包就多,
虚函数
子类中满足三同 函数名,参数,返回值相同的虚函数,就叫做重写(覆盖),只有虚函数 本质就是不同的人去做同一件事情,结果不同 virtual(只能是类的非静态的成员函数才能是虚函数):其他函数不能称为虚函数 :在最前面加一个virtual
静态成员函数不能加virtual
否则就是隐藏的关系
多态的构成条件(少一个都不行)
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
重写要求返回值相同,有一个例外:协变:要求返回值是父子关系的指针或者引用(基本不会见到)
class A class B
void func(Person p)
{
p.BuyTicket();
}
func(st);
func(ps);
析构函数的重写(析构函数)
析构函数也可以是虚函数,构成重写(函数名,返回值,参数,virtual)都相同 把析构函数都被特殊处理成destructor, 完成重写,构成多态,那么才能正确调用析构函数 只有动态申请的对象给了父类指针管理才可以,那么就需要定义成虚函数
class Person
{
public:
virtual ~Person()
{
cout << "person" << endl;
}
};
class Student : public Person
{
public:
virtual ~Student()
{
cout << "student" << endl;
}
};
int main()
{
Person *p1 = new Person;
Person *p2 = new Student;
Student *p3 = new Student;
delete p1;
delete p2;
delete p3;
return 0;
}
虚函数的重写允许 :两个都是虚函数,或者父类是虚函数,再满足三同就构成重写 虽然子类没有写virtual 是因为他继承了父类的virtual的属性,也完成了重写(是因为析构函数,父类析构函数加上virtual,就不存在不构成多态的了,没钓到子类析构函数,内存泄漏的) :在public:private也是可以的
一般来说父类是别人写的,子类是我们写的,只要父类加了virtual,子类就不用加virtual 我们建议都写上
C++11 override 和final
final
设计一个不能被继承的类 c++ 98的方式
class A
{
private:
A(int a = 0)
: _a(a)
{
}
public:
static A createobj(int a = 0)
{
return A(a);
}
protected:
int _a;
};
class B:public A
{
};
int main()
{
A aa=A::createobj(10);
return 0;
}
c++ 11
- 使用final 这个类就无法被继承
class A final
{
protected:
int _a;
};
class B:public A
{
};
int main()
{
return 0;
}
- 使用final这个函数就无法被重写,放在父类中
class C
{
public:
virtual void f() final
{
cout<<"hello "<<endl;
}
};
class D :public C
{
public:
virtual void f()
{
cout<<"world"<<endl;
}
};
int main()
{
return 0;
}
override
同样也是放在类的后面 放在子类重写的虚函数的后面,检查是否完成重写,如果没有完成重写就会报错
class car
{
public:
virtual void drive()
{
}
};
class benz: public car
{
public:
virtual void drive() override
{
}
};
int main()
{
return 0;
}
没加之后的报错
重载,覆盖,隐藏的对比
重载:
- 两个函数在同一个作用域
- 函数名相同,参数不同,返回值没有要求
覆盖: 3. 两个函数分别在基类和派生类中的作用域中 4. 函数名/参数/返回值,都相同(协变除外) 5. 两个函数必须是虚函数
重定义(隐藏) 6. 一个在基类一个在派生类 7. 函数名相同 8. 不构成重写就是隐藏
抽象类
在虚函数的后面加上=0,这个函数就叫做纯虚函数, 包含纯虚函数的类就叫做抽象类,不能实例化出对象, 派生类继承以后,也不能实例化出对象,只有子类重写这个虚函数,才能实例化出对象,
底层剖析
我们会发现子类和父类是不一样的虚表,下面继承的是重写的虚函数(覆盖) 父类对象是父类对象的虚函数,子类对象是子类对象的虚函数 重写是语法层面,覆盖是原理上 覆盖成我重写的虚函数,
多态为什么必须是指针和引用呢? 指针和引用,虚表就是各自的,而用对象,那么虚表都是父类的,无法调用子类的,切片没有办法把虚表指针拷过去
多态的原理: 基类的指针/引用,指向谁就去谁的虚函数表中找到对应的虚函数进行调用,在父类里面找到父类的虚函数,在子类里面找到子类的虚函数,
同类型的对象,他的虚表指针是一样的,指向同一张虚表,都是指向它的 普通函数和虚函数都存储在代码段,只不过虚函数要把地址存一份到虚表,方便实现多态,
多态就算是私有的也可以调用,因为它是从虚表里面去找的,所以没有影响,有了虚函数这一个,私有也都是可以调用的,
总结:
- 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员
- 基类b对象,和派生类d对象虚表是不一样的,这里我们发现func完成了重写,覆盖是指虚表中虚函数的覆盖,重写是语法上的 叫法,覆盖是原理层的叫法
- 子类继承下来没有重写的一部分,地址还是和父类的虚函数一样
- 子类也写了虚函数,这个虚函数也会在需表里面,只不过我们不好看
虚函数表在代码端(OS)/常量区(c语言)里面 虚表里面存的地址不是函数真正的地址,而是要跳几次才可以跳到目的函数
多继承中的虚表
打印虚表
class Base
{
public:
virtual void func1() { cout << "base1::func1" << endl; }
virtual void func2() { cout << "base1::func2" << endl; }
private:
int a;
};
class driver : public Base
{
public:
virtual void func1()
{
cout << "driver func1" << endl;
}
virtual void func3()
{
cout << "deriver func3" << endl;
}
void func4()
{
cout << "derive func4" << endl;
}
private:
int b;
};
typedef void(*vfptr)() ;
void printvftable(vfptr table[])
{
for(int i=0;table[i]!=nullptr;i++)
{
printf("vft[%d] :%p\n",i,table[i]);
vfptr f=table[i];
f();
}
cout<<endl;
}
int main()
{
person mike;
student jason;
func(mike);
func(jason);
person &r1 = jason;
person r2 = jason;
student s1;
student s2;
derive d;
Base b;
printvftable((vfptr*)(*(void**)&b));
driver s;
printvftable((vfptr*)(*(void**)&s));
return 0;
}
打印成功
|