1、你一定想知道虚函数是怎么做出来的,对不对?
如果能够了解 C++编译器 对于 虚函数 的实现方式,我们就能够知道为什么虚函数可以做到动态绑定。为了达到动态绑定的目的,C++编译器通过某个表格,在执行期“间接”调用实际上欲绑定的函数。这样的表格称为 虚函数表(vtable)。每一个内含虚函数的类,编译器都会为它做出一个虚函数表,表中的每一个元素都指向一个虚函数的地址。此外,编译器也会为类的对象加上一项成员变量,是一个指向该虚函数表的指针(vptr)。举个例子:
- class class1
- {
- public:
- int data1;
- int data2;
- memfunc();
- virtual vfunc1();
- virtual vfunc2();
- virtual vfunc3();
- };
class1对象在内存中占据的空间为:一个虚表指针、2个整数。C++类的成员函数(非虚函数),你可以想象成就是C语言中的函数,他只是被编译器改过名称,并增加一个参数(this指针),因而可以处理调用者(C++对象)中的成员变量。所以,在class1对象的内存中并没有看到与成员函数有关的任何东西。
每一个由此类派生出来的对象,都有这么一个vptr。当我们通过这个对象调用虚函数时,事实上就是通过vptr找到虚函数表,再找出虚函数的真正地址。虚函数表的内容是依据类中虚函数的声明次序,一一填入函数指针。派生类会继承基类的虚函数表(以及所有其它可以继承的成员),当我们在派生类中改写虚函数时,虚函数表就受了影响:表中元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。动态绑定机制,在执行期,根据虚函数表,做出了正确的选择。
2、静态成员(变量与函数)
我想你已经很清楚了,如果你依据一个类产生出3个对象,每个对象将各有一份成员变量。假设你有一个类,专门用来处理存款账户,它至少要有存户的姓名、地址、存款额、利率等成员变量:
- class SavingAccount
- {
- private:
- char m_name[40];
- char m_addr[60];
- double m_total;
- double m_rate;
- };
这家银行采用浮动利率,每个账户的利息都是根据当天的挂牌利率来计算。这时候m_rate就不适合成为每个账户对象中的一笔数据,否则每天一开市,光把所有账户内容叫出来,修改m_rate的值,就花掉不少时间。m_rate应该独立在各对象之外,成为类的数据。怎么做,在m_rate前加上static修饰词即可。
static成员变量不属于对象的一部分,而是类的一部分,所以程序可以在 还没有诞生任何对象的时候 就处理此种成员变量。但首先你必须初始化它。不要把static成员变量的初始化安排在类的构造函数中,因为构造函数可能一再被调用,而变量的初值却只应该设定一次。也不要把初始化操作安排在头文件中,因为它会被载入许多地方,因此也就可能被执行许多次。你应该在应用程序文件中,类以外的任何位置设定其初值。例如在main中,或全局函数中,或任何函数之外:double SavingAccount::m_rate = 0.0075;//设定static成员变量初值(定义,类中只是声明),这么做可曾考虑到m_rate是个private数据,没关系,设定static成员变量初值时,不受任何存取权限的束缚。请注意:static成员变量的类型也出现在初值设定语句中,因为这是一个设定操作,不是一个赋值操作。事实上,static在这时候才被定义出来,在类中只是声明了一下。如果你没有做这个初始化操作,会产生链接错误。
下面是几种存取static成员变量的方式,注意:此时,还没有诞生任何对象实例:
1、SavingAccount::m_rate=0.0075; 2、SavingAccount myAccount; myAccount.m_rate=0.0075;//m_rate需为public
你要搞清楚一个概念,static成员变量不是因为对象的实现才得以实现,它本来就存在,你可以想象他是一个全局变量,因此,第一种处理方式在意义上比较不会给人错误的印象。只要access level允许,任何函数(包括全局函数或成员函数,static 或 non-static)都可以存取static成员变量。但如果你希望在产生任何object对象之前就存取其class的private static成员变量,则必须设计一个static成员函数。
由于static成员函数不需要借助任何对象,就可以被调用执行,所以编译器不会为它暗加一个this指针。也因为如此,static成员函数无法处理类中的non-static成员变量。还记得吗?前面说过,成员函数之所以能够以一份单一函数代码处理各个对象的数据而不紊乱,完全靠的是this指针的指示。
static成员函数没有this参数的这种性质,正是我们的MFC应用程序在准备callback函数时所需要的。
3、C++程序的生与死:兼谈构造函数与析构函数
C++的new运算符 和 C的malloc函数都是用于配置内存,但前者比之后者的优点是,new不但配置对象所需的内存空间,同时会引发构造函数的执行。
所谓构造函数,就是对象诞生后第一个执行(并且是自动执行)的函数,它的函数名称必定要与类名称相同。相对于构造函数,自然就有个析构函数,也就是在对象行将毁灭但未毁灭之前一刻,最后(并且是自动执行)的函数。它的函数名称必定要与类名称相同,再在最前面加一个~符号。
一个有着层次结构的类群组,当派生类的对象诞生之前,构造函数的执行是由最基类 至 最尾端派生类;当对象要毁灭之前,析构函数的执行则是反其道而行。
结论:
1)对于全局对象,程序一开始,其构造函数就先被执行(比程序进入点更早);程序即将结束前其析构函数被执行。MFC程序就有这样一个全局对象,通常以application object称呼之。
2)对于局部对象,当对象诞生时,其构造函数被执行;当程序流程将离开该对象的存活范围时,其析构函数被执行。
3)对于静态(static)对象,当其对象诞生时其构造函数被执行;当程序将结束时,其析构函数被执行,但比全局对象的析构函数早一步执行。
4)对于以new方式产生出来的局部对象,当对象诞生时其构造函数被执行,析构函数则在对象被delete时执行。
4、四种不同的对象生存方式(stack-堆栈、heap-堆、global-全局对象、static-静态对象)
既然谈到了static对象,就顺便说一下所有可能的对象生存方式 及其 构造函数调用机制。在C++中,有四种方法可以产生一个对象,
第一种方法:是在堆栈(stack)中产生它:void MyFoo(){CFoo foo;//在堆栈中产生foo对象}
第二种方法:是在堆中产生它:void Myfoo(){CFoo* pFoo=new CFoo();//在堆中产生对象}
第三种方法:产生一个全局对象(同时也必然是个静态对象)CFoo foo;//在任何函数范围之外做次操作
第四种方法:产生一个静态局部对象void MyFoo(){static CFoo foo;}//在函数范围之内的一个静态对象
不论那一种做法,C++都会产生一个针对CFoo构造函数的调用操作。前2种情况,C++在配置内存之后,立刻产生一个隐藏的构造函数调用。第三种情况,由于对象实现于任何“函数活动范围”之外,显然没有地方来安置这样一个构造函数调用操作,这就需要startup代码帮忙,startup代码是什么?是更早于程序进入点执行起来的代码,由C++编译器提供,被链接到你的程序中。startup代码可能做些像函数库初始化、进程信息设立、I/Ostream产生等操作,以及对static对象的初始化操作。
分享到:
相关推荐
标签 "C++ 面试题"、"经典" 和 "题库" 强调了这个压缩包的性质,它是一个全面且具有代表性的C++面试问题集,适合用于自我测试和复习。标签中的“经典”可能意味着这些题目在过去多年中被反复引用,对于理解C++的核心...
2. **标准库文档**:C++标准库提供了大量的模板类和函数,如输入/输出流(iostream)、容器(vector、list、map等)、算法(sort、find等)等,是学习C++必不可少的内容。 3. **面向对象编程**:C++的面向对象特性...
以上就是C++中关于数据结构的一些基本实现,这些数据结构是计算机科学和软件工程中不可或缺的部分,熟练掌握它们对于编写高效的代码至关重要。通过实际编写和测试这些数据结构的代码,你可以加深对它们的理解并提升...
总的来说,这个项目提供了一个基于C++实现的IAPWS-IF97水蒸气性质计算工具,对于理解水蒸气状态变化及其相关工程应用具有重要意义。它涉及到计算机编程、数值计算、热力学等多个领域的知识,是IT技术与科学理论紧密...
文件操作是C++中处理输入输出的重要部分。在实验二中,学生将学习到如何读取和写入文件,包括文本文件和二进制文件,这对于数据持久化和程序状态保存至关重要。实验中可能会包括对文件指针的操作,文件打开模式的...
在C++编程中,二元关系的性质是数学逻辑和关系理论的重要概念,它们在算法设计、数据库理论以及形式逻辑等多个领域都有广泛应用。本代码主要关注五个关键性质:自反性、对称性、传递性、反自反性和反对称性。下面将...
2. **C++编程语言**: C++是一种通用的、面向对象的编程语言,具有高效性和灵活性,适用于开发系统软件、应用程序和游戏等。在网络封包抓取这样的低级操作中,C++的性能优势和对内存管理的精细控制使其成为理想的...
红黑树是一种自平衡二叉查找树(BST),由Rudolf Bayer在1972年提出,它的每个节点都带有颜色属性,可以是红色或黑色。...理解和掌握红黑树的原理及操作对于提升程序设计能力、优化算法性能具有重要意义。
学习《Thinking in C++ 2》不仅需要对C++基础知识有一定了解,还需要耐心和实践,因为书中涉及的概念和技巧都是C++编程中至关重要的部分。通过深入阅读和实践书中的示例,你将能够提升C++编程能力,掌握更高级的编程...
总结来说,理解堆的性质和C++中的实现方法对于提升编程能力非常重要。通过手动构建堆和使用STL中的堆函数,我们可以灵活地解决各种问题,如排序、优先队列和查找特定元素等。而堆排序作为一种原地排序算法,虽然平均...
描述部分简单介绍了这份课件的性质,表明它是一份经典的、被推荐的C++课程材料,而且是外国作者的作品,这暗示课件的英语水平可能较高,且内容较为权威。 标签"C++"直接表明这份课件的内容集中在C++语言上。C++是一...
提到的标签“C++标准库函数文档”则指向了本内容作为标准库函数相关文档的性质,意味着它应该包含标准库函数的详细介绍、用法示例和相关说明。文档通常是学习和使用标准库函数的重要参考材料。 在部分内容中,虽然...
它强调了书籍的“探索”性质,意味着书中内容不仅仅是对C++语言特性的简单介绍,而是更深层次的讲解,以及如何在实践中应用这些特性来解决编程问题。此外,“程序员的C++入门教程”表明这本书将从基础开始,带领读者...
2. C++开发 C++是一种静态类型、编译式、通用的编程语言,它广泛用于软件开发领域,包括游戏开发。Cocos2d-x框架中使用C++进行编程可以利用C++的高级特性和性能优势,从而编写出更加高效、灵活的游戏代码。 3. ...
图论是计算机科学中的一个重要分支,它研究的是网络和图的结构、性质及其在问题解决中的应用。在C++编程中,理解并掌握图论基础知识能够帮助开发者解决复杂的问题,如最短路径、网络流、搜索算法等。下面将详细阐述...
在C++编程中,计算矩阵的行列式是线性代数中的一个重要操作,它在解决线性方程组、分析矩阵的性质以及图形学等领域有着广泛的应用。行列式的值可以帮助我们判断一个矩阵是否可逆,如果一个矩阵的行列式不为零,则该...
伽马函数是数学中的一个重要概念,特别是在概率论、统计学和特殊函数理论中扮演着核心角色。它是一种延展了阶乘到实数和复数域的函数,通常表示为Γ(z)。在C++中实现伽马函数,我们需要理解其基本性质和计算方法。 ...
理解C++的跨平台性质,不要因为某种编程环境或语言的流行而轻易改变学习方向。学习C++的目标是掌握编程思维和技术,这些是可以在不同语言间迁移的。最后,保持耐心和毅力,编程需要时间和实践才能精通。 总结来说,...
求行列式则是判断矩阵是否可逆的重要手段,对于方阵,其行列式的值可以决定该矩阵是否有逆矩阵。行列式的计算通常通过递归的LU或LAPACK库来实现。 最后,特征值和特征向量是矩阵理论中的关键概念,它们揭示了矩阵的...
2. **Microsoft Visual C++ ATL/MFC ActiveX Control Run-Time**:ATL(Active Template Library)和MFC(Microsoft Foundation Classes)是微软提供的C++类库,用于简化COM对象和Windows控件的开发。对应的运行库...