c++类型兼容规则与虚函数实现多态的实现原理和区别
2017-09-19 21:51
801 查看
大一之后就没有系统的学过c++了,最近为了校园招聘,又把c++知识捡出来学习,弄了2个钟头终于把这里面的原理弄懂了。
背景:
类型兼容规则含义:类型兼容规则指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
类型兼容规则应用场合:
有如下代码:
class B{...}
class D:public B{...}
B b1,* pb1;
D d1;
派生类的对象可以隐含转化为基类对象。eg:b1 = d1
派生类的对象可以初始化基类的引用。eg:B &rb = d1
派生类的指针可以隐含转化为基类的指针。eg:pb1 = &d1
虚函数含义:使用关键字virtual定义的类成员函数。
虚函数实习多态需要满足的条件:
类之间满足类型兼容规则。
要声明虚函数。(自动声明虚函数需要满足派生类函数同基类虚函数同名、同参、同返回或满足赋值兼容规则的指针、引用返回值)
由成员函数调用或者通过指针、引用来访问虚函数。
实践分析:
在分析之前需要知道如下的几个类和对象的内存分配方式:
所有类成员函数,无论静态与否实际上在内存中都只有一个副本,且保存在内存代码段。为对象开辟的内存只保存类中的成员变量。
虚函数是通过,对象中的虚表指针(vptr)访问虚表(vritual table)的方式确定指定虚函数的入口指针,从而实现对应虚函数的访问。
每个对象都有个虚表指针,该指针是在构造函数中被赋值,所有同类对象共享内存中的一个虚表。对象复制时的虚表指针不参与赋值。
在虚表中,派生类的虚函数会覆盖基类中的同名同参同返回值的虚函数,并且隐藏基类中同名函数的所有重载。
场景1:派生类的指针可以隐含转化为基类的指针
program1:
#include <iostream>
using namespacestd;
class base{
public:
void display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base *p){
p->display();
}
int main(){
base b1;
derived d1;
fun(&b1);
fun(&d1);
return0;
}
output1:
Base::display
Base::display
program2:
#include <iostream>
using namespacestd;
class base{
public:
virtualvoid display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base *p){
p->display();
}
int main(){
base b1;
derived d1;
fun(&b1);
fun(&d1);
return0;
}
output2:
Base::display
derived::display
分析:
程序1没有使用虚函数,那么程序编译阶段处理语句p->display();的时候根据p的类型base将该函数,编译为base中display的函数体。编译的时候已经决定使用那个函数了。程序2使用了虚函数,编译阶段处理处理语句p->display();知道该语句是虚函数,对虚函数的访问是在运行过程中通过vptr指向的虚表实现动态调用。那么p所指向的对象内存中存放的vptr指向哪个虚表,该次执行便访问该虚表中对应的函数。因为派生类中display同基类中display同名同参同返回,所以在虚表中只保存派生类中的display入口地址。
场景2:派生类的对象可以初始化基类的引用
该过程同场景1一样,结果及原因都相同,即虚函数在该场景下可以重载。故不再赘述。
场景3:派生类的对象可以隐含转化为基类对象
program1:
#include <iostream>
using namespacestd;
class base{
public:
void display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base b){
b.display();
}
int main(){
base b1;
derived d1;
fun(b1);
fun(d1);
return0;
}
output1:
Base::display
Base::display
program2:
#include <iostream>
using namespacestd;
class base{
public:
virtualvoid display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base b){
b.display();
}
int main(){
base b1;
derived d1;
fun(b1);
fun(d1);
return0;
}
output2:
Base::display
Base::display
分析:
可以看出在该场景下b.display();没有虚函数没有实现多态。原因分析可能有两个,具体还有待进一步研究。
在编译阶段,因为虚函数是通过普通(非引用对象)调用的,所以编译阶段认为不符合,多态的条件。就直接根据类型原则,静态编译为base1的display函数了。
根据虚函数的调用原理,访问对象b的内存中vptr指向的虚表由于vptr不参与复制,它由类构造函数设置为指向属于base类所有对象的共公虚表,该虚表中指向dispaly函数的入口指针,只是在该类中定义的display函数的入口指针。故没有实现多态。
总结:
c++语法规则十分复制,而只要把一些机制的内部运行机理搞清楚,不用死记硬背,学起来就快很多了。
该文章中的分析只是,个人学习结果,具体正确性还有待进一步的验证。
背景:
类型兼容规则含义:类型兼容规则指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
类型兼容规则应用场合:
有如下代码:
class B{...}
class D:public B{...}
B b1,* pb1;
D d1;
派生类的对象可以隐含转化为基类对象。eg:b1 = d1
派生类的对象可以初始化基类的引用。eg:B &rb = d1
派生类的指针可以隐含转化为基类的指针。eg:pb1 = &d1
虚函数含义:使用关键字virtual定义的类成员函数。
虚函数实习多态需要满足的条件:
类之间满足类型兼容规则。
要声明虚函数。(自动声明虚函数需要满足派生类函数同基类虚函数同名、同参、同返回或满足赋值兼容规则的指针、引用返回值)
由成员函数调用或者通过指针、引用来访问虚函数。
实践分析:
在分析之前需要知道如下的几个类和对象的内存分配方式:
所有类成员函数,无论静态与否实际上在内存中都只有一个副本,且保存在内存代码段。为对象开辟的内存只保存类中的成员变量。
虚函数是通过,对象中的虚表指针(vptr)访问虚表(vritual table)的方式确定指定虚函数的入口指针,从而实现对应虚函数的访问。
每个对象都有个虚表指针,该指针是在构造函数中被赋值,所有同类对象共享内存中的一个虚表。对象复制时的虚表指针不参与赋值。
在虚表中,派生类的虚函数会覆盖基类中的同名同参同返回值的虚函数,并且隐藏基类中同名函数的所有重载。
场景1:派生类的指针可以隐含转化为基类的指针
program1:
#include <iostream>
using namespacestd;
class base{
public:
void display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base *p){
p->display();
}
int main(){
base b1;
derived d1;
fun(&b1);
fun(&d1);
return0;
}
output1:
Base::display
Base::display
program2:
#include <iostream>
using namespacestd;
class base{
public:
virtualvoid display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base *p){
p->display();
}
int main(){
base b1;
derived d1;
fun(&b1);
fun(&d1);
return0;
}
output2:
Base::display
derived::display
分析:
程序1没有使用虚函数,那么程序编译阶段处理语句p->display();的时候根据p的类型base将该函数,编译为base中display的函数体。编译的时候已经决定使用那个函数了。程序2使用了虚函数,编译阶段处理处理语句p->display();知道该语句是虚函数,对虚函数的访问是在运行过程中通过vptr指向的虚表实现动态调用。那么p所指向的对象内存中存放的vptr指向哪个虚表,该次执行便访问该虚表中对应的函数。因为派生类中display同基类中display同名同参同返回,所以在虚表中只保存派生类中的display入口地址。
场景2:派生类的对象可以初始化基类的引用
该过程同场景1一样,结果及原因都相同,即虚函数在该场景下可以重载。故不再赘述。
场景3:派生类的对象可以隐含转化为基类对象
program1:
#include <iostream>
using namespacestd;
class base{
public:
void display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base b){
b.display();
}
int main(){
base b1;
derived d1;
fun(b1);
fun(d1);
return0;
}
output1:
Base::display
Base::display
program2:
#include <iostream>
using namespacestd;
class base{
public:
virtualvoid display()const;
int a=1;
};
void base::display()const{
cout<<"Base::display"<<endl;
}
class derived:publicbase{
public:
virtualvoid display()const;
int b=2;
};
void derived::display()const{
cout<<"derived::display"<<endl;
}
void fun(base b){
b.display();
}
int main(){
base b1;
derived d1;
fun(b1);
fun(d1);
return0;
}
output2:
Base::display
Base::display
分析:
可以看出在该场景下b.display();没有虚函数没有实现多态。原因分析可能有两个,具体还有待进一步研究。
在编译阶段,因为虚函数是通过普通(非引用对象)调用的,所以编译阶段认为不符合,多态的条件。就直接根据类型原则,静态编译为base1的display函数了。
根据虚函数的调用原理,访问对象b的内存中vptr指向的虚表由于vptr不参与复制,它由类构造函数设置为指向属于base类所有对象的共公虚表,该虚表中指向dispaly函数的入口指针,只是在该类中定义的display函数的入口指针。故没有实现多态。
总结:
c++语法规则十分复制,而只要把一些机制的内部运行机理搞清楚,不用死记硬背,学起来就快很多了。
该文章中的分析只是,个人学习结果,具体正确性还有待进一步的验证。
相关文章推荐
- C++中动态类型与动态绑定、虚函数、运行时多态的实现
- 【继承与多态】C++:继承中的赋值兼容规则,子类的成员函数,虚函数(重写),多态
- C++多态,虚函数作用及底层实现原理
- C++中的动态类型与动态绑定、虚函数、运行时多态的实现
- C++中的动态类型与动态绑定、虚函数、运行时多态的实现
- C++中的动态类型与动态绑定、虚函数、运行时多态的实现
- C++中的动态类型与动态绑定、虚函数、运行时多态的实现
- 【继承与多态】C++:继承中的赋值兼容规则,子类的成员函数,虚函数(重写),多态
- C++中的动态类型与动态绑定、虚函数、运行时多态的实现
- c++ 虚函数实现多态的原理
- 理解C++的多态原理及实现
- C++虚函数实现原理分析
- C++中多态的实现原理
- (转)C++多态的实现原理,C++中虚函数和多态
- (转)C++中多态的实现原理
- C++的多态原理和实现
- C++多态如何实现 | sys_brk原理
- C++多态的实现原理
- c++虚函数原理及实现(转载)
- C++的多态原理和实现