虚函数

C++ 多态与虚函数

我是研究僧i 提交于 2020-02-05 10:38:56
这一篇介绍一下 C++ 面向对象三大特征之一的多态(之前面试某大厂的实习生被问到多态,后来又了解到一些设计模式,才体会到多态的强大,在这里把对多态的一点点浅显认识总结一下) 如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢 多态 父类的一个指针,可以有多种执行状态(父类的指针调用子类的函数),即多态 多态实际上只是一种思想,而虚函数是实现这个思想的语法基础 虚函数 虚表 若对象有虚函数,对象空间最开始 4Byte(32Bit目标平台)或 8Byte(64bit目标平台)内容是虚表(虚函数列表)的首地址,叫虚指针 在实例化对象时,编译器检测到虚函数(virtual修饰的成员函数)时,会将虚函数的地址放到虚表(类似于一个存放函数指针的数组)中 当实例化子类时,检测到有虚函数的重写,编译器会用子类重写的虚函数地址覆盖掉之前父类的虚函数地址,当调用虚函数时,检测到函数是虚函数就会从虚表中找对应的位置调用,若子类没有重写,虚表中的虚函数地址就还是父类的,若子类中有重写,虚表记录的就是子类重写的虚函数地址,即实现了父类的指针调用子类的函数 虚表中先记录父类中的虚函数地址,接着记录子类中虚函数地址(若子类重写父类的虚函数则是覆盖) 最后虚表还有一个尾值是 0 class Test { public : virtual void vfunc ( ) { cout << "Test virtual

虚函数(动多态)的处理流程

蓝咒 提交于 2020-02-04 15:42:12
多态: 同一接口 不同形态 1.静多态 编译阶段确定函数的调用(函数重载、模板) 2.动多态 运行时确定函数的接口(virtual触发、在运行时拿到函数的入口地址) 3.宏多态 预编译时确定函数的接口(很少涉及) 虚函数给予动多态支持 一个.cpp文件到一个.exe运行文件的过程: 1.编译 2.链接 3.运行 链接到运行过程中发生了什么? 备注: 符号表中存放的是函数的入口地址 在运行前所有的数据都是在磁盘上存储,运行时数据加载到内存中 1.合并段和合并符号表(所以在链接过后已经有了函数入口地址,但是此时的函数入口地址是存放在磁盘中的) 2.符号解析(把代码中的符号处理掉 在符号引用的地方找到符号定义的地方) 3.分配地址和空间 4.在text段进行符号的重定位 以Linux系统为例.o文件生成ELF文件格式 这样一来就实现了在运行时确定函数的接口,实现了动多态 来源: CSDN 作者: VegeTass 链接: https://blog.csdn.net/qq_44777506/article/details/104168514

构造函数为什么不能是虚函数

只谈情不闲聊 提交于 2020-02-04 06:57:38
1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。 2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。 3. 构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。 4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数

函数隐藏和函数覆盖

ぃ、小莉子 提交于 2020-02-03 01:45:55
函数隐藏和函数覆盖 1、函数隐藏 派生类中函数具有与基类同名的函数(参数列表不一定相同),从而派生类中隐藏了基类的同名函数。 2、函数覆盖 定义: 派生类中函数将基类中的函数覆盖的情况称为函数覆盖。 条件: 1)基类是虚函数 2)发生覆盖的两个函数分别位于基类和派生类中 3)函数名和参数列表必须完全相同(虚函数)。 函数覆盖总是与多态性相关联。 总之,函数的覆盖是发生在基类和派生类之间,两个函数必须完全相同,并且都是虚函数。除此外,则全是隐藏了。 附: 1、交换两个数: a=a+b; b=a-b; a=a-a; 这种方法可能引起溢出,更为常见的是通过异或: a = a ^ b; b = a ^ a; a = a ^ b; 2、编译过程 先由预处理命令对预处理指令(#include,#define,#if)进行处理,在内存中输出翻译单元(一种临时文件),编译器接受预处理器的输出,将源代码转换成忺机器语言指令的三个目标文件(头文件不参与编译),连接时,链接器将目标文件和类库文件连接生成exe文件。 为了防止程序中出现重定义(如类),我们一般在定义头文件时,都要用预处理指令来操作。 来源: CSDN 作者: hopegrace 链接: https://blog.csdn.net/hopegrace/article/details/104147162

c++ RTTI(运行时类型识别)

跟風遠走 提交于 2020-02-02 16:21:54
通过RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型。c++通过下面两个操作符提供RTTI。 (1)typeid:返回指针或引用所指对象的实际类型。 (2)dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用。 对于带虚函数的类,在运行时执行RTTI操作符,返回动态类型信息;对于其他类型,在编译时执行RTTI,返回静态类型信息。 当具有基类的指针或引用,但需要执行派生类操作时,需要动态的强制类型转换(dynamic_cast)。这种机制的使用容易出错,最好以虚函数机制代替之。 dynamic_cast 操作符 如果dynamic_cast转换指针类型失败,则返回0;如果转换引用类型失败,则抛出一个bad_cast类型的异常。 可以对值为0的指针使用dynamic_cast,结果为0。 dynamic_cast会首先验证转换是否有效,只有转换有效,操作符才进行实际的转换。 if (Derived *derivedPtr = dynamic_cast<Derived *>(basePtr)) { // use the Derived object to which derivedPtr points } else { // basePtr points at a Base object // use the Base object to

C++面试

泪湿孤枕 提交于 2020-02-02 14:31:08
vector中v[i]和v.at(i)的区别 v[5]; //A v.at[5]; //B 如果v非空,A和B没有任何区别。如果v为空,B会抛出std::out_of_range异常。 c++标准不要求vecor<T>::operator[]进行下标越界检查,原因是为了提高效率。如果需要下标越界检查,使用at。但性能会受到影响,因为越界检查增加了性能开销。 vector扩容原理 新增元素:vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素; 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了; 初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1; 不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容 哪些函数不能成为虚函数? 不能被继承的函数和不能被重写的函数。 1.普通函数 2.友元函数 3.构造函数 4.内联成员函数 5.静态成员函数 溢出 1.栈溢出(栈的大小通常是1M-2M) 栈溢出是指函数中的局部变量造成的溢出(注:函数中形参和函数中的局部变量存放在栈上) 栈溢出包含两种情况:1.分配的的大小超过栈的最大值,2.分配的大小没有超过最大值,但是分配的buff比接收buff小

typeid详解

为君一笑 提交于 2020-02-02 08:53:22
在揭开typeid神秘面纱之前,我们先来了解一下RTTI(Run-Time Type Identification,运行时类型识别),它使程序能够获取由基指针或引用所指向的对象的实际派生类型,即允许“用指向基类的指针或引用来操作对象”的程序能够获取到“这些指针或引用所指对象”的实际派生类型。在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和 typeid。 dynamic_cast允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转化类型,与之相对应的还有一个非安全的转换操作符 static_cast,因为这不是本文的讨论重点,所以这里不再详述,感兴趣的可以自行查阅资料。下面就开始今天我们的话题:typeid。 typeid是C++的关键字之一,等同于sizeof这类的操作符。typeid操作符的返回结果是名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义,稍后我们看一下vs和gcc库里面的源码),它的表达式有下图两种形式。 如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时计算;否则,typeid操作符返回表达式的静态类型,在编译时就可以计算。 ISO C++标准并没有确切定义type_info,它的确切定义编译器相关的,但是标准却规定了其实现必需提供如下四种操作

C++虚函数表剖析

只谈情不闲聊 提交于 2020-02-01 16:47:14
关键词:虚函数,虚表,虚表指针,动态绑定,多态 一、概述 为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表(下文简称虚表)。本文介绍虚函数表是如何实现动态绑定的。 二、类的虚表 每个包含了虚函数的类都包含一个虚表。 我们知道,当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。 我们来看以下的代码。类A包含虚函数vfunc1,vfunc2,由于类A包含虚函数,故类A拥有一个虚表。 class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1(); void func2(); private: int m_data1, m_data2; }; 类A的虚表如图1所示。 图1:类A的虚表示意图 虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。 虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了。 三、虚表指针 虚表是属于类的,而不是属于某个具体的对象

C++中构造函数、虚函数、析构函数的执行顺序

折月煮酒 提交于 2020-02-01 12:40:02
#include <iostream> using namespace std; class A { public: // 第一步:执行类A的构造函数,输出"构造函数A" A() { cout << "构造函数A" << endl; } virtual void func() { cout << "构造A" << endl; } // 第七步:执行类A的析构函数,输出"析构函数A" ~A() { cout << "析构函数A" << endl; } virtual void fund() { cout << "清除A" << endl; } }; class B : public A { public: // 第二步:执行类B的构造函数,调用类A里虚函数,输出"构造A" B() { func(); } // 第四步:执行主函数里的c.fun(),输出"开始...",并调用func(); // 由于fun()不是构造函数和析构函数,且func()为虚函数 // 所以最终结果输出"开始...类C" void fun() { cout << "开始..."; func(); } // 第六步:执行类B的析构函数,调用fund()函数; // 由于是在析构函数里,且fund()为虚函数,所以执行类A里的fund(); // 输出清除A ~B() { fund(); } }; class C

memset初始化结构体之内存泄漏

心已入冬 提交于 2020-02-01 06:07:31
void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。 str – 指向要填充的内存块。 c – 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。 n – 要被设置为该值的字节数。 声明:在花括号中间,每个结构代表一个作用域,可以防止命名冲突, struct { int number; int *ptr; char name [NAME_LEN + 1]; }part1,part2; 如果常用到,可以使用类型定义这样声明, typedef struct { int number; int *ptr; char name [NAME_LEN + 1]; }part_t; part_t part1,part2; 还有一种是结构标记声明, struct part{ int number; int *ptr; char name [NAME_LEN + 1]; }; struct part part1, part2; 初始化:可以在声明的同时初始化,如 part_t part1 = {22, &item, “Good boy”}; // 这种初始化和数组很相似。 也可以指定初始化: part_t part2 = {.number=33,