关于c++的多态的一些重要问题和原则

关于c++的多态的一些重要问题和原则


1.为什么需要虚函数?虚函数这个名字怎样得来的,有没有“实函数”的说法??   

2.何为“静态”成员函数,为什么书上说它不能为虚函数?   

3.为什么构造函数不能为虚函数,而析构函数经常为虚函数呢?构造函数中能不能调用虚函数实现多态?   
4.为什么引入友元函数而不全部用成员函数代替呢?   
5.多重继承用在什么地方,虚拟继承该怎样理解?
-----------------------------
1,c++有个重要特点是多态,这就是由虚函数来实现的;比如用父类的指针指向子类的对象,
   当用这个指针访问成员时就可以访问到子类的成员而不是父类的(这个成员函数就要有vi
   rtual修饰),这就是多态性。至于名字来由,我想就是因为用户用的是父类指针却可以访
   问到子类虚拟成员的原因吧(单从指针的名字上看是“虚假的”)。   
2,static   foo(..)。在类中的成员函数前加上static就是静态成员函数。它将会成员
   全局成员,始终存在,与有没有对象生成没关系,所以也就不会有动态识别而言,自然就
   没有了虚拟的静态函数。   
   多态实际上是由于类层次中对象的动态绑定机制实现的,而静态函数脱离对象(即静态函
   数中没有实现多态所需要的this指针),存在于静态空间,无法支持动态绑定,所以不能
   为虚函数。
   静态函数一般用来操作静态变量(注:静态成员函数只能操作静态变量!),静态成员和
   其他成员的区别是:静态成员属于类,而其他成员属于对象。   
3,构造函数为的只是初始化本类的成员变量,跟别的任何类没关系,所以就不用虚拟,而且虚
   拟机制需要由构造函数中生成的VPtr来支持,而在进入构造函数之前,VPtr都没有生成,
   从原理上讲也根本就无法实现虚构造函数。
而在继承时子类一般都是为了添加实现父类所没有的成员或者
   功能,假如有些成员中出现动态创建,那么就该在析构函数中销毁掉,而若析构函数不为虚
函数的话,当析构指向子类对象的父类指针时就不能通过多态调用子类的析构函数,来释放
子类中申请的资源(通常是堆内存),将造成析构不完全,甚至内存泄漏,到所以析构函数
必须是虚拟的(这样可以保护内存)。
   同样在构造函数中调用的虚函数也不能得到期望的多态结果,因为子类还没又被构造出来,
   VPtr还只是指向本类,根据这个VPtr调用虚函数只能调用到本地的版本,也就失去了其虚
   函数的特性,变成了调用当前类(构造函数所在的类)的成员函数,不会形成多态。   
4,成员函数需要特定的对象来修饰,只有在特定的对象中使用成员函数,不把友元声明为类
   成员就提高了程序的灵活性。  
   友元函数被引入的原因较多,比如你重载一个分数相乘的*,左端的成员不能自动从整数转
换成分数,而右端的成员可以,(好像是这样),这里面涉及到一个this指针不支持自动
转换的问题。友元函数可以去除这个弊病。 
5,多重继承用在当你的类需要继承不同的类的功能时,但假如有这样现象:   
   B<----A--->C         B,C都继承自A,而D继承自B和C(多重继承),为了不使得在内
   存中有两个A类的拷贝(实际上如果这样的话会带来麻烦),就要使用虚拟继承,这样就
   只有一个A拷贝在D的每个对象中。
   多重继承在com里用的较多。
   多重继承不常用,我知道有一种情况下比较好用,就是你要实现一个类,他的一部分功能
   有一个类已经实现了,另一部分功能在另外的一个类里已经实现了,此时可以考虑用多重
   继承来组合这两个类。当然多重继承还有其他的用处。不过这个功能实在少用。当然前面
   的人已经说过COM中用得比较多,在COM的情况,一般是继承一个接口,又继承另一个类的
   实现(类似于java的单继承一个类,同时实现多个接口),或者是继承多个接口。这些情
   况表明,你的这个类实现了多个接口。


转载:


1.为什么需要虚函数?虚函数这个名字怎样得来的,有没有“实函数”的说法??   
  答:虚函数的引入是为了实行多态,要了解虚函数首先要了解多态,所谓的多态,是一种程序在运行时改变自己行为的能力,举个简单的例子:考虑不同的图形对象,计算面积的方法是不同的。假如你为所有的图形类建立一个函数来计算她的面积。你可能会将这个函数都起同样的名字,那么,在运行时你需要知道某个对象是那个图形类的具体实例,在能调用正确的计算面积的函数。简单的做法是这样:   
  if   (obj   是三角形的实例)   
  {   
        调用三角形的面积计算方法。   
  }   
  else   if   (obj   时正方形的实例)   
  {   
        调用正方形的面积计算方法。   
  }   
  ...   
    
  可以看到,这种方法比较土。关键是她不具备任何扩展性,如果增加一个新的图形类型,这段代码就需要就修改。这是虚函数就可以派上用场了。   
  将每个具体的图形类都继承自类型Sharp,在Sharp中定义计算面积的方法,而子类重写该方法。那么你就可以用指向基类的指针来操作具体的子类对象,来调用到正确的方法。这里多态就提供了一种抽象的能力,让你不用关心对象具体的类型,只要知道该对象是从Sharp继承来的,就可以正确的计算出她的面积。至于C++是如何实现虚函数的,具体的机制我简单的说一下:在定义了虚函数的类的对象中,编译器会增加一个指针vptr,这个指针指向一个表,这个表叫做vtable,该表中存放了该类的每个虚函数的入口地址。当调用一个虚函数时,系统会根据这个指针查到函数的地址,在调用虚函数。他和普通的函数调用的区别就在这里,普通的函数调用,函数的地址是编译时就绑定的。所以虚函数的调用也叫动态绑定。你自己好好思考一下,为什么这样的实现可以实现多态。   
    
  2.何为“静态”成员函数,为什么书上说它不能为虚函数?   
  答:静态成员函数和普通的成员函数的区别在于,在普通的成员函数参数列表里有一个隐含的this指针,通过这个指针,成员函数才可以操作具体的对象。静态函数没有这个指针。所以,静态函数不能成为虚函数。因为虚函数需要知道这个指针。   
    
  3.为什么构造函数不能为虚函数,而析构函数经常为虚函数呢?   
  答:由于对象在构造函数执行完成后,才创造出来,所以在调用构造函数时,没法访问vptr,而构造函数本身更没法成为虚函数了。原因就是构造函数调用的时候,对象的vptr还没有被正确的初始化呢。   
    
  4.为什么引入友元函数而不全部用成员函数代替呢?   
  答:不是所有的操作都可以用成员函数做的。我的这个答案可能不能让人信服。但是我一时举不出来一个浅显的例子。  

--------------------------------------------------------------------------------------
1.关于重写override or overwrite函数的形式需要注意的
 参数列表不能变;返回类型一般情况下不能变(特例除外)。

参数列表变了,则virtual失效,相当于隐藏了基类中的函数。
原则是:派生类中改变基类中一个函数的特征(没有virtual的情况下同名就会覆盖隐藏,所有特征不变都会隐藏:注1;有virtual的情况下指参数列表或者特例之外的返回类型变化),所有该函数名字的基类版本都会被隐藏(有virtual的情况下导致多态失效)。

注1:没有virtual的情况下,任何时候在派生类中重新定义了基类中的一个重载函数,派生类中基类的所有使用这个函数名字的重载函数都被隐藏!

特例是:c++允许派生类中重写的虚函数的返回类型是基类函数返回类型的子类型,因为他们是is-a的关系!

题目:
Which virtual function redeclarations of the Derived class are correct?    
a. Base* Base::copy(Base*);       
   Base* Derived::copy(Derived*);    
b. Base* Base::copy(Base*);       
   Derived* Derived::copy(Base*);    
c. ostream& Base::print(int,ostream&=cout);       
   ostream& Derived::print(int,ostream&);    
d. void Base::eval() const;       
   void Derived::eval();
正解:

首先应该明确的是这题在考我们override重写而不是overload重载
对于重写参数列表必须相同,而他们的返回类型不必相同,但是必须符合一条原则:
子类函数的返回类型必须可以进行切割转变为父类函数的返回类型,否则不能达到virtual的效果
值得一提的是第一个选项 本来觉得也可以通过类似切割的技术把Derived对象转变为Base对象,但是实验发现似乎不能实现多态

Base *b = Derived();
Derived* d; 
b.func(d);
调用不到子类的func函数
那么归纳为这种继承要保持函数签名的参数列表的一致才能实现多态
但是虽然C选项可以实现这样的print(3,cout)但是子类是不能这样使用的print(3),所以也不能算是多态
只有B才能多态

2.构造函数为什么不能是虚函数?
虚函数调用基于虚表指针,虚表指针要通过this来得到。构造函数执行时,实例并未生成,没有this指针。   
  构造函数用来设定虚指针,虚指针用来调用需函数,如果构造函数自己是虚函数,那么......

另:为什么构造函数不能为虚函数呢?什么时候才算建立了一个对象呢?

一个对象的建立应该是在其调用了构造函数以后,也就是说构造函数的作用就是初始化并建立对象.

因此在还没有调用构造函数的时候,对象并没有建立,也就没有对象类型的说法.因此如果对象连类型都没有,构造函数

又怎么能判断其为基类还是派生类呢,因此构造函数不能为虚函数.

同样在构造函数中调用的虚函数也就失去了其虚函数的特性,变成了调用当前类(构造函数所在的类)的成员函数,不会形成多态,因此会出现以上结果,即在CBase构造函数调用的是基类而非派生类的fun1();

一旦构造函数调用完成,对象建立起来,就有了基类派生类的概念,此时虚函数就会发挥其多态的特性.


3.析构函数常常必须是虚函数(指支持多态的情况下)
否则将屏蔽子类析构函数的调用,造成资源没有释放,可能引起内存泄漏。
题目:
  (1).这题会造成内存泄漏吗??(p只是指向基类子对象)       
  #include<iostream.h>       
  class     test       
  {       
  int     a;       
  public:       
                ~test()       
                        {       
                        cout<<"unload     test/n";       
      
                        }       
  };       
      
  class     test1:public     test       
  {       
  public:       
                        int     a;       
                                    test1()       
                                {       
                                a=100;       
                                }       
      
                                ~test1()       
                                {       
                                cout<<"unload     test1/n";       
                                }       
  };       
  void     main()       
  {       
            test     *p=new     test1;       
            delete     p;       
  }       
      
  输出:unload     test       
      
      
  (2).为什么静态成员函数不可以被设置为虚拟的??

  (1).   基类的析构函数应该声明为virtual,这样,在释放对象的时候才会调用派生类的析构函数。这是多态性在析构函数上的反映。如果派生类的析构函数中需要释放某些资源的话,你的程序就有可能导致内存泄露。   
    
  (2).   静态成员函数是全局的,不可动态联编的,因此不能是虚函数。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值