C语言模拟模板实现动态栈

这篇博客介绍了如何在C语言中实现一个通用的符号栈和数字栈,通过void指针和存储数据类型大小来处理不同类型的元素。文章详细解释了遇到的两个问题:void指针无法直接进行算术运算和结构体赋值,并给出了解决方案,包括使用int型存储数据大小以及使用assign函数指针存储赋值操作。还展示了初始化、释放、push和pop等栈操作的代码实现。
摘要由CSDN通过智能技术生成

今天写程序发现好像要用到栈,而且是一个符号栈和一个数字栈,写两个栈代码会有不少重复的地方,但是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值