今天写程序发现好像要用到栈,而且是一个符号栈和一个数字栈,写两个栈代码会有不少重复的地方,但是C语言又没有模板,所以只能找些替代方案了。
一个比较容易想到的方式就是在栈结构中保存一个void类型的指针,并给它分配空间存储数据,因为void指针可以转换成任意其他类型的指针,所以可以用来保存任意类型的数据。但是这么做会遇到两个问题,一是void类型的指针不能直接与整数进行加减运算,也不能像普通数组一样使用[]运算符获取数组中存储的数据,另一个问题是栈中存储的数据未必会是基本数据类型,可能还会有结构,那么结构的赋值需要单独实现。
下面我们分别想办法解决这两个问题,稍微探究一下,我们可以看出问题一出现的根本原因是编译器并不知道void类型数组中一个数据的大小是多少。如果数组中存储的是具体的类型,例如int,一般int在机器中占4个字节,当我们使用像a[3]这样的表达式获取数据时,编译器会在a的地址位置偏移 3 * 4 = 12个字节,并从这里开始取出4个字节作为获得的int型数据。但是对于void类型,编译器并不知道它存储的每个数据有多大,所以通过void类型指针取数据和对指针进行算数运算在C语言标准中都是不被允许的,属于未定义行为。
知道了原因,问题就变得好解决了,我们只需要在栈的结构中保存一个int型数据存储栈中数据类型占据的字节数就好了。每次进行指针操作时,要先把void类型的指针转换为unsigned char类型的指针,然后在运算时乘上相应的字节数就好了。为什么要转换成unsigned char类型的指针呢?前面说过void类型指针不能直接进行运算,所以我们要把指针转换成能进行运算的具体数据类型,unsigned char就是一个很合适的类型,因为一个unsigned char只占据一个字节,通过它我们就可以控制指针移动任意字节数了。现在我们的栈定义就成了:
struck Stack{
void *valueArray; //存储数据
int valueTypeSize; //类型占据的字节数
int arrayUsedSize; //栈中数据的数量
int arrayAllocSize; //栈分配的空间大小
};
我们可以使用相应函数处理指针,下面是获得栈顶元素的实现:
void *stackGetTop(Stack *stack) {
if (stack->arrayUsedSize > 0) {
return &(((unsigned char *)(stack->valueArray))[(stack->arrayUsedSize - 1) * stack->valueTypeSize]);
} else {
return NULL;
}
}
接下来是问题二,其实这个问题在C++中也会出现,我有时可能会需要自己实现复制构造函数来实现对象的赋值,所以本质上我们就是要实现一个赋值函数,为了让函数和栈结构绑定,我们使用函数指针把相应的赋值函数存储在栈中。为了减小传递参数的开销,我们所有参数都是指针类型的。函数原型如下:
void (*assign)(void *assignedValue, void *fromValue);
这个函数指针直接放到结构体里有点丑,我们使用typedef给函数类型起一个别名,然后放入Stack结构体中。得到的函数和别名如下:
typedef void (*assignFunction)(void * assignedValue, void * fromValue);
struct Stack {
void *valueArray;
int valueTypeSize;
int arrayUsedSize;
int arrayAllocSize;
assignFunction assign;/*void (*assign)(void *assignedValue, void *fromValue);*/
};
至于typedef的用法不是本文介绍的内容范围,有兴趣可以自己必应一下,关键词:typedef 函数指针。
好了现在两个问题都解决掉了,现在可以实现栈了,栈的构造函数可能会复杂一点,它要接受赋值函数指针和数据类型占据的字节数作为参数,用于初始化栈,首先给出初始化栈和free栈的函数实现:
#define DEFAULT_STACK_SIZE 128
typedef void (*assignFunction)(void * assignedValue, void * fromValue);
typedef struct Stack {
void *valueArray;
int valueTypeSize;
int arrayUsedSize;
int arrayAllocSize;
assignFunction assign;/*void (*assign)(void *assignedValue, void *fromValue);*/
} Stack;
/*stackSize取0为默认大小*/
Stack *initStack(int typeSize, int stackSize, assignFunction assign) {
Stack *stack = malloc(sizeof(Stack));
if (stackSize == 0) {
stackSize = DEFAULT_STACK_SIZE;
}
stack->arrayAllocSize = stackSize;
stack->arrayUsedSize = 0;
stack->valueTypeSize = typeSize;
stack->valueArray = malloc(stack->valueTypeSize * stack->arrayAllocSize);
stack->assign = assign;
return stack;
}
void freeStack(Stack *stack) {
if (stack->arrayAllocSize != 0) {
free(stack->valueArray);
}
free(stack);
}
之后是Push和Pop操作,我实现的栈是动态扩展的,所以不会出现上溢出的错误,但是pop时可能会下溢出。出错一律返回-1。
int stackPush(Stack *stack, void *value) {
if (stack->arrayAllocSize == stack->arrayUsedSize) {
stack->arrayAllocSize *= 2;
void *tmp = realloc(stack->valueArray, stack->valueTypeSize * stack->arrayAllocSize);
if(tmp == NULL){
return -1;
}
stack->valueArray = tmp;
}
stack->assign(&(((unsigned char *)(stack->valueArray))[stack->arrayUsedSize * stack->valueTypeSize]), value);
stack->arrayUsedSize++;
return 0;
}
int stackPop(Stack *stack) {
if (stack->arrayUsedSize > 0) {
stack->arrayUsedSize--;
return 0;
} else {
return -1;
}
}
getTop的代码已经在上面贴出了这里就不重复了。
转载自rubsky