数据抽象
如果你正在设计一个栈,目前这个栈只存放int数据,那么你会如何设计它呢?其实栈的结构并不复杂,我们在头脑中就能建立一个栈的模型,当然我们在纸上把它的结构画出来也许会更好一点:
这张图就是一个以数组实现的栈的模型,栈底的索引是0,Pointer是栈顶指针,它指向索引5这个位置,说明现在栈里刚好有5个元素,当Pointer指向索引0时,栈为空,表示栈里已经没有元素了,当Pointer指向索引9时(这个位置不属于我们,因为它超出了范围,而且在图里也没有,但可以猜到这个位置在索引8的上一个位置,在这里我们并没有使用这块内存,仅仅只是当做一个标致),表示栈已经满了(当然我们可以动态增加,比如利用realloc)。
一个栈我想应该实现这些功能吧:
1、入栈(Push):把一个元素添加到栈顶的位置,并使栈顶指针向上移动一个位置。
2、出栈(Pop):把一个元素从栈顶取出,并使栈顶指针向下移动一个位置。
3、获得栈顶元素(Peek):把一个元素从栈顶取出,并不移动栈顶指针的位置。
注意:入栈时要检查栈是否已经满了,如果栈已经满了,输出信息提示用户,并不做任何操作。出栈和获得栈顶元素时要检查栈是否为空,如果栈为空,输出信息提示用户,不做任何操作并返回0(返回0的原因是大多时候它并不会造成一些错误,而且测试条件时也为假)。
可能还需要这样一些功能:
1、获得栈元素的数量(GetSize):这个很简单,我们只需要返回栈顶指针对应的索引值就行了,它刚好是栈元素的数量。
2、获得栈底指针(GetBottomPointer):返回这个数组的名字,它代表这个栈的底指针。
3、获得栈顶指针(GetTopPointer):返回栈顶指针即可。
2和3可以用在遍历栈时用到,比如:
程序代码:int* bPointer = GetBottomPointer(&stack);
int* tPointer = GetTopPointer(&stack);
while (bPointer != tPointer) // (1)
printf("%d", *bPointer++);
// or
while (tPointer != bPointer) // (2)
printf("%d", *--tPointer);
(1)可以从栈底到栈顶遍历这个栈。(2)可以从栈顶到栈底遍历这个栈。
看来一共有6个函数:
1、void Push(Stack*)。
2、int Pop(Stack*)。
3、int Peek(Stack*)。
4、int GetSize(Stack*)。
5、int* GetTopPointer(Stack*)。
6、int* GetBottomPointer(Stack*)。
好了,可以写代码了:
程序代码:/**
* Stack.h
* Code by Kenier Lee.
*/
#ifndef _KENIER_LEE_STACK_H
#define _KENIER_LEE_STACK_H
#define STACK_SIZE 1024 /* Size of the Stack */
typedef struct StackTag Stack; /* Prepare type definition*/
struct StackTag {
int storage[STACK_SIZE]; /* Array of int */
int* tp; /* Top pointer */
};
void Push(Stack*, int);
int Pop(Stack*);
int Peek(Stack*);
int GetSize(Stack*);
int* GetTopPointer(Stack*);
int* GetBottomPointer(Stack*);
#endif /* _KENIER_LEE_STACK_H */这里栈的大小是1024,Stack里有一个字段是tp,它表示栈顶指针。
程序代码:/**
* Stack.c
* Code by Kenier Lee.
*/
#include "Stack.h"
#include <stdio.h>
void InitStack(Stack* stack) {
stack-> tp = stack->storage;
}
void Push(Stack* stack, int value) {
int size = GetSize(stack);
if (size == STACK_SIZE) /* Full */
printf("Error: The stack is full.\n");
else
*stack->tp++ = value;
}
int Pop(Stack* stack) {
int value = 0;
if (stack->tp == stack->storage) /* Empty */
printf("Error: The stack is empty.\n");
else
value = *--stack->tp;
return value;
}
int Peek(Stack* stack) {
int value = 0;
if (stack->tp == stack->storage) /* Empty */
printf("Error: The stack is empty.\n");
else
value = *stack->tp;
return value;
}
int GetSize(Stack* stack) {
return stack->tp - stack->storage;
}
int* GetTopPointer(Stack* stack) {
return stack->tp;
}
int* GetBottomPointer(Stack* stack) {
return stack->storage;
}下面是测试代码,记得链接Stack.o
程序代码:/**
* StackTest.c
* Code by Kenier Lee.
*/
#include "Stack.h"
#include <stdio.h>
int main(void) {
Stack stack;
int i, *tPointer, *bPointer;
InitStack(&stack);
for (i = 0; i < 10; ++i)
Push(&stack, i);
for (i = 0; i < 10; ++i)
printf("%d ", Pop(&stack));
printf("\n");
for (i = 0; i < 10; ++i)
Push(&stack, i);
printf("Sizeof stack: %d\n", GetSize(&stack));
tPointer = GetTopPointer(&stack);
bPointer = GetBottomPointer(&stack);
while (bPointer != tPointer)
printf("%d ", *bPointer++);
printf("\n");
bPointer = GetBottomPointer(&stack);
while (tPointer != bPointer)
printf("%d ", *--tPointer);
printf("\n");
for (i = 0; i < 10; ++i)
printf("%d ", Pop(&stack));
printf("\n");
printf("%d\n", Pop(&stack));
printf("%d\n", Peek(&stack));
return 0;
} /* Output:
9 8 7 6 5 4 3 2 1 0
Sizeof stack: 10
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0
Error: The stack is empty.
0
Error: The stack is empty.
0
*/
这样的函数调用:InitStack(&stack);
Push(&stack, i);
Pop(&stack);
Peek(&stack);
GetSize(&stack);
GetTopPointer(&stack);
GetBottomPointer(&stack);
每个函数调用都需要传递Stack结构体的指针,更危险的是我们可以随意修改Stack里的所有字段,比如我们修改的tp字段的值,这样的修改显然是致命的,而我们无法阻止这些操作。在最开始调用任何函数之前必须先调用InitStack函数,不然可能会出现一些对话框,提示该内存不能为"write"或"read"(在Linux下面是段错误),这表示我们使用了不属于我们程序的内存,因为未初始化的tp的值是未知的。最后我们来看看Stack的C++版本:
程序代码:/**
* Stack.h
* Code by Kenier Lee.
*/
#ifndef _KENIER_LEE_STACK_H
#define _KENIER_LEE_STACK_H
class Stack {
public:
enum { STACK_SIZE = 1024 }; // Size of the Stack
Stack();
void push(int);
int pop();
int peek();
int getSize();
int* getTopPointer();
int* getBottomPointer();
private:
int storage[STACK_SIZE]; // Array of int
int* tp; // Top pointer
};
#endif // _KENIER_LEE_STACK_H不需要typedef,我们也不能直接修改tp的值了,因为它是私有的,我们能使用的只有从public:到private:之前的名字。
程序代码:/**
* Stack.cpp
* Code by Kenier Lee.
*/
#include "Stack.h"
#include <iostream>
using namespace std;
Stack::Stack() {
tp = storage;
}
void Stack::push(int value) {
int size = getSize();
if (size == STACK_SIZE) /* Full */
cout << "Error: The stack is full." << endl;
else
*tp++ = value;
}
int Stack::pop() {
int value = 0;
if (tp == storage) /* Empty */
cout << "Error: The stack is empty." << endl;
else
value = *--tp;
return value;
}
int Stack::peek() {
int value = 0;
if (tp == storage) /* Empty */
cout << "Error: The stack is empty." << endl;
else
value = *tp;
return value;
}
int Stack::getSize() {
return tp - storage;
}
int* Stack::getTopPointer() {
return tp;
}
int* Stack::getBottomPointer() {
return storage;
}这是定义之前声明的那些成员函数,其中还有一个构造函数Stack(),它与类名相同,在创建Stack对象时它将被自动调用,它可以替代InitStack,而且比InitStack好,因为不用担心它不会被调用。
程序代码:/**
* StackTest.cpp
* Code by Kenier Lee.
*/
#include "Stack.h"
#include <iostream>
using namespace std;
int main() {
Stack stack; // Constructor called
int i, *tPointer, *bPointer;
for (i = 0; i < 10; ++i)
stack.push(i);
for (i = 0; i < 10; ++i)
cout << stack.pop() << " ";
cout << endl;
for (i = 0; i < 10; ++i)
stack.push(i);
cout << "Sizeof stack: " << stack.getSize() << endl;
tPointer = stack.getTopPointer();
bPointer = stack.getBottomPointer();
while (bPointer != tPointer)
cout << *bPointer++ << " ";
cout << endl;
bPointer = stack.getBottomPointer();
while (tPointer != bPointer)
cout << *--tPointer << " ";
cout << endl;
for (i = 0; i < 10; ++i)
cout << stack.pop() << " ";
cout << endl;
cout << stack.pop() << endl;
cout << stack.peek() << endl;
} /* Output:
9 8 7 6 5 4 3 2 1 0
Sizeof stack: 10
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0
Error: The stack is empty.
0
Error: The stack is empty.
0
*/
现在我们又看到了这样的调用:stack.push(i);
stack.pop();
stack.peek();
stack.getSize();
stack.getTopPointer();
stack.getBottomPointer();
我感觉这样要清晰得多,很容易看出这些操作是指对于那个对象的,并且如果你现在写这样的代码:stack.tp++ 或 stack.tp = 0 ... 等等修改tp字段值的操作都会被编译器看作是错误,因为tp被声明为私有的(只能在这个类的成员函数中使用),这样就安全多了。Stack stack;这句话被执行的时候,该类的构造函数Stack()就自动调用了,它完成了初始化的工作,不用担心初始化会被遗忘。
C++提供了一种,把函数与自定义数据类型绑定在一起的特性,这被称做数据抽象,而且上面的代码量明显要比前一个C版本的要少,语法更清楚,数据抽象的用途远不止这些,更多的特性,如果以后我有时间再与大家一一分享吧!
不知道看了这些代码之后同鞋们有没有兴趣去学习 C Plus Plus呢?
[ 本帖最后由 lz1091914999 于 2012-2-6 22:10 编辑 ]








