首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

c++深入札记

2013-09-28 
c++深入笔记C类型转换分为:隐式类型转换和显式类型转换第1部分. 隐式类型转换又称为“标准转换”,包括以下几

c++深入笔记

C++类型转换分为:隐式类型转换和显式类型转换

第1部分. 隐式类型转换

又称为“标准转换”,包括以下几种情况:
1) 算术转换(Arithmetic conversion) : 在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型。

 

int ival = 3;
double dval = 3.14159;

ival + dval;//ival被提升为double类型

2)一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型

c++深入札记int *pi = 0; // 0被转化为int *类型
c++深入札记ival = dval; // double->int
c++深入札记

例外:void指针赋值给其他指定类型指针时,不存在标准转换,编译出错

3)将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型

extern double sqrt(double);

cout << "The square root of 2 is " << sqrt(2) << endl;
//2被提升为double类型:2.0

4)从一个函数返回一个表达式,表达式类型与返回类型不一致:目标转换类型为函数的返回类型

double difference(int ival1, int ival2)
{
    return ival1 - ival2;
    //返回值被提升为double类型
}


第2部分. 显式类型转换
被称为“强制类型转换”(cast)
C     风格: (type-id)
C++风格: static_cast、dynamic_cast、reinterpret_cast、和const_cast..

 

 关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。下面对它们一一进行介绍。

static_cast

用法:static_cast < type-id > ( expression )

说明:该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。

来源:为什么需要static_cast强制转换?
情况1:void指针->其他类型指针
情况2:改变通常的标准转换
情况3:避免出现可能多种转换的歧义


它主要有如下几种用法:
  • 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  • 把void指针转换成目标类型的指针(不安全!!)
  • 把任何类型的表达式转换成void类型。注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

    dynamic_cast

    用法:dynamic_cast < type-id > ( expression )

    说明:该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

    来源:为什么需要dynamic_cast强制转换?
    简单的说,当无法使用virtual函数的时候

    典型案例:
    Wicrosoft公司提供给我们一个类库,其中提供一个类Employee.以头文件Eemployee.h和类库.lib分发给用户
    显然我们并无法得到类的实现的源代码
    //Emplyee.h
    class Employee 
    {
    public:
        virtual int salary();
    };

    class Manager : public Employee
    {
    public: 
        int salary();
    };

    class Programmer : public Employee
    {
    public:
        int salary();
    };
    我们公司在开发的时候建立有如下类:
    class MyCompany
    {
    public:
        void payroll(Employee *pe);
        //c++深入札记
    };

    void MyCompany::payroll(Employee *pe)
    {
        //do something
    }
    但是开发到后期,我们希望能增加一个bonus()的成员函数到W$公司提供的类层次中。
    假设我们知道源代码的情况下,很简单,增加虚函数:
    //Emplyee.h
    class Employee 
    {
    public:
        virtual int salary();
        virtual int bonus();
    };

    class Manager : public Employee
    {
    public: 
        int salary();
    };

    class Programmer : public Employee
    {
    public:
        int salary();
        int bonus();
    };

    //Emplyee.cpp
    c++深入札记
    int Programmer::bonus()
    {
        //c++深入札记
    }
    c++深入札记payroll()通过多态来调用bonus()
    class MyCompany
    {
    public:
        void payroll(Employee *pe);
        //c++深入札记
    };

    void MyCompany::payroll(Employee *pe)
    {
        //do something
        //pe->bonus();
    }
    但是现在情况是,我们并不能修改源代码,怎么办?dynamic_cast华丽登场了!
    在Employee.h中增加bonus()声明,在另一个地方定义此函数,修改调用函数payroll().重新编译,ok
    //Emplyee.h
    class Employee 
    {
    public:
        virtual int salary();
    };

    class Manager : public Employee
    {
    public: 
        int salary();
    };

    class Programmer : public Employee
    {
    public:
        int salary();
        int bonus();//直接在这里扩展
    };

    //somewhere.cpp

    int Programmer::bonus()
    {
        //definec++深入札记
    }

    class MyCompany
    {
    public:
        void payroll(Employee *pe);
        //
    };

    void MyCompany::payroll(Employee *pe)
    {
        Programmer *pm = dynamic_cast<Programmer *>(pe);
        
        //如果pe实际指向一个Programmer对象,dynamic_cast成功,并且开始指向Programmer对象起始处
        if(pm)
        {
            //call Programmer::bonus()
        }
        //如果pe不是实际指向Programmer对象,dynamic_cast失败,并且pm = 0
        else
        {
            //use Employee member functions
        }
    }



    dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

    在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
    class Base
    {
    public:
        int m_iNum;
        virtual void foo();
    };

    class Derived:public Base
    {
    public:
        char *m_szName[100];
    };

    void func(Base *pb)
    {
        Derived *pd1 = static_cast<Derived *>(pb);

        Derived *pd2 = dynamic_cast<Derived *>(pb);
    }
    在上面的代码段中,
    如果pb实际指向一个Derived类型的对象,pd1和pd2是一样的,并且对这两个指针执行Derived类型的任何操作都是安全的;
    如果pb实际指向的是一个Base类型的对象,那么pd1将是一个指向该对象的指针,对它进行Derived类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针(即0,因为dynamic_cast失败)。
    另外要注意:Base要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

    另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
    class Base
    {
    public:
        int m_iNum;
        virtual void f(){}
    };



    class Derived1 : public Base
    {

    };

    class Derived2 : public Base
    {

    };

    void foo()
    {
        derived1 *pd1 = new Drived1;

        pd1->m_iNum = 100;

        Derived2 *pd2 = static_cast<Derived2 *>(pd1); //compile error

        Derived2 *pd2 = dynamic_cast<Derived2 *>(pd1); //pd2 is NULL

        delete pd1;
    }
    在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。

    reinpreter_cast

    用法:reinpreter_cast<type-id> (expression)

    说明:type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

    该运算符的用法比较多。

    const_cast

    用法:const_cast<type_id> (expression)

    说明:该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

    常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

    Voiatile和const类试。举如下一例:
    class B{

    public:

    int m_iNum;

    }

    void foo(){

    const B b1;

    b1.m_iNum = 100; //comile error

    B b2 = const_cast<B>(b1);

    b2. m_iNum = 200; //fine
    }
    上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。





    关于虚函数表的解析的博客,讲的很好,但是关于多重继承那一块不是很理解。

    先看下列代码:

    1. #include <iostream>
    2. using namespace std;
    3. class Base1 {
    4.     public:
    5.         //虚函数定义
    6.         virtual void f() { cout << "Base1::f" << endl; }
    7.         virtual void g() { cout << "Base1::g" << endl; }
    8.         virtual void h() { cout << "Base1::h" << endl; }
    9. };
    10. class Base2 {
    11.     public:
    12.         //虚函数定义
    13.         virtual void f() { cout << "Base2::f" << endl; }
    14.         virtual void g() { cout << "Base2::g" << endl; }
    15.         virtual void h() { cout << "Base2::h" << endl; }
    16. };
    17. class Base3 {
    18.     public:
    19.         //虚函数定义
    20.         virtual void f() { cout << "Base3::f" << endl; }
    21.         virtual void g() { cout << "Base3::g" << endl; }
    22.         virtual void h() { cout << "Base3::h" << endl; }
    23. };
    24. //多继承时的情况---无虚函数覆盖
    25. class DerivedMulti:public Base1,public Base2,public Base3{
    26. public:
    27.     //虚函数定义
    28.     virtual void f1() { cout << "DerivedMulti::f1" << endl; }
    29.     virtual void g1() { cout << "DerivedMulti::g1" << endl; }
    30. };
    31. int main()
    32. {
    33.     typedef void(*Fun)(void);
    34.      cout << "多重继承时的情况(无虚函数覆盖):" << endl;
    35.     DerivedMulti dMultiObj;
    36.     cout << "DerivedMulti类第一个虚函数表地址:" << (int*)(&dMultiObj) << endl;
    37.     cout << "依次调用三个虚函数表中的虚函数:" << endl;
    38.     cout << "第一个虚函数表中的虚函数:" << endl;
    39.     Fun pFun = NULL;
    40.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)));
    41.     pFun();
    42.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 1);
    43.     pFun();
    44.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 2);
    45.     pFun();
    46.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 3);
    47.     pFun();
    48.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 4);
    49.     pFun();
    50.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 5);
    51.     pFun();
    52.     cout << "第二个虚函数表中的虚函数:" << endl;
    53.     pFun = (Fun)*((int *)*((int*)(&dMultiObj + 1)));
    54.     pFun();
    55.     pFun = (Fun)*((int *)*((int*)(&dMultiObj + 1)) + 1);
    56.     pFun();
    57.     pFun = (Fun)*((int *)*((int*)(&dMultiObj + 1)) + 2);
    58.     pFun();
    59.     cout << "第三个虚函数表中的虚函数:" << endl;
    60.     pFun = (Fun)*((int *)*((int*)(&dMultiObj + 2)));
    61.     pFun();
    62.     pFun = (Fun)*((int *)*((int*)(&dMultiObj + 2)) + 1);
    63.     pFun();
    64.     pFun = (Fun)*((int *)*((int*)(&dMultiObj + 2)) + 2);
    65.     pFun();
    66.     return 0;
    67. }

    c++深入札记
    上述代码是关于多重继承无虚函数覆盖时的情况,按照博客上所说的,子类虚函数表的结构应该如下所示:

    c++深入札记

    但是我在代码中依次访问各个虚函数表中出现的虚函数时,运行到第二个虚函数表时程序就异常终止了:

    c++深入札记

    谁能帮忙看看是什么原因?

    运行环境是:Codeblocks 10.05(GCC)

    评论 (1) ? 分享 ? 链接 ? 2012-12-11 
    • 0&dMultiObj + 1 加1不对吧,好久没写指针了 – 柏拉图说 2012-12-111个答案票 数 c++深入札记
      • mario

        c++深入札记
        1667
      • 修改了你的代码,下面这样才是正确的

        1. int main()
        2. {
        3.     typedef void(*Fun)(void);
        4.      cout << "多重继承时的情况(无虚函数覆盖):" << endl;
        5.     DerivedMulti dMultiObj;
        6.     cout << "DerivedMulti类第一个虚函数表地址:" << (int*)(&dMultiObj) << endl;
        7.     cout << "依次调用三个虚函数表中的虚函数:" << endl;
        8.     cout << "第一个虚函数表中的虚函数:" << endl;
        9.     Fun pFun = NULL;
        10.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)));
        11.     pFun();
        12.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 1);
        13.     pFun();
        14.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 2);
        15.     pFun();
        16.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 3);
        17.     pFun();
        18.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)) + 4);
        19.     pFun();
        20.     cout << "第二个虚函数表中的虚函数:" << endl;
        21.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)+ 1));
        22.     pFun();
        23.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)+ 1) + 1);
        24.     pFun();
        25.     pFun = (Fun)*((int *)*((int*)(&dMultiObj)+ 1) + 2);
        26.     pFun();
        27.     cout << "第三个虚函数表中的虚函数:" << endl;
        28.     pFun = (Fun)*((int *)*((int*)(&dMultiObj )+ 2));
        29.     pFun();
        30.     pFun = (Fun)*((int *)*((int*)(&dMultiObj )+ 2) + 1);
        31.     pFun();
        32.     pFun = (Fun)*((int *)*((int*)(&dMultiObj )+ 2) + 2);
        33.     pFun();
        34.     return 0;
        35. }

        pFun = (Fun)((int *)((int)(&dMultiObj)) + 5); //这里已经越界了,错误。 pFun = (Fun)((int )((int)(&dMultiObj + 1))); //这里错误,指针越界了。 (&dMultiObj + 1) 这里的指针运算是DerivedMulti的指针运算,地址是0x22ff1c;指向下一个DerivedMulti了。
        改为((int*)(&dMultiObj)+ 1) 这样才是指向下一个虚函数指针vptr。
        看一下DerivedMulti 的内存布局图就清楚了。

        c++深入札记

        评论 (1) ? 链接 ? 2012-12-12
        • 0(&dMultiObj + 1) 这里的指针运算是DerivedMulti的指针运算,地址是0x22ff1c;指向下一个DerivedMulti了。-----这个地方我搞错了~~哎~ – 江南烟雨 2012-12-12

           多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的对象),当调用函数时可以自动判断调用的是哪个对象的函数。一个函数说明为虚函数,表明在继承的类中覆盖这个函数时,当调用这个函数时应当查看以确定调用哪个对象的这个函数。
                   (注:虚函数只能借助于指针或者引用来达到多态的效果。直接通过类的对象进行函数调用,而非指针或引用,即使被调用的函数是虚函数也是静态绑定,即在编译时决议出函数的地址,不会有多态的行为发生。若通过指针或引用来调用虚成员函数,会产生动态绑定,即使指针的类型和对象的类型是一样的)

            普通函数的处理:一个特定的函数都会映射到特定的代码,无论时编译阶段还是连接阶段,编译器都能计算出这个函数的地址,调用即可。
              虚函数的处理:被调用的函数不仅依据调用的特定函数,还依据调用的对象的种类。对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。        虚函数表的结构:它是一个函数指针表,每一个表项都指向一个函数。任何一个包含至少一个虚函数的类都会有这样一张表。需要注意的是Virtual Table只包含虚函数的指针,没有函数体。实现上是一个函数指针的数组。虚函数表解决了继承、覆盖的问题,保证其容真实反应实际的函数。每个派生类的Virtual Table继承了它各个基类的Virtual Table,如果基类Virtual Table中包含某一项,则其派生类的Virtual Table中也将包含同样的一项,但是两项的值可能不同。如果派生类覆盖(override)了该项对应的虚函数,则派生类Virtual Table的该项指向覆盖后的虚函数,没有覆盖的话,则沿用基类的值。
                      每一个类只有唯一的一个Virtual Table,不是每个对象都有一个Virtual Table,恰恰是每个同一个类的对象都有一个指针,这个指针指向该类的Virtual Table(当然,前提是这个类包含虚函数)。那么,每个对象只额外增加了一个指针的大小,一般说来是4字节。
                      Virtual Table就是编译期间建立,执行期间查表执行。

                这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保 证正确取到虚函数的偏移量)。所以,在类对象的内存布局中,首先是该类的Virtual Table指针,然后才是对象数据。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

                假设我们有这样的一个类:

                class Base {

                public:

                virtual void f() { cout << "Base::f" << endl; }

                virtual void g() { cout << "Base::g" << endl; }

                virtual void h() { cout << "Base::h" << endl; }

                };

                按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

                typedef void(*Fun)(void);

                Base b;

                Fun pFun = NULL;

                cout << "虚函数表地址:" << (int *)*(int*)(&b) << endl;

                cout << "虚函数表 — 第一个函数地址:" << (int*)*((int*)*(int*)(&b)) << endl;

                // Invoke the first virtual function

                pFun = (Fun)*((int*)*(int*)(&b));

                pFun();

                实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

                虚函数表地址:0012FED4

                虚函数表 — 第一个函数地址:0044F148

                Base::f

                通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

                (Fun)*((int*)*(int*)(&b)+0); // Base::f()

                (Fun)*((int*)*(int*)(&b)+1); // Base::g()

                (Fun)*((int*)*(int*)(&b)+2); // Base::h()

                画个图解释一下,如下所示:

              c++深入札记

                注意:在上面这个图中,在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

                下面将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。


                   1. 一般继承(无虚函数覆盖)

                下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

              c++深入札记

                请注意,在这个继承关系中,子类没有覆盖任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

                对于实例:Derive d; 的虚函数表如下:

              c++深入札记

                我们可以看到下面几点:

                1)虚函数按照其声明顺序放于表中。

                2)父类的虚函数在子类的虚函数前面。

                

                2. 一般继承(有虚函数覆盖)

                覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数覆盖了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

              c++深入札记

                为了让大家看到被继承过后的效果,在这个类的设计中,只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

              c++深入札记

                我们从表中可以看到下面几点,

                1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

                2)没有被覆盖的函数依旧。

                这样,我们就可以看到对于下面这样的程序,

                Base *b = new Derive();

                b->f();

                由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。


                3. 多重继承(无虚函数覆盖)

                下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

              c++深入札记

                对于子类实例中的虚函数表,是下面这个样子:

              c++深入札记

                我们可以看到:

                1) 每个父类都有自己的虚表。

                2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

                这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。


                   4. 多重继承(有虚函数覆盖)

                下面我们再来看看,如果发生虚函数覆盖的情况。

              c++深入札记

                下图中,我们在子类中覆盖了父类的f()函数:

              c++深入札记

                下面是对于子类实例中的虚函数表的图:

                我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

                Derive d;

                Base1 *b1 = &d;

                Base2 *b2 = &d;

                Base3 *b3 = &d;

                b1->f(); //Derive::f()

                b2->f(); //Derive::f()

                b3->f(); //Derive::f()

                b1->g(); //Base1::g()

                b2->g(); //Base2::g()

                b3->g(); //Base3::g()


                安全性

                过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

                一、通过父类型的指针访问子类自己的虚函数

                我们知道,子类没有覆盖父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数覆盖的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

                Base1 *b1 = new Derive();

                b1->g1(); //编译出错

                任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可 以通过指针的方式访问虚函数表来达到违反C++语义的行为。

                二、访问non-public的虚函数

                另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

                如:

                class Base {

                private:

                virtual void f() { cout << "Base::f" << endl; }

                };

                class Derive : public Base{

                };

                typedef void(*Fun)(void);

                void main() {

                Derive d;

                Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

                pFun();

                }

               

               

               

               

              AUTODESK的面试题在网上是闹的沸沸扬扬,作为一个名企,这是可以理解的,况且其面试题质量也是不错的。抽一些闲暇时间,把网上传的比较多的70道题简单的解答了一遍,不为别的,只为再熟悉一下在大学学过的一些基础知识。希望对大家有用。当然,这只是我的个人解答,有什么不对的或者需要补充的,大家尽管提上来,好的话我加上去的。。。

                   

              1. 在类的普通成员函数中调用虚函数,情况是怎么样的?(对象、引用、指针)

              多态, 事实上,这是 Template Method模式的关键
              2. 关于成员变量初始化顺序,几个有依赖关系的成员变量要初始化,让写出构造函数。

              在初始化列表中,成员变量的初始化顺序是其在类中声明顺序,而非列表中的顺序。

              3. 写一个双链表。

              Struct ListNode

              {

                  int nData;

                  ListNode* pPreviousNode;

                  ListNode* pNextNode;

              }

              一般链表都会有一个表头节点与指向表头节点的头指针,应该会提供列表接口,按此数据结构实现即可。

              4. 写个is-a和has-a。

              这个比较简单

              Class Pet{};

              Class Dog: public Pet{};

              Class Boy{Pet* m_pPet;};

              5. struct vs. class.

              1)默认访问属性, struct为public, class为private

              2)class可以用来声明模板参数,而struct不能

              6. 称8个小球的问题

              没题

              7. stl 里面vector的实现(内部空间的申请与分配)

              Vector中文名字是动态数组,其内部数据结构就是一个数组,但是在数组元素不够用的时候,就要动态的重新分配,一般是现在大小的两倍,然后把原数组的内容拷贝过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降

              8. struct /class的区别

              重复了

              9. 为什么要用struct

              成员的默认属性不同,用struct的话,主要是作为数据的集合。

              10. 怎样使一个class不能被实例化

              1,构造函数私有化,2,抽象类

              11. 私有继承和public继承的区别。

              私有继承:只继承实现,不继承实现 has-a

              公有继承:继承接口与实现    is-a

              12. void *p的问题

              不能++

              13. 引用和指针的区别与联系。引用是否可以更改

              联系:支持多态,可以用来引用同一对象

              区别:指针可以为NULL,引用不可以;指针可以重赋值,引用不可以;

              14. windows编程基础,线程与进程的区别

              程序是一系列静态的指令序列

              进程是程序的一次动态执行,进程其实是一个资源的容器,包括一个私有的虚拟地址空间,一些初始的代码与数据,一些系统资源的句柄等

              线程是一个进程中的执行体,一般包括CPU寄存器状态,两个栈(内核模式,用户模式)以及一个TLS(Thread-Local Storage)等

              15. com+是否熟悉

              COM+是COM技术的延伸与发展,它包括了所有COM的基本功能(基于接口的编程模型,基本组件服务),并组合了DCOM(使组件技术延伸到了分布式领域)和MTS-Microsoft Transaction Server(提供了服务器端的组件管理与配置管理),并新增了一些服务:负载平衡,内存数据库,事件模型,队列服务等,主要用于Windows DNA(Distributed interNet Application Architecture)三层结构的中间层。

              16. 简述一下hash算法

              哈希表的目的是表查询插入修改能够达到O(1)的算法复杂度,通过对key编码来确定其存储地址来实现,当不同的key得到相同的编码时,便需要进行冲突检测与处理,一般方法有除留余数法,线性探测法,平方探测法,这使其无法真正达到O(1)

              17. 一个32位的数据,怎样找到最左边的一个1?

              如果是在最左位,这个数是负数,否则的话,左移一位,看是否变成负数,这是O(n)的算法,也可以用一个模板去与,并不断改变这个模板

              O(n/2)的算法:二分方式查找???

              18. 一个4*4的格子,填入1~15然后给个目标状态,怎样去搜索。

              题意不懂

              19. 给你100万个数据,数据的值在0~65535之间用最快的速度排序

              多关键字基数排序MSD(MOST SIGNIFICANT DIGIT FIRST)

              20. 如果我们的一个软件产品,用户回复说:运行速度很慢,你怎么处理?

              询问其Workflow, 用户的硬件环境

              21. 八皇后问题,详述解法(八皇后问题说的是在8*8国际象棋棋盘上,要求在每一行放置一个皇后,且能做到在竖方向,斜方向都没有冲突)

              回溯法

              22. kmp快速匹配算法 ---不算轻松的搞定

              普通的模式匹配算法,一旦不匹配,模式串右移一位;但是其实根据一直条件,我们可以算出应该向右移几位以避免不必要的比较;算法实现比较曲折

              23. 无向图中两点间最短路问题---伟大的迪杰克斯拉算法

              假设一共有N个节点,需要一个一维数组Previous[N]来记录前一个节点序号;一个一维数组TotalLength[N]来记录从原点到当前节点最短路径;一个二维数组Weights[N][N]来记录各点之间边的权重(如果存在),然后从源点到终点进行深度搜索或广度搜索,按以下规则:搜索到某个节点b时,假设其前一个节点为a,把TotalLength[a] + Weights[a][b]与TotalLength[b]相比较,如果小于TotalLength[b],则TotalLength[b] = TotalLength[a] + Weights[a][b], Previous[b] = a;反之则不做任何操作。这样到搜索结束后,从Previous[N]数组中就能得到整条最短路径了

              24. 空间中任意给两个向量,求角平分线

              先单位化,假设单位化后结果为nv1, nv2,则角平分线为(nv1+nv2) / 2

              25. 什么是平衡树

              左右子树都是平衡树,且高度相差不超过1的有序二叉树

              26. 哈夫曼编码问题

              理论基础:霍夫曼树是带权路径长度(WPL:Weighted Path Length)最小的二叉树,它不一定是完全二叉树,应该是权值大的外结点离根节点最近的扩充二叉树。霍夫曼编码是为了实现数据的最小冗余编码,是数据压缩学的基础。它根据字符在电文中出现的频率为权值,构造霍夫曼树,左为0,右为1.其有两个效果,一是保证电文有最短的编码,二是字符间不需要分隔符,因为不同的字符必定有不同的开头(成为前缀编码)。

              27. 有向图求环

              以该节点为源点与终点吗进行深度优先或广度优先搜索

              28. .给n个点,求凸包问题

              凸包(convex hull)是指一个最小凸多边形,满足这N个点都在多边形上,或其内。算法描述:

              求出最右的那个点作为凸多边形的一个顶点(P0),遍历其他所有点(Pi),如果其他点都在向量P0Pi的同一侧,则Pi也为凸多边形的顶点。

              29. 四则运算(给一个前缀表达式(波兰式)或后缀表达式(逆波兰式),然后求解;给一个中缀表达式)

              +*-CDBA -/EF---------------------> A+B*(C-D)-E/F      前缀-中缀

              操作符进栈,一个变量tmp放上一个中间操作数(运算结果),遇到操作数检查tmp是否为空,空的话取两个操作数,不空的话取一个操作数,另一个就是tmp了,操作符出栈运算,结果放入tmp中,如果是操作符,tmp清空

               ABCD-*+EF/- ---------------------> A+B*(C-D)-E/F    后缀-中缀

              操作数进栈,遇到操作符,两个操作数出栈,计算结果入栈

              30. STL中container有哪些?

              序列容器: vector, list, deque, bitset

              关联容器:     set, multiset, map, multimap

              适配容器:stack, queue, priority_queue

              类容器:         string, valarray, bitset

              扩展容器:hash_set, hash_multiset, hash_map, hash_multimap

              31. map中的数据存储方式是什么?

              红黑树,是一种平衡二叉搜索树,具有良好的最坏情况运行时间(统计性能好与AVL树)

              32. map和hashmap有什么区别?

              内部数据结构不同, map是红黑树,hashmap是哈希表

              33. hashmap是标准库中的吗?

              不是的,但在SGI stl与vc2005中都提供了。

              34. vector中的erase方法跟algorithm的remove有什么区别?

              vector中erase是真正删除了元素,迭代器访问不到了。algorithm中的remove只是简单的把要remove的元素移到了容器最后面,迭代器还是可以访问到的。因为algorithm通过迭代器操作,不知道容器的内部结构,所以无法做到真正删除。

              35. object是什么?

              具有内部状态,以及操作的软件构造,用来表示真实存在(物理上或概念上)的对象

              36. C++中如何阻止一个类被实例化?

              纯虚函数;构造函数私有化(友元)

              37. 一般在什么时候构造函数被声明成private呢?

               singleton模式;阻止某些操作(如阻止拷贝构造)

              38. 什么时候编译器会生成默认的copy constructor呢?

              用户没有自定义copy constructor;在代码中使用到了copy constructor;

              39. 如果你已经写了一个构造函数,编译器还会生成copy constructor吗?

              如果我写的是copy constructor,不会

              如果我写的不是copy constructor,同38

              40. 为什么说如果一个类作为基类,则它的析构函数要声明成virtual的?

              因为,如果delete一个基类的指针时,如果它指向的是一个子类的对象,那么析构函数不为虚就会导致无法调用子类析构函数,从而导致资源泄露。当然,另一种做法是将基类析构函数设为protected.

              41. inline的函数和#define有什么区别?什么时候会真的被inline,什么时候不会呢?

              1) 宏是在预编译阶段简单文本替代, inline在编译阶段实现展开

              2)宏肯定会被替代,而复杂的inline函数不会被展开

              3)宏容易出错(运算顺序),且难以被调试,inline不会

              4)宏不是类型安全,而inline是类型安全的,会提供参数与返回值的类型检查

              当出现以下情况时inline失败

              函数size太大

              inline虚函数

              函数中存在循环或递归

              函数调用其他inline函数

              42. 如果把一个类的成员函数写在类的声明中是什么意思?

              inline此函数(inline与template类似,必须在.h中实现)

              43. public继承和private继承有什么架构上的区别?

              public是is-a的关系,继承接口与实现

              private是has-a的关系,只继承实现

              44. 在多继承的时候,如果一个类继承同时继承自class A和class B,而class A和B中都有一个函数叫foo(),如何明确的在子类中指出override哪个父类的foo()?

              首先,foo在A,B总应该都是虚函数,否则就直接覆盖了,就没有这个问题了;其次,这个问题从语法角度来看似乎是无法解决。因为我们不能改原有设计(不然也没这个问题了:)),所有只好从extend来考虑:

              class EA: public class A

              {

              public:

                 virtual void foo(){fooA();}

              private:

                 virtual void fooA() = 0;

              }

              class EB: public class B

              {

              public:

                 virtual void foo(){fooB();}

              private:

                 virtual void fooB() = 0;

              }

              这样,我就可以override不同的函数来达到这个目的了

              class AB: public EA, pubic EB

              {

              private:

              virtual void fooA(){}

              virtual void fooB(){}

              }

              45. 虚拟继承的语法是什么?

                  A

                   \

                   C

                /

                  D

              class A{};

              class B: virtual public A{};

              class C: virtual public A{};

              class D: public B, public C{};

              46. 部分模版特例化和全部模版特例化有什么区别?

              偏特化只使用于类模板,而全特化适用与函数模板,类模板。

              偏特化的结果还是一个模板,而全特化的结果是一个具体的类型。

              47. 编一个函数,使一个单项链表转置。

              应该是逆序吧

              这个小算法竟然花了我不少时间,没有测试过的:

               

              struct ListNode
              {
                  int data;
                  ListNode* next;
              };

              void ReverseList(ListNode* p)
              {
                  ListNode* p0 NULL;
                  ListNode* p1 p->next;
                  ListNode* p2 p1 p1->next NULL;

                  // 三个指针,分别表示当前处理节点,前一节点与后一节点
                  // 复用头节点的next来保存节点
                  while (NULL != p2)
                  {
                      p->next p2->next; //暂存

                      p1->next p0;      //逆转
                      p2->next p1;

                      p0 p1;            //往下一个节点
                      p1 p2;
                      p2 p->next;
                  }
                  p->next p1;    //p1末元素变为首元素,链到头节点上
              }

               

              48. 拆解一个整数,比如4,可以拆解成4=3+1;4=2+2;4=2+1+1;4=1+1+1+1

              首先,对一个数进行拆分后,可能又要对最后一个因子进行拆分,所以要用递归;其次,第n+1个因子是小于等于第n个因子的;再者,对最后一个因子,我可以直接输出,也可以继续拆分。

              算法如下:

              N久的。。。。

               

              void print(int res[], int num)
              {
                  for (int 0; num; ++i)
                  {
                      printf("%d ", res[i]);
                  }
                  printf("\n");
              }
              // n表示总数,m表示最大因子
              void split(int n, int m)
              {
                  static int res[100]; //保存结果
                  static int num -1; //当前因子下标
                  num++;
                  //递归终止条件,为0不可再分,直接输出
                  if(0 == n) 
                  {
                      print(res, num+1);
                      num--;
                      return;
                  }
                  else
                  {
                      if(n == m) 
                      {
                          // 不拆,直接输出
                          res[num] m;
                          print(res,num+1);
                          num--;
                      }
                      else
                      {
                          // 拆分出第一个
                          res[num] m;
                          n-m;
                          //最大因子不可能大于总数
                          if(m>n) n;

                          // 循环,第二个因子可以继续拆分,而且按照最大因子不同可以拆分成多个
                          for (int m; i>=1; --i)
                          {
                              split(n, i);
                          }
                          num--;
                         
                  }
              }

              void Split(int n)
              {
                  for (int n-1; i>=1; i--)
                  {
                      split(n, i);
                  }
              }

               


              唉,老了,这个小东西搞了我

              49. 不用库函数,实现strcpy或者memcpy等函数

              一个字节一个字节的拷过去吧,但是要考虑源内存与目标内存的重叠。

              50. 内联函数的作用和缺点

              把代码直接插入到调用的地方,可以减少函数调用的次数,但是会增加代码的size,还有,如果内联失败,在每个调用的obj里,都会产生一份该函数的拷贝,这样既没有怎么减少代码的size,又没有减少函数的调用,赔了夫人又折兵。。。

              51. 指针和引用的区别

              指针可以不初始化,引用必须初始化

              指针可以是NULL,而引用必须引用一个实在的对象

              指针可以重指向其他对象,引用一旦初始化,便不再改变

              52. 友元的意义

              使被声明为友元的函数或类可以访问某个类的非共有成员。

              53. 虚函数的意义

              实现多态

              54. Overload, Overwrite, Override各自的特点和意义

              Overload: 函数重载(名字相同,参数不同)

              Overwrite:覆盖

              Override: 虚函数重载

              55. 头文件中的ifndef/define/endif干什么用?

              防止该头文件被重复引用。

              56.#i nclude <filename.h>和#i nclude “filename.h” 有什么区别?

              #i nclude <filename.h>:从标准库路径去寻找该文件,对于VC来说,应该还包括VC环境设置选项中的包含目录以及工程属性中指定的目录

              #i nclude “filename.h”:先在当前目录查找,如果找不到,按上面那种方式寻找

              57. 在C++程序中调用被C 编译器编译后的函数,为什么要加extern “C”?

              C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不同。C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题

              58. 一个类有基类、内部有一个其他类的成员对象,构造函数的执行顺序是怎样的?

              先执行基类的(如果基类当中有虚基类,要先执行虚基类的,其他基类则按照声明派生类时的顺序依次执行),再执行成员对象的,最后执行自己的。

              59. 请描述一个你熟悉的设计模式

              这个看你熟悉什么了。singleton最简单了,template method用的最多了,bridge挺炫的,command吹吹undo,redo也不错。。。。。

              60. 在UML中,聚合(aggregation)和组合(composition)有什么区别?

              其实从名字就能分别出来了。

              聚合表示只是简单的聚聚,没什么本质的联系,所以这些对象的生存时间也就没什么关系了;

              组合表示了更加紧密的一种关系,这些对象有着共同的生存期。

              一个典型的例子是孙悟空,手臂,金箍棒的关系。。。。

              61. C#和C++除了语法上的差别以外,有什么不同的地方?

              C++是直接生成可执行代码,而C#是先生成中间代码,等到第一次执行时,才由JIT(Just In Time)生成可执行的机器码。

              还有就是(1) c#有垃圾自动回收机制,程序员不用担心对象的回收。(2)c#严禁使用指针,只能处理对象。如果希望使用指针,则仅可在unsafe程序块中能使用指针。(3)c#只能单继承。(4)必须通过类名访问静态成员。不能像C++中那样,通过对象访问静态成员。(5)在子类中重写父类的虚函数时必须用关键字override,覆盖父类的方法要用关键字new

              62. New delete与malloc free的区别

              对于类,New 和delete会调用构造,析构函数

              new,delete都是能感知到类型的。new返回一个制定的类型,delete删除一个指定的类型,从而不用给定size。而malloc与free都是处理void类型的。用时时必须经过强制类型转换。

              63. #define DOUBLE(x) x+x,i = 5*DOUBLE(10);i是多少?正确的声明是什么?

              I = 5*10+10 = 60 60

              正确的声明是:

              #define DOUBLE(x) (x+x)

              64. 有哪几种情况只能用intialization list而不能用assignment?

              当类中含有const、reference成员变量;基类的构造函数都需要参数;类中含有其他类的成员对象,而该类的构造函数都需要参数。

              65. C++是不是类型安全的?

              不是。两个不同类型的指针之间可以强制转换。C#是类型安全的。

              66. main 函数执行以前,还会执行什么代码?

              全局对象的构造函数会在main 函数之前执行。

              67. 描述内存分配方式以及它们的区别。

              (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

              (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。用的是cache,速度较快但容量较小。

              (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new 申请任意多少的内存,程序员自己负责在何时用free或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

              68. 比较一下C++中static_cast和 dynamic_cast 的区别。

              Static_cast可以显式的做一些自动转换,如一些int, char一些基础类型的转换,以及指针之间的转换。但是其不保证安全性。Dynamic_cast主要作用其实在于把一个基类指针转化为子类指针,因为这个基类指针真正指向的不一定是我们想转换的类型的对象,所以转换可能失败,dynamic_cast能够知道失败而返回NULL,而static_cast就没那么聪明了,原因是dynamic_cast会利用rtti去查找该转换是否可行.(耗费时间多点。)

              69. 当一个类A中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。

              不为零,不同的对象应该有不同的地址,假设我声明一个A的数组A a[2],如果为零,那么a[0]和a[1]的地址岂不相同了

              70. 已知两个链表head1和head2各自有序,请把它们合并成一个链表依然有序,要求用递归方法进行。

              归并排序,应该比较简单。要注意的是如果一个链表为空,那么可以简单的把另一个直接链过去了。

               





热点排行