請特別關注程序設計技術,而不是各種語言特征。
--《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++中,通常是該類型對象的引用或者指針,才能實現多態)。