重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这里接上面C++之多态(上篇)
成都创新互联专业为企业提供常德网站建设、常德做网站、常德网站设计、常德网站制作等企业网站建设、网页设计与制作、常德企业网站模板建站服务,10余年常德做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。本篇目录从上面虚函数的分析中我们已经知道了多态的原理,接下来我们从更深层次去探索多态。
class Person
{public:
virtual void BuyTicket()
{cout<< "买票-全价"<< endl;
}
virtual void Func1(){}
};
class Student :public Person
{public:
virtual void BuyTicket()
{cout<< "买票-半价"<< endl;
}
virtual void Func2(){}
};
int main()
{//同一个类型的对象共用一个虚表
Person p1;
Person p2;
//vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个
Student s1;
Student s2;
return 0;
}
通过上图我们发现,同一个类型的对象共用同一个虚表,vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个,除此之外,我们发现,vs的监视窗口下,子类自己的虚函数Func2(), 它是在子类自己的虚函数表中的,但是vs的监视窗口却没有显示出来,下面我们用一段程序将其展示出来。
虚表的本质是一个函数指针数组
通过上图我们发现对象虚表的地址在它对象的地址处的值取前4个字节即可。
class Person
{public:
virtual void BuyTicket()
{cout<< "Person::买票-全价"<< endl;
}
virtual void Func1()
{cout<< "Person::Func1()"<< endl;
}
};
class Student :public Person
{public:
virtual void BuyTicket()
{cout<< "Student::买票-半价"<< endl;
}
virtual void Func2()
{cout<< "Student::Func2()"<< endl;
}
};
typedef void(*VFPTR)();
//void PrintVFTable(VFPTR table[])//打印虚函数表
void PrintVFTable(VFPTR* table,size_t n)//打印虚函数表中的虚函数地址并且调用虚函数
{//for (size_t i = 0; table[i] != nullptr; ++i)
for (size_t i = 0; i< n; ++i)
{printf("vft[%d]:%p->", i, table[i]);
//table[i]();
VFPTR pf = table[i];//有点类似与强制类型转换
pf();
}
cout<< endl;
}
int main()
{//同一个类型的对象共用一个虚表
Person p1;
Person p2;
//vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个
Student s1;
Student s2;
//取对象头部虚函数表指针传递过去
//这里的2,3是我们知道对象的虚函数的个数
PrintVFTable((VFPTR*)*(int*)&p1,2);
PrintVFTable((VFPTR*)*(int*)&s1,3);
return 0;
}
4.3 C++ 11 override和final从上面可以看出,C++对函数的重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名的字母次序写反而无法构成重载,而这种错误在编译期间是不会报出来的,只有程序运行时没有得到预期结果才来debug会得不偿失,因此:C++ 11提供了override和final这两个关键字,可以用来帮助用户检测是否重写。
1.final:修饰虚函数,表示该虚函数不能被重写
class Car
{public:
virtual void Drive()final{}
};
class Benz :public Car
{public:
virtual void Drive() //error
{cout<< "Benz-舒适"<< endl;
}
};
2.override:检查派生类虚函数是否重写基类某个虚函数,如果没有编译报错
class Car
{public:
virtual void Drive(){}
};
class Benz :public Car
{public:
//检查子类虚函数是否完成重写
virtual void Drive()override
{cout<< "Benz-舒适"<< endl;
}
};
4.4 重载、重写(覆盖)、隐藏(重定义)的对比 (函数之间的关系)5.抽象类
5.1概念在虚函数的后面写上 =0,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
class Car
{public:
virtual void Drive() = 0;
};
class Benz :public Car
{public:
virtual void Drive()
{cout<< "Benz-舒适"<< endl;
}
};
class BwM:public Car
{public:
virtual void Drive()
{cout<< "BMW-操控"<< endl;
}
};
int main()
{//Car c;//抽象类不能实例化对象
//BwM b;
Car* ptr = new BwM;
ptr->Drive();//多态的体现
ptr = new Benz;
ptr->Drive();
return 0;
}
5.2接口继承和实现继承普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
6.单继承和多继承关系的虚函数表需要注意的是在单继承和多继承关系中, 下面我们去关注的是派生类对象的虚表模型,因为基类的虚表模型前面我们已经看过了,没什么需要特别研究的。
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;
};
typedef void(*VFPTR)();
//void PrintVFTable(VFPTR table[])//打印虚函数表
void PrintVFTable(VFPTR* table,size_t n)//打印虚函数表中的虚函数地址并且调用虚函数
{//for (size_t i = 0; table[i] != nullptr; ++i)
for (size_t i = 0; i< n; ++i)
{printf("vft[%d]:%p->", i, table[i]);
table[i]();
VFPTR pf = table[i];//有点类似与强制类型转换
pf();
}
cout<< endl;
}
int main()
{Derive d;
PrintVFTable((VFPTR*)*(int*)&d,3);//打印Base1虚函数表
PrintVFTable((VFPTR*)*(int*)((char*)&d + sizeof(Base1)), 2);//法一:打印Base2虚函数表
//法二:打印Base2虚函数表
Base2* ptr2 = &d;//切片得到Base2的地址
PrintVFTable((VFPTR*)(*(int*)ptr2), 2);
return 0;
}
更深层次的问题
观察上图我们发现Base1中的func1和Base2中的func1都被Derive进行了重写,它们的内容是一样的,应该指向同一份函数,但是它们的地址为什么不一样呢?
下篇文章我们会揭晓!!!
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧