如何用代码表示用virtual函数来实现多态的实现机制

&&/&&&&/&&&&/&&
我们知道,在同一类中是不能定义两个名字相同、参数个数和类型都相同的函数的,否则就是&重复定义&。但是在类的继承层次结构中,在不同的层次中可以出现名字相同、参数个数和类型都相同而功能不同的函数。例如在例12.1(具体代码请查看:)程序中,在Circle类中定义了 area函数,在Circle类的派生类Cylinder中也定义了一个area函数。这两个函数不仅名字相同,而且参数个数相同(均为0),但功能不同,函数体是不同的。前者的作用是求圆面积,后者的作用是求圆柱体的表面积。这是合法的,因为它们不在同一个类中。 编译系统按照同名覆盖的原则决定调用的对象。在例12.1程序中用cy1.area( ) 调用的是派生类Cylinder中的成员函数area。如果想调用cy1 中的直接基类Circle的area函数,应当表示为 cy1.Circle::area()。用这种方法来区分两个同名的函数。但是这样做 很不方便。
人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数。在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们。例如,用同一个语句&pt-&display( );&可以调用不同派生层次中的display函数,只需在调用前给指针变量 pt 赋以不同的值(使之指向不同的类对象)即可。
打个比方,你要去某一地方办事,如果乘坐公交车,必须事先确定目的地,然后乘坐能够到达目的地的公交车线路。如果改为乘出租车,就简单多了,不必查行车路线,因为出租车什么地方都能去,只要在上车后临时告诉司机要到哪里即可。如果想访问多个目的地,只要在到达一个目的地后再告诉司机下一个目的地即可,显然,&打的&要比乘公交车 方便。无论到什么地方去都可以乘同&辆出租车。这就是通过同一种形式能达到不同目的的例子。
C++中的虚函数就是用来解决这个问题的。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
请分析例12.2。这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况。
[例12.2] 基类与派生类中有同名函数。在下面的程序中Student是基类,Graduate是派生类,它们都有display这个同名的函数。
#include &iostream&
#include &string&
//声明基类Student
class Student
Student(int, string,float);
//声明构造函数
void display( );//声明输出函数
protected:
//受保护成员,派生类可以访问
//Student类成员函数的实现
Student::Student(int n, string nam,float s)//定义构造函数
void Student::display( )//定义输出函数
cout&&&num:&&&num&&&\nname:&&&name&&&\nscore:&&&score&&&\n\n&;
//声明公用派生类Graduate
class Graduate:public Student
Graduate(int, string, float, float);//声明构造函数
void display( );//声明输出函数
// Graduate类成员函数的实现
void Graduate::display( )//定义输出函数
cout&&&num:&&&num&&&\nname:&&&name&&&\nscore:&&&score&&&\npay=&&&pay&&
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){}
int main()
Student stud1(1001,&Li&,87.5);//定义Student类对象stud1
Graduate grad1(2001,&Wang&,98.5,563.5);//定义Graduate类对象grad1
Student *pt=&stud1;//定义指向基类对象的指针变量pt
pt-&display( );
pt=&grad1;
pt-&display( );
运行结果如下:
num:1001(stud1的数据)
score:87.5
num:2001 (grad1中基类部分的数据)
score:98.5
假如想输出grad1的全部数据成员,当然也可以采用这样的方法:通过对象名调用display函数,如grad1.display(),或者定义一个指向Graduate类对象的指针变量ptr,然后使ptr指向gradl,再用ptr-&display()调用。这当然是可以的,但是如果该基类有多个派生类,每个派生类又产生新的派生类,形成了同一基类的类族。每个派生类都有同名函数display,在程序中要调用同一类族中不同类的同名函数,就要定义多个指向各派生类的指针变量。这两种办法都不方便,它要求在调用不同派生类的同名函数时采用不同的调用方式,正如同前面所说的那样,到不同的目的地要乘坐指定的不同的公交车,一一 对应,不能搞错。如果能够用同一种方式去调用同一类族中不同类的所有的同名函数,那就好了。
用虚函数就能顺利地解决这个问题。下面对程序作一点修改,在Student类中声明display函数时,在最左面加一个关键字virtual,即
& & virtual void display( );
这样就把Student类的display函数声明为虚函数。程序其他部分都不改动。再编译和运行程序,请注意分析运行结果:
num:1001(stud1的数据)
score:87.5
num:2001 (grad1中基类部分的数据)
score:98.5
pay=1200 (这一项以前是没有的)
看!这就是虚函数的奇妙作用。现在用同一个指针变量(指向基类对象的指针变量),不但输出了学生stud1的全部数据,而且还输出了研究生grad1的全部数据,说明已调用了grad1的display函数。用同一种调用形式&pt-&display()&,而且pt是同一个基类指针,可以调用同一类族中不同类的虚函数。这就是多态性,对同一消息,不同对象有 不同的响应方式。
说明:本来基类指针是用来指向基类对象的,如果用它指向派生类对象,则进行指针类型转换,将派生类对象的指针先转换为基类的指针,所以基类指针指向的是派生类对象中的基类部分。在程序修改前,是无法通过基类指针去调用派生类对象中的成员函数的。虚函数突破了这一限制,在派生类的基类部分中,派生类的虚函数取代了基类原来的虚函数,因此在使基类指针指向派生类对象后,调用虚函数时就调用了派生类的虚函数。 要注意的是,只有用virtual声明了虚函数后才具有以上作用。如果不声明为虚函数,企图通过基类指针调用派生类的非虚函数是不行的。
虚函数的以上功能是很有实用意义的。在面向对象的程序设计中,经常会用到类的继承,目的是保留基类的特性,以减少新类开发的时间。但是,从基类继承来的某些成员函数不完全适应派生类的需要,例如在例12.2中,基类的display函数只输出基类的数据,而派生类的display函数需要输出派生类的数据。过去我们曾经使派生类的输出函数与基类的输出函数不同名(如display和display1),但如果派生的层次多,就要起许多不同的函数名,很不方便。如果采用同名函数,又会发生同名覆盖。
利用虚函数就很好地解决了这个问题。可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数。由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。
虚函数的使用方法是:
在基类用virtual声明成员函数为虚函数。
这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。
需要说明;有时在基类中定义的非虚函数会在派生类中被重新定义(如例12.1中的area函数),如果用基类指针调用该成员函数,则系统会调用对象中基类部分的成员函数;如果用派生类指针调用该成员函数,则系统会调用派生类对象中的成员函数,这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。
以前介绍的函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者是横向重载,后者可以理解为纵向重载。但与重载不同的是:同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)。
推荐文章 TOP10  面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为。在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体。也可以这样说就是实现了&一个接口,多种方法&。
  从实现的角度来讲,多态可以分为两类:编译时的多态性和运行时的多态性。前者是通过静态联编来实现的,比如C++中通过函数的重载和运算符的重载。后者则是通过动态联编来实现的,在C++中运行时的多态性主要是通过虚函数来实现的,也正是今天我们要讲的主要内容。
  1.不过在说虚函数之前,我想先介绍一个有关于基类与派生类对象之间的复制兼容关系的内容。它也是之后学习虚函数的基础。我们有时候会把整型数据赋值给双精度类型的变量。在赋值之前,先把整形数据转换为双精度的,在把它赋值给双精度类型的变量。这种不同类型数据之间的自动转换和赋值,称为赋值兼容。同样的,在基类和派生类之间也存在着赋值兼容关系,它是指需要基类对象的任何地方都可以使用公有派生类对象来代替。为什么只有公有继承的才可以呢,因为在公有继承中派生类保留了基类中除了构造和析构之外的所有成员,基类的公有或保护成员的访问权限都按原样保留下来,在派生类外可以调用基类的公有函数来访问基类的私有成员。因此基类能实现的功能,派生类也可以。
  那么它们具体是如何体现的呢?(1)派生类对象直接向基类赋值,赋值效果,基类数据成员和派生类中数据成员的值相同;(2)派生类对象可以初始化基类对象引用;(3)派生类对象的地址可以赋给基类对象的指针;(4)函数形参是基类对象或基类对象的引用,在调用函数时,可以用派生类的对象作为实参;
1 #include "stdafx.h" 2 #include&iostream& 3 #include&string& 4
5 class ABCBase 6 { 7 private: 8
std::string ABC;
9 public:10
ABCBase(std::string abc)11
}14 void showABC();15 };16 17 void ABCBase::showABC()18 {19
std::cout&&"字母ABC=&"&&ABC&&std::20 }21 22 class X:public ABCBase23 {24 public:25
X(std::string x):ABCBase(x){}26 };27 28 void function(ABCBase &base)29 {30 base.showABC();31 }32 33 34 int main()35 {36
ABCBase base("A");37 base.showABC();38 39
X x("B");40 base=x;41 base.showABC();42 43
ABCBase &base1=x;44
base1.showABC();45 46
ABCBase *base2=&x;47
base2-&showABC();48 49
function(x);50 51 return0;52 }
要注意的是:第一,在基类和派生类对象的赋值时,该派生类必须是公有继承的。第二,只允许派生类对象向基类对象赋值,反过来不允许;
  2.紧接着来讲一下虚函数,它允许函数调用与函数体之间的联系在运行时才建立,即在运行时才决定如何动作。虚函数声明的格式:
  virtual 返回类型 函数名(形参表)
    函数体
那么定义虚函数有什么用呢?让我们先来看看下面这个示例:
1 #include "stdafx.h" 2 #include &iostream& 3 #include &string& 4
6 class Graph 7 { 8 protected: 9 double10 double11 public:12
Graph(double x,double y);13 void showArea();14 };15 16 Graph::Graph(double x,double y)17 {18 this-&x=x;19 this-&y=y;20 }21 22 void Graph::showArea()23 {24
std::cout&&"计算图形面积"&&std::25 }26 27 class Rectangle:public Graph28 {29 public:30
Rectangle(double x,double y):Graph(x,y){};31 void showArea();32 };33 34 void Rectangle::showArea()35 {36
std::cout&&"矩形面积为:"&&x*y&&std::37 }38 39 class Triangle:public Graph40 {41 public:42
Triangle(double d,double h):Graph(d,h){};43 void showArea();44 };45 46 void Triangle::showArea()47 {48
std::cout&&"三角形面积为:"&&x*y*0.5&&std::49 }50 51 class Circle:public Graph52 {53 public:54
Circle(double r):Graph(r,r){};55 void showArea();56 };57 58 void Circle::showArea()59 {60
std::cout&&"圆形面积为:"&&3.14*x*y&&std::61 }62 63 int main()64 {65
Graph *66 67
Rectangle rectangle(10,5);68
graph-&showArea();70 71
Triangle triangle(5,2.4);72
graph-&showArea();74 75
Circle circle(2);76
graph-&showArea();78 79 return0;80 }
结果似乎和我们想象的不一样,既然Graph类(图形类)的对象graph指针分别指向了Rectangle类(矩形类)对象,Triangle类(三角类)对象,以及Circle类(圆类)对象,那么就应该执行它们自己所对应成员函数showArea(),怎么结果会是Graph类(图形类)的对象graph里的成员函数呢?这好像和我们在一节里所讲到的派生类成员覆盖了基类中使用相同名称的成员(派生类对象调用同名成员函数是来自于自己类中成员函数,而非基类中上的)有所不同啊,其实当基类对象指针指向公有派生类的对象时,它只能访问从基类继承下来的成员,而不能访问派生类中定义的成员。但是使用动态指针就是为了表达一种动态调用的性质即当前指针指向哪个对象,就调用那个对象对应类的成员函数。那要怎么来解决的,这时虚函数就体现出了它的作用。其实我们只需要对上一个示例代码中所有的类里出现的showArea()函数声明之前加一个关键字virtual:
1 #include "stdafx.h" 2 #include &iostream& 3 #include &string& 4
6 class Graph 7 { 8 protected: 9 double10 double11 public:12
Graph(double x,double y);13 voidvirtual showArea();//定义为虚函数或virtual void showArea()14 };15 16 Graph::Graph(double x,double y)17 {18 this-&x=x;19 this-&y=y;20 }21 22 void Graph::showArea()23 {24
std::cout&&"计算图形面积"&&std::25 }26 27 class Rectangle:public Graph28 {29 public:30
Rectangle(double x,double y):Graph(x,y){};31 virtualvoid showArea();//定义为虚函数32 };33 34 void Rectangle::showArea()35 {36
std::cout&&"矩形面积为:"&&x*y&&std::37 }38 39 class Triangle:public Graph40 {41 public:42
Triangle(double d,double h):Graph(d,h){};43 virtualvoid showArea();//定义为虚函数44 };45 46 void Triangle::showArea()47 {48
std::cout&&"三角形面积为:"&&x*y*0.5&&std::49 }50 51 class Circle:public Graph52 {53 public:54
Circle(double r):Graph(r,r){};55 virtualvoid showArea();//定义为虚函数56 };57 58 void Circle::showArea()59 {60
std::cout&&"圆形面积为:"&&3.14*x*y&&std::61 }62 63 int main()64 {65
Graph *66 67
Rectangle rectangle(10,5);68
graph-&showArea();70 71
Triangle triangle(5,2.4);72
graph-&showArea();74 75
Circle circle(2);76
graph-&showArea();78 79 return0;80 }
其它代码原封不动,这样运行出来的结果就是我们所需要的:
在基类中的某成员函数被声明为虚函数后,在之后的派生类中科以重新来定义它。但定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型的顺序,都必须和基类中的原型完全相同。其实在上述修改后的示例代码里,只要在基类中显式声明了虚函数,那么在之后的派生类中就需要用virtual来显式声明了,可以略去,因为系统会根据其是否和基类中虚函数原型完全相同来判断是不是虚函数。因此,上述派生类中的虚函数如果不显式声明也还是虚函数。最后对虚函数做几点补充说明:(1)因为虚函数使用的基础是赋值兼容,而赋值兼容成立的条件是派生类之从基类公有派生而来。所以使用虚函数,派生类必须是基类公有派生的;(2)定义虚函数,不一定要在最高层的类中,而是看在需要动态多态性的几个层次中的最高层类中声明虚函数;(3)虽然在上述示例代码中main()主函数实现部分,我们也可以使用相应图形对象和点运算符的方式来访问虚函数,如:rectangcle.showArea(),但是这种调用在编译时进行静态联编,它没有充分利用虚函数的特性。只有通过基类对象来访问虚函数才能获得动态联编的特性;(4)一个虚函数无论配公有继承了多少次,它仍然是虚函数;(5)虚函数必须是所在类的成员函数,而不能是友元函数,也不能是静态成员函数。因为虚函数调用要靠特定的对象类决定该激活哪一个函数;(6)内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的即使虚函数在类内部定义,编译时将其看作非内联;(7)构造函数不能是虚函数,但析构函数可以是虚函数;
  如果在main()主函数中用new建立一个派生类无名对象和定义一个基类对象指针,并将无名对象的地址赋给基类对象指针时,当我们用delete运算符来撤销无名对象时,系统只执行基类析构函数,而不执行派生类析构函数。比如:
1 #include "stdafx.h" 2 #include &iostream& 3 #include &string& 4
6 class Graph 7 { 8 protected: 9 double10 double11 public:12
Graph(double x,double y);13 voidvirtual showArea();//定义为虚函数或virtual void showArea()14 ~Graph();15 };16 17 Graph::Graph(double x,double y)18 {19 this-&x=x;20 this-&y=y;21 }22 23 void Graph::showArea()24 {25
std::cout&&"计算图形面积"&&std::26 }27 28 Graph::~Graph()29 {30
std::cout&&"调用图形类析构函数"&&std::31 }32 33 class Rectangle:public Graph34 {35 public:36
Rectangle(double x,double y):Graph(x,y){};37 virtualvoid showArea();//定义为虚函数38 ~Rectangle();39 };40 41 void Rectangle::showArea()42 {43
std::cout&&"矩形面积为:"&&x*y&&std::44 }45 46 Rectangle::~Rectangle()47 {48
std::cout&&"调用矩形类析构函数"&&std::49 }50 51 int main()52 {53
graph=new Rectangle(10,5);55
graph-&showArea();56 57 58 59 return0;60 }
因为在撤销指针graph所指的派生类对象,在调用析构函数时,采用静态联编,只调用了Graph类的析构函数。如果也想调用派生类Rectangle类的析构函数的话,可将Graph类的析构函数定义为虚析构函数。其定义的一般格式:
  virtual ~类名()
    函数体
虽然派生类的析构函数与基类的析构函数名字不同,但是如果将基类的析构函数定义为虚函数,由该基类派生而来的所有派生类的析构函数都自动成为虚函数。我们把上一示例中的Graph类的析构函数前加上关键字virtual,那么执行结果:
显然这个结果才是我们所需要的。
  3.上述示例中用了虚函数后,会发现其实Graph类(图形类)中的虚函数的函数体根本没有被用到过,就算被用到,该基类体现了图形的抽象的概念,并不与具体事物相联系。所以基类中的虚函数也没有实质性的功能。因此我们只需要在基类中留下一个函数名,而具体的实现留给派生类去定义。在C++中就是用纯虚函数来说明的。纯虚函数的一般形式:
  virtual 返回类型 函数名(形参表)=0;
这里的"=0"并不是函数的返回值等于零,它只是起到形式上的作用,告诉编译系统"这是纯虚函数"。纯虚函数不具备函数功能,不能被调用。
1 class Graph 2 { 3 protected: 4 double 5 double 6 public: 7
Graph(double x,double y); 8 voidvirtual showArea()=0;//定义纯虚函数 9 };10 11 Graph::Graph(double x,double y)12 {13 this-&x=x;14 this-&y=y;15 }
  4.如果一个类中至少有一个纯虚函数,那么就称该类为抽象类。所以上述中Graph类就是抽象类。对于抽象类有以下几个注意点:(1)抽象类只能作为其他类的基类来使用,不能建立抽象类对象;(2)不允许从具体类中派生出抽象类(不包含纯虚函数的普通类);(3)抽象类不能用作函数的参数类型、返回类型和显示转化类型;(4)如果派生类中没有定义纯虚函数的实现,而只是继承成了基类的纯虚函数。那么该派生类仍然为抽象类。一旦给出了对基类中虚函数的实现,那么派生类就不是抽象类了,而是可以建立对象的具体类;
  5.最后还是一样,我将用一个实例来总结一下今天所讲的内容(开发工具:vs2010):
1 #include "stdafx.h"
2 #include &iostream&
3 #include &string&
6 class Graph //抽象类
8 protected:
9 double 10 double 11 public: 12
Graph(double x,double y); 13 //void virtual showArea();//定义为虚函数或virtual void showArea() 14 voidvirtual showArea()=0;//定义纯虚函数 15 virtual~Graph();//定义虚析构函数 16 }; 17
18 Graph::Graph(double x,double y) 19 { 20 this-&x=x; 21 this-&y=y; 22 } 23
24 void Graph::showArea() 25 { 26
std::cout&&"计算图形面积"&&std:: 27 } 28
29 Graph::~Graph() 30 { 31
std::cout&&"调用图形类析构函数"&&std:: 32 } 33
34 class Rectangle:public Graph 35 { 36 public: 37
Rectangle(double x,double y):Graph(x,y){}; 38 void showArea();//虚函数 39 ~Rectangle();//虚析构函数 40 }; 41
42 void Rectangle::showArea() 43 { 44
std::cout&&"矩形面积为:"&&x*y&&std:: 45 } 46
47 Rectangle::~Rectangle() 48 { 49
std::cout&&"调用矩形类析构函数"&&std:: 50 } 51
52 class Triangle:public Graph 53 { 54 public: 55
Triangle(double d,double h):Graph(d,h){}; 56 virtualvoid showArea();//虚函数 57 ~Triangle();//虚析构函数 58 }; 59
60 void Triangle::showArea() 61 { 62
std::cout&&"三角形面积为:"&&x*y*0.5&&std:: 63 } 64
65 Triangle::~Triangle() 66 { 67
std::cout&&"调用三角形类析构函数"&&std:: 68 } 69
71 class Circle:public Graph 72 { 73 public: 74
Circle(double r):Graph(r,r){}; 75 virtualvoid showArea();//虚函数 76 ~Circle();//虚析构函数 77 }; 78
79 void Circle::showArea() 80 { 81
std::cout&&"圆形面积为:"&&3.14*x*y&&std:: 82 } 83
84 Circle::~Circle() 85 { 86
std::cout&&"调用圆形类析构函数"&&std:: 87 } 88
89 int main() 90 { 91
{ 92 //Graph g(10,10);//抽象类不能建立对象 93
Graph * 95
Rectangle rectangle(10,5); 97
graph=& 98
graph-&showArea(); 99 100
Triangle triangle(5,2.4);101
graph=&102
graph-&showArea();103 104 105
Graph *graph1;106
graph1=new Circle(2);//new运算符建立无名对象107
graph1-&showArea();108 109
delete graph1;//delete运算符撤销派生类Circle无名对象110
}111 112 return0;113 }
阅读(...) 评论()谁和我说说什么时候用多态啊
[问题点数:40分,结帖人abc]
谁和我说说什么时候用多态啊
[问题点数:40分,结帖人abc]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2012年8月 C/C++大版内专家分月排行榜第三2012年7月 C/C++大版内专家分月排行榜第三
2012年7月 C/C++大版内专家分月排行榜第二2012年6月 C/C++大版内专家分月排行榜第二
2012年8月 C/C++大版内专家分月排行榜第三2012年7月 C/C++大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
UID空间积分0 积分7418阅读权限70帖子精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
富足长乐, 积分 7418, 距离下一级还需 582 积分
帖子主题精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
论坛徽章:0
本帖最后由 wwwsq 于
22:12 编辑
#include &stdafx.h&
typedef void (*fVoid)();
& & & & static void test()
& & & & & & & & printf(&hello A\n&);
& & & & fV
& & & & A()
& & & & & & & & print = A::
class B : public A
& & & & static void test()
& & & & & & & & printf(&hello B\n&);
& & & & B()
& & & & & & & & print = B::
int main()
& & & & aa.print();
& & & & A* a = &b;
& & & & a-&print();
& & & & getchar();
& & & & return 0;
这样做的好处主要是绕过了vtable。
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
UID7672252空间积分0 积分48471阅读权限100帖子精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
帖子主题精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
论坛徽章:1
1.&&stdafx.h/ getchar()&&我不知道怎么吐槽了……
2. print = A::
print = B::
你拿到gcc上面去编译看看?
你拿到clang上面去编译看看?
结合上一条。
所以我说你即使从我还没入大学就开始从事C++工作, 但到现在都还没从VC++那里毕业, 连语言规范与语言实现都分不清楚。
3. static fV 确实是你本意?
getchar() 之前如果再插入 aa.print() , 结果是什么?
这叫多态? 所有object 都会因为新创建的 object 而改变行为好吗?
4. 上面那个算是你的笔误, 下面讨论去掉 static。
你只演示了一个虚函数的情况, 想过多个虚函数没?
你这种设计是我一开始就预计到了的。 这不叫绕过了 vtable , 而是将 vtable 嵌入到每个 object 当中。
自己去看“C++设计与演化” 与 “C++对象模型” 。 最终 cfront (以及后续许多C++编译器) 都抛弃了这种模型。
而使用的是 one vtbl per class, one vptr per object 的模型。 这样可以让 object 的大小不会随虚函数增加而持续增加。
你批评现有模型是因为 object 与 vbtl 不连续, 会影响 cache, 增加 swap 几率。
难道增加 object 的大小 —— 增加总内存使用 —— 就不会增加 swap 几率?
综上, 你所做的, 是手动实现了一个被C++主流编译器抛弃的虚函数模型。
你的原始代码我贴在下面:
#include &stdafx.h&
typedef void (*fVoid)();
& && &&&static void test()
& && && && && & printf(&hello A\n&);
& && &&&static fV
& && &&&A()
& && && && && & print = A::
fVoid A::print = NULL;
class B : public A
& && &&&static void test()
& && && && && & printf(&hello B\n&);
& && &&&B()
& && && && && & print = B::
int main()
& && &&&aa.print();
& && &&&A* a = &b;
& && &&&a-&print();
& && &&&getchar();
& && &&&return 0;
这样做的好处主要是绕过了vtable。
wwwsq 发表于
&一些人总认为可以用自己的思想驾驭编程语言,殊不知自己颇为自信的思想其实也被编程语言所影响着& 不管你信不信,反正我是信了。
ユッキは&&あっん~ん~&&由乃が守ってあげる&&ね? ユッキ
CU13:wwwsq,nvoy,534231qwz,qopu,jzpzy,zscoreh,uW.namrebyC
UID空间积分0 积分7418阅读权限70帖子精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
富足长乐, 积分 7418, 距离下一级还需 582 积分
帖子主题精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
论坛徽章:0
1.&&stdafx.h/ getchar()&&我不知道怎么吐槽了……
2. print = A::
print = B::
你拿到gcc上面 ...
OwnWaterloo 发表于
确实不应该用static。
性能问题,你去测测再看。
UID空间积分0 积分7418阅读权限70帖子精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
富足长乐, 积分 7418, 距离下一级还需 582 积分
帖子主题精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
论坛徽章:0
滑铁卢同学那么看不起VC,你一定没研究过GOT。研究过GOT的话,你至少要承认VC也有可取之处。
UID7672252空间积分0 积分48471阅读权限100帖子精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
帖子主题精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
论坛徽章:1
滑铁卢同学那么看不起VC,你一定没研究过GOT。研究过GOT的话,你至少要承认VC也有可取之处。
wwwsq 发表于
我哪句话说我看不起VC?
只要在Windows下, 代码我都会用VC编译。
涉及了过多晦涩C++技巧的代码, 我甚至会用VC8,9,10 3个版本编译。
VC6 对C++ 支持太差, 但 C 代码我依然会用它编译一次。
同时还有2个版本的clang, 3个版本的 gcc 。
再说一次, 我看不起的是将VC++当作C++的人。
&一些人总认为可以用自己的思想驾驭编程语言,殊不知自己颇为自信的思想其实也被编程语言所影响着& 不管你信不信,反正我是信了。
ユッキは&&あっん~ん~&&由乃が守ってあげる&&ね? ユッキ
CU13:wwwsq,nvoy,534231qwz,qopu,jzpzy,zscoreh,uW.namrebyC
UID空间积分0 积分7418阅读权限70帖子精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
富足长乐, 积分 7418, 距离下一级还需 582 积分
帖子主题精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
论坛徽章:0
我哪句话说我看不起VC?
只要在Windows下, 代码我都会用VC编译。
涉及了过多晦涩C++技巧的代码,&&...
OwnWaterloo 发表于
哈,连什么叫member function,什么叫POD都搞不清楚的人,还来跟我争论什么叫C++?
UID7672252空间积分0 积分48471阅读权限100帖子精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
帖子主题精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
论坛徽章:1
要说性能问题, 我只有一句话问你: 你打算如何测试增加的内存消耗对其他进程带来的影响?
&一些人总认为可以用自己的思想驾驭编程语言,殊不知自己颇为自信的思想其实也被编程语言所影响着& 不管你信不信,反正我是信了。
ユッキは&&あっん~ん~&&由乃が守ってあげる&&ね? ユッキ
CU13:wwwsq,nvoy,534231qwz,qopu,jzpzy,zscoreh,uW.namrebyC
UID空间积分0 积分7418阅读权限70帖子精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
富足长乐, 积分 7418, 距离下一级还需 582 积分
帖子主题精华可用积分7418 信誉积分698 专家积分10 在线时间790 小时注册时间最后登录
论坛徽章:0
回复&&wwwsq
要说性能问题, 我只有一句话问你: 你打算如何测试增加的内存消耗对其他进程带来的影响?
OwnWaterloo 发表于
跟你讲道理的话,需要先培训你的逻辑能力。
你还是先去测试一下性能,再来讨论比较好。这样,我可以只需要解释why就可以了。
UID7672252空间积分0 积分48471阅读权限100帖子精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
帖子主题精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
论坛徽章:1
哈,连什么叫member function,什么叫POD都搞不清楚的人,还来跟我争论什么叫C++?
wwwsq 发表于
这就是你口胡了。
自己胡乱编造术语, 胡乱理解他人的话, 最终还要将黑锅栽到别人身上?
&一些人总认为可以用自己的思想驾驭编程语言,殊不知自己颇为自信的思想其实也被编程语言所影响着& 不管你信不信,反正我是信了。
ユッキは&&あっん~ん~&&由乃が守ってあげる&&ね? ユッキ
CU13:wwwsq,nvoy,534231qwz,qopu,jzpzy,zscoreh,uW.namrebyC
UID7672252空间积分0 积分48471阅读权限100帖子精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
帖子主题精华可用积分48471 信誉积分645 专家积分243 在线时间2737 小时注册时间最后登录
论坛徽章:1
回复&&wwwsq
要说性能问题, 我只有一句话问你: 你打算如何测试增加的内存消耗对其他进程带来的影响?
OwnWaterloo 发表于
&&
跟你讲道理的话,需要先培训你的逻辑能力。
你还是先去测试一下性能,再来讨论比较好。这样,我可以只需要解释why就可以了。
wwwsq 发表于
三番五次的凸显自己理解能力低下, 你这是在故意买萌吗?
&一些人总认为可以用自己的思想驾驭编程语言,殊不知自己颇为自信的思想其实也被编程语言所影响着& 不管你信不信,反正我是信了。
ユッキは&&あっん~ん~&&由乃が守ってあげる&&ね? ユッキ
CU13:wwwsq,nvoy,534231qwz,qopu,jzpzy,zscoreh,uW.namrebyC
北京皓辰网域网络信息技术有限公司. 版权所有 京ICP证:060528号 北京市公安局海淀分局网监中心备案编号:
广播电视节目制作经营许可证(京) 字第1234号
中国互联网协会会员&&联系我们:
感谢所有关心和支持过ChinaUnix的朋友们
转载本站内容请注明原作者名及出处

我要回帖

更多关于 virtual 多态 的文章

 

随机推荐