文章目录
1.c++面向对象三大特性
- 面向对象:对象是指具体的某一个事物,这些事物的抽象就是类,类中包含数据(成员变量)和动作(成员方法)。
- 面向对象的三大特性:
- 封装:将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性。
- 继承:让某种类型对象获得另⼀个类型对象的属性和⽅法。
- 多态:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现方式。
- 实现多态有两种⽅式
覆盖(override): 是指⼦类重新定义⽗类的虚函数的做法
重载(overload): 是指允许存在多个同名函数,⽽这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)
- 实现多态有两种⽅式
2.c++的三个访问修饰符
public 、 private 和 protected 。
这些修饰符决定了类中的成员对外部代码的可⻅和访问权限
- public 修饰符⽤于指定类中的成员可以被类的外部代码访问。公有成员可以被类外部的任何代码(包括类的实例)访问。
- private 修饰符⽤于指定类中的成员只能被类的内部代码访问。私有成员对外部代码是不可⻅的,只有类内部的成员函数可以访问私有成员。
- protected 修饰符⽤于指定类中的成员可以被类的派⽣类访问。受保护成员对外部代码是不可⻅的,但可以在派⽣类中被访问。
3.多重继承
⼀个类可以从多个基类(⽗类)继承属性和⾏为。
在C++等⽀持多重继承的语⾔中,
⼀个派⽣类可以同时拥有多个基类。
多重继承可能引⼊⼀些问题,如菱形继承问题, ⽐如当⼀个类同时继承了两个拥有相同基类的类,⽽最终的派⽣类⼜同时继承了这两个类时, 可能导致⼆义性和代码设计上的复杂性。为了解决这些问题,C++ 提供了虚继承, 通过在继承声明中使⽤ virtual 关键字,可以避免在派⽣类中⽣成多个基类的实例,从⽽解决了菱形继承带来的⼆义性。
4.重载与重写
-
重载:重载是指在同⼀作⽤域内,使⽤相同的函数名但具有不同的参数列表或类型,使得同⼀个函数名可以有多个版本。
-
重写:重写是指派⽣类(⼦类)重新实现(覆盖)基类(⽗类)中的虚函数,以提供特定于派⽣类的实现。重写是⾯向对象编程中的多态性的⼀种体现,主要涉及基类和派⽣类之间的关系,⽤于实现运⾏时多态。
-
区别
重写 | 重载 | |
---|---|---|
范围 | 重写发生在不同的类之间(子类和父类之间) | 重载发生在同一个类的内部 |
参数 | 重写的函数的函数名、参数列表和返回值类型都需要和原函数相同,父类中被重写的函数需要有 virtual 修饰 | 重载的函数需要与原函数有相同的函数名、不同的参数列表,不关注函数的返回值类型 |
vritual关键字 | 必须有 | 可以有,可以没有 |
5.c++多态怎么实现
- 多态概念:多态就是不同继承类的对象,对同一消息做出不同的响应,基类的指针指向或绑定到派生类的对象,使得基类指针呈现不同的表现方式。在基类的函数前加上 virtual 关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
- 多态实现原理:多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中。
- 简单解释:当基类的指针指向派生类的对象时,通过派生类的对象的虚表指针找到虚函数表(派生类的对象虚函数表),进而找到相应的虚函数进行调用。
6.成员函数/成员变量/静态成员函数/静态成员变量的区别
-
成员函数:
- 成员函数是属于类的函数,它们可以访问类的成员变量和其他成员函数。
- 成员函数可以分为普通成员函数和静态成员函数。
- 普通成员函数使⽤对象调⽤,可以访问对象的成员变量。
- 普通成员函数的声明和定义通常在类的内部,但定义时需要使⽤类名作为限定符。
-
成员变量:
- 成员变量是属于类的变量,存储在类的每个对象中。
- 每个对象拥有⼀份成员变量的副本,它们在对象创建时分配,并在对象销毁时释放。
- 成员变量的访问权限可以是 public 、 private 或 protected 。
-
静态成员函数:
- 静态成员函数属于类⽽不是对象,因此可以直接通过类名调⽤,⽽不需要创建类的实例。
- 静态成员函数不能直接访问普通成员变量,因为它们没有隐含的 this 指针。
- 静态成员函数的声明和定义也通常在类的内部,但在定义时需要使⽤类名作为限定符。
-
静态成员变量:
- 静态成员变量是属于类⽽不是对象的变量,它们在所有对象之间共享。
- 静态成员变量通常在类的声明中进⾏声明,但在类的定义外进⾏定义和初始化。
- 静态成员变量可以通过类名或对象访问。
7.构造函数和析构函数
- 构造函数:构造函数是在创建对象时⾃动调⽤的特殊成员函数。它的主要⽬的是初始化对象的成员变量,为对象分配资源,执⾏必要的初始化操作。
- 析构函数:析构函数是在对象⽣命周期结束时⾃动调⽤的特殊成员函数。它的主要⽬的是释放对象占⽤的资源、执⾏必要的清理操作。
8.c++构造函数有几种
- 默认构造:没有任何参数的构造函数。如果⽤户没有为类定义构造函数,编译器会⾃动⽣成⼀个默认构造
函数。默认构造函数⽤于创建对象时的初始化,当⽤户不提供初始化值时,编译器将调⽤默认构造函数。 - 带参构造:接受⼀个或多个参数,⽤于在创建对象时传递初始化值。
- 拷贝构造:⽤于通过已存在的对象创建⼀个新对象,新对象是原对象的副本。参数通常是对同类型对象的
引⽤。 - 委托构造函数:在⼀个构造函数中调⽤同类的另⼀个构造函数,减少代码重复。
9.什么是虚函数什么是虚函数表
-
虚函数:C++中的虚函数的作⽤主要是实现了多态的机制。虚函数允许在派⽣类中重新定义基类中定义的函数,使得通过基类指针或引⽤调⽤的函数在运⾏时根据实际对象类型来确定。这样的机制被称为动态绑定或运⾏时多态。
在基类中,通过在函数声明前⾯加上 virtual 关键字,可以将其声明为虚函数。派⽣类可以重新定义虚函数,如果派⽣类不重新定义,则会使⽤基类中的实现。 -
虚函数表:虚函数的实现通常依赖于⼀个被称为虚函数表(虚表)的数据结构。每个类(包括抽象类)都有⼀个虚表,其中包含了该类的虚函数的地址。每个对象都包含⼀个指向其类的虚表的指针,这个指针被称为虚指针(vptr)。
当调⽤⼀个虚函数时,编译器会使⽤对象的虚指针查找虚表,并通过虚表中的函数地址来执⾏相应的虚函数。这就是为什么在运⾏时可以根据实际对象类型来确定调⽤哪个函数的原因。
10.虚函数和纯虚函数的区别
-
虚函数
- 有实现: 虚函数有函数声明和实现,即在基类中可以提供默认实现。
- 可选实现: 派⽣类可以选择是否覆盖虚函数。如果派⽣类没有提供实现,将使⽤基类的默认实现。
- 允许实例化: 虚函数的类可以被实例化。即你可以创建⼀个虚函数的类的对象。
- 调⽤靠对象类型决定: 在运⾏时,根据对象的实际类型来决定调⽤哪个版本的虚函数。
- ⽤ virtual 关键字声明:虚函数使⽤ virtual 关键字声明,但不包含 = 0 。
-
纯虚函数:
- 没有实现: 纯虚函数没有函数体,只有函数声明,即没有提供默认的实现。
- 强制覆盖: 派⽣类必须提供纯虚函数的具体实现,否则它们也会成为抽象类。
- 禁⽌实例化: 包含纯虚函数的类⽆法被实例化,只能⽤于派⽣其他类。
- ⽤ = 0 声明: 纯虚函数使⽤ = 0 在函数声明末尾进⾏声明。
- 为接⼝提供规范: 通过纯虚函数,抽象类提供⼀种接⼝规范,要求派⽣类提供相关实现。
11.抽象类和纯虚函数
- 抽象类:是不能被实例化的类,它存在的主要⽬的是为了提供⼀个接⼝,供派⽣类继承和实现。抽象类中可以包含普通的成员函数、数据成员和构造函数,但它必须包含⾄少⼀个纯虚函数。即在声明中使⽤ virtual 关键字并赋予函数⼀个 = 0 的纯虚函数。
- 纯虚函数:是在抽象类中声明的虚函数,它没有具体的实现,只有函数的声明。通过在函数声明的末尾使⽤ = 0 ,可以将虚函数声明为纯虚函数。派⽣类必须实现抽象类中的纯虚函数,否则它们也会成为抽象类。
12.虚析构
虚析构函数是⼀个带有 virtual 关键字的析构函数。 主要作⽤是确保在通过基类指针删除派⽣类对象时,能够正确调⽤派⽣类的析构函数,从⽽释放对象所占⽤的资源。
通常,如果⼀个类可能被继承,且在其派⽣类中有可能使⽤ delete 运算符来删除通过基类指针指向的对象,那么该基类的析构函数应该声明为虚析构函数。
13.为什么要虚析构,为什么不能虚构造
虚析构函数允许在运⾏时根据对象的实际类型调⽤正确的析构函数,从⽽实现多态性。如果基类的析构函数不是虚的,当通过基类指针删除指向派⽣类对象的对象时,只会调⽤基类的析构函数,⽽不会调⽤派⽣类的析构函数。这可能导致派⽣类的资源未被正确释放,造成内存泄漏。
构造函数在对象的创建阶段被调⽤,对象的类型在构造函数中已经确定。因此,构造函数调⽤不涉及多态性,也就是说,在对象的构造期间⽆法实现动态绑定。虚构造函数没有意义,因为对象的类型在构造过程中就已经确定,不需要动态地选择构造函数。
14.那些函数不能被声明为虚函数
常⻅的不不能声明为虚函数的有:
普通函数(⾮成员函数),静态成员函数,
内联成员函数,构造函数,友元函数。
15.浅拷贝和深拷贝的区别
主要区别在于如何处理对象内部的动态分配的资源。
-
深拷贝:该对象和原对象占用不同的内存空间,既拷贝存储在栈空间中的内容,又拷贝存储在堆空间中的内容。
-
浅拷贝:该对象和原对象占用同一块内存空间,仅拷贝类中位于栈空间中的内容。