多态与虚函数
晚绑定:编译时不能确定函数名和地址的关系,在运行的过程中确定
需要用到运行时的查表方案
virtual
没有继承关系,不要把成员函数定义成虚函数
就近原则(同名隐藏)
class Object
{
public:
int value;
public:
Object(int x = 0) :value(x) {}
};
class Base : public Object
{
public:
int value;
int num;
public:
Base(int x = 0) :Object(x), value(x + 10), num(x + 20) {}
void fun()
{
value = 10;//访问的是Base的value(就近原则)
num = 20;
Object::value = 20;//访问的是Object的value
}
};
int main()
{
Base base;
base.value = 10;//访问的是Base的value(就近原则)
base.Object::value = 20;//访问的是隐藏父对象的value
}
同名隐藏和可访问属性没有任何关系
class Object
{
public:
void fun(int x,int y,int z) {
cout << "Object::fun " << x << endl;
}
};
class Base : public Object
{
public:
void fun(int x) {
cout << "Base::fun " << x << endl;
}
void fun(int a, int b) {
cout << "Base::fun(int,int): " << a << " " << b << endl;
}
};
int main()
{
Base base;
base.fun(12);//调动base自己的
base.fun(1, 2);//调动base自己的
//base.fun(1, 2, 3);//编译不通过,因为Base里的fun函数把隐藏基类的fun函数隐藏掉了
base.Object::fun(1,2,3);//编译通过,告诉编译器调动的是隐藏基对象的fun函数
return 0;
}
对于Base这个类型我们有两个fun函数,参数和类型不一致,这两个函数在Base中是一种重载关系。
但一定要注意,Base里的两个fun函数和父类Object的fun函数不具有重载关系
跨类型之间,没有函数重载的概念,这叫同名隐藏
函数重载:全局函数或者同属于一个类的
虚函数
class Animal
{
private:
string name;
public:
Animal(const string& na) :name(na) {}
~Animal() {}
virtual void eat() {}
virtual void walk() {}
virtual void talk() {}
virtual void PrintInfo() const{}
const string& GetName() const { return name; }
};
class Dog : public Animal
{
private:
string owner;
public:
Dog(const string& own, const string& na) :Animal(na), owner(own) {}
~Dog() {}
public:
virtual void eat() { cout << "Dog Eat: bone " << endl; }
virtual void walk() { cout << "Dog Walk: run " << endl; }
virtual void talk() { cout << "Dog talk: wang wang .." << endl; }
virtual void PrintInfo() const {
cout<<"Dog Owner: " << owner << endl;
cout << "Dog Name: " << GetName() << endl;
}
};
class Cat : public Animal
{
private:
string owner;
public:
Cat(const string& own, const string& na) :Animal(na), owner(own) {}
~Cat() {}
public:
virtual void eat() { cout << "Cat Eat: fish " << endl; }
virtual void walk() { cout << "Cat Walk: silent " << endl; }
virtual void talk() { cout << "Cat talk: Miao miao .." << endl; }
virtual void PrintInfo() const {
cout << "Cat Owner: " << owner << endl;
cout << "Cat Name: " << GetName() << endl;
}
};
void fun(Animal& animal)
{
animal.eat();
animal.walk();
animal.talk();
animal.PrintInfo();
}
int main()
{
Dog dog("yhping", "hashiqi");
Cat cat("tulun", "Persian");
fun(dog);
fun(cat);
return 0;
}
先看下面这个例子:
有一个动物,有走的方法
派生出狗,狗把这个方法重写
派生出人,人把这个方法重写
狗是动物,人是动物,都是继承来的
狗走的方法是四条腿
人走的方法是两条腿
不同对象的生成,它体现的走法也不一样
当我们在动物,定义一个引用,当我们引用狗对象的时候,调动狗的虚方法,当引用人对象,调用人的虚方法
回到主题上
我们有动物(名字,说话声,走路方式)
派生出狗和猫(是一个的意思)
我们把虚方法重写了
动物的引用,引用猫,调用的是猫,引用狗,调用的是狗,体现多态性
赋值兼容规则:可以把子对象给父引用,可以把子对象给父指针
看下面:
学生是一个人,可以用人里面的方法
人不一定是学生,不能调动show()
p能否调动show()?
p->show();不能编译通过 p指针是父类型的,p里面没有show函数,编译不能通过
干货来了
父指针指向子对象调动虚方法,可以转向去调动子类的虚方法,多态性
三同:返回类型,函数名,参数列表相同
并不认为子类重写了父类的虚函数
也不认为重载
而是认为同名隐藏
唯一的一个例外:
这种情况也称作多态
只有类的成员函数才能声明成虚函数
下面这三种函数不能被声明成虚函数
virtual static void show() {}//错误,静态函数无this指针
virtual friend void print();//错误,无this指针
virtual inline void f();//错误,调用内联,代码在调用点展开了,编译时已把代码展开了
构造函数,拷贝构造函数,移动构造函数,不能定义成虚函数
构造函数是在置虚表指针
析构函数可以定义成虚函数,析构函数是在重置虚表指针
指针调动虚函数,或者引用调动虚函数,才能实现多态性(有查表代码)
虚表
从头开始看代码
定义了一个Object类,定义了一个私有成员value;还有Object的构造函数,定义了add,fun,print这样的虚函数
Base类继承Object,定义了一个私有成员num,然后还有构造函数,重写了add,fun,添加了自己的虚函数show
Test类继承Base:定义了一个私有成员count,然后还有构造函数,重写了add,print,show函数
在这里发生了同名覆盖(虚函数)
我们先编译Object类型,发现有3个虚函数:add,fun,print,会做一个虚表,虚表在只读的数据区,填进去的是add,fun,print的函数地址
然后编译Base类型,会把上面这个虚表拷贝一份,虚表的名字改为Base vftable,Base重写了add函数,把原本的Object的add覆盖掉,fun重写了,把原本Object的fun覆盖了,Object的print没有重写,就留在原来位置上。然后又新写了一个虚函数show添加进去
当编译Test类型的时候,把Base的虚部拷贝下来,Test重写add函数,覆盖Base的add函数,Base的fun函数没有重写,仍然是Base的fun函数,print重写了,覆盖Object的print函数,show函数重写了,覆盖Base的show函数
以上就是同名覆盖
上面这种情况就没有虚表(以上是同名隐藏)
只有虚函数,编译时才有虚表!
test对象的大小是多大?
test对象里有Object,Object由两部分构成,虚表指针和value构成
Base继承Object,只有自己的一个成员:num
test继承Base:只有自己的一个成员:count
当我们构建test,首先得调动Object的构造函数,value=0;虚表指针指向Object,当我们构建好Object对象时,Object的构造函数返回到Base的构造函数,我们构建Base对象,num=0+10=10;重置虚表,虚表指针指向Base的虚表。构建完成Base,回到构造Test对象,count=10;重置虚表,虚表指针指向Test的虚表
如果是只构建Base对象
最后的虚表指向Base的虚表
如果是只构建Object对象
最后的虚表指向Object的虚表
虚表指针只有一个哦
#include<iostream>
#include<string>
using namespace std;
class Object {
private: int value;
public: Object(int x = 0) :value(x)
{
}
virtual void add() { cout << "Object::add()" << endl; }
virtual void fun() { cout << "Object::fun()" << endl; }
virtual void print() const { cout << "Object::print()" << endl; }
};
class Base : public Object {
private: int num;
public: Base(int x = 0) :Object(x + 10), num(x)
{
}
virtual void add() { cout << "Base::add()" << endl; }
virtual void fun() {
cout << "Base::fun()" << endl;
this->show();
}
virtual void show() { cout << "Base::show()" << endl; }
};
class Test : public Base {
private: int count;
public: Test(int x = 0) :Base(x), count(x + 10)
{
}
virtual void add() { cout << "Test::add()" << endl; }
virtual void print() const { cout << "Test::print()" << endl; }
virtual void show() { cout << "Test::show()" << endl; }
void call() {
cout << "Test::call()" << endl;
this->fun();
}
};
这里的op不能调动show函数,op指向函数,编译时受类型限制,op是Object类型,不能编译通过
yhp函数通过对象地址查虚表,调动
如果是对象点调动,则不能查虚表
调动的各自对象的虚表
void yhp(Object* op)
{
op->add();
op->fun();
op->print();
printf("\n");
}
int main()
{
Object obj;
Base base;
Test test;
yhp(&obj);
yhp(&base);
yhp(&test);
return 0;
}
void tulun(Base* bp)
{
bp->add();
bp->fun();
bp->print();
bp->show();//对于编译器来说,Base有4个虚函数,都可以放进去
}
int main()
{
Object obj;
Base base;
Test test;
tulun(&base);
tulun(&test);
return 0;
}
#include<iostream>
#include<string>
using namespace std;
class Object {
private: int value;
public: Object(int x = 0) :value(x)
{
}
virtual void add() { cout << "Object::add()" << endl; }
virtual void fun() { cout << "Object::fun()" << endl; }
virtual void print() const { cout << "Object::print()" << endl; }
};
class Base : public Object {
private: int num;
public: Base(int x = 0) :Object(x + 10), num(x)
{
}
virtual void add() { cout << "Base::add()" << endl; }
virtual void fun() {
cout << "Base::fun()" << endl;
this->show();
}
virtual void show() { cout << "Base::show()" << endl; }
};
class Test : public Base {
private: int count;
public: Test(int x = 0) :Base(x), count(x + 10)
{
}
virtual void add() { cout << "Test::add()" << endl; }
virtual void print() const { cout << "Test::print()" << endl; }
virtual void show() { cout << "Test::show()" << endl; }
void call() {
cout << "Test::call()" << endl;
this->fun();
}
};
int main()
{
Test test;
test.call();
return 0;
}
此主函数打印结果是什么?
Test::call()
Base::fun()
Test::show()
原因:先打印Test::call(),Test没有改写fun虚函数,转向调用Bse的fun虚函数,打印Base::fun(),this->show()此时查的虚表仍然是test的虚表,调用test的show虚函数,打印Test::show(),因为构建的是test对象,已经把base的虚表覆盖了,base是test的隐藏父对象,是test对象的一部分,base对象的this和test对象的this一样,都是指向一样的位置地址,都是指向test对象的空间的最顶部的位置地址
执行
ecx获取的是test的地址
edx存放的是虚表的地址
调动第一个函数,add函数
[edx]意思是取它所指之物
edx指向虚表的首地址,调动add,[edx]就是取add这个函数的地址,调动完add函数,走了。
调动print函数
edx+8
add函数相当于0位置,每个指针4个字节,edx加8就是print函数的地址了。
为什么会形成多态?
虚表里的函数顺序永远和定义的基类里的虚函数的顺序一致
不按字典序
编译器编译的时候,产生Object的虚表
编译Base的时候,把虚表拷贝下来,产生Base的虚表,已定义的就用指针修改了,变成Base的虚表
编译Test的时候,拷贝虚表,改成Test的虚表
怎么调动?
首先根据这个指针p,指向base,把base的虚表抽出来,放到edx(虚表首地址),调动fun,从edx的第0下标抽取,调用print,edx+4,调动show,edx+8,
如果给的是&test,test对象的地址,p指针调用,把test的虚表的指针给edx,和上面方法一致。
父指针指哪个子对象,把哪个对象的虚表地址(前4个字节)给edx。
抽内容,内容就是函数地址
**子对象重写函数,只是覆盖掉,顺序不变的!**这就是为什么先生成基类的虚表
为了可以edx+4,edx+8
顺序不变!!!
调动构造函数构建obj1
调动构造函数obj2
进入fun函数,调动构造函数构obja
fun函数结束,把obja析构掉
回退到主函数结束,要析构2个对象地址,调动2次析构函数
程序怎么知道调动两个对象?
在栈帧规则中
给主函数分配栈帧
在底层信息(或者在上层),调动一次构造函数就记一次
当主函数要退出时,它就在这看记了几次,2个,调用2次析构函数
仍然把创建对象个数记录起来
没有写析构函数,程序就不记录。
fun函数中也有记录创建对象的个数