6

我只是......我不完全确定我理解封装。也许这可能与我仍在课堂上学习编程并且没有做出任何类似的事实有关。真实世界的程序可供其他人使用,但我只是不明白它试图完成什么. 我知道它限制了对某些类的成员和函数的访问。但是就像..限制谁?我见过几个例子,他们有一个私有数据成员,但他们有公共的 get 或 set 方法,允许无论如何操作数据成员。那么,有什么限制或隐藏的呢?

我的书是这样说的:

" 封装提供了两个重要的优势:

  1. 用户代码不能无意中破坏封装对象的状态。
  2. 封装类的实现可以随时间而改变,而无需更改用户级代码。"

我想我对他们使用的词感到困惑。或者有人可以给我一个例子,说明用户代码如何可能破坏对象的状态?

我知道我的问题到处都是,但在考虑封装时我的想法也是如此,所以我很难封装我对它的所有想法(..lol)

4

5 回答 5

14

我最喜欢的封装示例是驾驶汽车。

典型的驾驶员知道如何通过打开点火装置并踩下油门来使汽车向前行驶。他们不需要知道任何关于内燃机燃烧的知识就可以每天早上上班。

油门踏板为操作非常复杂的机器提供了一个非常简单的界面。这意味着,真正复杂的内部细节是从驱动程序中封装的。

现在,就代码而言,假设您想使用Map某种类型的 a,但您不知道如何为您的键编写通用散列函数,或者如何实现任何其他底层细节。

在 Java 中,您可以简单地使用HashMap,而不必担心标准库在下面做什么。这些细节是从用户那里封装的。

于 2013-10-23T06:32:07.477 回答
3

让我们分别处理这两点。

1. 类不变量

在编程中,当你有不变量时,推理会变得更容易。例如,您可能已经听说过循环不变量:

for (size_t i = 0; i < vec.size(); ++i) {
    // something that does not alter i
}

在这个循环中,0 <= i < vec.size()是一个不变量,它保证它vec[i]始终是一个有效的表达式。

类也可以有不变量,例如,如果您考虑std::stringsize()方法返回其缓冲区中的字符数。总是。

现在,假设您编写自己的字符串类:

// 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 可能会完美地暴露数据表示......以在进行更改时在您的盘子上进行更多更改为代价。

于 2013-10-23T07:06:00.430 回答
2

@Kepani 的出色解释。我只想解释来自http://www.tutorialspoint.com/cplusplus/cpp_data_encapsulation.htm的新声明

封装是一个面向对象的编程概念,它将数据和操作数据的函数绑定在一起,并确保两者免受外部干扰和误用。

因此,如果任何外部实体试图访问或更改类变量中的任何内容,那么它们可能会在不知不觉中损害其中包含的数据。所以简单来说,我们基本上是创建一个组并限制它的使用和访问。

就像汽车内部部件只能运行汽车一样,您只需要驾驶它,而不是进入其中使车轮运转或使用任何外部元件来移动车轮,因为它们可能不同步并损坏它。

于 2013-10-23T06:41:54.333 回答
0

封装的重点不是“你不能以任何方式改变私有数据”,而是私有数据的存储本身是隐藏的。例如:

class Employee
{
  ... 
};

class Company
{
  public:
   std::vector<Employee> employees;
   ... 
};

现在,Employees 向量可能很好地适用于员工人数较少的公司,但如果公司继续发展,它可能会导致问题,因为搜索速度很慢[在现实中,可能不会!]。但是,由于我们已经暴露了std::vector<Employee>,我们无法更改 Company 内部的存储类型(没有破坏其余代码的风险)。如果employees是私有的,我们可以将其更改为对应用程序有意义的任何其他类型。

如果我们改为:

class Company
{
   private:
     std::vector<Employee> employees;
     ...
};

我们可以轻松地将其更改为:

class Company
{
    private:
      std::map<std::string, Employee> employees;
};

现在,std::map是一棵“树”,可以在 log2(n) 步中搜索 n 个元素,其中一个向量平均需要 (n/2) 次搜索 - 如果我们有 10000 名员工,那大约是 16 步和 5000 步之间的差异步骤来找到正确的东西。如果公司发展到 100000 名员工,向量将平均再走 45000 步,但只需要再走 3 步即可在地图中找到正确的步数。

重点是“我们可以改变数据的存储方式,并控制数据的访问方式”。

于 2013-10-23T06:49:09.037 回答
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

通过将数据成员设为私有,我们可以防止人们随意更改它们;它们只能通过公共接口进行更改,我们仔细实施以维护不变量。

对于奖励积分,您可以考虑如何在我的示例中正确修复内存泄漏;但这有点超出了这个问题的范围。

于 2013-10-23T06:57:50.187 回答