我只是......我不完全确定我理解封装。也许这可能与我仍然在课堂上学习编程而没有制作任何像其他人使用的.REAL世界程序这一事实有关,但我只是不明白它想要完成什么。我理解它限制访问某些类的成员和函数。但是喜欢......从谁那里限制它?我已经看到了几个他们有私有数据成员的例子,但他们有公共的get或set方法,无论如何都可以操纵数据成员。那么如何限制或隐藏任何东西?
我的书上写着以下内容:
” 封装提供了两个重要的优点:
我想我对他们使用的单词感到困惑。用户代码如何可能破坏对象的状态,或者有人能给我一个例子吗?
我知道我的问题在所有地方都是如此,但在考虑封装时我的想法也是如此,所以我很难将所有关于它的想法都包含在内(..lol)
答案 0 :(得分:14)
我最喜欢的封装示例是开车。
典型的驾驶员知道如何通过打开点火装置并踩下油门踏板使汽车前进。他们不需要了解内部发动机燃烧的任何信息,以便每天早上上班。
油门踏板为操作非常复杂的机器提供了一个非常简单的界面。意思是,真正复杂的内部细节是来自驱动程序的封装。
现在,就代码而言,假设你想使用某种Map
,但你不知道如何为你的密钥编写通用哈希函数,或者如何实现任何其他底层函数的信息。
在Java中,您可以简单地使用HashMap
,而无需担心标准库在底层正在做什么。这些详细信息来自用户封装。
答案 1 :(得分:3)
让我们分别处理这两点。
<强> 1。类不变量
在编程中,当你有不变量时,推理会变得更容易。例如,您可能已经听说过循环不变量:
for (size_t i = 0; i < vec.size(); ++i) {
// something that does not alter i
}
在这个循环中,0 <= i < vec.size()
是一个不变量,它保证vec[i]
始终是一个有效的表达式。
类也可以有不变量,例如,如果您考虑std::string
,其size()
方法返回其缓冲区中的字符数。总是
现在,假设您编写自己的字符串类:
// Invariant: size represents the number of characters in data.
struct String {
size_t size;
char* data;
};
记录你希望的是不变的,这是很好的,但我完全可以做到:
void reset(String& str) {
delete str.data;
str.data = 0;
}
并忘记重置str.size
,因此违反了不变。
但是,如果你收起班级成员:
// Invariant: size() returns the number of characters accessible via data()
class String {
public:
size_t size() const { return _size; }
char const* data() const { return _data; }
// methods which maintain the invariant
private:
size_t _size;
char* _data;
};
现在只有你可以违反不变量。因此,如果出现错误,您需要更少的代码进行审核。
<强> 2。实施改变保温
背后的想法是,您应该能够切换信息的内部表示,而无需调整类的用户。例如:
class Employee {
public:
std::string const& name() const { return _name; } // Bad
private:
std::string _name;
}; // class Employee
现在,如果我发现std::string
不是名称的合适表示(例如,我需要宽字符):
class Employee {
public:
std::string const& name() const { return _name; } // error!
private:
std::basic_string<char32_t> _name;
}; // class Employee
我被困住了。我不能再返回std::string const&
(我再也没有内部std::string
)。我可以更改 name()
的返回来制作副本:
std::string Employee::name() const { return encodeUtf8(_name); }
不幸的是,它仍可能破坏客户:
std::string const& name(Employee const& e) {
std::string const& n = e.name(); // Bind temporary variable to const&
return n; // Returns reference to local variable!!
}
如果Employee
从一开始设计 std::string name() const
,那么我们可以毫无问题地进行更改。
注意:在实际使用中,您必须考虑使用绝缘材料来制作外部API,但内部API可能会完美地暴露数据表示......当更改时,您需要更多的更改制成。
答案 2 :(得分:2)
@Kepani的优秀解释。我想解释http://www.tutorialspoint.com/cplusplus/cpp_data_encapsulation.htm
的新陈述因此,如果任何外部实体试图访问或更改类变量中的任何内容,那么它们在不知不觉中可能会损害其中包含的数据。因此,简单来说,我们基本上创建了一个组并限制其使用和访问。
就像汽车内部装置只能驾驶汽车一样,你只需要驾驶汽车,不要进入汽车内部并让车轮运转或使用任何外部元件来移动车轮,因为它们可能不同步并损坏它
答案 3 :(得分:0)
关于封装的要点不在于&#34;您无法以任何方式更改私人数据&#34;而是私有数据的存储本身被隐藏。例如:
class Employee
{
...
};
class Company
{
public:
std::vector<Employee> employees;
...
};
现在,员工队伍可以很好地为拥有少量员工的公司工作,但如果公司不断发展,可能会因为搜索速度慢而导致问题[在现实中,可能不会! ]。但是,由于我们已经公开了std::vector<Employee>
,我们无法改变公司内部存储的类型(没有破坏其余代码的风险)。如果employees
是私有的,我们可以将其更改为对应用程序有意义的任何其他类型。
如果我们改为:
class Company
{
private:
std::vector<Employee> employees;
...
};
我们可以轻松将其更改为:
class Company
{
private:
std::map<std::string, Employee> employees;
};
现在,std::map
是&#34;树&#34;,可以在log2(n)步骤中搜索n个元素,其中向量平均需要(n / 2)次搜索 - 如果我们拥有10000名员工,在大约16步和5000步之间找到正确的东西是有区别的。如果该公司增长到10万名员工,那么该向量平均需要45000步,而在地图中只需另外3步即可找到正确的步数。
重点是&#34;我们可以改变数据的存储方式,并控制数据的访问方式&#34;。
答案 4 :(得分:0)
举个例子,假设我们有一个非常简单的字符串类:
struct String
{
char * data = nullptr;
size_t size = 0;
void resize(size_t new_size) {
data = realloc(data, new_size);
size = new_size;
}
char & at(size_t i) {
if (i >= size) throw std::range_error();
return data[i];
}
};
我们可以看到这个类有几个不变量:
data
必须指向由malloc
和朋友分配的内存; size
成员必须与分配的大小匹配。没有封装,很容易打破不变量:
String s;
s.size = 42;
s[10] = 'X'; // BOOM! out-of-range access
s.data = "Hello!";
s.resize(3); // BOOM! tries to reallocate static memory
通过将数据成员设为私有,我们可以防止人们随意更改数据;它们只能通过公共接口进行更改,我们会仔细实施以维护不变量。
对于奖励积分,您可以考虑如何在我的示例中正确修复内存泄漏;但这有点超出了这个问题的范围。