您的位置:首页 > 编程语言 > C语言/C++

C++基础7【难】 多态:实现原理 vptr指针 证明vptr存在 类的步长 纯虚函数:抽象类 案例 【面试题】

2016-07-06 11:14 1581 查看
1):多态的原理探究
证明vptr指针的存在
添加一个虚函数,类的大小也不会发生改变

2):【面试题】构造函数中能调用虚函数,实现多态吗
父类指针 子类指针,步长问题
1,父类结构 与 子类结构大小一样时
2,父类结构 与 子类结构大小不一样时

3):【纯虚函数】抽象类

【纯虚函数不能被实例化】
纯虚函数的实例;

证明,纯虚函数不会发生二义性

抽象类在多继承中的应用·案例
纯虚函数,计算程序员工资
多态案例:C++实现socket通信

4):【面试题】

=========================================================

多态的原理探究:
1.热身
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Parent
{
public:
Parent(int a= 0)
{
this->a = a;
}
virtual void printf()//动手脚1 写了virtual关键字会特殊处理
{
cout << "我是你爹\n";
}
private:
int a;
};

class Child : public Parent
{
public:
Child(int a):Parent(2)
{
this->a = a;
}
virtual void printf()
{
cout << "我是儿子\n";
}
private:
int a;
};

void fun(Parent *p)
{
p->printf();	//动手脚2,会有多态发生
}

/*
【面试】
多态成立的3个条件:
继承
重写
父类指针指向子类
*/
int main()
{
Parent p1(2);//动手脚3,提前布局,产生vptr指针
Child  c1(1);//子类;里面也会有vptr指针
fun(&p1);
fun(&c1);

return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
我是你爹
我是儿子
chunli@http://990487026.blog.51cto.com~$


多态的原理探究
1.当类中声明虚函数时,编译器会在类中生成一个虚函数。 2.虚函数表是一个存储类成员函数指针的数据结构。 3.虚函数表是由编译器自动生成与维护的。 4.virtual成员函数会被编译器放入虚函数表中。 5.存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。








说明1: 通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。 说明2: 出于效率考虑,没有必要将所有成员函数都声明为虚函数============================================================

证明vptr指针的存在
加了virtual关键字,编译器会在类中自动添加一个指针
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Parent
{
public:
virtual void printf()
{
}
double a;
};

class Child
{
public:
double a;
};

int main()
{
cout << sizeof(Parent) << endl;
cout << sizeof(Child) << endl;
return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
16
8
chunli@http://990487026.blog.51cto.com~$


即使再添加一个虚函数,类的大小也不会发生改变
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Parent
{
public:

virtual void printf1()
{
}
virtual void printf()
{
}
double a;
};

class Child
{
public:
double a;
};

int main()
{
cout << sizeof(Parent) << endl;
cout << sizeof(Child) << endl;
return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
16
8
chunli@http://990487026.blog.51cto.com~$


【面试题】构造函数中能调用虚函数,实现多态吗
不可以,从vptr分布初始化来回答

【vptr分布初始化,示意图】



【代码实现】
chunli@http://990487026.blog.51cto.com~$
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Parent
{
public:
Parent(int a= 0)
{
this->a = a;
printf();	//这里是调用父类的函数 还是调用子类的函数?
}
virtual void printf()
{
cout << "我是你爹\n";
}
private:
int a;
};

class Child : public Parent
{
public:
Child(int a):Parent(2)
{
this->a = a;
printf();	//这里是调用父类的函数 还是调用子类的函数?
}
virtual void printf()
{
cout << "我是儿子\n";
}
private:
int a;
};

void fun(Parent *p)
{
p->printf();
}

int main()
{
//Parent p1(2);
Child  c1(1);
//fun(&p1);
//fun(&c1);

return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
我是你爹
我是儿子
chunli@http://990487026.blog.51cto.com~$


父类指针 子类指针,步长问题

1,父类结构 与 子类结构大小一样时
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Parent
{
public:
Parent(int a= 0)
{
this->a = a;
}
virtual void printf()
{
cout << "我是你爹\n";
}
private:
int a;
};

class Child : public Parent
{
public:
Child(int a):Parent(2)
{
this->a = a;
}
virtual void printf()
{
cout << "我是儿子\n";
}
private:
int a;
};

void fun(Parent *p)
{
p->printf();
}

int main()
{
Child  c1(1);
Child 	*pC = NULL;
Parent 	*pP = NULL;
Child arr[] ={Child(1),Child(2),Child(3)};
pC = arr;
pP = arr;
pC->printf();
pP->printf();

pC++;
pP++;
pC->printf();
pP->printf();

pC++;
pP++;
pC->printf();
pP->printf();

return 0;
}

编译运行,发生多态了
chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
我是儿子
我是儿子
我是儿子
我是儿子
我是儿子
我是儿子
chunli@http://990487026.blog.51cto.com~$


2,父类结构 与 子类结构大小不一样时
在子类的大小比父类大,程序就会宕掉
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Parent
{
public:
Parent(int a= 0)
{
this->a = a;
}
virtual void printf()
{
cout << "我是你爹\n";
}
private:
int a;
};

class Child : public Parent
{
public:
Child(int a):Parent(2)
{
this->a = a;
}
virtual void printf()
{
cout << "我是儿子\n";
}
private:
int a;
int b;
};

void fun(Parent *p)
{
p->printf();
}

int main()
{
Child  c1(1);
Child 	*pC = NULL;
Parent 	*pP = NULL;
Child arr[] ={Child(1),Child(2),Child(3)};
pC = arr;
pP = arr;
pC->printf();
pP->printf();

pC++;
pP++;
pC->printf();
pP->printf();

pC++;
pP++;
pC->printf();
pP->printf();

return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
我是儿子
我是儿子
我是儿子
Segmentation fault (core dumped)
chunli@http://990487026.blog.51cto.com~$


父类指针 子类指针,步长
【看画图】



【结论】:多态是父类指针指向子类对象,和 父类指针步长++,是两个不同的概念

【纯虚函数】含有纯虚函数的类叫抽象类
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Figure
{
public:
virtual int get_area() = 0;
};

int main()
{
return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
chunli@http://990487026.blog.51cto.com~$


【纯虚函数不能被实例化】
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Figure
{
public:
virtual int get_area() = 0;
};

int main()
{
Figure f1;
return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
main.cpp: In function ‘int main()’:
main.cpp:12:8: error: cannot declare variable ‘f1’ to be of abstract type Figure
Figure f1;
^


纯虚函数的实例;
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Figure
{
public:
virtual int get_area() = 0;
};

class Cricle :public Figure
{
public :
Cricle(int a)
{
this->a = a;
}
virtual int get_area()
{
return 3.14 * a *a;
}
private:
int a;
};

class Tri :public Figure
{
public :
Tri(int a,int b)
{
this->a = a;
this->b = b;
}
virtual int get_area()
{
return 0.5 * a *b;
}
private:
int a;
int b;
};

class Square :public Figure
{
public :
Square(int a,int b)
{
this->a = a;
this->b = b;
}
virtual int get_area()
{
return a *b;
}
private:
int a;
int b;
};

void fun(Figure *p)
{
cout <<"面积=" << p->get_area()  << endl;
}
int main()
{
Cricle c1(100);
Tri    t1(10,20);
Square s1(10,20);
fun(&c1);
fun(&t1);
fun(&s1);
return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
面积=31400
面积=100
面积=200
chunli@http://990487026.blog.51cto.com~$


证明,纯虚函数不会发生二义性
1,热身
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class A
{
public:
int a;
};

class B1:virtual public A
{
public:
int b1;
};

class B2:virtual public A
{
public:
int b1;
};
class C :public B1,public B2
{
public:
int c;
};

int main()
{
C c;
c.c = 10;
c.a = 10;	//不知道访问哪个
return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
chunli@http://990487026.blog.51cto.com~$


抽象类在多继承中的应用·计算器案例
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Interface1
{
public:
virtual int add(int a,int b) = 0;
virtual void printf()= 0;
};

class Interface2
{
public:
virtual int mult(int a,int b) = 0;
virtual void printf()= 0;
};

class Parent
{
public:
int get_a()
{
return 1;
}
private:
int a;
};

class Child:public Parent,public Interface1,public Interface2
{
public:
virtual int mult(int a,int b)
{
cout << "Child  mult \n";
return 0;
}
virtual void printf()
{
cout << "Child \n";
}
virtual int add(int a,int b)
{
cout << "Child add \n";
return 0;
}

};

int main()
{
Interface1 *i1 = NULL;
Interface2 *i2 = NULL;
Child c1;
i1 = &c1; 	i1->add(1,2);  i1->printf();
i2 = &c1; 	i2->mult(1,2);  i2->printf();

return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
Child add
Child
Child  mult
Child
chunli@http://990487026.blog.51cto.com~$


纯虚函数,计算程序员工资
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <iostream>
using namespace std;

class Salary
{
public:
virtual int salary() = 0;
};

class Programmer_low:public Salary
{
public:
virtual int salary()
{
return 6000;
}
private:
};

class Programmer_mid:public Salary
{
public:
virtual int salary()
{
return 12000;
}
private:
};

class Programmer_high:public Salary
{
public:
virtual int salary()
{
return 24000;
}
private:
};

int main()
{
Salary *p = NULL;
Programmer_low l;
Programmer_mid m;
Programmer_high h;
p = &l; 	cout << "薪水="<<p->salary() << endl;
p = &m; 	cout << "薪水="<<p->salary() << endl;
p = &h; 	cout << "薪水="<<p->salary() << endl;

return 0;
}

chunli@http://990487026.blog.51cto.com~$ g++  -o run main.cpp  && ./run
薪水=6000
薪水=12000
薪水=24000
chunli@http://990487026.blog.51cto.com~$


多态案例:C++实现socket通信
我是客户,思科是通信提供商
我这边的有一个程序,抽象类已经定义完成,需要思科提供抽象类函数数据发送接收的实现:

4个文件:
chunli@http://990487026.blog.51cto.com~$ ll
total 16K
-rw-rw-r-- 1 chunli chunli 912 Jul  7 11:20 CSckFactoryImp1.cpp
-rw-rw-r-- 1 chunli chunli 478 Jul  7 11:09 CSckFactoryImp1.h
-rw-rw-r-- 1 chunli chunli 449 Jul  7 11:24 CSocketProtocol.h
-rw-rw-r-- 1 chunli chunli 997 Jul  7 11:26 main.cpp
chunli@http://990487026.blog.51cto.com~$
main 和 CSocketProtocol 是我定义的抽象类
CSckFactoryImp1 是思科的实现

源代码:
chunli@http://990487026.blog.51cto.com~$ cat CSocketProtocol.h
#include <iostream>
using namespace std;
#ifndef _CSocketProtocol_H_
#define _CSocketProtocol_H_
class CSocketProtocol
{
public:
CSocketProtocol()	{}
virtual ~CSocketProtocol(){}
virtual int cltSocketInit() = 0;
virtual int cltSocketSend(unsigned char *buf /*in*/,  int buflen /*in*/)= 0;
virtual int cltSocketRev(unsigned char *buf /*in*/, int *buflen /*in out*/) = 0;
virtual int cltSocketDestory() = 0;
private:
void **handle;
};

#endif
chunli@http://990487026.blog.51cto.com~$ cat main.cpp
#include <string.h>
#include <iostream>
#include "CSocketProtocol.h"
#include "CSckFactoryImp1.h"
using namespace std;

int SckSendAndRec(CSocketProtocol *sp,unsigned char *in,int inlen,unsigned char *out,int *outlen)
{
int ret  = 0;
ret = sp->cltSocketInit();
if(ret != 0)
{
goto End;
}
cout << "我发送的报文是:" << in << endl;
ret = sp->cltSocketSend(in,inlen);
if(ret != 0)
{
goto End;
}
ret = sp->cltSocketRev(out,outlen);
if(ret != 0)
{
goto End;
}
cout << "我接收的报文是:" << out << endl;
End:
ret = sp->cltSocketDestory();
return 0;
}
int main()
{
int ret = 0;
unsigned char in[4096];
int inlen;
unsigned char out[4096];
int outlen;
CSocketProtocol *sp =NULL;
sp = new CSckFactoryImp1;

strcpy((char*)in,"Hello World!");
inlen = strlen((char *)in);;

ret = SckSendAndRec(sp,in,inlen,out,&outlen);
if(ret != 0)
{
cout << "Error in SckSendAndRec:"<<ret << endl;
return ret;
}
delete sp;	//应该使用虚析构函数
return 0;
}

chunli@http://990487026.blog.51cto.com~$


思科提供的代码:
chunli@http://990487026.blog.51cto.com~$ cat CSckFactoryImp1.h
#include <iostream>
#include "CSocketProtocol.h"
using namespace std;

#ifndef _CSckFactoryImp1_H_
#define _CSckFactoryImp1_H_

class CSckFactoryImp1:public CSocketProtocol
{
public:
virtual int cltSocketInit() ;
virtual int cltSocketSend(unsigned char *buf /*in*/,  int buflen /*in*/);
virtual int cltSocketRev(unsigned char *buf /*in*/, int *buflen /*in out*/) ;
virtual int cltSocketDestory();
private:
unsigned char *p;
int len;
};
#endif
chunli@http://990487026.blog.51cto.com~$ cat CSckFactoryImp1.cpp
#include "CSckFactoryImp1.h"
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;
int CSckFactoryImp1::cltSocketInit()
{
p = NULL;
len = 0;
return 0;
}
int CSckFactoryImp1::cltSocketSend(unsigned char *buf /*in*/,  int buflen /*in*/)
{
if(buf == NULL)
{
cout << "cltSocketSend buf == NULL \n";
return -1;
}
p = (unsigned char *)malloc(sizeof(unsigned char ) * buflen);
if(p == NULL)
{
cout << "Error in CSckFactoryImp1.cpp  cltSocketSend:"<< endl;
return -2;
}
memcpy(this->p,buf,buflen);
len = buflen;
return 0;
}
int CSckFactoryImp1::cltSocketRev(unsigned char *buf /*in*/, int *buflen /*in out*/)
{
if(buf == NULL)
{
cout << "cltSocketRev buf == NULL  \n";
return -1;
}
*buflen = this->len;
memcpy(buf,this->p,this->len);
return 0;
}
int CSckFactoryImp1::cltSocketDestory()
{
if(p != NULL)
{
free(p);
p = NULL;
len = 0;
}
return 0;
}

chunli@http://990487026.blog.51cto.com~$


编译运行:
chunli@http://990487026.blog.51cto.com~$ g++ -g -o run main.cpp CSckFactoryImp1.cpp  && ./run
我发送的报文是:Hello World!
我接收的报文是:Hello World!
chunli@http://990487026.blog.51cto.com~$


面试题1:请谈谈你对多态的理解

多态的实现效果
当用父类对象指向子类对象的时候,函数在子类来回穿梭表现不同的形态.
多态:同样的调用语句有多种不同的表现形态;
多态实现的三个条件
有继承、有virtual重写、有父类指针(引用)指向子类对象。

多态的C++实现
virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用

多态的理论基础
动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。
动态联编,在运行的时候才确定是执行父类的函数还是子类的函数
静态联编,C++编译的时候就已经确定要执行的函数。
多态的重要意义
设计模式的基础 是框架的基石。

实现多态的理论基础
函数指针做函数参数
C函数指针是C++至高无上的荣耀。C函数指针一般有两种用法(正、反)。
多态原理探究
Vptr分布初始化

面试题2:谈谈C++编译器是如何实现多态
1.当类中声明虚函数时,编译器会在类中生成一个虚函数。
2.虚函数表是一个存储类成员函数指针的数据结构。
3.虚函数表是由编译器自动生成与维护的。
4.virtual成员函数会被编译器放入虚函数表中。
5.存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。












说明1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
说明2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数

面试题3:谈谈你对重写,重载理解





面试题4:是否可类的每个成员函数都声明为虚函数,为什么。

面试题5:构造函数中调用虚函数能实现多态吗?为什么?

面试题6:虚函数表指针(VPTR)被编译器初始化的过程,你是如何理解的?

面试题7:父类的构造函数中调用虚函数,能发生多态吗?

面试题8:为什么要定义虚析构函数?

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息