在《C语言模块化编程》中介绍了C语言模块化编程的一些规则和模板,本文通过使用模块化编程重构C代码的实战,进一步深入理解C模块化编程。
本文地址:http://wuyudong.com/2016/12/05/3136.html,转载请注明出处。
首先看一个完整的C程序:
#include <stdio.h> #include <string.h> int main() { char *strings[128]; char string[256]; char *p1, *p2; int nstrings; int found; int i, j; nstrings = 0; while (fgets(string, 256, stdin)) { for (i = 0; i < nstrings; i++) { found = 1; for (p1 = string, p2 = strings[i]; *p1 && *p2; p1++, p2++) { if (*p1 > *p2) { found = 0; break; } } if (found) break; } for (j = nstrings; j > i; j--) strings[j] = strings[j - 1]; strings[i] = strdup(string); nstrings++; if (nstrings >= 128) break; } for (i = 0; i < nstrings; i++) fprintf(stdout, "%s", strings[i]); return 0; }
初次见到这个代码的时候,你能说出这个程序表示什么意思吗?
如果写成下面这样,会不会好些?
#include <stdio.h> #include <string.h> #define MAX_STRINGS 128 #define MAX_STRING_LENGTH 256 void ReadStrings(char **strings, int *nstrings, int maxstrings, FILE *fp) { char string[MAX_STRING_LENGTH]; *nstrings = 0; while (fgets(string, MAX_STRING_LENGTH, fp)) { strings[(*nstrings)++] = strdup(string); if (*nstrings >= maxstrings) break; } } void WriteStrings(char **strings, int nstrings, FILE *fp) { int i; for (i = 0; i < nstrings; i++) fprintf(fp, "%s", strings[i]); } int CompareStrings(char *string1, char *string2) { char *p1 = string1; char *p2 = string2; while (*p1 && *p2) { if (*p1 < *p2) return -1; else if (*p1 > *p2) return 1; p1++; p2++; } return 0; } void SortStrings(char **strings, int nstrings) { int i, j; for (i = 0; i < nstrings; i++) { for (j = i + 1; j < nstrings; j++) { if (CompareStrings(strings[i], strings[j]) > 0) { char *swap = strings[i]; strings[i] = strings[j]; strings[j] = swap; } } } } int main() { char *strings[MAX_STRINGS]; int nstrings; ReadStrings(strings, &nstrings, MAX_STRINGS, stdin); SortStrings(strings, nstrings); WriteStrings(strings, nstrings, stdout); return 0; }
上面的代码将开始代码中的循环部分拆分成若干函数,这样结构上清晰很多,而且很容易看出代码的作用其实是升序排序所输入的字符串
模块化
将执行分解到模块:
Readstrings、Sortstrings、Writestrings
接口隐藏细节–使变化的影响在局部
为什么后面的代码好?
(1)容易理解
int main() { char *strings[MAX_STRINGS]; int nstrings; ReadStrings(strings, &nstrings, MAX_STRINGS, stdin); SortStrings(strings, nstrings); WriteStrings(strings, nstrings, stdout); return 0; }
只看上面的main函数就知道整个程序的作用
(2)容易测试与debug
上面main函数中的代码执行过程为:输入一系列字符串,接着排序,最后打印到终端显示排序后的
如果想在输入完字符串后整体输出到终端,然后再排序,可以这样
int main() { char *strings[MAX_STRINGS]; int nstrings; ReadStrings(strings, &nstrings, MAX_STRINGS, stdout); WriteStrings(strings, nstrings, stdout); SortStrings(strings, nstrings); WriteStrings(strings, nstrings, stdout); return 0; }
(3)容易代码复用
如果想实现一个函数,其功能为将两个文件中的内容合并然后输出到终端,可以调用Readstrings、Writestrings函数来实现,具体代码如下:
MergeFiles(FILE * fp1, FILE * fp2) { char *strings[MAX_STRINGS]; int nstrings; ReadStrings(strings, &nstrings, MAX_STRINGS, fp1); WriteStrings(strings, nstrings, stdout); ReadStrings(strings, &nstrings, MAX_STRINGS, fp2); WriteStrings(strings, nstrings, stdout); }
(4)容易扩展
对于下面的代码
int CompareStrings(char *string1, char *string2) { char *p1 = string1; char *p2 = string2; while (*p1 && *p2) { if (*p1 < *p2) return -1; else if (*p1 > *p2) return 1; p1++; p2++; } return 0; }
作用是比较字符串的大小,如果想改成比较两个字符串的长度,很简单:
int StringLength(char *string) { char *p = string; while (*p) p++; return p - string; } int CompareStrings(char *string1, char *string2) { return StringLength(string1) - StringLength(string2); }
单独编译
将字符串数组放进单独的文件:
接口声明:stringarray.h
提供相应的实现: stringarray.c
允许复用:其他程序
stringarray.h
extern void ReadStrings(char **strings, int *nstrings, int maxstrings, FILE *fp); extern void WriteStrings(char **strings, int nstrings, FILE *fp); extern void SortStrings(char **strings, int nstrings); extern int CompareStrings(char *string1, char *string2);
stringarray.c
#include <stdio.h> #include <string.h> #define MAX_STRING_LENGTH 256 void ReadStrings(char **strings, int *nstrings, int maxstrings, FILE *fp) { char string[MAX_STRING_LENGTH]; *nstrings = 0; while (fgets(string, MAX_STRING_LENGTH, fp)) { strings[(*nstrings)++] = strdup(string); if (*nstrings >= maxstrings) break; } } void WriteStrings(char **strings, int nstrings, FILE *fp) { int i; for (i = 0; i < nstrings; i++) fprintf(fp, "%s", strings[i]); } int CompareStrings(char *string1, char *string2) { char *p1 = string1; char *p2 = string2; while (*p1 && *p2) { if (*p1 < *p2) return -1; else if (*p1 > *p2) return 1; p1++; p2++; } return 0; } void SortStrings(char **strings, int nstrings) { int i, j; for (i = 0; i < nstrings; i++) { for (j = i + 1; j < nstrings; j++) { if (CompareStrings(strings[i], strings[j]) > 0) { char *swap = strings[i]; strings[i] = strings[j]; strings[j] = swap; } } } }
sort.c
#include “stringarray.h” #define MAX_STRINGS 128 int main() { char *strings[MAX_STRINGS]; int nstrings; ReadStrings(strings, &nstrings, MAX_STRINGS, stdin); SortStrings(strings, nstrings); WriteStrings(strings, nstrings, stdout); return 0; }
Makefile
sort: sort.o stringarray.a cc -o sort sort.o stringarray.a sort.o: sort.c stringarray.h cc -c sort.c stringarray.a: stringarray.c cc -c stringarray.c ar ur stringarray.a stringarray.o clean: rm sort sort.o sortarray.a sortarray.o
结构化
我们可以看到在stringarray.h中的字符串数组与其元素的数量使用两个单独的变量存储,可以构建一个结构体将它们放在一起
stringarray.h
#define MAX_STRINGS 128
struct StringArray {
char *strings[MAX_STRINGS];
int nstrings;
};
extern void ReadStrings(struct StringArray *stringarray, FILE *fp);
extern void WriteStrings(struct StringArray *stringarray, FILE *fp);
extern void SortStrings(struct StringArray *stringarray);
sort.c
#include <stdio.h> #include “stringarray.h” int main() { struct StringArray *stringarray = malloc(sizeof(struct StringArray)); stringarray->nstrings = 0; ReadStrings(stringarray, stdin); SortStrings(stringarray); WriteStrings(stringarray, stdout); free(stringarray); return 0; }
使用typedef
stringarray.h
#define MAX_STRINGS 128 typedef struct StringArray { char *strings[MAX_STRINGS]; int nstrings; } *StringArray_T; extern void ReadStrings(StringArray_T stringarray, FILE *fp); extern void WriteStrings(StringArray_T stringarray, FILE *fp); extern void SortStrings(StringArray_T stringarray);
sort.c
#include <stdio.h> #include “stringarray.h” int main() { StringArray_T stringarray = malloc(sizeof(struct StringArray)); stringarray->nstrings = 0; ReadStrings(stringarray, stdin); SortStrings(stringarray); WriteStrings(stringarray, stdout); free(stringarray); return 0; }
不透明指针
stringarray.h
typedef struct StringArray *StringArray_T;
extern StringArray_T NewStrings(void);
extern void FreeStrings(StringArray_T stringarray);
extern void ReadStrings(StringArray_T stringarray, FILE *fp);
extern void WriteStrings(StringArray_T stringarray, FILE *fp);
extern void SortStrings(StringArray_T stringarray);
sort.c
#include <stdio.h> #include “stringarray.h” int main() { StringArray_T stringarray = NewStrings(); ReadStrings(stringarray, stdin); SortStrings(stringarray); WriteStrings(stringarray, stdout); FreeStrings(stringarray); return 0; }
抽象数据类型
模块化支持单数据结构上的操作
- 接口声明操作,而不是数据结构
- 实现对客户隐藏(封装)
- 使用编程语言的特征来保证封装
常见的做法
- 通过模块处理分配和释放数据结构
- 函数和变量的名字以<模块名> _开始
- 接口中尽可能多的提供通用性/灵活性
- 使用void*来实现多态性
上面的代码比较的是string类型的数组,如果是int类型或者float……其他类型呢?是不是要重新写代码?
这里对数组类型进行抽象,具体步骤如下:
ADT – 接口
array.h
#ifndef ARRAY_H #define ARRAY_H typedef struct Array *Array_T; extern Array_T Array_new(void); extern void Array_free(Array_T array); extern void Array_insert(Array_T array, void *datap); extern void Array_remove(Array_T array, void *datap); extern int Array_getLength(Array_T array); extern void *Array_getKth(Array_T array, int k); #endif
ADT – Client 1
如果是string类型,string_client.c
#include “array.h” #include <stdio.h> int main() { Array_T array; int i; array = Array_new(); Array_insert(array, (void *)“CS217 ”); Array_insert(array, (void *)“IS ”); Array_insert(array, (void *)“FUN ”); for (i = 0; i < Array_getLength(array); i++) { char *str = (char *)Array_getKth(array, i); printf(str); } A rray_free(array); return 0; }
如果是int类型数组,int_client.c
#include “array.h” #include <stdio.h> int main() { Array_T array; int one = 1, two = 2, three = 3, i; array = Array_new(); Array_insert(array, (void *)&one); Array_insert(array, (void *)&two); Array_insert(array, (void *)&three); for (i = 0; i < Array_getLength(array); i++) { int *datap = (int *)Array_getKth(array, i); printf(“%d “, *datap); } A rray_free(array); return 0; }
ADT–实现
array.c
#include “array.h” #define MAX_ELEMENTS 128 struct Array { void *elements[MAX_ELEMENTS]; int num_elements; }; Array_T Array_new(void) { Array_T array = malloc(sizeof(struct Array)); array->num_elements = 0; return array; } void Array_free(Array_T array) { free(array); } void Array_insert(Array_T array, void *datap) { int index = array->num_elements; array->elements[index] = datap; array->num_elements++; } int Array_getLength(Array_T array) { return array->num_elements; } void *Array_getKth(Array_T array, int k) { return array->elements[k]; } void Array_remove(Array_T array, void *datap) { int index, i; for (index = 0; index < array->num_elements; index++) if (array->elements[index] == datap) break; if (index < array->num_elements) { for (i = index + 1; i < array->num_elements; i++) array->elements[i - 1] = array->elements[i]; array->num_elements--; } }
总结
模块化是设计好的软件的关键
1、将程序分解成模块
2、提供清晰和易扩展的接口
抽象数据类型
1、模块化支持在数据结构上操作
2、设计良好的接口隐藏实现,但是提供灵活性
优点
编译分离
容易理解
容易测试与debug
容易代码复用
容易扩展
Comments