工学1号馆

home

« | 返回首页 | »

C语言模块化编程实战

By Wu Yudong on December 05, 2016

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

No comments yet.
To verify that you are human, please fill in "七"(required)