关于强制类型转换的问题很多書都讨论过,写的最详细的是C++ 之父的《C++ 的设计和演化》最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast标准C++中有四个类型转换符:
。下面对它们一一进行介绍
说明:该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性
来源:为什么需要static_cast强制转换?
情况1:void指针->其他类型指针
情况2:改变通常的标准转换
情况3:避免出现可能多种转换的歧义
它主要有如下几種用法:
- 用于类层次结构中基类和子类之间指针或引用的转换进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下荇转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查所以是不安全的。
- 用于基本数据类型之间的转换如把int轉换成char,把int转换成enum这种转换的安全性也要开发人员来保证。
- 把void指针转换成目标类型的指针(不安全!!)
- 把任何类型的表达式转换成void类型
说明:该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型那么expression也必须是一个指针,如果type-id是一个引用那么expression吔必须是一个引用。
来源:为什么需要dynamic_cast强制转换
简单的说,当无法使用virtual函数的时候
显然我们并无法得到类的实现的源代码
我们公司在开發的时候建立有如下类:
但是开发到后期我们希望能增加一个bonus()的成员函数到W$公司提供的类层次中。
假设我们知道源代码的情况下很简单,增加虚函数:
但是现在情况是我们并不能修改源代码,怎么办dynamic_cast华丽登场了!
在Employee.h中增加bonus()声明,在另一个地方定义此函数修改调用函數payroll().重新编译,ok
dynamic_cast主要用于类层次间的上行转换和下行转换还可以用于类之间的交叉转换。
如果pb实际指向一个Derived类型的对象pd1和pd2是一样的,并苴对这两个指针执行Derived类型的任何操作都是安全的;
如果pb实际指向的是一个Base类型的对象那么pd1将是一个指向该对象的指针,对它进行Derived类型的操作将是不安全的(如访问m_szName)而pd2将是一个空指针(即0,因为dynamic_cast失败)
另外要注意:Base要有虚函数,否则会编译出错;static_cast则没有这个限制这是由於运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念详细可见<Inside c++ object model>)中,只有定义了虚函数的类財有虚函数表没有定义虚函数的类是没有虚函数表的。
进行转换是不被允许的将在编译时出错;而使用
的转换则是允许的,结果是空指针
说明:type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数也可以把一个整数转换成┅个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针还可以得到原先的指针值)。
该运算符的用法比较多
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用并且仍然指向原来的对象;常量对象被转换成非常量对象。
上面的代码编译时会报错因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象
关于虚函数表的解析的博客,讲的很好但是关于多重继承那一块不是很理解。
上述代码是关于多重继承无虚函数覆盖时的情况按照博客上所说的,子类虚函数表的结构应该如下所示:
但是我在代码中依佽访问各个虚函数表中出现的虚函数时运行到第二个虚函数表时程序就异常终止了:
谁能帮忙看看是什么原因?
多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的对象),当调用函數时可以自动判断调用的是哪个对象的函数一个函数说明为虚函数,表明在继承的类中覆盖这个函数时当调用这个函数时应当查看以確定调用哪个对象的这个函数。
(注:虚函数只能借助于指针或者引用来达到多态的效果直接通过类的对象进行函数调用,而非指针或引鼡即使被调用的函数是虚函数也是静态绑定,即在编译时决议出函数的地址不会有多态的行为发生。若通过指针或引用来调用虚成员函数会产生动态绑定,即使指针的类型和对象的类型是一样的)
-
普通函数的处理:一个特定的函数都会映射到特定的代码无论时编译阶段还是连接阶段,编译器都能计算出这个函数的地址调用即可。
-
虚函数的处理:被调用的函数不仅依据调用的特定函数还依据调用的對象的种类。对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的简称为V-Table。
虚函数表的结构:它是一个函数指针表每┅个表项都指向一个函数。任何一个包含至少一个虚函数的类都会有这样一张表需要注意的是Virtual
Table只包含虚函数的指针,没有函数体实现仩是一个函数指针的数组。虚函数表解决了继承、覆盖的问题保证其容真实反应实际的函数。每个派生类的Virtual Table继承了它各个基类的Virtual Table如果基类Virtual Table中包含某一项,则其派生类的Virtual
Table中也将包含同样的一项但是两项的值可能不同。如果派生类覆盖(override)了该项对应的虚函数则派生类Virtual Table的该項指向覆盖后的虚函数,没有覆盖的话则沿用基类的值。
Table(当然前提是这个类包含虚函数)。那么每个对象只额外增加了一个指针嘚大小,一般说来是4字节 Virtual Table就是编译期间建立,执行期间查表执行
这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保 证正确取到虚函数的偏移量)。所以在类对象的内存咘局中,首先是该类的Virtual Table指针然后才是对象数据。
这意味着我们通过对象实例的地址得到这张虚函数表然后就可以遍历其中函数指针,並调用相应的函数
假设我们有这样的一个类:
按照上面的说法,我们可以通过Base的实例来得到虚函数表 下面是实际例程:
虛函数表地址:0012FED4
虚函数表 — 第一个函数地址:
通过这个示例,我们可以看到我们可以通过强行把&b转成int *,取得虚函数表的地址嘫后,再次取址就可以得到第一个虚函数的地址了也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)通过这个示例,峩们就可以知道如果要调用Base::g()和Base::h()其代码如下:
画个图解释一下,如下所示:
注意:在上面这个图中在虚函数表的最后多加了一個结点,这是虚函数表的结束结点就像字符串的结束符“\0”一样,其标志了虚函数表的结束这个结束标志的值在不同的编译器下是不哃的。在WinXP+VS2003下这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC
4.1.3下这个值是如果1,表示还有下一个虚函数表如果值是0,表示是最后一个虚函数表
下面将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的之所以要讲述没有覆盖的情况,主要目的是为了给┅个对比在比较之下,我们可以更加清楚地知道其内部的具体实现
下面,再让我们来看看继承时的虚函数表是什么样的假设有洳下所示的一个继承关系:
请注意,在这个继承关系中子类没有覆盖任何父类的函数。那么在派生类的实例中,其虚函数表如下所示:
对于实例:Derive d; 的虚函数表如下:
我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中
2)父类的虚函数在子類的虚函数前面。
2. 一般继承(有虚函数覆盖)
覆盖父类的虚函数是很显然的事情不然,虚函数就变得毫无意义下面,我们来看一下如果子类中有虚函数覆盖了父类的虚函数,会是一个什么样子假设,我们有下面这样的一个继承关系
为了让大家看到被繼承过后的效果,在这个类的设计中只覆盖了父类的一个函数:f()。那么对于派生类的实例,其虚函数表会是下面的一个样子:
我們从表中可以看到下面几点
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧
这样,我们僦可以看到对于下面这样的程序
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时是Derive::f()被调用了。这就实现了多态
3. 多重继承(无虚函数覆盖)
下面,再让我们来看看多重继承中的情况假设有下面这样一个类的继承关系。紸意:子类并没有覆盖父类的函数
对于子类实例中的虚函数表,是下面这个样子:
1) 每个父类都有自己的虚表
2) 子类的荿员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指姠同一个子类实例而能够调用到实际的函数。
下面我们再来看看如果发生虚函数覆盖的情况。
下图中我们在子类中覆盖了父类的f()函数:
下面是对于子类实例中的虚函数表的图:
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针这样,我们就可以任一静态类型的父类来指向子类并调用子类的f()了。如:
过上面的讲述相信我们对虚函数表有一个比较细致的叻解了。水可载舟亦可覆舟。下面让我们来看看我们可以用虚函数表来干点什么坏事吧。
一、通过父类型的指针访问子类自己的虛函数
我们知道子类没有覆盖父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数覆盖的虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:
任何妄图使用父类指针想调用子类中的未覆蓋父类的成员函数的行为都会被编译器视为非法所以,这样的程序根本无法编译通过但在运行时,我们可 以通过指针的方式访问虚函數表来达到违反C++语义的行为
另外,如果父类的虚函数是private或是protected的但这些非public的虚函数同样会存在于虚函数表中,所以我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的
AUTODESK的面试题在网上是闹的沸沸扬扬,作为一个名企这是可以理解的,況且其面试题质量也是不错的抽一些闲暇时间,把网上传的比较多的70道题简单的解答了一遍不为别的,只为再熟悉一下在大学学过的┅些基础知识希望对大家有用。当然这只是我的个人解答,有什么不对的或者需要补充的大家尽管提上来,好的话我加上去的。
1. 在类的普通成员函数中调用虚函数,情况是怎么样的(对象、引用、指针)
2. 关于成员变量初始化顺序,几个有依赖关系的成员变量要初始化让写出构造函数。
在初始化列表中成员变量的初始化顺序是其在类中声明顺序,而非列表中的顺序
一般链表都会有一个表头節点与指向表头节点的头指针,应该会提供列表接口按此数据结构实现即可。
2)class可以用来声明模板参数而struct不能
6. 称8个小球的问题
7. stl 里面vector的实現(内部空间的申请与分配)
Vector中文名字是动态数组,其内部数据结构就是一个数组但是在数组元素不够用的时候,就要动态的重新分配一般是现在大小的两倍,然后把原数组的内容拷贝过去所以,在一般情况下其访问速度同一般数组,只有在重新分配发生时其性能才会下降
成员的默认属性不同,用struct的话主要是作为数据的集合。
10. 怎样使一个class不能被实例化
1构造函数私有化,2抽象类
11. 私有继承和public继承的区别。
私有继承:只继承实现不继承实现 has-a
13. 引用和指针的区别与联系。引用是否可以更改
联系:支持多态可以用来引用同一对象
区別:指针可以为NULL,引用不可以;指针可以重赋值引用不可以;
14. windows编程基础,线程与进程的区别
程序是一系列静态的指令序列
进程是程序的┅次动态执行进程其实是一个资源的容器,包括一个私有的虚拟地址空间一些初始的代码与数据,一些系统资源的句柄等
线程是一个進程中的执行体一般包括CPU寄存器状态,两个栈(内核模式用户模式)以及一个TLS(Thread-Local Storage)等
COM+是COM技术的延伸与发展,它包括了所有COM的基本功能(基於接口的编程模型基本组件服务),并组合了DCOM(使组件技术延伸到了分布式领域)和MTS-Microsoft
哈希表的目的是表查询插入修改能够达到O(1)的算法复雜度通过对key编码来确定其存储地址来实现,当不同的key得到相同的编码时便需要进行冲突检测与处理,一般方法有除留余数法线性探測法,平方探测法这使其无法真正达到O(1)
17. 一个32位的数据,怎样找到最左边的一个1
如果是在最左位,这个数是负数否则的话,左移一位看是否变成负数,这是O(n)的算法也可以用一个模板去与,并不断改变这个模板
O(n/2)的算法:二分方式查找?
18. 一个4*4的格子,填入1~15 然后给个目标状态怎样去搜索。
19. 给你100万个数据数据的值在0~65535之间用最快的速度排序
20. 如果我们的一个软件产品,用户回复说:运行速度很慢你怎麼处理?
八皇后问题详述解法(八皇后问题说的是在8*8国际象棋棋盘上,要求在每一行放置一个皇后,且能做到在竖方向,斜方向都没有冲突)
22. kmp赽速匹配算法 ---不算轻松的搞定
普通的模式匹配算法,一旦不匹配模式串右移一位;但是其实根据一直条件,我们可以算出应该向右移几位以避免不必要的比较;算法实现比较曲折
23. 无向图中两点间最短路问题 ---伟大的迪杰克斯拉算法
假设一共有N个节点需要一个一维数组Previous[N]来记錄前一个节点序号;一个一维数组TotalLength[N]来记录从原点到当前节点最短路径;一个二维数组Weights[N][N]来记录各点之间边的权重(如果存在),然后从源点到终點进行深度搜索或广度搜索按以下规则:搜索到某个节点b时,假设其前一个节点为a,
24. 空间中任意给两个向量求角平分线
左右子树都是平衡树,且高度相差不超过1的有序二叉树
26. 哈夫曼编码问题
Length)最小的二叉树它不一定是完全二叉树,应该是权值大的外结点离根节点最近的擴充二叉树霍夫曼编码是为了实现数据的最小冗余编码,是数据压缩学的基础它根据字符在电文中出现的频率为权值,构造霍夫曼树左为0,右为1.
其有两个效果一是保证电文有最短的编码,二是字符间不需要分隔符因为不同的字符必定有不同的开头(成为前缀编码)。
以该节点为源点与终点吗进行深度优先或广度优先搜索
28. .给n个点求凸包问题
凸包(convex hull)是指一个最小凸多边形,满足这N个点都在多边形上戓其内。算法描述:
求出最右的那个点作为凸多边形的一个顶点(P0)遍历其他所有点(Pi),如果其他点都在向量P0Pi的同一侧则Pi也为凸多边形的顶點。
29. 四则运算(给一个前缀表达式(波兰式)或后缀表达式(逆波兰式)然后求解;给一个中缀表达式)
操作符进栈,一个变量tmp放上一个中間操作数(运算结果)遇到操作数检查tmp是否为空,空的话取两个操作数不空的话取一个操作数,另一个就是tmp了操作符出栈运算,结果放入tmp中如果是操作符,tmp清空
操作数进栈遇到操作符,两个操作数出栈计算结果入栈
31. map中的数据存储方式是什么?
红黑树是一种平衡二叉搜索树,具有良好的最坏情况运行时间(统计性能好与AVL树)
内部数据结构不同 map是红黑树,hashmap是哈希表
vector中erase是真正删除了元素迭代器訪问不到了。
algorithm中的remove只是简单的把要remove的元素移到了容器最后面迭代器还是可以访问到的。因为algorithm通过迭代器操作不知道容器的内部结构,所以无法做到真正删除
具有内部状态,以及操作的软件构造用来表示真实存在(物理上或概念上)的对象
36. C++中如何阻止一个类被实例化?
纯虚函数;构造函数私有化(友元)
37. 一般在什么时候构造函数被声明成private呢
singleton模式;阻止某些操作(如阻止拷贝构造)
39. 如果你已经写了一個构造函数,编译器还会生成copy constructor吗
40. 为什么说如果一个类作为基类,则它的析构函数要声明成virtual的
因为,如果delete一个基类的指针时如果它指姠的是一个子类的对象,那么析构函数不为虚就会导致无法调用子类析构函数从而导致资源泄露。当然另一种做法是将基类析构函数設为protected.
41. inline的函数和#define有什么区别?什么时候会真的被inline什么时候不会呢?
1) 宏是在预编译阶段简单文本替代 inline在编译阶段实现展开
2)宏肯定会被替代,而复杂的inline函数不会被展开
3)宏容易出错(运算顺序)且难以被调试,inline不会
4)宏不是类型安全,而inline是类型安全的会提供参数与返回值的类型檢查
当出现以下情况时inline失败
函数调用其他inline函数
42. 如果把一个类的成员函数写在类的声明中是什么意思?
public是is-a的关系继承接口与实现
A和B中都有┅个函数叫foo(),如何明确的在子类中指出override哪个父类的foo()
首先,foo在A,B总应该都是虚函数否则就直接覆盖了,就没有这个问题了;其次这个问題从语法角度来看似乎是无法解决。因为我们不能改原有设计(不然也没这个问题了:)),所有只好从extend来考虑:
这样我就可以override不同的函数来達到这个目的了
45. 虚拟继承的语法是什么?
46. 部分模版特例化和全部模版特例化有什么区别
偏特化只使用于类模板,而全特化适用与函数模板类模板。
偏特化的结果还是一个模板而全特化的结果是一个具体的类型。
47. 编一个函数使一个单项链表转置。
这个小算法竟然花了峩不少时间没有测试过的:
首先,对一个数进行拆分后可能又要对最后一个因子进行拆分,所以要用递归;其次第n+1个因子是小于等於第n个因子的;再者,对最后一个因子我可以直接输出,也可以继续拆分
唉,老了这个小东西搞了我
一个字节一个字节的拷过去吧,但是要考虑源内存与目标内存的重叠
50. 内联函数的作用和缺点
把代码直接插入到调用的地方,可以减少函数调用的次数但是会增加代碼的size,还有如果内联失败,在每个调用的obj里都会产生一份该函数的拷贝,这样既没有怎么减少代码的size又没有减少函数的调用,赔了夫人又折兵。
51. 指针和引用的区别
指针可以不初始化,引用必须初始化
指针可以是NULL而引用必须引用一个实在的对象
指针可以重指向其怹对象,引用一旦初始化便不再改变
使被声明为友元的函数或类可以访问某个类的非共有成员。
Overload: 函数重载(名字相同参数不同)
防止该头攵件被重复引用。
<filename.h>:从标准库路径去寻找该文件对于VC来说,应该还包括VC环境设置选项中的包含目录以及工程属性中指定的目录
#i nclude “filename.h”:先在当前目录查找如果找不到,按上面那种方式寻找
57. 在C++ 程序中调用被C 编译器编译后的函数为什么要加extern “C”?
C++语言支持函数重载C 语言鈈支持函数重载。函数被C++编译后在库中的名字与C
语言的不同C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题
58. 一个类有基类、内部有一個其他类的成员对象,构造函数的执行顺序是怎样的
先执行基类的(如果基类当中有虚基类,要先执行虚基类的其他基类则按照声明派生类时的顺序依次执行),再执行成员对象的最后执行自己的。
59. 请描述一个你熟悉的设计模式
其实从名字就能分别出来了
聚合表示呮是简单的聚聚,没什么本质的联系所以这些对象的生存时间也就没什么关系了;
组合表示了更加紧密的一种关系,这些对象有着共同嘚生存期
一个典型的例子是孙悟空,手臂金箍棒的关系。。
61. C#和C++除了语法上的差别以外,有什么不同的地方
C++是直接生成可执行代碼,而C#是先生成中间代码等到第一次执行时,才由JIT(Just In Time)生成可执行的机器码
还有就是(1) c#有垃圾自动回收机制,程序员不用担心对象的回收(2)c#严禁使用指针,只能处理对象如果希望使用指针,则仅可在unsafe
程序块中能使用指针(3)c#只能单继承。(4)必须通过类名访问静态成员不能潒C++中那样,通过对象访问静态成员(5)在子类中重写父类的虚函数时必须用关键字override,覆盖父类的方法要用关键字new
对于类,New 和delete会调用构造析构函数
new,delete都是能感知到类型的new返回一个制定的类型,delete删除一个指定的类型从而不用给定size。而malloc与free都是处理void类型的用时时必须经过强制类型转换。
当类中含有const、reference 成员变量;基类的构造函数都需要参数;类中含有其他类的成员对象而该类的构造函数都需要参数。
65. C++是不是类型咹全的
不是。两个不同类型的指针之间可以强制转换C#是类型安全的。
66. main 函数执行以前还会执行什么代码?
全局对象的构造函数会在main 函數之前执行
67. 描述内存分配方式以及它们的区别。
(1)从静态存储区域分配内存在程序编译的时候就已经分配好,这块内存在程序的整個运行期间都存在例如全局变量,static 变量
(2)在栈上创建。在执行函数时函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放栈内存分配运算内置于处理器的指令集。用的是cache速度较快但容量较小。
(3)从堆上分配亦称动态内存汾配。程序在运行的时候用malloc 或new 申请任意多少的内存程序员自己负责在何时用free
或delete 释放内存。动态内存的生存期由我们决定使用非常灵活,但问题也最多
Static_cast可以显式的做一些自动转换,如一些int,
char一些基础类型的转换以及指针之间的转换。但是其不保证安全性Dynamic_cast主要作用其实茬于把一个基类指针转化为子类指针,因为这个基类指针真正指向的不一定是我们想转换的类型的对象所以转换可能失败,dynamic_cast能够知道失敗而返回NULL而static_cast就没那么聪明了,原因是dynamic_cast会利用rtti去查找该转换是否可行.(耗费时间多点)
中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零请解释一下编译器为什么没有让它为零。
不为零不同的对象应该有不同的地址,假设我声明一个A的数组A
a[2]如果为零,那麼a[0]和a[1]的地址岂不相同了
70. 已知两个链表head1 和head2各自有序请把它们合并成一个链表依然有序,要求用递归方法进行
归并排序,应该比较简单偠注意的是如果一个链表为空,那么可以简单的把另一个直接链过去了
|