今天我们梳理了一下数据封装、数据抽象与接口,其实有之前的基础这里也很容易理解。封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制, C++ 通过创建类来支持封装和数据隐藏(public、protected、 private)。
在软件开发中,数据抽象是一个核心概念,它允许我们隐藏实现细节,仅通过公共接口与外部世界交互。这种封装机制不仅提高了代码的安全性,还促进了代码的复用和可维护性。C++作为一门强大的面向对象编程语言,通过类(Classes)和接口(Interfaces,尽管C++标准库中不直接称为“接口”,但可以通过纯虚类实现)等机制,提供了强大的数据抽象能力。
让我们举一个现实生活中的真实例子,比如一台电视机,您可以打开和关闭、切换频道、调整音量、添加外部组件(如喇叭、录像机、DVD 播放器),但是您不知道它的内部实现细节,也就是说,您并不知道它是如何通过缆线接收信号,如何转换信号,并最终显示在屏幕上。
因此,我们可以说电视把它的内部实现和外部接口分离开了,您无需知道它的内部实现原理,直接通过它的外部接口(比如电源按钮、遥控器、声量控制器)就可以操控电视。
现在,让我们言归正传,就 C++ 编程而言,C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。
优点
提高代码安全性:通过封装,可以限制对类内部状态的直接访问,从而避免数据被意外修改。
增强代码复用性:通过定义清晰的接口,可以在多个项目或模块中重用类。
简化代码维护:由于隐藏了实现细节,当内部实现发生变化时,只需确保接口保持不变,即可减少对外部代码的影响。
提高可读性:通过清晰的接口定义,可以更容易地理解代码的功能和用途。
数据抽象
在C++中,数据抽象主要通过类来实现。类是一种用户定义的类型,它封装了数据成员(属性)和成员函数(行为),其中成员函数可以访问和修改数据成员,但外部代码通常只能通过成员函数来间接访问这些数据成员。
在C++中定义一个类,你需要指定类的名称、数据成员(私有或保护)和成员函数(公有、保护或私有)。通常,数据成员被声明为私有(private),以保护其不受外部直接访问;成员函数被声明为公有(public),以提供类的接口。请看下面的实例:
#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 是用户不需要了解的,但又是类能正常工作所必需的。
抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。
在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可。
抽象类与接口
以前的文章已经学习了抽象与接口,今天涉及到安全性,进行一下总结,抽象类是指至少有一个纯虚函数的类。抽象类不能被实例化,只能作为基类被继承。它提供了一个接口,要求派生类实现特定的功能。
通过在函数声明后加 = 0
来定义纯虚函数。
class Shape {
public:
virtual double area() = 0; // 纯虚函数
};
在 C++ 中,接口通常是一个只包含纯虚函数的抽象类。派生类必须实现这些虚函数,才能创建实例。接口定义了行为,但不提供实现。
class Shape {
public:
virtual double area() = 0; // 纯虚函数
virtual void draw() = 0; // 另一个纯虚函数
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius; // 实现面积计算
}
void draw() override {
cout << "Drawing Circle" << endl; // 实现绘制
}
};
class Square : public Shape {
private:
double side;
public:
Square(double s) : side(s) {}
double area() override {
return side * side; // 实现面积计算
}
void draw() override {
cout << "Drawing Square" << endl; // 实现绘制
}
};
1、抽象类 Shape
Shape 是一个抽象类,不能直接实例化。它定义了两个纯虚函数:
area():计算形状的面积。
draw():绘制形状。
纯虚函数:通过在函数名后添加 = 0,表示这是一个纯虚函数,要求所有派生类必须实现这些函数。这样,Shape 类提供了一个接口,定义了所有形状必须具备的功能。
2. 派生类 Circle
继承:Circle 类从 Shape 类继承,表示 Circle 是一种形状。
构造函数:Circle(double r) 构造函数用于初始化 Circle 对象的半径。
实现纯虚函数:
area():实现了计算圆的面积的逻辑。
draw():实现了绘制圆的逻辑,输出 "Drawing Circle"。
抽象类 Shape:定义了通用的形状接口(area 和 draw),不提供具体实现,确保所有形状类(如 Circle 和 Square)必须提供这些功能。
派生类 Circle 和 Square:这两个类实现了 Shape 接口,提供了各自特定的形状的行为。
通过使用抽象类和接口,代码变得更加灵活和可扩展。你可以轻松地添加新的形状类,而不需要修改已有的代码,只需确保新类实现 area 和 draw 函数即可。这种设计降低了代码的耦合度,提高了可维护性。