本文以实例形式详细讲述了C语言接口与实现方法,对于深入掌握C语言程序设计有一定的借鉴价值。分享给大家供大家参考。具体分析如下:
一般来说,一个模块有两部分组成:接口和实现。接口指明模块要做什么,它声明了使用该模块的代码可用的标识符、类型和例程,实现指明模块是如何完成其接口声明的目标的,一个给定的模块通常只有一个接口,但是可能会有许多种实现能够提供接口所指定的功能。每个实现可能使用不同的算法和数据结构,但是它们都必须符合接口所给出的使用说明。客户调用程序是使用某个模块的一段代码,客户调用程序导入接口,而实现导出接口。由于多个客户调用程序是共享接口和实现的,因此使用实现的目标代码避免了不必要的代码重复,同时也有助于避免错误,因为接口和实现只需一次编写和调试就可多次使用。
接口
接口只需要指明客户调用程序可能使用的标识符即可,应尽可能地隐藏一些无关的表示细节和算法,这样客户调用程序可以不必依赖于特定的实现细节。这种客户调用程序和实现之间的依赖--耦合----可能会在实现改变时引起错误,当这种依赖性埋藏在一些关于实现隐藏的或是不明确的假设中时,这些错误可能很难修复,因此一个设计良好且描述精确的接口应该尽量减少耦合。
C语言对接口和实现的分离只提供最基本的支持,但是简单的约定能给接口/实现方法论带来巨大的好处。在C中,接口在头文件声明,头文件声明了客户调用程序可以使用的宏、类型、数据结构、变量以及例程。用户使用C语言的预处理指令#include导入接口。
下面的例子说明了本篇文章的接口中所使用的一些约定、接口:
extern int Arith_max(int x, int y); extern int Arith_min(int x, int y); extern int Arith_div(int x, int y); extern int Arith_mod(int x, int y); extern int Arith_ceiling(int x, int y); extern int Arith_floor (int x, int y);
该接口的名字为Arith,接口头文件也相应地命名为arith.h,接口的名字以前缀的形式出现在接口的每个标识符中。模块名不仅提供了合适的前缀,而且还有助于整理客户调用程序代码。
Arith接口还提供了一些标准C函数库中没有但是很有用的函数,并为出发和取模提供了良好的定义,而标准C中并没有给出这些操作的定义和只提供基于实现的定义。
实现
一个实现导出一个接口,它定义了必要的变量和函数以提供接口所规定的功能,在C语言中,一个实现是由一个或多个.c文件提供的,一个实现必须提供其导出的接口所指定的功能。实现应包含接口的.h文件,以保证它的定义和接口的声明时一致的。
Arith_min和Arith_max返回其整型参数中的最小值和最大值:
int Arith_max(int x, int y) { return x > y ? x : y; } int Arith_min(int x, int y) { return x > y ? y : x; }
Arith_div返回y除以x得到的商,Arith_mod返回相应的余数。当x与y同号的时候,Arith_div(x,y)等价于x/y,Arith_mod(x,y)等价于x%y
当x与y的符号不同的时候,C的内嵌操作的返回值就取决于具体的实现:
如果-13/5=2,-13%5=-3,如果-13/5=-3,-13%5=2
标准库函数总是向零取整,因此div(-13,2)=-2,Arith_div和Arith_mod的语义同样定义好了:它们总是趋近数轴的左侧取整,因此Arith_div(-13,5)=-3,Arith_div(x,y)是不超过实数z的最大整数,其中z满足z*y=x。
Arith_mod(x,y)被定义为x-y*Arith_div(x,y)。因此Arith_mod(-13,5)=-13-5*(-3)=2
函数Arith_ceiling和Arith_floor遵循类似的约定,Arith_ceiling(x,y)返回不小于实数商x/y的最小整数
Arith_floor(x,y)返回不超过实数商x/y的最大整数
完整实现代码如下:
#include "arith.h" int Arith_max(int x, int y) { return x > y ? x : y; } int Arith_min(int x, int y) { return x > y ? y : x; } int Arith_div(int x, int y) { if (-13/5 == -2 && (x < 0) != (y < 0) && x%y != 0) return x/y - 1; else return x/y; } int Arith_mod(int x, int y) { if (-13/5 == -2 && (x < 0) != (y < 0) && x%y != 0) return x%y + y; else return x%y; } int Arith_floor(int x, int y) { return Arith_div(x, y); } int Arith_ceiling(int x, int y) { return Arith_div(x, y) + (x%y != 0); }
抽象数据类型
抽象数据类型(abstract data type,ADT)是一个定义了数据类型以及基于该类型值提供的各种操作的接口
一个高级类型是抽象的,因为接口隐藏了它的表示细节,以免客户调用程序依赖这些细节。下面是一个抽象数据类型(ADT)的规范化例子--堆栈,它定义了该类型以及五种操作:
#ifndef STACK_INCLUDED #define STACK_INCLUDED #define T Stack_T typedef struct T *T; extern T Stack_new (void); extern int Stack_empty(T stk); extern void Stack_push (T stk, void *x); extern void *Stack_pop (T stk); extern void Stack_free (T *stk); #undef T #endif
实现
包含相关头文件:
#include <stddef.h> #include "assert.h" #include "mem.h" #include "stack.h" #define T Stack_T
Stack_T的内部是一个结构,该结构有个字段指向一个栈内指针的链表以及一个这些指针的计数:
struct T { int count; struct elem { void *x; struct elem *link; } *head; };
Stack_new分配并初始化一个新的T:
T Stack_new(void) { T stk; NEW(stk); stk->count = 0; stk->head = NULL; return stk; }
其中NEW是一个另一个接口中的一个分配宏指令。NEW(p)将分配该结构的一个实例,并将其指针赋给p,因此Stack_new中使用它就可以分配一个新的Stack_T
当count=0时,Stack_empty返回1,否则返回0:
int Stack_empty(T stk) { assert(stk); return stk->count == 0; }
assert(stk)实现了可检查的运行期错误,它禁止空指针传给Stack中的任何函数。
Stack_push和Stack_pop从stk->head所指向的链表的头部添加或移出元素:
void Stack_push(T stk, void *x) { struct elem *t; assert(stk); NEW(t); t->x = x; t->link = stk->head; stk->head = t; stk->count++; } void *Stack_pop(T stk) { void *x; struct elem *t; assert(stk); assert(stk->count > 0); t = stk->head; stk->head = t->link; stk->count--; x = t->x; FREE(t); return x; }
FREE是另一个接口中定义的释放宏指令,它释放指针参数所指向的空间,然后将参数设为空指针
void Stack_free(T *stk) { struct elem *t, *u; assert(stk && *stk); for (t = (*stk)->head; t; t = u) { u = t->link; FREE(t); } FREE(*stk); }
完整实现代码如下:
#include <stddef.h> #include "assert.h" #include "mem.h" #include "stack.h" #define T Stack_T struct T { int count; struct elem { void *x; struct elem *link; } *head; }; T Stack_new(void) { T stk; NEW(stk); stk->count = 0; stk->head = NULL; return stk; } int Stack_empty(T stk) { assert(stk); return stk->count == 0; } void Stack_push(T stk, void *x) { struct elem *t; assert(stk); NEW(t); t->x = x; t->link = stk->head; stk->head = t; stk->count++; } void *Stack_pop(T stk) { void *x; struct elem *t; assert(stk); assert(stk->count > 0); t = stk->head; stk->head = t->link; stk->count--; x = t->x; FREE(t); return x; } void Stack_free(T *stk) { struct elem *t, *u; assert(stk && *stk); for (t = (*stk)->head; t; t = u) { u = t->link; FREE(t); } FREE(*stk); }
相信本文所述对大家的C程序设计有一定的借鉴价值。
本文向大家介绍C语言栈的表示与实现实例详解,包括了C语言栈的表示与实现实例详解的使用技巧和注意事项,需要的朋友参考一下 1.基本概念: C语言的栈是指限定仅在表尾进行插入和删除操作的线性表。 栈作为C语言中一种常用的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一
本文向大家介绍C#接口实现方法实例分析,包括了C#接口实现方法实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#接口实现方法。分享给大家供大家参考。具体如下: 在讲解C#实现接口的实例解析之前我们来看看C#接口的定义,如果一个类派生于一个接口,它就会执行某些函数。并不是所有的面向对象语言都支持接口,所以本节将详细介绍C#实现接口的问题。 C#实现接口注意: 熟悉COM的开发人员应
本文向大家介绍C语言单链表实现方法详解,包括了C语言单链表实现方法详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C语言单链表实现方法。分享给大家供大家参考,具体如下: slist.h slist.cpp main.cpp 附:单链表优化版本 slist.h slist.cpp main.cpp 希望本文所述对大家C语言程序设计有所帮助。
本文向大家介绍Go语言接口用法实例,包括了Go语言接口用法实例的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了Go语言接口用法。分享给大家供大家参考。具体分析如下: 接口类型是由一组方法定义的集合。 接口类型的值可以存放实现这些方法的任何值。 希望本文所述对大家的Go语言程序设计有所帮助。
本文向大家介绍C语言双向链表的表示与实现实例详解,包括了C语言双向链表的表示与实现实例详解的使用技巧和注意事项,需要的朋友参考一下 1.概述: C语言中一种更复杂的链表是“双向链表”或“双面链表”。其表中的每个节点有两个连接:一个指向前一个节点,(当这个“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当这个“连接”为最后一个“连接”时,指向空值或者空列表) 一个双向链表
本文向大家介绍C语言单链队列的表示与实现实例详解,包括了C语言单链队列的表示与实现实例详解的使用技巧和注意事项,需要的朋友参考一下 1.概述: C语言的队列(queue),是指先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。 而单链队列使用链表作为基本数据