一、封装
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。
1、封装的意义:
- 封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
- 保护成员属性,不让类以外的程序直接访问和修改;
- 隐藏方法细节,使代码模块化。
2、关于对象封装的原则:
内聚:内聚是指一个模块内部各个部分之间的关联程度
耦合:耦合指各个模块之前的关联程度
封装原则:隐藏对象的属性和实现细节,仅对外公开访问方法,并且控制访问级别
在面向对象方法中,用类来实现上面的要求。用类实现封装,用封装来实现高内聚,低耦合。
二、继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。
1、继承的意义:
主要实现重用代码,节省开发时间。
2、继承的特点:
- 子类拥有父类非private的属性和方法。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
3、子类继承父类静态属性方法是否可以被继承和重写?
结论:非静态属性和方法可以被继承和重写。但是静态属性和方法可以被继承,但是没有被重写(overwrite)而是被隐藏,即调用的都是父类的属性和方法。
原因:
- 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
- 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
4、继承的缺点:
- 父类变,子类就必须变。
- 继承破坏了封装,对于父类而言,它的实现细节对与子类来说都是透明的。
- 继承是一种强耦合关系。
【注意】
继承时的构造函数:
(1)、基类的构造函数不能被继承,派生类中需要声明自己的构造函数;
(2)、派生类声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成;
(3)、派生类的构造函数需要给基类的构造函数传递参数;
(4)、单一继承时的构造函数:派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表) {本类成员初始化赋值语句;};
(5)、当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数;
(6)、若基类中未声明构造函数,派生类中也可以不声明,全采用缺省形式构造函数;
(7)、当基类声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类构造函数;
(8)、构造函数的调用次序:A、调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右);
B、调用成员对象的构造函数,调用顺序按照它们在类中的声明的顺序;
C、派生类的构造函数体中的内容。
继承时的析构函数:
(1)、析构函数也不被继承,派生类自行声明;
(2)、声明方法与一般(无继承关系时)类的析构函数相同;
(3)、不需要显示地调用基类的析构函数,系统会自动隐式调用;
(4)、析构函数的调用次序与构造函数相反。
三、多态:
1、多态的定义:
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
2、C++支持两种多态性:
(1)、编译时多态性(静态多态,在编译时就可以确定对象使用的形式):通过重载函数实现;
(2)、运行时多态性(动态多态,其具体引用的对象在运行时才能确定):通过虚函数实现(覆盖、重写)。
3、重载、重写、隐藏的区别:
函数重载(Overload):指在相同作用域里(如同一类中),函数同名不同参,返回值则不用理会,不同参可以是不同个数,也可以是不同类型。效果:根据实参的个数和类型调用对应的函数体。
函数覆盖(Override)(函数重写):指派生类中的函数覆盖基类中的同名 同参 虚函数,因此作用域不同。效果:基类指针或引用访问虚函数时会根据实例的类型调用对应的函数。
函数隐藏(Hide):对于子类中与基类同名的函数,如果不是覆盖那就成了隐藏。两种情况:(1)、同名不同参;(2)、同名同参但基类不是virtual函数。
4、重写实现多态实例:
#include <iostream>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun()
{
printf("2\n");
}
};
class B : public A
{
public:
void foo()
{
printf("3\n");
}
void fun()
{
printf("4\n");
}
};
int main(void)
{
A a;
B b;
A* p = &a;
p->foo();//1
p->fun();//2
p = &b;
p->foo();//1
p->fun();//4
B* ptr = (B*)&a;
ptr->foo();//3
ptr->fun();//2
return 0;
}
5、纯虚函数:
在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“= 0”。
将函数定义为纯虚函数,则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。
由于纯虚函数所在的类中没有它的定义,在该类的构造函数和析构函数中不允许调用纯虚函数,否则会导致程序运行错误,但其它成员函数可以调用纯虚函数。
6、多态的好处:
- 可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
- 可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
- 接口性(interface-ability)。多态是基类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
- 灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
- 简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。