• C/C++易错难点笔记01


    疑惑点

    C++是一门奇妙的语言。非常多时候你对底层不熟悉,非常难知道某些情况下的结果,以下是我不断积累的疑惑点,这里将其记录下来。

    类的转换问题

    代码:

    class A{
    public:
        virtual void f()
        {
            cout << "A" << endl;
        }
    };
    
    class B: public A{
    public:
        virtual void f()
        {
            cout << "B" << endl;
        }
    };
    
    int _tmain(int argc, _TCHAR* argv[]){
        A* pa =  new A();
        pa->f();
        B* pb = (B*)pa; 
        pb->f();
    
        delete pa,pb;
        pa = new B();
        pa->f(); //多态
        pb = (B*)pa;
        pb->f();
    }

    以下语句发生什么变化。


    B* pb = (B*)pa;
    解析:事实上什么变化也没有发生。还是输出A,不存在覆盖问题,pb指向pa原来地址。

    虚函数中返回值类型不同。能覆盖吗?

    答:不能覆盖。派生类重写基类的虚函数。返回值类型也必须同样。

    基类默认构造派生类是否须要显示调用

    例如以下代码报错吗?

    class A{
    public:
        A(int a){}
    };
    
    class B: public A{
    public:
        B(){}
    };

    解释。在基类没有默认构造的情况下,派生类是须要显式调用。

    所以上面代码编译不通过。

    基类和派生类之间的转换问题

    以下程序结果是什么:

    class A{public: int m_a;};
    class B{public: int m_b;};
    class C:public A,public B{public: int m_c;};
    
    int _tmain(int argc, _TCHAR* argv[]){
        C* pc = new C; 
        pc->m_a = 1;    pc->m_b = 2;    pc->m_c = 3;
        B* pb = dynamic_cast<B*>(pc); 
        A* pa = dynamic_cast<A*>(pc); 
        B pbb = *pc; //这里会发生什么?
    
        cout << pc << endl;
        cout << pb << endl;
        cout << pa << endl;
        cout << &pbb << "sizeof:" << sizeof(pbb) << "m_b" << pbb.m_b << endl;
        if (pc == pb)
            cout << "equal" << endl;
        else
            cout << "not equal" << endl;
    
        if ((int)pc == (int)pa)
            cout << "equal" << endl;
        else
            cout << "not equal" << endl;
    }

    解析:每一个语句解释例如以下,
    1. A* pa = dynamic_cast<A*>(pc); 此时pa指向了子类A的那部分,地址值与pc同样。
    2. B pbb = *pc; 这里会发生分割,调用了B类拷贝构造,将B的那部分分割到pbb的所在的栈空间中。


    3. if (pc == pb) 这里会发生隐式类型转换,pc = (C*)pb
    4. if ((int)pc == (int)pa) 尽管没有隐式类型转换,但地址同样。

    dynamic_cast问题

    以下代码中哪条语句会出现故障。

    class A{
    public:
        virtual void foo(){cout << "A foo()" << endl; }
        void pp(){ cout << "A pp()" << endl; }
    };
    
    class B:public A{
    public:
        void foo(){cout << "B foo()" << endl; }
        void pp(){ cout << "B pp()" << endl; }
        void funB(){ cout << "B::funB()" << endl;}
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        A a;
        A *pa = &a;
        (dynamic_cast<B*>(pa))->foo();  //语句1
        (dynamic_cast<B*>(pa))->pp();   //语句2
        (dynamic_cast<B*>(pa))->funB(); //语句3
        system("pause");
        return 0;
    }

    解析:语句1会出现故障。foo()是虚函数。编译器会依据对象的虚函数指针查找虚函数表,定位foo函数。

    dynamic_cast不是强制类型转换,而是带有某种“咨询”性质的。假设不能转换,dynamic_cast会返回NULL。表示不成功。


    上面3条语句相当于:

        B* bnull = NULL;
        bnull->foo();
        bnull->pp();
        bnull->funB();

    上面的转换时不成功的,所以返回的是NULL指针。又由于pp和funB函数未使用不论什么成员数据,也不是虚函数。不须要this指针,也不须要动态绑定,所以能够正常执行。

    虚拟继承和带有虚函数的继承内存分布情况

    以下代码输出结果是多少?

    class A{
    public:
        virtual void foo(){};
    private:
        char ca[3];
    };
    
    class B:virtual public A{
    public:
        virtual void foo(){};
    private:
        char cb[3];
    };
    
    class C:virtual public B{
    public:
        virtual void foo(){};
    private:
        char cb[3];
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
        cout << sizeof(C) << endl;
        system("pause");
        return 0;
    }

    解析:结果是8,16。24。
    然而,以下代码输出结果又是多少?
    去掉了虚拟继承

    class A{
    public:
        virtual void foo(){};
    private:
        char ca[3];
    };
    
    class B:public A{
    public:
        virtual void foo(){};
    private:
        char cb[3];
    };
    
    class C:public B{
    public:
        virtual void foo(){};
    private:
        char cb[3];
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
        cout << sizeof(C) << endl;
        system("pause");
        return 0;
    }

    解析:结果为8,12。6。
    1. 带有虚函数的虚拟继承中,也分两种情况,1.派生类中定义了新的虚函数。而且部分重写了虚基类的虚函数,这个时候。派生类中有3个虚表指针,一个指向虚基类的虚函数指针,一个自己的虚表指针(实现多态),另一个基类自己虚表指针。

    2.派生类所有重写虚基类虚函数,且没有新定义虚函数,这时候,派生类中含有两个虚表指针。另一个基类自己虚表指针,个指向虚基类的虚函数指针。
    2. 基类不带虚函数的虚继承中,分两种情况,1.基类和派生类中都没有虚函数。这个时候,派生类仅仅会多加入一个虚表指针,指向虚基类的虚函数(尽管虚基类中没有虚函数)。2.基类中没有虚函数。派生类中有虚函数,则会生产两个虚表指针。一个指向自己虚函数的的虚表指针和一个指向虚基类的虚函数指针


    3. 带虚函数的普通继承中,这个时候不论是基类还是派生类,仅仅要类中有虚函数。都会有且仅仅有一个虚函数指针。
    再看以下代码:
    这里写图片描写叙述
    验证了第一个的第一种情况。
    在看以下:
    这里写图片描写叙述
    这验证了第二个的另外一种情况。
    另一个非常有趣的问题:
    这里写图片描写叙述
    怎么没有内存对其了呢?这时假设再定义一个int就会有内存对齐了。假设没有就是以1字节对齐。
    最后,总结下,虚拟继承和虚函数多态机制是分开的。虚拟继承会保留基类中的虚表指针,而且加入一个指向虚基类的虚拟指针,它并不会实现多态。

  • 相关阅读:
    Centos7安装go.10.1环境
    centos7安装PHP5
    Linux 无文件攻击memfd_create()具体操作步骤
    centos7 '/mnt/hgfs'下共享文件夹不显示问题
    fiddler连接代理手机无法上网问题解决办法
    centos 镜像软件安装包版本低,手动安装过程
    0 upgraded, 0 newly installed, 0 to remove and 112 not upgraded解决方法
    JavaScript高级程序设计(第3版)第七章读书笔记
    JavaScript高级程序设计(第3版)第六章读书笔记
    JavaScript高级程序设计(第3版)第五章读书笔记
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7064531.html
Copyright © 2020-2023  润新知