=========================定义=========================

      将一个调用函数连接上正确的被调用函数,这个过程就叫做函数的联编,简称联编。在C++中,一共有两种联编的方式:

  1. 静态联编
    #define:静态联编是指联编工作出现在编译连接阶段
    #特点:① 静态联编就是指被调用函数执行调用函数之间的关系以及它们在内存中的地址在编译的时候已经确定好了,运行会发生变化
               ② 由于对象不用对自身进行跟踪,因此速度浪费比较小,但是灵活性较差。
  2. 动态联编
    #define:动态联编是指在程序运行的时候才进行的联编工作。
    #特点:① 由于编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数。要确切之道该调用的函数,就必须要求联编工作在程序运行时才能进行。
               ② 虽然可以追踪对象,灵活性较强,但是速度浪费严重。

=========================virtual的作用=========================

      首先,我们来看几个例子:

01 //例1 用父类对象的指针指向子类对象
02 #include <iostream>
03 using namespace std;
04   
05 class father
06 {
07 public:
08     father(){};
09     father(int i){age=i;}
10     void print()const{cout<<"father's age is "<<age<<endl;}
11 protected: //protected声明的成员可以被派生类访问
12     int age;
13 };
14   
15 class son:public father
16 {
17 public:
18     son(){};
19     son(int j){age=j;}
20     void print()const{cout<<"son's age is "<<age<<endl;}
21 };
22   
23 int main()
24 {
25     cout<<"一般情况:\n";
26     father dad(56);
27     dad.print();
28     son boy(22);
29     boy.print();
30     cout<<"指针情况:\n";
31     father *liu=new father(56);
32     liu->print();
33     son *uniqueliu=new son(22);
34     //father *uniqueliu=new son(22);
35     uniqueliu->print();
36 }

      我们先来看一下这个程序的运行结果:

上面这个结果很好理解吧,我就不解释了。但是如果我们这里把程序33行的注释去掉,把32行注释起来。那么程序运行的结果就会出现如下的情况:

这个就很奇怪了。我们的初衷是想用一个父类指针来指向一个子类对象。但是最后调用的print函数却是父类对象的。这个原因就是因为我们没有在父类中的print()前面加上关键字virtual的原因了。这是因为在函数print()函数前面加上了关键字virtual,就表示该函数是有多种形态的,说白了,就是这个print()函数可以被很多对象所拥有,而且各自实现的功能是不一样的,这样一来,就是先了多态。总结一下,我们只要在基类的成员函数前面加上virual,那么就算派生类的对象重新实现了同名函数,编译器就会自动判断是哪个对象调用了它,然后用该对象的同名函数,而不会采用基类的函数了。程序如下所示:

01 //例2 用父类对象的指针指向子类对象+virtual
02 #include <iostream>
03 using namespace std;
04   
05 class father
06 {
07 public:
08     father(){};
09     father(int i){age=i;}
10     virtual void print()const{cout<<"father's age is "<<age<<endl;}
11 protected: //protected声明的成员可以被派生类访问
12     int age;
13 };
14   
15 class son:public father
16 {
17 public:
18     son(){};
19     son(int j){age=j;}
20     void print()const{cout<<"son's age is "<<age<<endl;}
21 };
22   
23 int main()
24 {
25     cout<<"一般情况:\n";
26     father dad(56);
27     dad.print();
28     son boy(22);
29     boy.print();
30     cout<<"指针情况:\n";
31     father *liu=new father(56);
32     liu->print();
33     //son *uniqueliu=new son(22);
34     father *uniqueliu=new son(22);
35     uniqueliu->print();
36 }

这样,我们在程序的第10行上加上了virtual关键字。它就将基类的print()函数声明成了虚函数。这样一来,我们就可以调用子类的print()函数了,上图发真相!

=========================virtual与联编之间的关系=========================

      相对于例1来讲,在这里面调用的关系就是静态联编,因为在程序的编译阶段就已经把对象和它们所指向的函数紧紧地联系在一起了,运行的时候自然就不会有任何改变。而对于例2来讲,由于在父类的[rint()函数前面加上了关键字virtual,那么这个时候父类中的print()函数就变成了虚函数,虚函数就可以实现运行时的动态联编。这是因为virtual会让编译器自动地区寻找与之对应的对象,所以输出才会于红框所示,“son's age is 22”。

特别注意一点!!!只有在使用指针或者是引用的时候,才能够实现动态联编。

 回复 引用 查看   
#1楼2011-07-22 22:51 | hailong      
解释的很好啊,O(∩_∩)O~,你说的最后,必须是在指针或引用的时候才会发生,然后我想如果直接是调用复制构造函数的时候,是不是会发生,结果进行了修改,看了下结果:
01 //例1 用父类对象的指针指向子类对象
02 #include <iostream>
03 using namespace std;
04 class son;
05 class father
06 {
07 public:
08     father(){};
09     father(int i){age=i;}
10     father(father &f):age(f.age){}
11     father(son &s){}
12     virtual void print()const{cout<<"father's age is "<<age<<endl;}
13 protected: //protected声明的成员可以被派生类访问
14     int age;
15 };
16   
17 class son:public father
18 {
19 public:
20     son(){};
21     son(int j){age=j;}
22     void print()const{cout<<"son's age is "<<age<<endl;}
23 };
24   
25 int main()
26 {
27     cout<<"一般情况:\n";
28     father dad(56);
29     dad.print();
30     son boy(22);
31     boy.print();
32     cout<<"指针情况:\n";
33     father *liu=new father(56);
34     liu->print();
35     //son *uniqueliu=new son(22);
36     father uniqueliu=boy;
37     uniqueliu.print();
38 }

而看了相关介绍,知道在指针或者引用对象时,会产生并调用虚函数表,当基类被派生类赋值时,调用成员函数时,如果基类的虚函数在派生类中存在则调用派生类,若无则调用基类的。
如:
01 //例1 用父类对象的指针指向子类对象
02 #include <iostream>
03 using namespace std;
04 class son;
05 class father
06 {
07 public:
08     father(){};
09     father(int i){age=i;}
10     father(father &f):age(f.age){}
11     father(son &s){}
12     virtual void print()const{cout<<"father's age is "<<age<<endl;}
13     virtual void is_print()const{cout << "father's print\n";}
14 protected: //protected声明的成员可以被派生类访问
15     int age;
16 };
17   
18 class son:public father
19 {
20 public:
21     son(){};
22     son(int j){age=j;}
23     void print()const{cout<<"son's age is "<<age<<endl;}
24 };
25   
26 int main()
27 {
28     cout<<"一般情况:\n";
29     father dad(56);
30     dad.print();
31     son boy(22);
32     boy.print();
33     cout<<"指针情况:\n";
34     father *liu=new father(56);
35     liu->print();
36     //son *uniqueliu=new son(22);
37     father &uniqueliu=boy;
38     uniqueliu.print();
39     uniqueliu.is_print();
40 }
 回复 引用 查看   
#2楼[楼主]2011-07-22 23:13 | uniqueliu      
@hailong
引用而看了相关介绍,知道在指针或者引用对象时,会产生并调用虚函数表,当基类被派生类赋值时,调用成员函数时,如果基类的虚函数在派生类中存在则调用派生类,若无则调用基类的

学习了~~~^_^
另外,hailong兄弟,我认为你最后这句话
引用uniqueliu.is_print();

是点睛之笔啊!你这样一写,就更加诠释了虚函数的作用和你上面那句话“调用虚函数表”的作用了。非常谢谢你给我的提醒,thank you very much!!!!我们以后一定得多交流哈,^_^
posted on 2011-10-18 10:59  罗鹏  阅读(160)  评论(0编辑  收藏  举报