您的位置:首页 > 编程语言 > C语言/C++

c语言接口与实现--第11章序列理解

2018-01-24 09:58 197 查看
本章中强调序列是本书中最有用的ADT(abstact data type)之一,尽管序列的规格相对简单,但可以用作数组、链表、栈、队列和双端队列,实现这些数据结构的ADT所需的设施通常都包含在序列中。前面这些基本是书中的原话,话不多说,上代码。

seq.h

#ifndef SEQ_INCLUDED
#define SEQ_INCLUDED

#define T Seq_T
typedef struct T *T;

extern T Seq_new(int hint);
extern T Seq_seq(void *x, ***);

extern void Seq_free(T *seq);
extern int Seq_length(T seq);

extern void *Seq_get(T seq, int i);
extern void *Seq_put(T seq, int i, void *x);

extern void *Seq_addlo(T seq, void *x);
extern void *Seq_addhi(T seq, void *x);

extern void *Seq_remlo(T seq);
extern void *Seq_remhi(T seq);

#undef T

#endif


seq.c

#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "assert.h"
#include "seq.h"
#include "array.h"
#include "arrayrep.h"
#include "mem.h"

#define T Seq_T

struct T
{
struct Array_T array;
int length;
int head;
};

T Seq_new(int hint)
{
T seq;

assert(hint >= 0);
NEW0(seq);
if(hint == 0)
{
hint = 16;
}
ArrayRep_init(&seq->array, hint, sizeof(void *),
ALLOC(hint*sizeof(void *)));
return seq;
}

T Seq_seq(void *x, ...)
{
va_list ap;
T seq = Seq_new(0);

va_start(ap, x);
for(; x; x=va_arg(ap, void *))
{
Seq_addhi(seq, x);
}
va_end(ap);

return seq;
}

void Seq_free(T *seq)
{
assert(seq && *seq);
assert((void *)*seq == (void *)&(*seq)->array);

Array_free((Array_T *)seq);
}

int Seq_length(T seq)
{
assert(seq);
return seq->length;
}

void *Seq_get(T seq, int i)
{
assert(seq);
assert(i>=0 && i<seq->length);

return ((void **)seq->array.array)[(seq->head+i)%seq->array.length];
}

void *Seq_put(T seq, int i, void *x)
{
void *prev;

assert(seq);
assert(i>=0&&i<seq->length);
prev = ((void **)seq->array.array)[(seq->head+i)%seq->array.length];
((void **)seq->array.array)[(seq->head+i)%seq->array.length] = x;

return prev;
}

void *Seq_remhi(T seq)
{
int i;
assert(seq);
assert(seq->length > 0);

i = --seq->length;
return ((void **)seq->array.array)[(seq->head+i)%seq->array.length];
}

void *Seq_remlo(T seq)
{
int i = 0;
void *x;
assert(seq);
assert(seq->length > 0);

x =((void **)seq->array.array)[(seq->head+i)%seq->array.length];
seq->head = (seq->head + 1)%seq->array.length;
--seq->length;
return x;
}

void *Seq_addhi(T seq, void *x)
{
int i = 0;
assert(seq);
if(seq->lenth == seq->array.lenth)
{
expand(seq);
}

if(--seq->head < 0)
{
seq->head = seq->array.lenth - 1;
}
seq->length++;

return ((void **)seq->array.array)[(seq->head+i)%seq->array.length]=x;
}
static void expand(T seq)
{
int n = seq->array.length;
Array_resize(&seq->array, 2*n);

if(seq->head > 0)
{
void **old = &((void **)seq->array.array)[seq->head];
memcpy(old+n, old, (n-seq->head)*sizeof(void *));
seq->head += n;
}
}


这本书在介绍数据结构这块的接口设计基本类似,所以我觉得我们在学习的时候需要注意总结,看别人的代码接口是如何设计的,参数设计成什么样,返回值是什么类型。

我在做开发的时候,需求很明确,但是如何在实现需求的同时,写出良好的代码和实现清晰的设计逻辑,是个很大的问题,用超生游击队小品(暴露年龄了,哈哈)的话说“憋三天憋四天,憋出个海南岛吐鲁番”,到最后功能是实现了,但代码实现混乱,冗余,可读性差,甚至自己都不想再看写过的东西,这些都是缺乏设计的表现。



现在主要针对上面的截图说一下我的理解



函数说明

T Seq_new(int hint)
{
T seq;

assert(hint >= 0);
NEW0(seq);
if(hint == 0)
{
hint = 16;
}
ArrayRep_init(&seq->array, hint, sizeof(void *),
ALLOC(hint*sizeof(void *)));
return seq;
}


创建一个Seq_T* 变量seq, 并分配hint*sizeof(void *)大小的单元格,如果hint=0,则默认创建16个单元格,即上图中的结构,此时seq->length=0,seq->head=0, seq->array.length=16,seq->array.size=sizeof(void *), seq->array.array为ALLOC返回的首地址。

T Seq_seq(void *x, ...)
{
va_list ap;
T seq = Seq_new(0);

va_start(ap, x);
for(; x; x=va_arg(ap, void *))
{
Seq_addhi(seq, x);
}
va_end(ap);

return seq;
}


往已创建的seq里面写数据,采用可变参数形式va_list实现,用Seq_addhi在seq尾部填充数据,seq->head值不变,seq->length长度递增,比如书中的例子

name=Seq_seq(“c”, “ML”, “c++”, “Icon”, “AWK”, NULL);

void Seq_free(T *seq)
{
assert(seq && *seq);
assert((void *)*seq == (void *)&(*seq)->array);

Array_free((Array_T *)seq);
}


释放序列,seq有Seq_T *类型强制转换为Array_T*类型,然后通过Array_free函数来释放,我们看看为什么seq可以转换为Array_T*来释放,先看Array_free的实现

// T 为Array_T
void Array_free(T *array)
{
assert(array && *array);
FREE((*array)->array);  // 1
FREE(*array);           // 2
}


可以看到Seq_T只比Array_T多两个非指针变量int length和 int head,上面函数中“1”可以将seq->array数组释放掉,”2”可以将seq释放掉。

有必要说一下代码片段seq[i] 127

((void **)seq->array.array)[(seq->head+i)%seq->array.length];


这里的void ** 是二级指针,从顺序结构上看直接作用于最后的array,是将array转换为void *类型的数组,我们知道数组地址可以赋值给指针变量,比如int a[], int *b; b=a; 所以void ** 是void *类型的数组,将array这段内存空间转换为数组后,可以直接用数组序号来定位某个元素。

理解了之前图中的数据结构,对于元素的增减就比较好理解了

void * Seq_remlo(T seq);
void * Seq_remhi(T seq);
void * Seq_addlo(T seq, void * x);
void * Seq_addhi(T seq, void * x);


最后说明一下expand函数,代码如下

// 在add函数中有调用,当seq->length == seq->array.length时
// 证明已占用单元格数量与开辟的单元格数量相等,即所有单元格已全部占用,无剩余空间,
// 如需要再加入数据,需要重新申请更大的空间
static void expand(T seq)
{
int n = seq->array.length;
Array_resize(&seq->array, 2*n);

if(seq->head > 0)
{
void **old = &((void **)seq->array.array)[seq->head];
memcpy(old+n, old, (n-seq->head)*sizeof(void *));
seq->head += n;
}
}




演化结构如下图所示

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: