c++ 数据抽象 封装 接口(抽象类)
Posted expedition
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++ 数据抽象 封装 接口(抽象类)相关的知识,希望对你有一定的参考价值。
一、数据抽象
即,只向外界提供关键信息,并隐藏其后台的实现细节 ———— 一种依赖于接口和实现分离的编程(设计)技术
例如,程序可以调用 sort() 函数,而不需要知道函数中排序数据所用到的算法
c++ 中,我们使用类来定义我们自己的抽象数据类型(ADT)。您可以使用类 iostream 的 cout 对象来输出数据到标准输出
#include <iostream> using namespace std; int main( ) cout << "Hello C++" <<endl; return 0;
而不需要理解 cout 是如何在用户的屏幕上显示文本。您只需要知道公共接口即可,cout 的底层实现可以自由改变
(1)访问标签 强制抽象
我们使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:
- 使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由它的公共成员来定义的。
- 使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。
访问标签出现的频率没有限制。每个访问标签指定了紧随其后的成员定义的访问级别。指定的访问级别会一直有效,直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。
(2)数据抽象的好处
两个重要的优势:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变会导致哪些影响。如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。
c++中任何带有公有和私有成员的类都可以作为数据抽象的实例:
#include <iostream> using namespace std; class Adder public: // 构造函数 Adder(int i = 0) total = i; // 对外的接口 void addNum(int number) total += number; // 对外的接口 int getTotal() return total; ; private: // 对外隐藏的数据 int total; ; int main( ) Adder a; a.addNum(10); a.addNum(20); a.addNum(30); cout << "Total " << a.getTotal() <<endl; return 0;
结果:
Total 60
上面的类把数字相加,并返回总和。公有成员 addNum 和 getTotal 是对外的接口,用户需要知道它们以便使用类。私有成员 total 是用户不需要了解的,但又是类能正常工作所必需的。
(3)设计策略
抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。
在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可。
二、封装
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。我们已经知道,类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。
默认情况下,在类中定义的所有项目都是私有的。例如:
class Box public: double getVolume(void) return length * breadth * height; private: double length; // 长度 double breadth; // 宽度 double height; // 高度 ;
变量 length、breadth 和 height 都是私有的(private)。这意味着它们只能被 Box 类中的其他成员访问,而不能被程序中其他部分访问。这是实现封装的一种方式。
实例:任何带有公有和私有成员的类都可以作为数据封装和数据抽象的实例
#include <iostream> using namespace std; class Adder public: // 构造函数 Adder(int i = 0) total = i; // 对外的接口 void addNum(int number) total += number; // 对外的接口 int getTotal() return total; ; private: // 对外隐藏的数据 int total; ; int main( ) Adder a; a.addNum(10); a.addNum(20); a.addNum(30); cout << "Total " << a.getTotal() <<endl; return 0;
结果:
Total 60
上面的类把数字相加,并返回总和。公有成员 addNum 和 getTotal 是对外的接口,用户需要知道它们以便使用类。私有成员 total 是对外隐藏的,用户不需要了解它,但它又是类能正常工作所必需的。
设计策略
通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。
这通常应用于数据成员,但它同样适用于所有成员,包括虚函数。
三、接口
接口描述了类的行为和功能,而不需要完成类的特定实现。
C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的,如下所示:
class Box public: // 纯虚函数 virtual double getVolume() = 0; private: double length; // 长度 double breadth; // 宽度 double height; // 高度 ;
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。
因此,如果一个 ABC 的子类需要被实例化,则必须实现每个虚函数,这也意味着 C++ 支持使用 ABC 声明接口。如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。
可用于实例化对象的类被称为具体类。
#include <iostream> using namespace std; // 基类 class Shape public: // 提供接口框架的纯虚函数 virtual int getArea() = 0; void setWidth(int w) width = w; void setHeight(int h) height = h; protected: int width; int height; ; // 派生类 class Rectangle: public Shape public: int getArea() return (width * height); ; class Triangle: public Shape public: int getArea() return (width * height)/2; ; int main(void) Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); // 输出对象的面积 cout << "Total Rectangle area: " << Rect.getArea() << endl; Tri.setWidth(5); Tri.setHeight(7); // 输出对象的面积 cout << "Total Triangle area: " << Tri.getArea() << endl; return 0;
结果:
Total Rectangle area: 35 Total Triangle area: 17
设计策略
面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。
外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。
这个架构也使得新的应用程序可以很容易地被添加到系统中,即使是在系统被定义之后依然可以如此。
以上是关于c++ 数据抽象 封装 接口(抽象类)的主要内容,如果未能解决你的问题,请参考以下文章
TypeScript,面向对象,类、构造函数、继承、抽象类、接口和封装