疑惑点

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字节对齐。

最后,总结下,虚拟继承和虚函数多态机制是分开的。虚拟继承会保留基类中的虚表指针,而且加入一个指向虚基类的虚拟指针,它并不会实现多态。

C/C++易错难点笔记01的更多相关文章

  1. 关于Verilog HDL的一些技巧、易错、易忘点(不定期更新)

    本文记录一些关于Verilog HDL的一些技巧.易错.易忘点等(主要是语法上),一方面是方便自己忘记语法时进行查阅翻看,另一方面是分享给大家,如果有错的话,希望大家能够评论指出. 关键词: ·技巧篇 ...

  2. 对象引用 方法传参 值传递 引用传递 易错点 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. JavaGuide易错点总结

    基础知识易错点 1. object.equals("str") 容易报空指针异常,应使用"str".equals(object); 还可以使用JDK7引入的工具 ...

  4. Redis:学习笔记-01

    Redis:学习笔记-01 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 1. Redis入门 2.1 ...

  5. JavaScript易错知识点整理

    前言 本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一 ...

  6. JavaScript 易错知识点整理

    本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES ...

  7. 软件测试之loadrunner学习笔记-01事务

    loadrunner学习笔记-01事务<转载至网络> 事务又称为Transaction,事务是一个点为了衡量某个action的性能,需要在开始和结束位置插入一个范围,定义这样一个事务. 作 ...

  8. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!

    <30天自制操作系统>笔记(01)——hello bitzhuwei's OS! 最初的OS代码 ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以 ...

  9. 《The Linux Command Line》 读书笔记01 基本命令介绍

    <The Linux Command Line> 读书笔记01 基本命令介绍 1. What is the Shell? The Shell is a program that takes ...

随机推荐

  1. Python学习笔记-小记

    1.字符串string 推断一个字符(char)是数字还是字母 str.isalpha() #推断是否为字母 str.isdigit() #推断是否为数字 推断一个字符串是否为空 if not str ...

  2. Android开发之——编码规范

    1. 前言 这份文档参考了 Google Java 编程风格规范和 Google 官方 Android 编码风格规范.该文档仅供参考,只要形成一个统一的风格,见量知其意就可. 2. 源文件基础 2.1 ...

  3. JavaScript 获取小数任一小数点后的位数的小数

    用Javascript取float型小数点后两位,例22.127456取成22.13,怎样做? 1.这样的方法最不推荐: function get(){ var s = 22.127456 + &qu ...

  4. CentOS 7 NAT模式上网配置

    一 VMware 配置 在“编辑”选项卡中,选择“虚拟网络编辑器”,如下图: 选择VMnet8,修改子网IP与子网掩码,注意不要给“使用本地DHCP服务将IP地址分配给虚拟机”选项打勾,如下图: 点击 ...

  5. Flask上下文管理机制

    前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...

  6. 在Ubuntu Server下搭建LAMP环境

    1 LAMP的安装 LAMP通常是指Linux+Apache+MySQL+PHP组合形成的一套可以运行PHP程序的体系,并不是一个软件的名称.没有安装MySQL的服务器依然可以在其它条件完备的情况下运 ...

  7. .NET中的异步操作及线程同步

    执行异步操作 CLR使用了WIN的线程处理能力,但保留了与其分离的权利.某些时候CLR的线程与Win的线程不是完全的匹配. 线程的系统开销较大,应限制其数量. 创建:分配并初始化一线程内核对象,保留1 ...

  8. struts2的DTD配置文件

    新手可以看看,高手可以跳过…… 最近在学习struts2这个框架,自己也动手写过一些DTD文件,所以很好struts2这个DTD文件是怎么写的,接下来就一个一个的分析 根元素是struts,然后又4个 ...

  9. python 一句话输出26个英文字母

    chr(i) # return i character ord(c) # return integer >>> [chr(i) for i in range(97,123)] ['a ...

  10. PHP魔术方法__clone()篇

    PHP中定义了一个内置方法__clone()来调整兑现的克隆行为: 当一个对象被克隆的时候会自动执行__clone()方法,而复制的对象可以在其方法内进行调整 header('Content-type ...