MyException - 我的异常网
当前位置:我的异常网» C++ » C/C++难题笔记——疑难点

C/C++难题笔记——疑难点

www.MyException.Cn  网友分享于:2015-08-23  浏览:0次
C/C++难点笔记——疑难点

疑惑点

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字节对齐。
最后,总结下,虚拟继承和虚函数多态机制是分开的,虚拟继承会保留基类中的虚表指针,并且添加一个指向虚基类的虚拟指针,它并不会实现多态。

版权声明:本文为博主原创文章,未经博主允许不得转载。

文章评论

什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
代码女神横空出世
代码女神横空出世
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
为什么程序员都是夜猫子
为什么程序员都是夜猫子
旅行,写作,编程
旅行,写作,编程
程序员的鄙视链
程序员的鄙视链
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
编程语言是女人
编程语言是女人
程序员必看的十大电影
程序员必看的十大电影
如何成为一名黑客
如何成为一名黑客
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
程序员应该关注的一些事儿
程序员应该关注的一些事儿
漫画:程序员的工作
漫画:程序员的工作
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
每天工作4小时的程序员
每天工作4小时的程序员
程序员和编码员之间的区别
程序员和编码员之间的区别
我是如何打败拖延症的
我是如何打败拖延症的
那些争议最大的编程观点
那些争议最大的编程观点
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
鲜为人知的编程真相
鲜为人知的编程真相
 程序员的样子
程序员的样子
总结2014中国互联网十大段子
总结2014中国互联网十大段子
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
我的丈夫是个程序员
我的丈夫是个程序员
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
10个调试和排错的小建议
10个调试和排错的小建议
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
程序员都该阅读的书
程序员都该阅读的书
老程序员的下场
老程序员的下场
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有