在上一次课中分别实现了整型栈和泛型栈,需要注意的是以前我们在实现整型栈的时候栈空间元素存放的是入栈元素本身的值,而对于泛型栈,我们在泛型栈空间里存放的是指向元素的指针。
但是,如果我们的栈内元素本身就是指针或者句柄怎么办(他们本身也开辟了存储空间,我们需要释放这些空间)?在这里我们要修改stack结构体的定义,在里面添加一项用来指向函数的指针成员,这个函数具体让用户自己定义来实现释放自定义数据类型空间。
经过修改后的stack结构体为:
typedef struct
{
void* element;
int elemSize;
int logLength;
int alloclength;
void (*freefn)(void*);//只有stack里面是指针才需要调用
}stack;
重点:void (*freefn)(void *elem) 为函数指针,其表示用于回收抽象指针(void *elem)中需要回收的数据,比如动态分配的字符串和结构体中动态分配的内容,我们可以通过传递函数指针,使得在回收栈时,不必弹出所有栈中数据进行回收,而是通过stackDestroy调用我们定义的回收方法,进行数据回收。
修改后的stackNew函数:
void StackNew(stack* s, int size, void (*freefunc)(void*))
{
s->logLength= 0;
s->alloclength= 4;
s->elemSize = size;
s->element= malloc(s->alloclength*s->elemSize);
s->freefn = freefunc;
}
修改后的stackDispose函数:
void StackDestroy(stack* s)
{
if(s->freefn != NULL)
//for循环中用到的是logLength而不是alloclength,因为并不需要让栈释放未使用的栈空间里面的内容。
for(int i=0; i<s->logLength; i++)
{
s->freefn((char*)s->element+i*s->elemSize);
}
free (s->element);
s->element= NULL;
}
在实际应用中我们根据自己定义的数据结构空间的申请方式来定义,例如:
Stack stringStack;
StackNew(&stringStack, sizeof(char *), StringFree);
Void StringFree(void *elem)
{
//这里我们将void指针强制转换成char**类型,然后再进行解引用
free(*(char **) elem);
}
因此,按如上方法就可以得到一个完整的泛型栈。
之后,继续讲到rotate函数,这个函数的功能是把数组中front到middle的内容挪到最后面。
void rotate(void* front, void* middle, void* end)
{
//使用(char *)将指针减法变成了普通的减法,因此得到的就是实际的物理字节的总数
int frontSize=(char *)middle-(char *)front;
int backSize=(char *)end-(char *)middle;
char buffer[frontSize];
memcpy(buffer,front,frontSize);
memmove(front,middle,backSize);
memcpy((char *)end-frontSize,buffer,frontSize);
}
注意:memcpy在源数据区域与目的数据区域重叠时不能使用,memmove可以;但memcpy效率更高,因此在确认数据不重叠后,建议使用memcpy。
C语言中通用的快速排序函数:
void qsort(void *base,int size,int elemSize,int(*cmpfn)(void*,void*))
{
}
具体可以参考: C语言标准库函数qsort详解
堆栈示意图: