此篇博客主要总结面向对象相关概念
语法基础概念参考:语法基础
C++11参考:概念深入
持续更新
目录
1.ADT是什么?简述你对“数据抽象”和“信息隐藏”的认识。
面向对象
1.ADT是什么?简述你对“数据抽象”和“信息隐藏”的认识。
抽象数据类型(Abstract Data Type),简称 ADT,是用户自定义的具有一些简单数据类型的集合
数据抽象:对具体事物描述的一个概括。
信息隐藏:C++中的封装就是信息隐藏的一种,即尽可能的隐藏对象的内部细节,对外形成一个边界,只保留有限的对外接口使之与外部反生关系。
2.声明虚构造函数?能否声明虚析构函数?
在 C++中,不能声明虚构造函数,多态是不同的对象对同一消息有不同的行为特性,虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的, 因此虚构造函数是没有意义的;
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存
3.C++中析构函数的作用
析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。
析构函数名也应与类名相同,只是在函数名前面加一个位取反符~
类析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。
4.C++中类成员的访问权限
C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。
5.虚函数和纯虚函数的区别
虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class)。抽象类不能定义对象,只能做接口派生。
虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义。
对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
虚函数:virtual void func()
纯虚函数:virtual void func()=0
对于虚函数,子类可以(也可以不)重新定义基类的虚函数,该行为称之为复写Override。
对于纯虚函数,子类必须提供纯虚函数的个性化实现。
6.虚函数底层实现
有虚函数的类必存在一个虚表。
虚表的构建:基类的虚表构建,先填上虚析构函数的入口地址,之后所有虚函数的入口地址按在类中声明顺序填入虚表;派生类的虚表构建,先将基类的虚表内容复制到派生类虚表中,如果派生类覆盖了基类的虚函数,则虚表中对应的虚函数入口地址也会被覆盖,为了后面寻址的一致性。
虚函数表中有序放置了父类和子类中的所有虚函数,并且相同虚函数在类继承链中的每一个虚函数表中的偏移量都是一致的。C++采用的这种绝对地址+偏移量的方法调用虚函数,查找速度快执行效率高
每个类对应一张虚函数表 ,每个对象有一个虚函数指针指向虚表
7.虚基类 -菱形继承
如果不利用域限定需要访问的函数,那么就会出现模糊调用的问题
利用虚继承就可以解决菱形继承的问题,共同基类设置为虚基类,具体实现是:A和C中不再保存Base的具体内容,而是保存了一份偏移地址 ,所以在D调用fun()时,调用的就是Base的fun(),但对于A、C相同的变量名,D在调用时还是要利用域限定来处理 ,解决多继承产生的二义性问题。
8.拷贝构造函数
一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数,ClassType(const ClassType &);无返回值;
(1) 当用类的一个对象去初始化该类的另一个对象时;
(2) 如果函数的形参是类的对象,调用函数时将使用实参对象初始化形参对象;
(3) 如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数。
拷贝初始化和直接初始化直接初始化直接调用与实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数
9.友元函数&友元类
友元函数是使用关键字 friend 关键字声明的函数,它可以访问相应类的保护成员和私有成员。
友元类是使用 friend 关键字声明的类,它的所有成员函数都是相应类的友元函数。
友元函数不是成员函数,不能继承。
10. 3 种继承方式之间的差别
使用 using 关键字可以改变基类成员在派生类中的访问权限
//基类People
class People
{
public:
void show();
}
//派生类Student
class Student : public People
{
private:
using People::show; //将public改为private
};
using 只能改变基类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用,所以基类中的 private 成员在派生类中无论如何都不能访问。
11.多态性
多态是指同样的消息被不同类型的对象接收时导致完全不同的行为
动态绑定: 在基类中声明相应的函数为 virtual 型,然后在派生类中实现该函数,这样就可以通过 基类指针调用派生类对象的函数
C++中的多态可分为四类:重载多态、强制多态、包含多态和参数多态,其中包含多态是研究类族中定义于不同类中的同名函数的多态行为,主要通过虚函数来实现。多态使得接 口与实现得到分离,要利用统一接口实现运行时多态一般需要动态绑定,而虚函数是动态绑定的基础,就使得虚函数在多态中很重要。
重载:参数不同,必须具有相同的函数名,在程序编译阶段确定操作的对象的,属静态关联
12.不可以被重载&只能重载为成员函数的运算符
不可以被重载:类属关系运算符.、成员指针运算符.*、作用域运算符::、 sizeof 运算符和三目运算符?:
必须重载为成员函数:
= -> () [] new delete
13.继承性
层次结构的上层是最具有通用性的,而下层的部分,即后代具有特殊性。
继承与派生:继承保持已有类的特性,在已有类的基础上新增自己的特性而产生新类的过程称为派生。
14.存储类说明符
auto存储类:声明变量时根据初始化表达式自动推断该变量的类型
register存储类:定义存储在寄存器,能对其使用取地址运算符(&)。
static存储类:指示编译器在程序的生命周期内保持局部变量的存在,用于保持局部变量的值。
extern存储类:用于提供一个全局变量的引用,对于所有的程序文件都是可见的。
mutable存储类:适用于类的对象,mutable数据成员通过const成员函数修改。
15.构造对象顺序
1、若有直接或间接虚基类,先执行虚基类的构造函数。
2、若有其他基类,则按照它们在继承声明列表中出现的次序,分别执行它们的构造函数。
3、按照在类定义中出现的顺序,对派生类中新增的成员对象进行初始化。
4、执行构造函数的函数体。
16.类模板
不关心操作的数据元素类型,只关心操作方法。
template < typename T > // T 泛指类型,具体的元素类型
class Operator // 类模板名
{
public:
T function(T a, T b) // function 具体的操作方法
{ }
};
template < typename T > // 类模板函数在外部实现时的声明方式
T Operator<T>::function(T a, T b)
{
/* do something */
}
编译器通过具体参数和类模板在定义对象时生成类的实体。
编译器会对类模板本身代码进行一次编译,还会对参数替换后的类的代码进行一次编译。
17.必须使用【初始化列表】初始化数据成员
需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
需要初始化const修饰的类成员或初始化引用成员数据;
子类初始化父类的私有成员;向上转换
18.函数模板
所谓的函数模板,实际上是建立一个通用的函数,其函数的类型和形参的类型不具体指定
相对于函数重载而言,模板具有得天独厚的优势,它不需要重复定义,所以使用起来比函数重载更简洁,但应注意的一点,函数模板只适用于函数的参数个数相同而类型不同,且函数体相同的情况,如果参数的个数不同,则不能用函数模板
相比于宏,宏缺乏类型检查
19.多继承
- C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。
- 多重继承的优点很明显,就是对象可以调用多个基类中的接口;
- 如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性(采用虚基类解决)