Chinaunix首页 | 论坛 | 博客
  • 博客访问: 141697
  • 博文数量: 10
  • 博客积分: 2431
  • 博客等级: 大尉
  • 技术积分: 266
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-02 00:56
文章分类
文章存档

2016年(1)

2013年(2)

2011年(3)

2010年(4)

分类: C/C++

2013-11-21 13:09:58

    上回出了几道有挑战的题,当然那些不会真的做面试题的,让一大半人都挂的题目是没有出的必要的。C++是一个语言标准,不是一个实现标准,语言标准只规定了源代码长什么样合法,没有规定看到想到的和编译出来的东西就一定一样。例如,一个类有virtual关键字修饰的函数,那么就会有这个类就会有虚函数表吗? 不一定啊,因为C++标准压根就没有规定要如何实现虚函数!所谓的虚函数表只是一种流行的,实现虚函数的方式而已。
    C++是马,而某个具体的C++编译器实现是"白马"。白马非马也!有了上一篇文章的基础,我们继续讨论和构造/析构/赋值相关的话题。
【第一题】用VC2008/VC2012/GCC4.7编译下面的代码Release版,输出多少?

点击(此处)折叠或打开

  1. #include<iostream>
  2. using namespace std;
  3. static int i=0;
  4. class My{
  5. public:
  6.     My() {i+=1;}
  7.     My(const My&){i+=2;}
  8.     My& operator=(const My&){
  9.         i+=3;
  10.         return *this;
  11.     }
  12. };
  13. class Derived: public My
  14. {
  15. public:
  16.     Derived(){}
  17.     Derived(const Derived&d){}
  18. };
  19. int main(int argc, char* argv[])
  20. {
  21.     Derived d;
  22.     Derived d2(d);
  23.     cout<<i<<endl;
  24.     return 0;
  25. }

    这题的关键是Derived d2(d)这句话,继承类的拷贝构造函数,会调用基类的哪个构造函数呢? 没有显示指定初始化列表,那就是调用基类的默认构造函数,因此本题的答案是2。
【第二题】用VC2008/VC2012/GCC4.7编译下面的代码Release版,输出多少?

点击(此处)折叠或打开

  1. #include<iostream>
  2. using namespace std;
  3. static int i=0;
  4. class My{
  5. public:
  6.       My() {f();}
  7.     virtual void f(){i+=1;}
  8. };
  9. class Derived: public My
  10. {
  11. public:
  12.     Derived(){}
  13.     virtual void f(){i+=2;}
  14. };
  15. int main(void)
  16. {
  17.     My* pD=new Derived();
  18.     cout<<i<<endl;
  19.     delete pD;
  20.     return 0;
  21. }
    这道题的关键是,基类构造函数里面调用了一个虚函数f,那么实际是调的基类的f还是继承累的f呢?<>这本书的条款"Nevel call virtual functions during construction or destruction"有很好的说明,但是书上举例还是不够充分,解释的也不算清楚。因为:
    C++的"类"和"对象"只是语言级的概念,C++标准根本就没有规定编译的结果里面也存在对象,这样就能给编译器和优化器以无穷的空间----反过来说,我们不能假设对象真的有物理存在,因为构造函数有可能被内联,甚至release版连对象都优化得没有了,"多态"这个概念也是可以被编译器优化掉的。因此ctor/dtor要调用类内部的虚函数而根本把所谓多态置之脑后。
    所以,C++在ctor/dtor当中遇到虚函数调用的时候,直接当成非虚函数调用类内部的版本。这道题调用的是My::f(),输出是1。如果允许在基类构造期间调用继承类的函数,那么该函数需要访问继承类的成员例如指针,可此时继承类还没有构造,指针错误,崩溃了。
【第三题】用VC2008/VC2012/GCC4.7编译下面的代码Release版,输出多少?【第二题】用VC2008/VC2012/GCC4.7编译下面的代码Release版,输出多少?【第二题】用VC2008/VC2012/GCC4.7编译下面的代码Release版,输出多少?【第三题】以下程序的运行结果是什么:

点击(此处)折叠或打开

  1. class My
  2. {
  3.     My* pSelf;
  4. public:
  5.     My(){pSelf=this;}
  6.     ~My(){delete pSelf;}
  7. };
  8. int main(void)
  9. {
  10.     My m;
  11.     return 0;
  12. }
    析构函数无限递归,堆栈溢出崩溃。
【第四题】以下代码有什么问题?
用VC2008/VC2012/GCC4.7编译下面的代码,会有什么问题?用VC2008/VC2012/GCC4.7编译下面的代码,会有什么问题?用VC2008/VC2012/GCC4.7编译下面的代码,会有什么问题?用VC2008/VC2012/GCC4.7编译下面的代码,会有什么问题?

点击(此处)折叠或打开

  1. #include<iostream>
  2. using namespace std;
  3. class My{
  4. public:
  5.     virtual void f()=0;
  6.     void haha(){f();}
  7.     virtual ~My(){
  8.      cout<<__FUNCTION__<<endl;
  9.      f();
  10.     }
  11. };
  12. class You:public My{
  13. public:
  14.     void f(){cout<<__FUNCTION__<<endl;}
  15.     ~You(){
  16.      cout<<__FUNCTION__<<endl;
  17.      haha();
  18.     }
  19. };
  20. int main(){
  21.     My *p=new You;
  22.     delete p;
  23.     return 0;
  24. }
    不要着急说,调用一个haha()调用一个不存在的虚函数导致空指针错误。因为上面的代码根本编译不过。因为编译器让析构函数~My()调用本累的f(),而本类的f()是纯虚的,没有实现体,因此提示undefined reference to 'My::f()'。把上面的代码改一改,就能编过了:

点击(此处)折叠或打开

  1. #include<iostream>
  2. using namespace std;
  3. class My
  4. {
  5. public:
  6.      virtual void f() = 0;
  7.      void haha() { f(); }
  8.      virtual ~My(){
  9.          cout<<__FUNCTION__<<endl;
  10.          haha();
  11.      }
  12. };
  13. class You: public My
  14. {
  15. public:
  16.      void f(){cout<<__FUNCTION__<<endl;}
  17.      ~You(){
  18.          cout<<__FUNCTION__<<endl;
  19.          haha();
  20.      }
  21. };
  22. int main(int argc, char* argv[])
  23. {
  24.      My* pm=new You();
  25.      delete pm;
  26.      return 0;
  27. }
    此时~My调用了一个非纯虚的函数haha,没有问题,而haha里面去调用f。运行到delete pm的时候,~My()->haha调用了My::f(),这是虚函数调用,指向一个纯虚(空指针),因此崩溃(pure virtual function call)。如果我把My* pm=new You()改成You* py=new You()会让这个错误错误消失吗? 不会,因为析构函数先~You析构继承类的部分,然后进入~My。这个~My调用的时候,继承类的部分已经不存在了,因此此时虚函数的调用路径回到了基类,程序还是崩溃了。
-----------------------------------------------------------------
    虚拟机语言如C#/Java对象生命周期是GC全局管理,因此不存在这样的陷阱,多态的基类引用在ctor里面调用虚函数,是调进继承类。一下两段代码都是输出两个"Derived"。

点击(此处)折叠或打开

  1. class Base
  2.     {
  3.         public Base() { f(); }
  4.         public virtual void f() { Console.WriteLine("Base"); }
  5.     }
  6.     class Derived : Base
  7.     {
  8.         public Derived() { f(); }
  9.         public override void f() { Console.WriteLine("Derived"); }
  10.     }
  11.     [STAThread]
  12.     static void Main(string[] args)
  13.     {
  14.         Base pb = new Derived();
  15.     }

点击(此处)折叠或打开

  1. public class JavaApplication1 {

  2.     /**
  3.      * @param args the command line arguments
  4.      */
  5.     static public class Base
  6.     {
  7.         public Base() { f(); }
  8.         public void f() { System.out.println("Base"); }
  9.     }
  10.     static public class Derived extends Base
  11.     {
  12.         public Derived() { f(); }
  13.         public void f() { System.out.println("Derived"); }
  14.     }
  15.     public static void main(String[] args) {
  16.         // TODO code application logic here
  17.         JavaApplication1.Base pb = new JavaApplication1.Derived();
  18.     }
  19. }

阅读(2313) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~