1、const
1.1 指针常量和常量指针
说说const int *a, int const *a, const int a, int *const a, const int *const a分别是什么,有什么特点。
const int *a==int const *a; //可以通过 a 访问整数值,但不能通过 a 修改该整数的值,指针本身是可变的,可以指向不同的整数
const int a; //a变量变成常量,不可修改
int *const a; //a的值可以更改,但是指向它的指针不能更改
int const *const a; //a本身和指向它的指针都不能更改
1.2 const成员函数
常函数内不能修改成员变量
对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
1.3 const和#define的区别
1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。
而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。
(2)有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
(3)#define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用。
(4)#define定义的常量不分配内存,而const定义的常量会分配在常量存储区中。
具体可看以下博客7.5小节
2、 虚函数
2.1 作用
父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
2.2 实现
每个虚函数都会有一个与之对应的虚函数表,该虚函数表的实质是一个指针数组,存放的是每一个对象的虚函数入口地址。对于一个子类来说,他会继承父类的虚函数表同时增加自己的虚函数入口地址,如果子类重写了基类的虚函数的话,那么继承过来的虚函数入口地址将被子类的重写虚函数入口地址替代。那么在程序运行时会发生动态绑定,将父类指针绑定到实例化的对象实现多态。每个类只有一个虚函数表,虚函数表是在编译的时候就确定的了。
2.3 纯虚函数
用户不能创建基类的实例,只能创建派生类的实例
2.4 虚函数调用时机
虚函数在运行时根据实际对象的类型来确定调用哪个函数,而不是根据指针或引用的类型来确定。当一个虚函数被定义为类的成员函数时,它会被标记为虚函数。在调用虚函数时,程序会查找该函数的实际类型,并在运行时调用该类型的实现。这就允许程序在运行时动态地选择执行哪个版本的虚函数,从而实现多态性。虚函数通常与父类指针或引用一起使用,可以实现父类指针或引用调用子类的函数。
2.5 大小
虚函数表是一个存储虚函数指针的数组,每个类有一个虚函数表,每个对象有一个指向虚函数表的指针。虚函数表的大小取决于类中有多少个虚函数,而对象中的虚函数表指针的大小取决于编译器和操作系统。一般来说,在32位系统下,指针占4个字节,在64位系统下,指针占8个字节。
2.6 C++哪些函数不能定义为虚函数
构造函数:构造函数不能被声明为虚函数。因为构造函数是用来创建对象的,而虚函数是根据对象的类型来动态调用的。如果构造函数是虚函数,那么在创建对象时就无法确定调用哪个版本的构造函数,会导致逻辑错误
友元函数:友元函数实际上并不属于类的成员函数,所以不能被定义为虚函数
普通函数:普通函数只能被重载,不能被重写。
2.7 为什么虚函数不能是模版函数
因为模板函数在编译时会被实例化为多个不同的函数,而虚函数需要在运行时才能确定调用哪个函数。在C++中,虚函数的实现依赖于虚函数表(vtable)和虚函数指针(vptr),而这些在编译时就需要确定下来。因此,虚函数不能是模板函数。
2.8 虚函数表既然希望类的所有对象共享为什么不放在全局区
虚函数表不能放在全局区,因为全局区是存放全局变量和静态变量的,而虚函数表不是变量,而是一组指向类成员函数的指针。如果放在全局区,会导致内存浪费和混乱。
混乱:虚函数表是在编译期就确定了大小和内容的,而全局区是在运行期才分配空间的。如果把虚函数表放在全局区,就需要在运行期动态地为每个类分配空间,并且要保证不同类之间不会发生冲突。这样就增加了程序的复杂度和出错的可能性。
3、菱形继承
菱形继承(Diamond Inheritance)是一种多重继承的情况,其中一个子类同时继承自两个直接或间接共同父类,而这两个父类又继承自同一个共同的父类。这样就形成了一种菱形的继承结构,因此称为"菱形继承"。
解决方法:虚继承,出现二义性通过添加作用域解决,而继承爷爷类的数据有两份,通过虚继承解决。
4、类型转换
4.1 static_cast
static_cast 用于执行非多态类型之间的类型转换,例如整型和浮点型之间的转换、基类和派生类之间的指针或引用转换、void 指针和其他指针类型之间的转换等。该转换在编译时完成,通常不会检查运行时错误。
4.2 dynamic_cast
dynamic_cast 用于在运行时进行多态类型的转换。它通常用于将基类指针或引用转换为派生类指针或引用,以及在类层次结构中进行下行转换
4.3 dynamic_cast与虚函数的区别
功能:虚函数: 实现运行时多态性,使得可以在基类指针或引用中调用派生类的方法。
dynamic_cast: 用于类型安全的向下转型或交叉转型,确保转换成功与否。
实现方式:虚函数: 通过虚表(vtable)机制实现动态绑定。
dynamic_cast: 依赖于运行时类型识别(RTTI)来安全地转换类型。
使用场景:虚函数: 用于实现和利用多态性。
dynamic_cast: 用于在复杂的类层次结构中进行安全的类型转换。
4.4 reinterpret_cast
reinterpret_cast 用于在不同的指针类型之间进行转换,**例如将一个指针转换为一个整数,或将一个整数转换为一个指针。**该转换通常不进行类型检查,因此潜在地不安全,只应在极少数特殊情况下使用。
4.5 const_cast
const_cast 用于在去除变量的 const 修饰符或 volatile 修饰符时使用。它可以将指向常量对象的指针或引用转换为指向非常量对象的指针或引用,或者将指向非常量对象的指针或引用转换为指向常量对象的指针或引用。
4.6 volatile关键字
在 C++ 中,关键字 volatile 用于声明一个变量是易变的(volatile variable),即该变量可能会在程序中的任意时刻被意外地改变。这意味着,当读取一个易变的变量时,编译器不会从缓存中读取该变量的值,而是每次都会从内存中重新读取该变量的值。同样地,当写入一个易变的变量时,编译器也不会将该变量的值存储在缓存中,而是立即将该变量的值写入内存中。