前言
第五次学习,面向对象的内容。浅学一下,感觉后续还是需要看书,深究的话。
数据抽象
数据抽象是什么?
数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
数据抽象可以说是一种依赖于接口和实现分离的编程(设计)技术,也可以说是一种关注于将数据和相关操作分离的高级别思维方式。
举一个汽车驾驶的例子:
当你驾驶汽车时,你不需要了解发动机的每个细节和内部工作原理。你只需要知道如何使用刹车、油门和方向盘来控制汽车,这是汽车的抽象接口。汽车制造商将发动机、传动系统等复杂的细节隐藏在汽车的抽象层下,以简化用户体验。
数据抽象包括下列关键要点:
- 类和对象:在C++中,数据抽象通常通过类和对象来实现。
- 成员变量:类中的成员变量用来存储数据,它们通常被声明为私有,以限制外界对其的直接访问。
- 成员函数:类中的成员函数用于执行操作,这些操作可以访问和操作成员变量,成员函数通常提供了一个公共接口,以允许外部代码与类交互
- 封装:数据结构通过封装实现,即将成员变量声明为私有,以限制直接访问,然后提供公共的成员函数来访问和修改这些私有成员。
- 抽象接口:成员函数提供一个抽象接口,它定义了如何与类的对象进行交互,而不需要了解内部实现细节
关于数据封装和抽象接口(稍后会讲)
数据抽象的实例:
#include <iostream>
class Circle {
private:
// 对外隐藏的数据(成员变量)
double radius;
public:
// 构造函数
Circle(double r) : radius(r) {
}
// 计算圆的面积
double computeArea() {
return 3.14159 * radius * radius;
}
};
int main() {
Circle myCircle(5.0); // 创建一个圆对象
double area = myCircle.computeArea(); // 计算圆的面积
std::cout << "圆的面积是:" << area << std::endl;
return 0;
}
上述例子中,Circle 类封装了一个圆的半径,并提供了一个用于计算圆的面积的公有成员函数。成员变量 radius 被声明为私有,而构造函数用于初始化对象的状态。
数据抽象的俩个好处:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告
数据封装
数据封装是面向对象编程中的一个重要概念,它允许你将数据和操作数据的方法封装在一个类中,以实现数据的隐藏和保护。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
在C++中,可以使用类来实现数据封装,在类中,类的成员变量用于存储数据,类的成员函数用于操作数据,如下:
class Person {
private:
std::string name;
int age;
public:
// 构造函数
Person(const std::string& n, int a) : name(n), age(a) {}
// 成员函数,用于获取和设置私有成员变量
std::string getName() const { return name; }
int getAge() const { return age; }
void setAge(int newAge) { age = newAge; }
};
在上述代码中,private关键字用于限制成员变量name和age的访问,使它们只能被类的成员函数访问,这里实现了数据的封装和隐藏。
可以通过访问类的成员函数来操作对象的数据:
int main() {
Person person("Alice", 25);
std::cout << "Name: " << person.getName() << std::endl;
std::cout << "Age: " << person.getAge() << std::endl;
person.setAge(26);
std::cout << "Updated Age: " << person.getAge() << std::endl;
return 0;
}
上述操作演示了如何使用类来封装数据,并通过成员函数访问和修改数据。
关于数据抽象和数据封装
数据抽象和数据封装是OOP中的俩个相关但不同的概念,它们通常一起使用,但具有不同的焦点和目的。
数据抽象:
- 数据抽象是一种概念,强调将数据的关键特征和行为从具体的实现细节中分离出来,以便更好的处理数据。
数据封装:
- 数据封装是数据抽象的一种实现方式,它强调将数据和操作数据的方法封装在同一个类中,以提供访问控制盒数据的隐藏。
- 数据封装的目标是将数据的实现细节隐藏在类的私有部分,同时提供公共接口(公有成员函数)来访问和操作数据。
接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的具体实现。
在C++中,接口通常通过抽象类来实现。抽象类是一种类,它不能实例化为对象,但可以用作其他类的基类,以定义一组纯虚函数,从而强制子类提供这些函数的实现。这允许你创建一种类似于接口的抽象类型,其中子类必须实现指定的接口。
在C++11及以后的标准中,也可以使用接口关键字 interface 来定义接口。
定义和使用接口的基本步骤:
定义接口(抽象类):
使用class关键字定义一个抽象类,并在其中声明一些纯虚函数,示例如下
class Shape {
public:
virtual double getArea() const = 0;
virtual double getPerimeter() const = 0;
};
创建子类:
创建一个或多个子类,继承自抽象类,并提供纯虚函数的具体实现,子类必须实现抽象类中定义的所有纯虚函数。
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() const override {
return 3.14 * radius * radius;
}
double getPerimeter() const override {
return 2 * 3.14 * radius;
}
};
使用抽象类:
可以创建类的子类对象,然后通过基类指针或引用来访问子类的实现,
int main() {
Circle circle(5.0);
Shape* shapePtr = &circle;
std::cout << "Area: " << shapePtr->getArea() << std::endl;
std::cout << "Perimeter: " << shapePtr->getPerimeter() << std::endl;
return 0;
}
设计策略
数据抽象:
抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。
在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可。
数据封装:
通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。
这通常应用于数据成员,但它同样适用于所有成员,包括虚函数。
接口(抽象类):
面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。
外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。
这个架构也使得新的应用程序可以很容易地被添加到系统中,即使是在系统被定义之后依然可以如此。