接下来我们来学习通用指针的实际应用,来实现一个可以存放任意类型数据通用栈。栈在内存中为一段连续的内存空间,有一个针指void *p指向栈顶元素。当有新元素入栈时,p向上(向低地址)移动4byte,然后将新元素入栈,此时p指向新元素地址,即p永远指向栈顶元素。当对栈执行出栈操作时,将栈顶元素出栈,再将p向下(向高地址)移动4byte,于是p仍指向栈顶元素。有两个特殊情况需要处理:
我们在实现通用栈时要注意,实际上栈中元素并不是真正意义上的“数据内容”,而是指向这些“数据内容”的指针,这也就是为什么在入栈和出栈时p总是移动4byte的原因。下面就来看一下通用栈的实现过程,首先定义一个栈的数据结构:
typedef struct
{
//栈大小,以元素个数为单位
int stack_size;
//元素个数
int ele_count;
//栈地址
void *stack_addr;
//栈顶地址
void *p;
//回调函数,在销毁栈时释放未出栈元素的内存
void (*destructor)(void *);
} stack;
定义两个函数,一个是栈的“构造函数”,另一个是栈的“析构函数”。当然,在C语言中并没有这两个名词,这里只是借用一下,换句话说就是定义两个函数,一个用于栈的创建,一个用于栈的销毁:
stack* stack_init(void (*destructor)(void *))
{
//申请栈结构所在的内存空间
stack *s = malloc(sizeof(stack));
//初始大小为2
s->stack_size = 2;
//当前元素个数为0
s->ele_count = 0;
//申请栈空间
s->stack_addr = malloc(4 * s->stack_size);
//将指针p设置为栈顶元素
s->p = s->stack_addr + s->stack_size * 4;
//设置回调函数,在栈销毁时用于释放未出栈元素的内存
s->destructor = destructor;
//返回栈指针
return s;
}
void stack_des(stack *s)
{
//循环释放未出栈元素所占用的内存
for (int i = 0; i < s->ele_count; i++)
{
//回调函数,释放内存
s->destructor((void*) (*(unsigned int *) s->p));
//p向下移动
s->p += 4;
}
//释放栈内存
free(s->stack_addr);
//释放线结构内存
free(s);
}
然后再定义两个函数,一个是入栈函数push,另一个是出栈函数pop:
void stack_push(stack *s, void *p_ele)
{
//p向上移动4byte
s->p -= 4;
//将元素入栈,入栈的是一个地址
*(unsigned int *) s->p = (unsigned int) p_ele;
//元素个数加1
s->ele_count++;
//如果栈满
if (s->stack_size == s->ele_count)
{
//申请2倍大小的内存空间
s->stack_addr = realloc(s->stack_addr, s->stack_size * 2 * 4);
//设定指针p的新值
s->p = s->stack_addr + s->stack_size * 2 * 4 - s->ele_count * 4;
//复制原数据到新的栈底
memcpy(s->stack_addr + s->stack_size * 4, s->stack_addr, s->stack_size * 4);
//栈大小变为2倍
s->stack_size *= 2;
}
}
void stack_pop(stack *s, void *p_ele)
{
//如果栈空,出栈失败
if (s->ele_count == 0)
{
return;
}
//出栈
*(unsigned int *) p_ele = *(unsigned int *) s->p;
//p向下移动4byte
s->p += 4;
//元素个数减1
s->ele_count--;
}
再定义一个用于释放内存的函数:
void destructor_int(void *p)
{
free(p);
}
这个内存释放函数中直接调用了free函数来释放内存,之所以这样定义而不是在stack_des函数中直接调用free是因为我们所实现的是一个通用栈,这个栈可以存放任意类型的元素,包括用户自定义的数据结构,所以当释放用户自定义的数据结构时就不能直接调用free了,必须要自己编写一个释放内存的函数。最后来看一下main函数中使用栈的实现:
typedef struct
{
//用于存放名字,这个变量在使用前需要申请内存
char *name;
char sex;
int age;
} human;
//这是专门用于释放human类型元素的回调函数
void destructor_human(void *p)
{
human *h = (human *) p;
//释放名字属性
free(h->name);
//释放human
free(h);
}
#define NAME_LEN (20)
int main(int argc, char **args)
{
//用于int类型的栈,回调函数为destructor_int
stack *s = stack_init(&destructor_int);
//压栈5个元素
for (int i = 0; i < 5; i++)
{
int *val = malloc(sizeof(int));
*val = i;
stack_push(s, val);
}
//出栈3个元素
for (int i = 0; i < 3; i++)
{
int *val;
stack_pop(s, &val);
printf("%d\n", *val);
free(val);
}
//销毁栈
stack_des(s);
//用于存放human类型的栈,回调函数为destructor_human
stack *s2 = stack_init(&destructor_human);
//创建一个human并入栈
human *h1 = malloc(sizeof(human));
h1->name = malloc(NAME_LEN);
memcpy(h1->name, "Tom", NAME_LEN);
h1->sex = 'F';
h1->age = 12;
stack_push(s2, h1);
//创建一个human并入栈
human *h2 = malloc(sizeof(human));
h2->name = malloc(NAME_LEN);
memcpy(h2->name, "Jerry", NAME_LEN);
h2->sex = 'M';
h2->age = 11;
stack_push(s2, h2);
//创建一个human并入栈
human *h3 = malloc(sizeof(human));
h3->name = malloc(NAME_LEN);
memcpy(h3->name, "Lily", NAME_LEN);
h3->sex = 'F';
h3->age = 16;
stack_push(s2, h3);
//循环出栈,出栈2个元素
for (int i = 0; i < 2; i++)
{
human *h;
stack_pop(s2, &h);
printf("Name: %s, Sex: %c, Age: %d\n", h->name, h->sex, h->age);
free(h);
}
//销毁栈
stack_des(s2);
return 0;
}
运行结果:
4 3 2 Name: Lily, Sex: F, Age: 16 Name: Jerry, Sex: M, Age: 11
可以看到,栈是一个先进后出、后进先出的数据结构。运行结果正确。
Copyright © 2015-2023 问渠网 辽ICP备15013245号