C++函数名字重写、重载、重定义程序实例

【菜科解读】
C++中经常出现函数名字一样,但参数列表或返回值不同的函数,要搞清楚函数的正确调用关系,需理清三个概念:重写(override)、重载(overload)、重定义(redefine)。
一、三个基本概念
1、重定义(redefine):派生类对基类的成员函数重新定义,即派生类定义了某个函数,该函数的名字与基类中的函数名字一样。
特点:(1)不在同一个作用域(分别位于基类、派生类) (2)函数的名字必须相同 (3)对函数的返回值、形参列表无要求
特殊情况:若派生类定义的该函数与基类的成员函数完全一样(返回值、形参列表均相同),且基类的该函数为virtual,则属于派生类重写基类的虚函数。
作用效果:若重新定义了基类中的一个重载函数,则在派生类中,基类中该名字的函数(即其他所有重载版本)都被自动隐藏,包括同名的虚函数。
2、重载(overload):函数名字相同,但它的形参个数或者顺序,或者类型不同,但是不能靠返回类型来判断。
特点:(1)位于同一个类中 (2)函数的名字必须相同 (3)形参列表不同(可能是参数个数 or 类型 or 顺序 不同),返回值无要求
特殊情况:若某一个重载版本的函数前面有virtual修饰,则表示它是虚函数。
但它也是属于重载的一个版本
不同的构造函数(无参构造、有参构造、拷贝构造)是重载的应用
作用效果和原理:编译器根据函数不同的参数表,将函数体与函数调用进行早绑定。
重载与多态无关,只是一种语言特性,与面向对象无关。
3、重写(override):派生类重定义基类的虚函数,即会覆盖基类的虚函数 (多态性)
特点:(1)不在同一个作用域(分别位于基类、派生类) (2)函数名、形参列表、返回值相同 (3)基类的函数是virtual
特殊情况:若派生类重写的虚函数属于一个重载版本,则该重写的函数会隐藏基类中与虚函数同名的其他函数。
作用效果:父类的指针或引用根据传递给它的子类地址或引用,动态地调用属于子类的该函数。
这个晚绑定过程只对virtual函数起作用
具体原理是由虚函数表(VTABLE)决定的,在第三节介绍。
二、程序实例
1、两个类:基类(取名Test)和派生类(取名XX) 名字不规范,哈哈随便取得!
基类和派生类的结构
//Base classclass Test{public: int a; Test() { coutf(10,20);//调用的是派生类的函数,发生多态 return 1;}
分析:(1)test 1中进行了重载测试,根据传递参数的不一样,调用不同的函数 (早绑定,与多态无关)
(2)test 2中Test b = d;定义了一个基类对象,用派生类对象来进行初始化。
这会调用基类的拷贝构造函数,生成基类的对象b,基类的拷贝构造函数初始化b的VPTR,指向b的VTABLE。
因此所有的函数调用都只发生在基类,不会产生多态。
这是一个对象切片过程(参见《C++编程思想.第二版》P370),对象切片是当它拷贝到一个新的对象时,会去掉原来对象的一部分,而不是像使用指针或引用那样简单地改变地址的内容。
(3)test 3和test 4中,定义的基类指针和引用,故会发生多态。
三、晚绑定原理:虚函数表
编译器会对每一个包含虚函数的类(或者从包含虚函数的基类派生的类)创建一个表(VTABLE),里面存放特定类的虚函数的地址。
然后编译器秘密地放置一指针vpointer(VPTR),指向这个对象的vtable。
当通过基类指针做虚函数调用时(即多态调用时),编译器静态地插入能取得这个VPTR并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数并引起晚绑定的发生。
C++,函数,名字,重写,、,重载,重,定义,程序,C++,C++中的虚函数总结
简称为V-Table。
在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。
这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
一、什么是虚函数、纯虚函数、抽象基类虚函数:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数。
纯虚函数:是一种特殊的虚函数,使用virtual关键字,并且在其后面加上=0。
抽象基类:在基类中加入至少一个纯虚函数,使基类成为抽象类。
二、为什么要使用虚函数在理解这个问题前,就必须要理解什么是晚捆绑。
晚捆绑是相对于早捆绑而言的,那么什么又是捆绑呢?把函数体与函数调用相联系称为捆绑,当捆绑在程序运行之前完成时,这称为早捆绑。
那么当捆绑根据对象的类型,发生在运行时,就称为晚捆绑。
而使用晚捆绑,无需检查对象的类型,只需要检查对象是否支持特性和方法即可。
为了引发晚捆绑,C++要求在基类中声明这个函数时使用virture关键字。
晚捆绑只对virtual函数起作用,而且只在使用含有virtual函数的基类的地址时发生。
三、关于重写如果一个函数在基类中被声明为virtual,那么在所有的派生类中它都是virtual,在派生类中virtual函数的重定义通常称为重写。
四、在C++中如何实现晚捆绑虚函数主要有两个步骤:1、每一个类产生出一堆指向虚函数的指针,放在表格中。
这个表格被称为virtual table(vtbl)2、每一个类对象被安插一个指针,指向相关的virtual table,通常这个指针被称为vptr结构图如下:每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个唯一的vtbl。
如果在这个派生类中没有对在基类中声明为virtual的函数进行重新定义,编译器就使用基类的这个虚函数地址。
然后编译器在这个类中放置vptr。
当使用简单继承时,对于每个对象都只有一个vtbl。
vptr必须被初始化为指向相应的vtbl的起始地址。
五、虚函数的存放类型信息假如没有虚函数,那么对象的长度就是所期望的长度:比如当个int的长度。
而带有单个虚函数的One Virtual,对象的长度是No Virtual的长度加上一个void指针的长度。
如果有一个或多个虚函数,编译器都只在这个结构中插入一个单个指针,这个指针指向虚函数表。
在32为的机器上,一个指针占3字节的空间,因此求sizeof得到4;如果是64位的机器,一个指针占8字节的空间,因此求sizeof则得到8.六、关于抽象类和纯虚函数1、当继承一个抽象类时,必须实现所有的纯虚函数,否则继承出的类也将是一个抽象类 2、声明一个纯虚函数,就等于告诉编译器在vtbl中为函数保留一个位置,但在这个位置不放地址。
只要有一个函数在类中被声明为纯虚函数,则vtbl就是不完全的。
3、纯虚函数禁止对抽象类的函数以传值方式调用,这是一种防止对象切片的方法。
抽象类可以保证在向上类型转换期间总是使用指针或引用。
4、对于纯虚函数,如果要创建对象,必须要在派生类中定义。
七、什么是对象切片在继承的过程中,通常派生类不仅具有基类的特征,也具有自身的一些特征。
当派生类向上进行类型转换称为基类时,就会发生那些自身的特征被切除,只保留继承了基类的特征,这种现象就是对象切片。
例如:狗类继承了宠物类,具有宠物类的名称这个属性,同时又有啃骨头的特性,当狗类要被转换为宠物类时,就必须抛弃自己爱啃骨头的爱好,这样只保留了对应于宠物类的那部分。
流程如下:八、虚函数和构造函数1、由于基类构造函数总是在继承类构造函数中被调用,这就确保了在派生类中,基类的所有成员都是有效的,即所有成员都已经建立。
2、虚机制在构造函数中不工作。
有两种理由: A、在任何构造函数中,我们只能知道基类已被初始化,但不能知道哪个类是从这个基类继承来的。
但是,虚函数在继承层次上是向前和向后调用。
它可以调用派生类中的函数。
B、构造函数的vptr的状态是由最后调用的构造函数确定的,这就意味着当最后调用的构造函数还没有完成之前,当前的构造函数完全不知道这个对象是否是基于其他类的。
但是,当这一系列的构造函数调用正发生时,每个构造函数都已经设置vptr指向它自己的vtbl,如果函数调用使用虚机制,它将只产生通过它自己的vtbl的调用,而不是最后派生的vtbl。
九、虚析构函数和析构函数1、析构函数自最晚派生的类开始,并向上到基类。
这就意味着每个析构函数知道它所在类从哪一个类派生而来,但不知道从它派生出哪些类。
2、析构函数可以为虚函数,因为这个对象已经知道它是什么类型,但是在构造期间就不知道了。
一旦对象已被构造,它的vptr就已经被初始化,所以能发生虚函数调用。
3、虚构函数的纯虚性的唯一效果是阻止基类的实例化十、虚函数、纯虚函数、抽象类的作用虚函数的作用:每个类必须提供一个可以被调用的虚函数,但每个类可以按它们认为合适的任何方式处理。
如果某个类不想做什么特别的事,可以借助于基类中提供的缺省处理函数。
也就是说,虚函数的声明是在告诉子类的设计者,"你必须支持虚函数,但如果你不想写自己的版本,可以借助基类中的缺省版本。
纯虚函数的作用:让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。
所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。
所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
C++,中的,虚,函数,总结,对,C++,了解,的,人都,
Ecplise编译Cygwin环境、使用CDT插件开发C/C++
编译环境采用的是Cygwin。
使用Ecplise4.2 + CDT8.1.2 +Cygwin2.774。
建议先阅读第6条的注意事项。
强烈介意:先安装配置cygwin再安装cdt插件一、具体安装步骤为了保证安装顺利,请按以下步骤来进行。
1、安装Cygwin下载地址:http://cygwin.com/setup.exe官网:http://cygwin.com/下载好后,点击setup.exe进行安装,出现如下图所示界面 点击“NEXT”后我们看到如下界面:我们看到有三种安装模式:Install from Internet,这种模式直接从Internet安装,适合网速较快的情况;Download Without Installing,这种模式只从网上下载Cygwin的组件包,但不安装;Install from Local Directory,这种模式与上面第二种模式对应,当你的Cygwin组件包已经下载到本地,则可以使用此模式从本地安装Cygwin。
从上述三种模式中选择适合你的安装模式,这里我们选择第一种安装模式,直接从网上安装,当然在下载的同时,Cygwin组件也保存到了本地,以便以后能够再次安装。
选中后,点击“下一步”。
这一步选择Cygwin的安装目录,以及一些参数的设置。
默认的安装位置是C:\cygwin\,你也可以选择自己的安装目录,然后选择“下一步”,这一步我们可以选择安装过程中从网上下载的Cygwin组件包的保存位置,选择完以后,点击“下一步”,这一步选择连接的方式,选择你的连接方式,然后点击下一步,会出现选择下载站点的对话框,如下图所示,此步选择第一个163提供的镜像网站就可以,国内下载速度挺快,如果有其他镜像,可以输入URL后,点击“Add”进行添加,然后再在列表中选中。
选择完成后,点击“下一步”,进过下载几个文件后显示如下界面下面就在这个界面中下载我们需要编译C/C++代码的Packages,我们只需要下载“Devel”分支下几个包就可以了,gcc,gcc-core,gcc-g++,gcc-mingw-core,gcc-mingw-g++,make ,gdb,binutils。
在上图的search框中输入gcc,程序会自动进行搜索,如下图,通过选择“Devel”分支下的包,找到安装我们需要的包,然后点击Skip来选择最新的安装包这是已经成功安装最新版本包后的图示,大家单击“Skip”来选择最新版本的安装。
同理,输入“make”后,选择“Devel”分支下的包,下载如图所示的包:输入“gdb”后,选择“Devel”分支下的包,下载如图所示的包:输入“binutils”后,选择“Devel”分支下的包,下载如图所示的包:选完以后,我们选择下一步,进入安装过程,如下图所示,安装的时间依据你选择的组件以及网络情况而定。
安装完成后,安装程序会提示是否在桌面上创建Cygwin图标等,点击完成退出安装程序。
2、配置Windows的环境变量熟悉Java开发环境的都应该知道怎么找到环境变量,实在不知道的去百度下吧。
将cygwin\bin目录加入到环境变量PATH中。
然后,打开cmd命令行窗口,分别试一下gcc , g++命令。
如果显示“访问被拒绝” (access denied), 进入cygwin安装目录\bin下检查g++.exe, gcc.exe是否只有1k大小,如果是,那么文件是符号链接,那么把文件重命名一下(备份)。
然后把g++-3.exe(或者g++-4.exe)拷贝一份,重命名为g++.exe。
把gcc-3.exe拷贝一份,重命名为gcc.exe。
3、下载Ecplise CDT插件下载地址:http://www.eclipse.org/cdt/downloads.php参考:Ecplise插件的安装的四种方法。
或者通过Ecplise自动升级功能进行安装,如下图所示:添加地址:http://download.eclipse.org/tools/cdt/releases/juno4、配置Ecplise的C/C++开发环境(1)设置Ecplise与Cygwin的路径映射按以下步骤打开Ecplise:Window -> Preferences->C/C++->Debug-> Common Source Lookup Path -> add -> new "Path Mapping".映射方法,如下图:eclipse中调试时,由于GDB使用的unix格式的路径,而eclipse使用的是windows路径,导致找不到匹配的代码, 把linux的路径映射windows的路径:比如 /cygwin/c 映射成 C:\ 。
(2)配置C/C++的"Makefile Project",选择“PE Windows Parser", 和”Cygwin PE Parser" 两项。
5、在Ecplise中创建工程编写C/C++代码(1)创建C/C++工程点击”Next“,显示如下图:点击”Next“,显示如下图:点击”Finish“完成创建。
当编写好代码后,可以对你的工程进行编译,点击“Project”选项下的"Builder Project"项目即可。
编译后的工程,如下图所示:此时是成功编译后“Consloe”控制台的显示画面。
点击上面选中的,然后点击鼠标右键,选择“Run”,即可运行程序。
此时控制台即可打印出文字了。
到此我们就完成了Ecplise + CDT + Cygwin开发环境的配置,以后你就可以享用其方便的功能了。
6、常见问题1、Eclipse下的CDT创建C++项目时候,不能自动生成includes文件夹有两种解决方法:(1)打开Window -> Preferences->C/C++->Environment,如下图所示,添加两个环境变量将C_INCLUDE_PATH 设为 /usr/include将CPLUS_INCLUDE_PATH 设为 /usr/include/c++如果在Cygwin的/usr/include/目录下没有c++这个目录,可以从\cygwin\lib\gcc\i686-pc-cygwin\3.4.4\include\c++拷贝一个到目录下,或者直接添加这个目录都可以。
此步也可以解决代码中提示的显示信息”Unresolved inclusion:“问题,尽管不影响编译,只是无法定位头文件中的符号。
(2)在Windows里面,加入环境变量CPLUS_INCLUDE_PATH 和C_INCLUDE_PATH,并都设置成,安装的Cygwin的对应lib路径:D:\cygwin\lib然后再新建C/C++项目的时候,就可以自动生产Includes文件夹了。
2、用eclipse 建立编译C++工程出现错误提示 “symbol could not be resolved”问题原因:是debug下的source lookup path没有映射正确。
解决办法:window -> preferences -> C/C++ -> debug -> source lookup path > Path Mapping\cygdirve\c C:\用cygwin下的c盘来替代当前的C盘 来查找需要的库文件,如果你的Cygwin安装在C盘,则映射C盘,如果安装在D盘,则映射为\cygdirve\d D:\,其他盘同理。
3、用eclipse 建立编译C++Debug工程出现错误提示以下错误依然是上面映射的问题,打开Cygwin自带的“Cygwin Terminal”,然后输入以下命令“mount”如图所示:上面就是我们设置的映射,如果没有你的Ecplise的Workspace所在的盘的位置的映射,请按照上面的说明进行添加,即可调试成功。
例如,我的Workspace在D:盘下,则我想调试程序,必须映射到D盘的位置。
4、解决Eclipse的CDT编写的c/c++程序的运行结果不能输出到console的问题(1)设置“Debug Configurations"里的“Environment”选项,添加PATH,指定X:\cygwin\bin的路径。
如图所示(2)如果build不能通过(编译正常通过后会出现类似下面的输出)**** Build of configuration Debug for project Test ****make allmake: Nothing to be done for `all‘.**** Build Finished ****请按照上面步骤重新安装。
Ecplise,编译,Cygwin,环境,、,使用,CDT,