首页 > 代码库 > 对数据类型封装和数据抽象的简单理解

对数据类型封装和数据抽象的简单理解

  请特别关注程序设计技术,而不是各种语言特征。

                        --《C++程序设计语言》 Bjarne Stroustrup


  本文是《C++程序设计语言》(Bjarne Stroustrup )的第二章的读书笔记,例子来源于这本书的第二章。
  在程序设计之中,我们倾向于将数据结构(也可以说是数据类型)以及一组对其操作的相关过程组织在一起,在逻辑上可以称将其为模块。此时程序分为一些模块,模块包括一组对数据的操作,数据隐藏于模块之中。以下以栈的设计为例,使用C和C++进行设计,简单理解模块化设计中的数据封装和数据抽象。
  对于C 语言我们可以对栈这一数据类型进行如下简单的设计:

typedef struct Stack {  int elem[MAX_SIZE];  int top;} Stack;Stack* createStack();void destroyStack(Stack*);void push(Stack*,int);int pop(Stack*);

  由于struct结构可以自由的有外界进行访问,因此可能对数据进行破坏。我们可以引入类似于win32编程使用到的句柄的设计。即:

typedef void* HStack;HStack createStack();void destroyStack(HStack hStack);void push(HStack hStatck,int) {     struct Stack* stack = (struct Stack*)hStack;     ........ }int pop(HStack hStack){     struct Stack* stack = (struct Stack*)hStack;    ........ }

  只有在实现该数据类型相关的操作,才知道Stack的内在结构,可以进行强制到转换。因此该模块的用户无需知道struct Stack的结构,改变struct Stack结构也不会影响到API用户的使用。

客户端代码:

void f(){    HStack h= createStack();    push(h,2);    int i = pop(h);    destroyStack(h);}

  因此C语言在进行类似的数据类型封装时,通常提供的一组操作中通常包含了初始化和回收的操作,通常需要用户有意识的进行调用。这让这一数据类型有点“伪类型”的感觉,C语言的特征无法让封装的数据类型进行自动的初始化和销毁。

  C++提供类这一用户自定义类型对数据类型封装进行支持。在进行程序设计时,通过确定需要哪些类型,为每个类型提供完整的操作。对栈的定义如下:

class Stack{public:    Stack(int max_size);    ~Stack();    void push(int);    int push();private:    int* m_elem;    int m_top;    int m_max_size;};

  构造函数Stack(int) 在建立这个类的对象时被调用,处理初始化问题。如果该类的一个对象出了其作用域,进行某些清理的时候,通过调用其析构函数。

  像上面的Stack这种类型的定义,我们可以称为就具体类型,涉及到具体的实现。在类型不常改变,或者类型用于局部变量的情况下,这种设计方法足够解决问题。在一些情况下我们希望有抽象类型。抽象类型可以将用户与实现细节隔离,得到更好的灵活性。此时用户面向抽象类型编程,而不是面向具体类型编程。 C++可以通过类的继承和抽象类实现这一方式。如:

class Stack {public:    virtual void push(int) = 0;    virtual int pop(int) = 0;};// 使用数组实现栈class ArrayStack : public Stack {};// 使用链表实现栈class ListStack : public Stack{};// 用户面向抽象类编程:void f(Stack& s){    s.push(2);    int i = s.pop();}

  通过定义Stack这一抽象类(在C++中可以理解为具有纯虚成员函数的类。 如:virtual void push(int) = 0;),为这一抽象类提供不同实现获得灵活性,用户面向这一抽象类编程(在C++中,通常是该类型对象的引用或者指针,才能实现多态)。

 

对数据类型封装和数据抽象的简单理解