目录
C++ 中的封装(Encapsulation)
封装(Encapsulation)是面向对象编程(OOP)的核心特性之一,它的主要目的是将数据和操作数据的方法绑定在一起,并限制对数据的直接访问,从而提高代码的安全性和可维护性。
封装的核心思想就是 “把内部实现藏起来,对外提供有限的接口”,就像 ATM 机一样,你只能操作它允许的按钮,而不能直接干预它的运作。
借助一个生活中的例子来理解封装: ATM 取款机 🏦💳
现实场景:在 ATM 机上,用户 只需要输入密码、选择金额,然后取钱,但 ATM 机的内部实现是对用户隐藏的。
- 你无法直接操作 ATM 机内部的电路,也看不到 ATM 如何与银行系统交互。
- 你只能通过 ATM 提供的按钮和屏幕进行操作。
这就像一个 封装良好的 C++ 类,它的内部运作是不可见的(私有的),但提供了一些公共接口(Public 方法)供用户使用。
1. 为什么需要封装?/封装的意义
- 数据安全性:防止对象的数据被外部代码随意修改,避免错误和不一致的状态。
- 隐藏实现细节:外部代码不需要关心类的具体实现,只需使用对外提供的接口即可。封装允许我们隐藏类的内部实现,对外部代码只暴露必要的接口,使得程序更加模块化。这样,类的实现可以随时更改,而不会影响外部代码。
- 提高代码的可维护性:封装可以减少代码之间的耦合,使得修改代码时影响更小。封装使代码模块化,每个类负责自己的功能,避免了全局变量的混乱,提高了代码的可维护性。
- 控制访问权限:通过
private
、protected
、public
关键字控制数据的访问级别。
2. C++ 封装的实现【通过访问控制修饰符实现】
C++ 通过 访问控制修饰符(private
、protected
、public
)来实现封装,使类的成员变量(数据)和成员函数(方法)具有不同的访问权限。通常,我们会:
- 将数据成员设为
private
,防止外部直接访问。 - 提供
public
方法来访问和修改数据。 - 隐藏实现细节,只暴露必要的接口。
封装允许我们通过 public
、private
、protected
关键字精确控制类成员的访问权限,避免外部代码误用类的内部数据。
访问控制 | 类内部访问 | 类外部访问 | 子类访问 |
---|---|---|---|
private | ✅ 可以 | ❌ 不可以 | ❌ 不可以 |
protected | ✅ 可以 | ❌ 不可以 | ✅ 可以 |
public | ✅ 可以 | ✅ 可以 | ✅ 可以 |
2.1 访问控制修饰符在代码中的具体形式
#include <iostream>
using namespace std;
class Example {
private:
int privateVar; // 私有成员,外部无法直接访问
protected:
int protectedVar; // 保护成员,子类可以访问
public:
int publicVar; // 公有成员,外部可以访问
void setPrivateVar(int value) {
privateVar = value;
}
int getPrivateVar() {
return privateVar;
}
};
int main() {
Example obj;
obj.publicVar = 10; // ✅ 可以直接访问
obj.setPrivateVar(5); // ✅ 通过 public 方法访问 private 数据
cout << "Private Variable: " << obj.getPrivateVar() << endl;
// ❌ obj.privateVar = 20; // 编译错误,无法直接访问 private 变量
// ❌ obj.protectedVar = 15; // 编译错误,无法直接访问 protected 变量
return 0;
}
✅ 说明:
privateVar
只能通过setPrivateVar()
和getPrivateVar()
访问,实现封装。publicVar
可以被外部直接访问。protectedVar
不能在main()
中访问,但可以在子类中访问(见后面示例)。
2.2 protected 访问控制修饰符的具体使用
#include<iostream>
// protected 访问控制修饰符的使用
class BaseClass{
protected:
int base_protected_var=10; // 受保护成员,类内部可以访问,类外部不可以访问,子类可以访问
void display(){
std::cout<<"这是基类的受保护方法"<<std::endl;
}
public:
int access_protected_InClass(){
return base_protected_var; // 受保护成员,类内部可以直接访问
}
int access_protected_OutClass(){
return base_protected_var; // 受保护成员,类外部不能直接访问,但可借助类内公有成员函数间接访问
}
};
class DerivedClass : BaseClass { // 子类(派生类)
public:
void show() {
std::cout << "子类访问父类的 protected 变量:" << base_protected_var << std::endl;
}
};
int main(){
BaseClass b1; // 基类实例化
// b1.base_var; // ❌ 受保护成员不能够在类外部被直接访问
// b1.display(); // ❌ 受保护成员不能够在类外部被直接访问
int result = b1.access_protected_OutClass(); // 受保护成员借助类内部公有成员函数在类外部简介访问
std::cout<<result<<std::endl; // 输出:10
DerivedClass child; // 子类实例化
child.show(); // 输出:子类访问父类的 protected 变量:10
return 0;
}
protected
变量只能在 父类和子类内部 访问,不能被外部代码直接访问。
✅ protected
主要用于 继承,让子类可以访问但不暴露给外部代码。
✅ private
变量 连子类都无法访问,更安全,但如果需要子类访问,就可以使用 protected
。
✅ 在 类外部,无论是 private
还是 protected
,都不能直接访问。
3. 封装示例
(1)错误示例:没有封装,数据不安全
#include <iostream>
using namespace std;
class Student {
public:
string name;
int age;
};
int main() {
Student s;
s.name = "张三"; // 直接访问并修改
s.age = 20; // 直接访问并修改
cout << "姓名: " << s.name << ", 年龄: " << s.age << endl;
return 0;
}
❌ 问题:
name
和age
直接暴露在外部,任何人都可以修改它们,可能导致数据不安全或不一致。
(2)正确示例:使用封装保护数据
#include <iostream>
using namespace std;
class Student {
private:
string name; // 私有数据成员
int age; // 私有数据成员
public:
// 设置姓名
void setName(string n) {
name = n;
}
// 获取姓名
string getName() {
return name;
}
// 设置年龄(增加数据有效性检查)
void setAge(int a) {
if (a > 0 && a < 120) { // 限制年龄范围
age = a;
} else {
cout << "无效的年龄" << endl;
}
}
// 获取年龄
int getAge() {
return age;
}
};
int main() {
Student s;
s.setName("张三");
s.setAge(20);
cout << "姓名: " << s.getName() << ", 年龄: " << s.getAge() << endl;
return 0;
}
✅ 改进:
- 数据成员
name
和age
设置为private
,外部不能直接访问,避免了随意修改。 - 提供
set
和get
方法,并在setAge
方法中增加年龄范围检查,保证数据有效性。
4. 友元(friend)与封装
在某些特殊情况下,我们可能希望外部函数或类可以访问 private
或 protected
成员。C++ 提供了 friend
关键字 允许特定函数或类访问私有成员。
示例:使用 friend
访问 private 成员
#include <iostream>
using namespace std;
class Student {
private:
string name;
int age;
public:
Student(string n, int a) : name(n), age(a) {}
// 声明友元函数
friend void showStudentInfo(Student s);
};
// 友元函数可以访问 private 成员
void showStudentInfo(Student s) {
cout << "姓名: " << s.name << ", 年龄: " << s.age << endl;
}
int main() {
Student s("张三", 20);
showStudentInfo(s); // 友元函数可以访问 private 成员
return 0;
}
✅ 友元的作用:
- 允许某个特定函数或类访问私有成员,但不影响封装的其他部分。
- 常用于运算符重载、调试等场景。
5. 总结
- 封装提高数据安全性,防止数据被外部随意修改。
- 隐藏实现细节,对外提供稳定的接口,降低代码耦合。
- 提高代码可维护性,修改代码时不会影响其他部分。
- 便于代码复用,封装良好的类可以在多个项目中使用。
- 控制访问权限,防止错误使用数据,提高代码的可靠性。
- 友元(
friend
)允许外部函数或类访问private
成员,但要谨慎使用,否则可能破坏封装性。
封装是 C++ 面向对象编程 的重要特性,它帮助我们保护数据、减少耦合、提高代码可维护性。