标签:
#include <iostream> using namespace std; //函数重写,在父类里有一个函数和在子类里有一个函数,他们的名字一致 //赋值兼容性原则:将子类对象赋给父类指针或者引用 //当函数重写遇上赋值兼容性,碰撞出了火花 //面向对象的新需求 //针对函数void howToPrintf(Parent *base) //如果传来父类对象,那么执行父类函数 //如果传来子类对象,那么执行子类函数 //一句话有多种形态 //c++编译器给我们提供的方案是虚函数解决方案 class Parent { public: ????Parent(int a = 0) ????{ ????????this->a = a; ????} ????virtual void print() //虚函数解决方案 ????{ ????????cout << "a = " <<a << endl; ????} private: ????int a; }; ? class Child : public Parent { public: ????void print() ????{ ????????cout << "b = " << b << endl; ????} ????Child(int b = 0) ????{ ????????this->b = b; ????} private: ????int b; }; ? void howToPrint(Parent *base) { ????base->print(); } ? void howToPrint(Parent &base) { ????base.print(); } void main() { ????Parent p1; ????p1.print(); ????Child c1; ????c1.print(); ? ????Parent *base = NULL; ????base = &p1; ????p1.print(); ????base = &c1; ????base->print(); //没有调用子类的函数 ????Parent &p2 = c1; ????p2.print(); ????howToPrint(&p1); ????howToPrint(&c1); ????howToPrint(p1); ????howToPrint(c1); ????//Child *child = NULL; ????//child = &p1; ? ????system("pause"); } |
将子类对象传递给父类指针或者引用,由于复制兼容性原则的存在,所以不会报错。
c++为静态编译语言,根据指针指向的类型去执行相应类的函数,言外之意,是c++编译器根据指针的类型到具体的类里面执行相应的代码,不管传来的是父类对象,还是子类对象。
如果在基类的函数加上virtual关键字,c++编译器会对该函数动一些手脚,产生了所谓的动态联编和迟绑定,这种现象综合起来,就是动态联编。
静态联编函数的地址在在编译的时候就已经确定了,但是动态联编不同,动态联编会到函数运行的时候才会查找函数的地址。
//英雄的战机hero Fight 10 --> adv 高级英雄战机 //敌人的战机 enermy Fight 15 --> ? #include <iostream> using namespace std; ? class HeroFighter { public: ????virtual int AttackPower() ????{ ????????return 10; ????} ? }; ? class EnermyFighter { public: ????int DestoryPower() ????{ ????????return 15; ????} ? }; ? ? ? class AdvHeroFighter : public HeroFighter { public: ????int AttackPower() ????{ ????????return 20; ????} ? }; ? class BestFight : public AdvHeroFighter { public: ????BestFight(); ????~BestFight(); public: ????int AttackPower() ????{ ????????return 50; ????} ? }; ? //一个模型,如果你把这个函数放在框架里面呢? //面向对象三大概念 //##封装##为什么牛逼?突破了c函数的概念 //##继承##为什么牛啊?继承可以使用原来的代码-->代码复用 //##多态##牛逼在哪里?下面的代码可以不变动 //我写了一个框架,这个框架产生的时间 //这个类产生的时间 //我的框架可以调用未来的人写的代码,而且对之前的业务模型不做任何修改 //这个层次比代码复用更高 //我们可以调用未来 ? //多态成立的三个条件 //1.要有继承 //2.要有虚函数重写 //3.要有父类指针指向子类对象 //三个条件缺一不可 ? //间接赋值三个条件 //1.定义两个变量,一个形参,一个实参 //2.建立关联,实参取地址传给形参 //3.*p(实参的地址)间接修改实参 void ObjFighter(HeroFighter *pBase, EnermyFighter *pEnermy) { ????if (pBase->AttackPower() > pEnermy->DestoryPower()) ????{ ????????printf("猪脚win!\n"); ????} ????else ????{ ????????printf("猪脚挂掉了!\n"); ????} } void play() { ????HeroFighter h1; ????EnermyFighter e1; ????AdvHeroFighter a1; ????if (h1.AttackPower() > e1.DestoryPower()) ????{ ????????printf("猪脚win!\n"); ????} ????else ????{ ????????printf("猪脚挂掉了!\n"); ????} ? ????if (a1.AttackPower() > e1.DestoryPower()) ????{ ????????printf("猪脚win!\n"); ????} ????else ????{ ????????printf("猪脚挂掉了!\n"); ????} } ? int main(int argc, char const *argv[]) { ????HeroFighter h1; ????EnermyFighter e1; ????AdvHeroFighter a1; ????ObjFighter(&h1, &e1);//第一代战机上场 ????ObjFighter(&a1, &e1);//第二代战机上场 ????system("pause"); ????return 0; } |
1.要有继承;
2.要有虚函数重写;
3.要有父类指针指向子类对象。
三个条件缺一不可。
因为它突破了c函数的概念,将与对象相关的刚发和属性都聚在一起。
继承可以使用原来的代码,从而实现了代码的复用,减小了工作量。
打一个比方,我写了一个框架,这个框架产生的时间和这个类产生的时间,可以相差n年,但我的框架可以调用未来的人写的代码,而且对之前的业务模型不做任何修改这个层次比代码复用更高,这意味着我们可以调用未来。
多态:同样的调用语句有多种不同的表现形态。
有继承,有virtual重写,有父类指针(引用)指向子类对象。
virtual关键字,告诉编译器这个函数要支持多态;不要根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用
动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。
设计模式的基础。
函数指针做函数参数。
当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
出于效率考虑,没有必要将所有成员函数都声明为虚函数。
当类中声明了virtual关键字的时候,c++编译器会自动给类加上一个vptr指针。
#include "iostream" using namespace std; ? class A { }; ? class B { public: ????virtual void print() ????{ ????????cout << "B print run..." << endl; ????} ? }; ? class C : public B { public: ????virtual void demo() ????{ ????????cout << "C Demo run..." << endl; ????} }; ? class D : public C { public: ????void print() ????{ ????????cout << "D print run..." << endl; ????} protected: private: }; ? void main() { ????A a; ????B b; ????C c; ????D d; ????cout << "sizeof(A)" << sizeof(a) << endl; //sizeof(A)1 ????cout << "sizeof(B)" << sizeof(b) << endl; //sizeof(B)4 ????cout << "sizeof(C)" << sizeof(c) << endl; //sizeof(C)4 ????cout << "sizeof(D)" << sizeof(d) << endl; //sizeof(D)4 ????d.print(); //D print run... 多态,父类某函数声明了virtual关键字,那么以后的子类以及子类的子类的该函数都是virtual类型 ????system("pause"); } |
由上面的例子可以看出很多东西,一个类即使什么东西也没有,他也会占用一个字节,当然,这个字节仅仅是占位符而已。函数是不占用类对象的存储空间的,但是给函数加上virtual关键字之后,类对象的存储空间到达了4字节,显然c++编译器给我们的类对象动了手脚,具体就是增加了一个vptr指针。
对象在创建的时候,由编译器对VPTR指针进行初始化,只有当对象的构造完全结束后VPTR的指向才最终确定。
父类对象的VPTR指向父类虚函数表
子类对象的VPTR指向子类虚函数表
?
#include "iostream" using namespace std; ? class AA { public: ????AA(int a = 0) ????{ ????????this->a = a; ????????print(); //在构造函数里面能实现多态吗? ????} ? ????//分析一下要想实现多态,c++编译器应该动什么手脚 ????//第一个需要动手脚的地方 起码这个函数print 我应该特殊处理 ????virtual void print() ????{ ????????cout << "parent: " << "a = " << a << endl; ????} protected: ????int a; }; ? class BB : public AA { public: ????BB(int a = 0, int b = 0) ????{ ????????this->a = a; ????????this->b = b; ? ????} ????virtual void print() ????{ ????????cout << "child: " << "a = " << a << " b = " << b << endl; ????} private: ????int b; }; ? ? void howToPrintf(AA *pBase) { ????//pBase 我怎么知道是父类对象还是子类对象 ????//动手脚2::区分是父类对象还是子类对象,提前布局 ????pBase->print(); // } void main() { ????//AA a1; ????BB b1; ????//howToPrintf(&a1); ????howToPrintf(&b1); ????system("pause"); } |
vptr指针的初始化是分步完成的。
#include <cstdlib> #include <iostream> using namespace std; ? /*重载发生在同一个类里,在编译期间就确定了函数的地址*/ class Parent { public: ????Parent() ????{ ????????cout << "Parent:Parent() run......" << endl; ????} public: ????void demo() ????{ ????????cout << endl; ????} ????void func() ????{ ????????cout << "Parent:void func() run......" << endl; ????} ? ????void func(int i) ????{ ????????cout << "Parent:void func(int i) run......" << endl; ????} ? ????virtual void func(int i, int j) ????{ ????????cout << "Parent:void func(int i, int j) run......" << endl; ????} }; ? /* *重写-->父子类之间,函数三要素(函数名、函数参数、函数返回类型)完全一样 *重写又分为两种 *1.如果父类中有virtual关键字,这种父子之间的关系叫做虚函数重写,这种情况下发生多态 (动态链编 迟绑定) * 2.如果父类中没有virtual关键字,这种父子之间的关系 重定义(静态链编) */ ? class Child : public Parent { ? public: ????/*此处2个参数,和子类func函数是什么关系*/ ????void func(int i, int j) /*虚函数重写*/ ????{ ????????cout << "Child:void func(int i, int j) run......" << endl; ????} ? ????/*如何在子类中重载父类的func函数呢? ????*/ ????/*此处3个参数的,和父类func函数是什么关系*/ ????void func(int i, int j, int k) /*和父类的func没任何关系*/ ????{ ????????cout << "Child:void func(int i, int j, int k) run......" << endl; ????} }; ? void run(Parent* p) { ????p->func(1, 2); } ? int main() { ????Parent p; ? ????p.func(); ????p.func(1); ????p.func(1, 2); ? ????Child c; ????//c.func(); /*这里运行不了*/ ????c.Parent::func(); /*运行父类的func函数*/ ????c.demo(); /*与c.func()相对应的,demo是父类的一个函数*/ ????c.func(1, 2); ? ????run(&p); ????run(&c); ? ????system("pause"); ? ????return 0; } ? /*问题1:child对象继承父类对象的func,请问这句话能运行吗?why *c.func(); *答案是运行不了,因为c++的隐藏规则 *1.子类里面的func无法重载父类里面的func *2.当父类和子类有相同的函数名、变量名出现,发生名称覆盖 *3.c.Parent::func(); *问题2 子类的两个func和父类里的三个func函数是什么关系? */ |
区分重载和重写,它们的特征如下:
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
重写的特征:
重写又分为两种:
C++的隐藏规则使问题复杂性陡然增加。这里"隐藏"是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
#include "iostream" using namespace std; ? //指针也是一种数据类型,指针数据的数据类型是指,它所指的内存空间的数据类型 //最后一点引申 指针的步长 。。。c++ ? class Parent { protected: ????int i; ????int???? j; public: ????virtual void f() ????{ ????????cout << "Parent::f" << endl; ????} }; ? ? class Child : public Parent { public: ????int k; public: ????Child(int i, int j) ????{ ????????printf("Child:...do\n"); ????} ? ????virtual void f() ????{ ????????printf("Child::f()...do\n"); ????} }; ? void howToF(Parent *pBase) { ????pBase->f(); } ? //指针的步长 在c++领域仍然有效,父类指针的步长和子类指针的步长不一样 //多态是靠迟绑定实现的(vptr+函数指针实现) int main06() { ????int i = 0; ????Parent* p = NULL; ????Child* c = NULL; ? ????//不要把父类对象还有子类对象同事放在一个数组里面 ????Child ca[3] = { Child(1, 2), Child(3, 4), Child(5, 6) }; ? ????//不要用父类指针做赋值指针变量,去遍历一个子类的数组。 ? ????p = ca; ????c = ca; ? ????p->f(); ????c->f(); //有多态发生 ? ????// ????p++; ????// ????c++; ????// ????// ????p->f();//有多态发生 ????// ????c->f(); ? ????for (i = 0; i < 3; i++) ????{ ????????howToF(&(ca[i])); ????} ? ????system("pause"); ????return 0; } |
我们要特别注意指向父类的指针和子类的指针,一般来说,它们的步长是不同的,一般来说,子类对象的指针比父类指针的步长要长(子类在父类的基础上增加了东西),因此上面的程序会出现错误,运行不了。
标签:
原文地址:http://www.cnblogs.com/lishuhuakai/p/4527022.html