在读了《程序设计抽象思想-C语言描述》第八章后,对此思想感触颇深,而且从中还看到了面向对象编程的影子,不禁感叹这些老外的近二十年前的书的经典,感觉为自己灵活,高效驾驭这古老而又现代的C语言指明的方向,废话少说,步入正题。
ADT相关知识这里简略列了一个图表,以供浏览:
这里我们参考此书中的例子,仍然以一个简单的计算器程序为例,即后缀式四则运算计算器。我们运用抽象数据类型,为堆栈定义一个ADT,通过stack.h来定义了堆栈类型的行为和ADT名字,然后通过stack.c实现这个接口,并用实际结构表示出stackADT的底层。这样有了堆栈模型,再实现一个计算器那将是随手拈来,虽然例子简单之至,但它的结果,揭示了此种思想带来的好处,在这古老而又底层的C语言之下,确保了程序的层次化和模块化,使程序既易理解,易维护。
代码示例:
1 /* stack.h --- 2 * 3 * Filename: stack.h 4 * Description:抽象数据类型ADT--堆栈 5 * Author: magc 6 * Maintainer: 7 * Created: 四 8月 9 08:59:34 2012 (+0800) 8 * Version: 9 * Last-Updated: 四 8月 9 17:04:55 2012 (+0800) 10 * By: magc 11 * Update #: 52 12 * URL: 13 * Keywords: 14 * Compatibility: 15 * 16 */ 17 18 /* Commentary: 19 * ADT 接口 20 * 只定义行为,而不限制底层表示和实现 21 * 22 */ 23 24 /* Change Log: 25 * 26 * 27 */ 28 29 /* Code: */ 30 #ifndef _stack_h 31 #define _stack_h 32 #include "../commonlib/genlib.h" 33 34 35 36 typedef struct stackCDT *stackADT; 37 38 typedef int stackElement; 39 40 /************************************************************************* 41 *功能描述:创建新堆栈 42 *参数列表: 43 *返回类型:堆栈指针 44 **************************************************************************/ 45 stackADT NewStack(void); 46 47 /************************************************************************* 48 *功能描述:释放堆栈内存空间 49 *参数列表: 50 *返回类型: 51 **************************************************************************/ 52 void FreeStack(stackADT stack); 53 54 /************************************************************************* 55 *功能描述:压栈 56 *参数列表: 57 *返回类型: 58 **************************************************************************/ 59 void Push(stackADT stack ,stackElement element ); 60 61 /************************************************************************* 62 *功能描述:出栈 63 *参数列表: 64 *返回类型:弹出的元素 65 **************************************************************************/ 66 stackElement Pop(stackADT stack); 67 68 /************************************************************************* 69 *功能描述:判断当前栈是否为空 70 *参数列表: 71 *返回类型:TRUE or FALSE 72 **************************************************************************/ 73 bool StackIsEmpty(stackADT stack ); 74 75 /************************************************************************* 76 *功能描述:判断当前栈是否已满 77 *参数列表: 78 *返回类型: 79 **************************************************************************/ 80 bool StackIsFull(stackADT stack); 81 82 /************************************************************************* 83 *功能描述:获取当前栈的深度 84 *参数列表: 85 *返回类型: 86 **************************************************************************/ 87 int StackDepth(stackADT stack); 88 89 /************************************************************************* 90 *功能描述:读取堆栈顶端元素值 91 *参数列表: 92 *返回类型: 93 **************************************************************************/ 94 int StackTop(stackADT stack); 95 96 /************************************************************************* 97 *功能描述:打印堆栈元素的内容 98 *参数列表: 99 *返回类型: 100 **************************************************************************/ 101 void StackPrint(stackADT stack); 102 103 #endif 104 /* stack.h ends here */
堆栈ADT的实现代码:
1 /* stack.c --- 2 * 3 * Filename: stack.c 4 * Description:堆栈ADT的实现 5 * Author: magc 6 * Maintainer: 7 * Created: 四 8月 9 10:31:03 2012 (+0800) 8 * Version: 9 * Last-Updated: 四 8月 9 17:12:57 2012 (+0800) 10 * By: magc 11 * Update #: 108 12 * URL: 13 * Keywords: 14 * Compatibility: 15 * 16 */ 17 18 /* Commentary: 19 * 20 * 21 * 22 */ 23 24 /* Change Log: 25 * 26 * 27 */ 28 29 /* Code: */ 30 #include <assert.h> 31 #include <ctype.h> 32 #include <errno.h> 33 #include <limits.h> 34 #include <string.h> 35 #include <stdarg.h> 36 #include <stdlib.h> 37 #include <stdio.h> 38 #include "stack.h" 39 #define MaxStackSize 100 40 41 struct stackCDT { 42 stackElement elements[MaxStackSize]; 43 int count; 44 }; 45 46 47 stackADT NewStack(void){ 48 stackADT stack = New(stackADT); 49 stack->count = 0; 50 // stack->elements 51 return stack; 52 } 53 54 void FreeStack(stackADT stack){ 55 FreeBlock(stack); 56 57 } 58 59 void Push(stackADT stack , stackElement element){ 60 if(StackIsFull(stack)) Error("the stack is full!\n"); 61 stack->elements[stack->count++] = element; 62 } 63 64 stackElement Pop(stackADT stack ){ 65 if(StackIsEmpty(stack)) Error("the stack is empty!\n"); 66 67 return stack->elements[--stack->count]; 68 } 69 70 bool StackIsFull(stackADT stack){ 71 return (stack->count == MaxStackSize); 72 73 } 74 bool StackIsEmpty(stackADT stack){ 75 return (stack->count == 0); 76 77 } 78 int StackDepth(stackADT stack){ 79 return (stack->count); 80 81 } 82 83 int StackTop(stackADT stack){ 84 return (stack->elements[stack->count-1]); 85 86 } 87 88 void StackPrint(stackADT stack){ 89 90 if(StackIsEmpty(stack)) Error("the stack is empty !\n"); 91 int i; 92 printf("the elements of stack is :\n"); 93 94 for (i = (stack->count-1); i > -1; i--) { 95 printf("%d,",stack->elements[i]); 96 97 } 98 } 99 100 /* stack.c ends here */
注:
1)在stack.h只定义了堆栈抽象类型的名字,而它的实际结果交给stack.c来实现和定义(stackCDT在stack.h中叫不完全类型),所以这里定义了stackCDT的结构,但这并不影响stack.h对堆栈的行为定义。
2)在这个实现中采用了数组形式保存堆栈中的元素。同样也可以通过链表形式来实现对堆栈元素的存储,这就是stack.h接口带来的好处,只看重行为,而不管底层实现,将这个工作交给接口的实现者,同时又对接口的调用者来说可以屏蔽,
计算器的接口:
1 /* SimpleCal.h --- 2 * 3 * Filename: SimpleCal.h 4 * Description: 简单计算器的接口 5 * Author: magc 6 * Maintainer: 7 * Created: 四 8月 9 16:53:19 2012 (+0800) 8 * Version: 9 * Last-Updated: 五 8月 10 11:17:30 2012 (+0800) 10 * By: magc 11 * Update #: 18 12 * URL: 13 * Keywords: 14 * Compatibility: 15 * 16 */ 17 18 /* Code: */ 19 #include "../commonlib/genlib.h" 20 21 void CalRun(); 22 23 int CalOperation(stackADT stack ,char oper ); 24 25 26 27 28 /* SimpleCal.h ends here */
计算器的实现:
1 /* SimpleCal.c --- 2 * 3 * Filename: SimpleCal.c 4 * Description: 简单后缀式四则运算计算器 5 * Author: magc 6 * Maintainer: 7 * Created: 四 8月 9 17:21:42 2012 (+0800) 8 * Version: 9 * Last-Updated: 五 8月 10 10:19:14 2012 (+0800) 10 * By: magc 11 * Update #: 118 12 * URL: 13 * Keywords: 14 * Compatibility: 15 * 16 */ 17 18 /* Commentary: 19 * 后缀式四则运算即先输入第一个操作数,再输入第二个操作数,最后输入运算符号,然后输出结果 20 * 原理:借用堆栈的模型,通过实例化堆栈抽象类型,实现操作数的暂存,此程序虽然实际意义不大,但其结构值得参考 21 * 22 */ 23 24 /* Change Log: 25 * 26 * 27 */ 28 29 /* Code: */ 30 #include <assert.h> 31 #include <ctype.h> 32 #include <errno.h> 33 #include <limits.h> 34 #include <string.h> 35 #include <stdarg.h> 36 #include <stdlib.h> 37 #include <stdio.h> 38 #include "../commonlib/genlib.h" 39 #include "stack.h" 40 #include "SimpleCal.h" 41 42 #define TRUE 1 43 44 45 int main(int argc, char * argv[]) 46 { 47 CalRun(); 48 return 0; 49 } 50 /************************************************************************* 51 *功能描述:启动运算进程 52 *参数列表: 53 *返回类型: 54 **************************************************************************/ 55 void CalRun(){ 56 stackADT stack = NewStack(); 57 58 printf("计算器成功启动。\n"); 59 stackElement num1,num2; 60 char tag; 61 62 while(TRUE){ 63 printf("请输入第一个操作数:\n"); 64 scanf("%d",&num1); 65 Push(stack,num1); 66 67 printf("请输入第二个操作数:\n"); 68 scanf("%d",&num2); 69 Push(stack,num2); 70 71 printf("请选择操作符(+ - * /): \n"); 72 scanf("%s",&tag); 73 int res = CalOperation(stack,tag); 74 printf("运算结果是%d\n",res); 75 76 printf("继续计算请输入c,退出请输入其它字符\n"); 77 scanf("%s",&tag); 78 if(tag != 'c')return; 79 } 80 81 } 82 /************************************************************************* 83 *功能描述:运算函数 84 *参数列表: 85 *返回类型: 86 **************************************************************************/ 87 int CalOperation(stackADT stack,char oper){ 88 89 int n1,n2,res = 0; 90 n2 = Pop(stack); 91 n1 = Pop(stack); 92 93 switch(oper){ 94 case '+': 95 res = n1 + n2; 96 break; 97 case '-' : 98 res = n1 - n2; 99 break; 100 case '*' : 101 res = n1 * n2; 102 break; 103 case '/' : 104 res = n1 / n2; 105 break; 106 default: 107 res = 0; 108 } 109 return res; 110 111 } 112 113 114 115 /* SimpleCal.c ends here */
注:
1) 写此代码时,我借用了同作者的另一本经典之作《C语言的科学与艺术》中的几个有用的库,如genlib,strlib等,有了这几个库,屏蔽了一些底层的操作,如malloc使用及错误检测,字符串的相关操作更方便。这里我把这部分代码打成一个静态库extend,再写代码直接引用即可,即提高开发效率,又增强程序健壮性,减少重复检测错误等必不可少而又繁琐的代码,使简单封闭后的C语言而简单实用了。相关代码我附在后面附件中了。
2) Makefile文件是我从网上搜索的一个通用的模板,简单设置一下产物,及参数即可使用,详情参考原博客:http://blog.csdn.net/cc_husyand/article/details/6135356
小结:
此例子虽简单,但具备了模块化,层次化开发的特征,好好体会ADT在编程中的威力
后续以火车厢重排问题来继续体会ADT的优势。
附件:
简单后缀式四则运算计算器代码:https://files.cnblogs.com/linux-sir/simple_cal.zip
《C语言的科学与艺术》扩展库代码:https://files.cnblogs.com/linux-sir/commonlib.zip