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指针)。

wKiom1d8euejlXh2AACaDYcHaxs877.png



wKioL1d8ewHyZ_yTAADk03cU3L4391.png


wKioL1d8exmCF4bmAABKdjS-4fo791.png

说明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分布初始化,示意图】

wKioL1d-FIaDogBLAABtPDITpvk760.png

【代码实现】


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~$


父类指针 子类指针,步长

【看画图】

wKioL1d-FPSRrjtbAABDBnoAbsw854.png

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






【纯虚函数】

含有纯虚函数的类叫抽象类


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指针)。

wKiom1d8euejlXh2AACaDYcHaxs877.png



wKioL1d8ewHyZ_yTAADk03cU3L4391.png


wKioL1d8exmCF4bmAABKdjS-4fo791.png

说明1: 

通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。 

说明2: 

出于效率考虑,没有必要将所有成员函数都声明为虚函数



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

wKioL1d8e5nSjEkXAAHa0-vfdIM402.png


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

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

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

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

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