C++面试笔记(2)

时间:2021-07-12 20:50:50

11 explicit 显式构造函数

explicit修饰的构造函数可用来防止隐式转换

class Test1
{
public:
Test1(int n) // 普通构造函数
{
num=n;
}
private:
int num;
}; class Test2
{
public:
explicit Test2(int n) // explicit(显式)构造函数
{
num=n;
}
private:
int num;
}; int main()
{
Test1 t1=12; // 隐式调用其构造函数,成功
Test2 t2=12; // 编译错误,不能隐式调用其构造函数
Test2 t2(12); // 显式调用成功
return 0;

12.C++ 虚函数 重载 覆盖之间的区别

C++ 虚函数 重载 重写的区别

多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数。Cpp多态性是通过虚函数实现的,虚函数允许子类重新定义成员函数。重写的话分为两种:直接重写成员函数,一个是重写虚函数,其中只有重写了虚函数的才体现了Cpp的多态性。


多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。


最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用 C++ 多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。


13 早绑定与晚绑定

(1)静态多态(早绑定)

函数重载

(2)动态多态(晚绑定)

虚函数:用 virtual 修饰成员函数,使其成为虚函数

注意:

普通函数(非类成员函数)不能是虚函数

静态函数(static)不能是虚函数

构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针)


14 虚析构函数

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象

lass Shape
{
public:
Shape(); // 构造函数不能是虚函数
virtual double calcArea();
virtual ~Shape(); // 虚析构函数
};
class Circle : public Shape // 圆形类
{
public:
virtual double calcArea();
...
};
int main()
{
Shape * shape1 = new Circle(4.0);
shape1->calcArea();
delete shape1; // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
shape1 = NULL;
return 0;
}

15 虚函数与纯虚函数

C++ Primer--虚函数与纯虚函数的区别

定义一个函数为虚函数,是为了允许用基类的指针调用子类的这个函数

定义一个函数为纯虚函数,才代表子类没有被实现

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

含有纯虚函数的类为抽象类

任何构造函数之外的非静态函数都可以是虚函数


16. 虚函数的底层实现?虚函数表?虚函数指针?

简述C++虚函数作用及底层实现原理

虚函数的作用:简单讲即实现了多态

基类定义了虚函数,子类可以重写该函数,当子类重新定义了父类的的虚函数后,父类指针根据赋给它的不同的子类指针,动态调用属于子类的该函数,且这样的函数调用是无法在编译器期间确认的,而是在运行期间确认,也叫晚绑定。

底层实现原理:C++对象模型

C++面试笔记(2)

(1) 每个class产生一堆指向虚函数的指针,放在表格中,这个表格称为虚函数表(vbtl)


(2) 每一个对象被添加了一个指针,指向相关的虚函数表vtbl。通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)都由每一个class的构造函数,析构函数和拷贝赋值运算符自动完成

(3)

另外,虚函数表地址的前面设置了一个指向type_info的指针,RTTI(Run Time Type Identification)运行时类型识别是有编译器在编译器生成的特殊类型信息,包括对象继承关系,对象本身的描述,RTTI是为多态而生成的信息,所以只有具有虚函数的对象在会生成。

RTTI:运行时的类型识别

程序在运行时能够根据基类的指针或者引用来获得该指针或对象所指向对象的实际类型


-- typeid:运算符返回其表达式或类型名的实际类型


-- dynamic_cast:将基类的指针或者引用安全地准换为派生类型的指针或引用

C++ 虚函数表和虚函数指针机制

虚函数指针:在含有虚函数类的对象中,指向虚函数表,在运行时确定

虚函数表:在程序只读数据段,存放虚函数指针,如果派生类实现了基类的某个虚函数,则在许表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建


17. 菱形继承?虚继承?虚继承与虚函数比较?

菱形继承和虚继承

菱形继承(钻石问题):

存在问题:浪费存储空间、存在二义性

这时候虚继承就挺身而出,扛下搞定此问题的重担了。虚继承是一种机制,类通过虚继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。虚基类用 virtual 声明继承关系就行了。这样一来,D 就只有 A 的一份拷贝。

class A
{
public:
A():a(1){};
void printA(){cout<<a<<endl;}
int a;
}; class B : virtual public A
{
}; class C : virtual public A
{
}; class D: public B , public C
{
}; int _tmain(int argc, _TCHAR* argv[])
{
D d;
cout<<sizeof(d);
d.a=10;
d.printA();
}

相同之处
都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)

不同之处


虚继承


虚基类依旧存在继承类中,只占用存储空间


虚基类表存储的是虚基类相对直接继承类的偏移


虚函数


虚函数不占用存储空间


虚函数表存储的是虚函数地址


18.智能指针

C++ 智能指针简单剖析

智能指针的作用:防止内存泄漏

auto_ptr(C++11中删除了)


shared_ptr:允许多个指针指向同一个对象

unique_ptr:独占所指的对象


weak_ptr: 弱引用,指向shared_ptr所管理的对象

为什么抛弃auto_ptr:避免潜在的内存崩溃

为什么用weak_ptr:解决shared_ptr相互引用时的死锁问题

C++11智能指针

智能指针是一个RAII(Resource Acquisition is initialization)类模型,用来动态的分配内存。它提供所有普通指针提供的接口,却很少发生异常。在构造中,它分配内存,当离开作用域时,它会自动释放已分配的内存。这样的话,程序员就从手动管理动态内存的繁杂任务中解放出来了。

RAII(Resource Acquisition is Intialization)

RAII的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终有效,最后在对象析构时释放资源。

分为常性类型与变性类型

本质:用对象代表资源,把管理资源的任务转换为管理对象的任务


19.强制类型转换运算符

Cpp四种强制类型转换运算符

【Cpp专题】static_cast, dynamic_cast, const_cast探讨

(1) static_cast

  • 用于非多态类型的转换
  • 不执行运行时类型检查(转换安全性不如 dynamic_cast)
  • 通常用于转换数值数据类型(如 float -> int)
  • 可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)

向上转换是一种隐式转换。

(2)dynamic_cast

  • 用于多态类型的转换
  • 执行行运行时类型检查
  • 只适用于指针或引用
  • 对不明确的指针的转换将失败(返回 nullptr),但不引发异常
  • 可以在整个类层次结构中移动指针,包括向上转换、向下转换

(3) const_cast

  • 用于删除 const、volatile 和 __unaligned 特性(如将 const int 类型转换为 int 类型 )

(4)reinterpret_cast

  • 用于位的简单重新解释
  • 滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一。
  • 允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全)

    也允许将任何整数类型转换为任何指针类型以及反向转换。
  • reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。
  • reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。