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:
出于效率考虑,没有必要将所有成员函数都声明为虚函数