13. 封装与访问控制——编程世界的数据“守护者”
13.1 封装的概念
封装是面向对象编程的三大特性之一,它就像是一个坚固的“盒子”,将数据(属性)和操作这些数据的函数(方法)捆绑在一起,形成一个独立的单元,也就是类。封装的主要目的是隐藏对象的内部实现细节,只向外部提供必要的接口,这样可以提高代码的安全性和可维护性。
例如,在一个银行账户系统中,账户的余额是一个重要的数据,我们不希望外部代码直接修改这个余额,而是通过特定的方法(如存款、取款)来操作它。这就是封装的体现,将账户余额这个数据封装在账户类中,只提供安全的操作接口给外部使用。
13.2 访问控制的重要性
访问控制是实现封装的重要手段,它通过访问修饰符来限制对类的成员(数据成员和成员函数)的访问权限。合理的访问控制可以防止外部代码意外或恶意地修改类的内部数据,保证数据的完整性和安全性。同时,也有助于将类的接口和实现分离,使得类的使用者只需要关注类提供的接口,而不需要了解类的内部实现细节。
13.3 C++ 中的访问修饰符
在 C++ 中,有三种主要的访问修饰符:public
、private
和 protected
。
13.3.1 public
(公有)
公有成员可以在类的外部直接访问。通常,类的接口(成员函数)会被声明为公有,以便外部代码可以调用这些函数来与对象进行交互。
#include <iostream> using namespace std; class Rectangle { public: // 公有数据成员 double length; double width; // 公有成员函数 double getArea() { return length * width; } }; int main() { Rectangle rect; // 直接访问公有数据成员 rect.length = 5.0; rect.width = 3.0; // 调用公有成员函数 cout << "矩形的面积是: " << rect.getArea() << endl; return 0; }
在这个示例中,length
、width
和 getArea
都是公有成员,在 main
函数中可以直接访问和调用。
13.3.2 private
(私有)
私有成员只能在类的内部访问,外部代码无法直接访问。通常,类的数据成员会被声明为私有,通过公有成员函数来间接访问和修改这些数据成员,以保证数据的安全性。
#include <iostream> using namespace std; class Rectangle { private: // 私有数据成员 double length; double width; public: // 公有成员函数,用于设置长度 void setLength(double len) { if (len > 0) { length = len; } else { cout << "长度必须为正数!" << endl; } } // 公有成员函数,用于设置宽度 void setWidth(double wid) { if (wid > 0) { width = wid; } else { cout << "宽度必须为正数!" << endl; } } // 公有成员函数,用于计算面积 double getArea() { return length * width; } }; int main() { Rectangle rect; // 通过公有成员函数设置长度和宽度 rect.setLength(5.0); rect.setWidth(3.0); // 调用公有成员函数计算并输出面积 cout << "矩形的面积是: " << rect.getArea() << endl; return 0; }
在这个示例中,length
和 width
是私有数据成员,main
函数无法直接访问它们,只能通过 setLength
和 setWidth
这两个公有成员函数来间接设置它们的值。
13.3.3 protected
(受保护)
受保护成员在类的内部和派生类(子类)中可以访问,在类的外部不能直接访问。受保护成员主要用于在继承关系中,让派生类可以访问基类的某些成员。
#include <iostream> using namespace std; // 基类 class Shape { protected: // 受保护数据成员 double area; public: // 公有成员函数,用于计算面积(这里只是示例,没有具体实现) virtual void calculateArea() = 0; // 公有成员函数,用于输出面积 void displayArea() { cout << "面积是: " << area << endl; } }; // 派生类 class Rectangle : public Shape { private: double length; double width; public: Rectangle(double len, double wid) : length(len), width(wid) {} // 实现基类的纯虚函数 void calculateArea() override { area = length * width; } }; int main() { Rectangle rect(5.0, 3.0); rect.calculateArea(); rect.displayArea(); return 0; }
在这个示例中,area
是 Shape
类的受保护数据成员,Rectangle
类作为 Shape
类的派生类,可以访问 area
成员并对其进行赋值操作。
13.4 封装与访问控制的优点
- 数据安全性:通过将数据成员声明为私有,外部代码无法直接访问和修改这些数据,只能通过类提供的公有成员函数进行操作,从而避免了数据被非法修改的风险。
- 代码可维护性:封装使得类的内部实现细节被隐藏起来,当类的内部实现发生变化时,只要类的接口不变,外部代码就不需要进行修改,降低了代码的耦合度,提高了代码的可维护性。
- 代码复用性:封装后的类可以作为一个独立的单元被复用,其他代码可以通过类提供的接口来使用这个类,而不需要关心类的内部实现细节。
13.5 课后小练习
- 定义一个表示银行账户的类
BankAccount
,包含私有数据成员accountNumber
(账号)、balance
(余额),公有成员函数deposit
(存款)、withdraw
(取款)、getBalance
(获取余额),通过访问控制保证账户余额的安全性。 - 定义一个表示图书的类
Book
,包含私有数据成员title
(书名)、author
(作者)、price
(价格),公有成员函数setTitle
、setAuthor
、setPrice
用于设置相应的信息,displayInfo
用于输出图书的信息,通过封装和访问控制实现数据的安全管理。
通过这部分的学习,你了解了封装和访问控制的概念、C++ 中的访问修饰符以及它们的优点。封装和访问控制是面向对象编程中非常重要的特性,能够帮助你编写出更加安全、可维护和可复用的代码。