在《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