c++总结

135 阅读6分钟

虚函数原理:

  • 定义了虚函数的类会产生一个虚函数表,里面存放着各个虚函数的地址
  • 每个类的对象添加了一个虚函数指针,指向虚函数表,调用函数时,通过虚函数指针去虚函数表中查询相应的函数地址,在运行

虚函数存放位置:

  • 虚函数表既不是程序代码也不是函数,所以没有放在代码区
  • 虚函数表在编译期间进行创建。因为虚函数的个数在编译阶段即可确定,所以不用动态创建,不需要放在堆区,而且虚函数表也不可以更改,所以虚函数表存储在只读数据区。
  • c++内存分配:堆区,栈区,代码断,只读数据区,可读可写数据区(全局和静态变量)
  • 虚函数表是针对一个类的,不属于某个类的对象,有点类似于静态变量。
  • blog.csdn.net/qq_36359022…

image.png

静态类型,动态类型

  • 静态类型:对象声明的类型,在编译期间就可以确定。
  • 动态类型:一般指指针或者引用所指的对象类型,在运行期间才可以确定 。
  • 静态绑定:绑定的静态类型,在编译期可以确定
  • 动态绑定:绑定的动态类型,在运行期间才可以确定。
  • 静态类型语言:静态类型语言中,变量的类型必须先声明,即在创建的那一刻就已经确定好变量的类型,而后的使用中,你只能将这一指定类型的数据赋值给变量。如果强行将其他不相干类型的数据赋值给它,就会引发错误,如 c++,Java,C#
  • 动态类型可以将任意类型的数据赋值给这个变量,赋值什么类型的变量就是什么类型。例如python,lua(使用union实现)
  • 强类型语言:只可以进行相关类型的隐式转换,例如int 到 float。例如c++,java,C#等等
  • 弱类型语言:可以进行相关和非相关隐式转换,例如int 到float,string到int等等,例如php,lua等
  • zhuanlan.zhihu.com/p/109803872

构造函数不可以是虚函数的原因:

- 虚函数执行需要虚函数指针,虚函数指针实在构造函数调用的时候被赋值的,所以构造函数执行的时候只知道它属于当前类,并不知道是否有其他类继承它
- 虚函数指针必须指向当前类的虚函数表,例如初始化一个对象时,先执行基类a的构造函数,在执行b的构造函数,此时虚函数指针指向b的虚函数表。(也就是说虚函数指针由被最后调用的虚函数指针决定,这也是为什么构造函数需要从后往前执行。

this指针

  • 在调用成员函数时,类对象的地址隐式的传递作为参数传递给函数,保存在this指针中,
  • this指针在成员函数执行前被构造,函数执行完毕后,被释放,所以this指针并不属于对象的一部分,所占用内存不会反映在sizeof上
  • 当类的非静态成员函数的参数个数一定时,this指针存储在ECX寄存器中;若该函数参数个数未定(可变参数函数),则存放在栈中。

成员函数

  • 普通函数放在代码区,属于类的公共部分,可以被所有类的对象访问,非虚函数在编译期间就可以确定其地址,与函数名(也算函数指针)绑定,所以函数名其实保存了函数入口的地址。
  • 非虚函数的地址与其对象的内存地址无关(只与该类的成员函数的地址空间相关),故对于一个父类的对象指针,调用非虚函数,不管是给他赋父类对象的指针还是子类对象的指针,他只会调用父类中的函数(只与数据类型(此为类类型)相关,与对象无关。
  • 函数名和指针的关系:函数名也是一种指针,因为函数名是函数的入口地址,所以函数的名字就可以被赋值一个对应的函数指针了,我们我可以通过函数指针来调用这个函数,和指针一样,在32位系统里面,函数名占4个字节,在16位系统里面占2个字节,在64位系统里面占8个字节。而指针本身不是地址,指针式用来保存地址的,指针的值就是地址,这是指针和函数名的不同点.

普通函数为什么不可以是虚函数

普通函数不属于类的对象,在编译时期将函数名和函数定义进行静态绑定,所有对函数名的调用,都指向该函数的定义。而虚函数是运行期就行动态绑定的,相冲突。

静态函数为什么不可以虚函数

虚函数的调用通过虚函数指针,虚函数指针保存在类对象中,普通函数的调用类对象的地址作为隐式参数传给函数,保存在this指针中,而静态函数的调用不需要this指针,所以不可以是虚函数。

inline为什么不可以是虚函数

inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制。此外,一切virtual函数都不可能是inline函数

友元不可以是虚函数的原因

C++不支持友元函数的继承,不能继承的函数指定不是虚函数。

析构函数为什么是虚函数

若基类的析构函数是虚函数,再用基类的指针释放子类的对象的时候,可以调用到子类的析构函数,否则仅仅只会调用基类的析构函数,从而使对象析构不完全,而引发错误。

c++执行过程

  • 预编译:主要是替换那些#开头的语句,例如#include ,#define 。将.c 文件转化成 .i文件
  • 编译:包括语法检查,代码分析等等,最终编译为汇编语言。将.c/.h文件转换成.s文件
  • 汇编:汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。将.s 文件转化成 .o文件
  • 链接:链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。将.o文件转化成可执行程序

重载,重写,覆盖

  • 重载相对于同一个类,相同函数名,不同的参数列表,不同的返回类型不可以成为重载的条件,因为函数调用时无法确定指明返回类型
  • 覆盖,子类覆盖父类的虚方法,相同的参数名,参数列表和返回类型
  • 重写,子类屏蔽了父类中相同函数名的方法。