✊✊✊每日一面——封装、继承和多态
- 一、简述一下什么是面向对象?
- 二、讲一讲封装、继承、多态是什么?
- 三、C++的多态怎么实现?
- 四、C++中类成员的访问权限和继承权限问题
- 五、C++中的重载、重写(覆盖)和隐藏的区别
- 六、知道C++中的组合吗?它与继承相比有什么优缺点吗?
一、简述一下什么是面向对象?
- 面向对象是一种编程思想,把一切东西看出是一个个对象,它们各自有各自的属性,把这些对象拥有的属性变量和操作这些属性的函数打包成一个类来表示
- 面向对象和面向过程的区别
- 面向过程:根据业务逻辑从上到下写代码
- 面向对象:将数据与函数绑定到一起,进行封装,这样能够快速的开发程序,减少重复代码的重写过程
二、讲一讲封装、继承、多态是什么?
-
封装: 将具体实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合度,使类成为一个具有内部数据的自我隐藏能力、功能独立的软件模块
- 意义:保护或者防止代码在无意间被破坏,保护类中的成员,不让类中以外的程序直接访问或者修改,只能通过提供的公用接口访问
-
继承: 子类继承父类的特征和行为,复用了基类的全体数据和成员函数,具有复制而来的数据成员和成员函数(基类私有成员可被继承,但无法访问),其中构造函数、析构函数、友元函数、静态数据成员、静态成员函数都不能被继承。基类中成员的访问方式只能由派生类决定是否访问它。增强了代码耦合性,但父类中的成员变量或者类本身被 final 关键字修饰时,修饰的类不能被继承,修饰的成员变量不能被重写
- 意义:基类的程序代码可以被派生类调用,提高了软件复用的效率,缩短了软件开发的周期
-
多态: 不同继承类的对象对同一信息做出不同的响应,基类的指针指向或者绑定到派生类对象,使得基类指针呈现不同的表现形式,在程序中体现了灵活多样的操作,提高了使用效率,简化了代码的编写和修改过程
三、C++的多态怎么实现?
C++的多态: 在基类的函数前加上 virtual 关键字,在派生类种重写该函数,运行时将会根据所指对象的实际类型来调用相应的函数,如果对象指针派生类,就调用派生类的函数,如果是基类的话,就调用基类的函数
虚表和虚表指针:
- 虚表:虚函数表的缩写,类中含有 virtual 关键字修饰的方法时,编译器会自动生成虚表
- 虚表指针:在含有虚函数的类实例化对象时,对象地址的前4个字节存储的指向虚表的指针 vfptr
多态的实现过程:
- 编译器在发现基类中由虚函数时,会自动为每个含有虚函数的类生成一份虚表,该表是一个
RTTI
构成的指针数组,虚表里面保存了虚函数的入口地址 - 编译器会在每个对象的前4个字节中保存一个虚表指针,即 vfptr,指向指向对象所属类的虚表。在构造时,根据对象的类型区初始化虚函数指针 vfptr,从而让它指向正确的虚表,在调用虚函数时,能够找到正确的虚函数进行调用
- 所谓的合适时机,在派生类定义对象时,程序自动调用构造函数,在构造函数中创建虚表并对虚表进行初始化,在构造子类对象时,会先调用父类的构造函数,此时,编译器只看到了父类,并为父类初始化了虚表指针,指向父类的虚表,当调用子类的构造函数时,为子类对象初始化虚表指针,指向子类的虚表
- 当派生类对基类的虚函数没有进行重写,派生类的虚表指针指向的是基类的虚表,当派生类对基类的虚函数进行重写时派生类的虚表指针指向的是自身的虚表,当派生类中有自己的虚函数时,在自己的虚表中将此虚函数地址添加在后面
- 这样指向派生类的基类指针运行时,就可以根据派生类对虚函数重写情况动态的进行调用,从而实现多态性。
四、C++中类成员的访问权限和继承权限问题
三种访问权限
-
public:用该关键字修饰的成员表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口;
-
private:用该关键字修饰的成员表示私有成员,该成员仅在类内可以被访问,在类体外是隐藏状态;
-
protected:用该关键字修饰的成员表示保护成员,保护成员在类体外同样是隐藏状态,但是对于该类的派生类来说,相当于公有成员,在派生类中可以被访问。
三种继承方式
-
若继承方式是public,基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
-
若继承方式是private,基类所有成员在派生类中的访问权限都会变为私有(private)权限;
-
若继承方式是protected,基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,私有成员在派生类中的访问权限仍然是私有(private)权限。
五、C++中的重载、重写(覆盖)和隐藏的区别
(1)重载(overload)
- 重载是指在同一范围定义中的同名成员函数才存在重载关系。主要特点是函数名相同,参数类型和数目有所不同,不能出现参数个数和类型均相同,仅仅依靠返回值不同来区分的函数。重载和函数成员是否是虚函数无关。
(2)重写(覆盖)(override)
-
重写指的是在派生类中覆盖基类中的同名函数,也就是重写函数体,其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类对象调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。
-
重载与重写的区别:
-
重写是父类和子类之间的垂直关系,重载是不同函数之间的水平关系
-
重写要求参数列表相同,重载则要求参数列表不同,返回值不要求
-
重写关系中,调用方法根据对象类型决定,重载根据调用时实参表与形参表的对应关系来选择函数体
-
-
(3)隐藏(hide)
- 隐藏指的是某些情况下,派生类中的函数屏蔽了基类中的同名函数,包括以下情况:
- 两个函数参数相同,但是基类函数不是虚函数。和重写的区别在于基类函数是否是虚函数。
- 两个函数参数不同,无论基类函数是不是虚函数,都会被隐藏。和重载的区别在于两个函数不在同一个类中。
六、知道C++中的组合吗?它与继承相比有什么优缺点吗?
一、继承
继承是 Is
的关系,比如说Student
继承Person
,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。
-
继承的缺点有以下几点:
-
父类的内部细节对子类是可见的
-
子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为
-
如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想
-
二、组合
组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。
-
组合的优点:
-
当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。
-
当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。
-
当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。
-
-
组合的缺点:
-
容易产生过多的对象。
-
为了能组合多个对象,必须仔细对接口进行定义。
-