首先看一个基于int类型的栈的实现,定义一个栈的结构体,定义如下:
typedef struct {
int *elems; // 指向栈空间地址指针
int total_elem; // 当前栈空间中的元素个数
int alloc_length; // 栈空间的实际大小
}Stack;
对栈的操作,主要要实现栈空间的初始化,释放以及入栈和出栈,需要实现以下四个基本函数:
void StackNew(Stack *s); // 创建一个栈空间
void StackDispose(Stack *s); // 释放栈空间
void StackPush(Stack *s, int elem); // 将元素elem入栈
int StackPop(Stack *s); // 出栈,并返回出栈元素
对于栈空间的初始化,其实非常简单,包含三个操作:
1、 设置栈空间元素个数,此时相当于初始化一个空栈
2、 设置默认的栈空间大小
3、 申请栈空间
以下是StackNew的源码实现
s->total_elem = 0;
s->alloc_length = 4; // 默认为4
s->elems = malloc(4 * sizeof(int)); // 申请栈空间
assert(s->elems != NULL);
对于栈空间的释放就更简单了,两句话就搞定了。
free(s->elems);
s->elems = NULL;
接下来就是栈操作的最重要的两个操作—入栈和出栈
对于入栈可概括为如下:
1、 是否有空余的空间
2、 如果有则执行4
3、 如果没有则重新分配栈空间,并拷贝原栈中元素到新栈空间中
4、 入栈,元素个数自增1
实现代码:if (s->total_elem == s->alloc_length) {
s->alloc_length *= 2; // 此处采用栈空间倍增的策略,你也可以换为你认为好的
s->elems = realloc(s->elems, s->alloc_length * sizeof(int));
assert(s->elems != NULL);
}
s->elems[s->total_elem++] = elem;
可能你对realloc很疑惑,该函数先判断当前的指针是否有足够的连续空间,如果有,扩大s->elems指向的地址,并且将s->elems返回,如果空间不够,先按照指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来s->elems所指内存区域,同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
在来看看最后的出栈操作
assert(s->total_elem > 0);
s->total_elem--;
return s->elems[s->total_elem];
也是相当的简单吧!
这就实现了一个简单的栈,当然这个只是针对int数据类型而言的。
那么如何设计一个更通用的呢?其实也不是很复杂,往下看就知道了
首先我们对上面定义的结构体进行稍微的修改,增加一个元素大小的成员,并将int *类型指针更改为void *类型:
typedef struct {
void *elems;
int elem_size;
int total_elem;
int alloc_length;
}Stack;
同样我们需要对初始化函数稍作修改,增加一个元素大小参数以及出栈时增加一个传出参数获取栈顶元素,并将入栈操作的第二个参数改为指针。其他的都基本上与上面类似,就直接贴源码了。
void StackNew(Stack *s, int elemSize) {
assert(elemSize > 0);
s->elem_size = elemSize;
s-> total_elem = 0;
s->alloc_length = 4;
s->elems = malloc(4 * elemSize);
assert(s->elems);
}
void StackDispose(Stack *s) {
free(s->elems);
s->elems = NULL;
}
与上面的栈实现的主要区别在于需要手动计算元素地址,因为void*类型,系统比不知道该怎么处理。所以需要自己计算出入栈和出栈时元素的地址。
static void StackGrow(Stack *s) {
s->alloc_length *= 2;
s->elems = realloc(s->elems, s->alloc_length * s->elem_size);
assert(s->elems != NULL);
}
void StackPush(Stack *s, void *elemAddr) {
if (s-> total_elem == s->alloc_length) {
StackGrow(s);
}
void *target = (char *)s->elems + s-> total_elem * s->elem_size;
memcpy(target, elemAddr, s->elem_size);
s->logic_length++;
}
void StackPop(Stack *s, void *elemAddr) {
assert(s->logic_length > 0);
s-> total_elem --;
void *source = (char *)s->elems + s-> total_elem * s->elem_size;
memcpy(elemAddr, source, s->elem_size);
}
通过
(char *)s->elems + s-> total_elem * s->elem_size;
语句,可计算出入栈或出栈时的栈地址,从而实现入栈或出栈。