序 面向对象
—–概述—–
⭐面向对象程序设计特点:抽象、封装、继承、多态。
—–抽象—–
⭐对具体对象进行概括,抽出一类对象的公共性质和本质、忽略非本质的细节,并加以描述的过程。
—–封装—–
⭐将抽象得到的数据和行为相结合,形成一个有机的整体。或者说将数据与操作数据的函数进行结合,形成类。
⭐抽象包含两个方面:数据抽象和行为抽象。
- 优势:
⭐通过封装是一部分成员充当类与外部的接口,而将其他成员隐藏起来,这样就达到了对成员访问权限的控制,是不同类之间的影响减少到最低限度,进而增强数据安全性和简化程序编写工作。
—–继承派生—–
⭐在一般概念基础上,派生出特殊概念。即在保持原有类特性的基础上,进行更具体、更详细的说明。
- 优势:
⭐继承使得基类(一般概念)中的属性和行为可以被派生类(特殊概念)共享,拜托重复分析重复开发的困境。
—–多态—–
⭐多态是指一段程序处理多种类型对象的能力。或者说同样的消息被不同类型对象接受时不同的行为。
⭐多态可通过重载多态、强制多态、类型参数化多态、包含多态四种形式来实现。
- 优势:
⭐REW:即相同函数调用的不同行为实现。
第一节 继承派生
概念
⭐类的继承,是新的类从已有类那里的得到已有的特性。原有的类称为父类或者基类,产生的新类称为子类或派生类。
⭐C++中可以多继承。(C#不可以)
派生类的生成过程:
- 吸收基类成员。
- 改造基类成员。(同名隐藏)
- 添加新的成员。
访问控制
⭐基类成员有三种访问属性:公有(public)、保护(protected)、私有(private)。
⭐所以对应继承方式也有三种:公有继承(public)、保护继承(protected)、私有继承(private)。
- 公有继承:
⭐基类私有成员不可直接访问。基类的公有、保护成员的访问属性在派生类中不变。
- 保护继承:
⭐基类私有成员不可直接访问。基类的公有、保护成员在派生类中以保护成员身份出现。
- 私有继承:
⭐基类私有成员不可直接访问。基类的公有、保护成员在派生类中以保护私有身份出现。
类型兼容规则
⭐类型兼容规则,是指在需要基类对象的任何地方,都可以使用公有派生类对象替代。
派生类的构造函数和析构函数
- 构造函数:
⭐如果在基类中编写带参的构造函数覆盖了默认构造函数,派生类就必须编写构造函数。
———–派生类构造函数指向次序————
- 调用基类构造函数,调用顺序按照继承时的声明顺序。
- 初始化派生类的新增成员对象,调用顺序它们按照在类中声明的顺序。
- 执行派生类构造函数函数体中的内容。
-
复制构造函数: ⭐如果要为派生类编写复制构造函数,一般需要为基类相应的复制构造函数传递参数。
- 析构函数:
- ⭐派生类析构函数的声明方法与没有继承关系的类中的析构函数的声明方法完全相同,只要在函数体中负责把派生类新增的非对象成员的清理工作做好就够了,系统自己会调用基类及对象成员的析构函数来对基类及对象成员进行清理。
———–派生类析构函数指向次序————(次序基本和构造函数相反)
- 执行析构函数函数体内容。
- 对派生类新增的类类型成员对象进行清理。
- 对所有基类继承的成员进行清理。
派生类成员访问属性、多继承问题、虚基类的概念
派生类成员访问属性:
⭐类成员访问属性有四种。除了public、protected、private外多出一种:不可访问成员。(即从父类的私有成员继承来的成员)
多继承问题:
⭐如果某个派生类有些直接基类是从同一个共同基类派生而来的。那么在这个派生内中就会出现成员同名现象。
⭐解决方法一:用直接基类名来限定。(域限定符”::”)
⭐解决方法二:虚基类。
虚基类解决多继承问题:
⭐将共同基类设置为虚基类,那么不同路径继承的同名成员就只有一个副本。(无需使用域限定符)
⭐使用虚基类解决多继承问题特点是:最远派生类容纳更少的数据、内存空间更节省。(根据实际问题需要选择合适方法)
虚基类的基本原理:
⭐虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象,这样一来既然是共享的那么每一个子类都不会独占。
⭐但是总还是必须要有一个类来完成基类的构造,那就是最远派生类的构造函数。中间类的构造函数对虚基类构造函数的调用都被自动忽略。也就是说虚基类构造函数只会被调用一次。
第二节 多态
概述
⭐多态是同样的消息被不同类型对象接受时导致不同行为。
多态分类:
———-多态一般可以分为四类————-
⭐重载多态:(函数重载、运算符重载)
⭐强制多态:把一个类型进行强制转换以符合函数或者操作的要求。(不同类型之间的运)
⭐包含多态:不同类的同名成员函数多态行为。(虚函数重写)
⭐参数多态:类模板相关。(泛型)
———-多态从实现的角度来讲可以分两类————-
⭐编译时多态:编译的过程中确定同名操作的具体操作对象。(静态绑定)
⭐运行时多态:运行过程中才动态确定同名操作的具体操作对象。(动态绑定)
动态绑定
编译器在编译时就依据某对象所属的类决定究竟执行哪一个成员函数,称为静态绑定(static binding)。
编译器仅能在执行过程中依据所指的实际对象来决定调用哪一个成员函数,也就是说“找出实际被调用的究竟是哪一个派生类的成员函数”这一解析操作会延迟至运行时,称为动态绑定(dynamic binding)。
(绑定是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个地址相关联)
运算符重载
⭐运算符重载是对已有的运算符赋予多重含义,是用一个运算符用于不同类型时导致不同的行为。
⭐运算符操作实质上就是函数重载。
- 注意事项:
⭐类属关系运算符”.”、作用域限定符”::”、三目运算符”?:”等不能重载。
运算符重载为成员函数:
⭐示例:T operator+(const T &obj2) const;
(表示的是返回当前对象(this)+obj2的结果)
- 运算符重载为非成员函数:
⭐示例:T operator+(const T &obj1, const T &obj2);
⭐必须friend关键字声明为友元函数。
虚函数
⭐虚函数时动态绑定的基础,它必须时非静态的成员函数。虚函数经过派生之后,在类族中就可以实现运行过程的多态。
- 虚函数实现运行时多态的条件:
⭐1、类必须有继承关系,即满足兼容规则。
⭐2、使用virtual在基类声明虚函数。并且派生类的对应函数也要被判断为虚函数,或者可以virtual显示声明。
⭐3、必须通过指针或者引用访问虚函数,或者由成员函数调用,不能用对象名来访问。(因为基类指针/引用可以指向派生类对象,但是基类对象不能表示派生类对象)(对象切片问题)
- 补充:
⭐基类虚函数被重写后依然可以加域限定符访问到。
⭐基类函数的非虚函数一般不要重写,虽然语法上没有限制,但是不能实现多态并且还会有其他问题。
⭐重写的方法后面可以加上一个override
。如果重写函数名错误会报错,还能告诉阅读者这是一个重写函数。
⭐虽然不可以声明虚构造函数,但是可以声明虚析构函数实现运行时多态,保证基类指针调用不同的构造函数针对不同对象进行清理工作。
纯虚函数和抽象类
- 纯虚函数:
⭐纯虚函数是一个在基类中声明的虚函数,他在该基类中没有定义具体的操作内容,要求个派生类根据实际需要给出各自的定义实现。
⭐声明格式: virtual T func(params) = 0;
- 抽象类:
⭐抽象类即包含纯虚函数的类。抽象类是一种特殊的类,抽象类无法实例化,它为类族提供统一的操作接口。
⭐抽象类的派生类如果没有给出所有纯虚函数的函数实现,那么它依然是个抽象类。