InitStack 的核心作用是初始化一个栈,在 C 语言中,栈通常使用结构体(struct)和动态内存分配(malloc)来实现,因为它可以灵活地管理内存大小,并且是面向对象思想的一种简单体现。

(图片来源网络,侵删)
下面我将分步讲解,从最基础的概念到完整的代码实现。
栈的基本概念
栈是一种后进先出 的数据结构,它只允许在一端进行插入和删除操作,这个端部被称为栈顶。
- 压栈:向栈顶添加一个元素。
- 出栈:从栈顶移除一个元素。
- 栈顶指针:始终指向栈顶元素。
栈的数据结构设计
在 C 语言中,我们通常用一个结构体来表示一个栈,这个结构体至少需要包含两个部分:
-
存储数据的数组或指针:用于存放栈中的元素。
(图片来源网络,侵删)- 静态数组:大小在编译时就确定了,不够灵活。
- 动态数组:使用指针指向一块动态分配的内存,可以在运行时调整大小,更常用。
-
栈顶指针:一个整数,用来记录当前栈顶元素在数组中的位置。
下面是一个典型的栈结构体定义:
#define MAXSIZE 100 // 假设栈的最大容量为100 (静态方式)
// 或者
// typedef int ElemType; // 定义栈中元素的类型,可以是 int, char, struct 等
// 栈的结构体定义
typedef struct {
int *data; // 动态分配的数组,用于存储栈元素
int top; // 栈顶指针,初始为 -1
int capacity; // 栈的当前最大容量
} Stack;
为什么用动态内存?
使用 int *data 和 capacity 可以让栈的大小在需要时进行扩容(当 top == capacity - 1 时,分配一个更大的空间),这使得我们的栈实现更加健壮和通用。
InitStack 函数的实现
InitStack 函数的任务就是创建一个空栈,对于一个空栈,它的状态应该是:

(图片来源网络,侵删)
data指向一块有效的内存空间。top的值为-1(表示栈中没有元素)。capacity有一个初始值。
下面是 InitStack 函数的完整代码。
完整代码示例
#include <stdio.h>
#include <stdlib.h> // 需要包含这个头文件来使用 malloc 和 free
// 1. 定义栈中元素的类型(这里用 int 作为示例)
typedef int ElemType;
// 2. 定义栈的结构体
typedef struct {
ElemType *data; // 指向动态分配的数组
int top; // 栈顶指针
int capacity; // 栈的容量
} Stack;
// 3. 初始化栈函数
// 参数:s - 一个指向 Stack 结构体的指针(我们想初始化这个栈)
// 返回值:无返回值 (void)
// 功能:为栈分配初始内存,并将栈顶指针和容量设置为初始值
void InitStack(Stack *s) {
// 1. 为 data 指针分配一块初始内存
// 这里我们先给一个初始容量,10
s->capacity = 10;
s->data = (ElemType *)malloc(s->capacity * sizeof(ElemType));
// 2. 检查内存是否分配成功
if (s->data == NULL) {
fprintf(stderr, "内存分配失败!\n");
exit(1); // 分配失败则退出程序
}
// 3. 将栈顶指针初始化为 -1,表示这是一个空栈
s->top = -1;
}
// --- 其他辅助函数 (为了完整性) ---
// 判断栈是否为空
int IsEmpty(Stack *s) {
return s->top == -1;
}
// 判断栈是否已满
int IsFull(Stack *s) {
return s->top == s->capacity - 1;
}
// 压栈 (Push)
void Push(Stack *s, ElemType value) {
if (IsFull(s)) {
printf("栈已满,无法压栈!\n");
// 这里可以添加扩容逻辑,
// s->capacity *= 2;
// s->data = (ElemType *)realloc(s->data, s->capacity * sizeof(ElemType));
return;
}
s->top++; // 栈顶指针上移
s->data[s->top] = value; // 将新元素存入栈顶
}
// 出栈 (Pop)
void Pop(Stack *s) {
if (IsEmpty(s)) {
printf("栈为空,无法出栈!\n");
return;
}
s->top--; // 栈顶指针下移,元素被“逻辑上”移除
}
// 获取栈顶元素 (Peek/GetTop)
ElemType GetTop(Stack *s) {
if (IsEmpty(s)) {
printf("栈为空,没有栈顶元素!\n");
return -1; // 假设元素不可能是 -1
}
return s->data[s->top];
}
// 销毁栈,释放内存
void DestroyStack(Stack *s) {
free(s->data); // 释放 data 指向的内存
s->data = NULL; // 避免成为野指针
s->top = -1;
s->capacity = 0;
}
// 4. 主函数:测试我们的栈和 InitStack 函数
int main() {
Stack myStack; // 声明一个栈变量
// 调用 InitStack 函数来初始化 myStack
InitStack(&myStack);
printf("栈初始化完成!\n");
printf("初始容量: %d\n", myStack.capacity);
printf("初始栈顶指针: %d\n", myStack.top);
// 测试压栈
printf("\n--- 压栈测试 ---\n");
Push(&myStack, 10);
Push(&myStack, 20);
Push(&myStack, 30);
printf("压栈 10, 20, 30 后,栈顶元素是: %d\n", GetTop(&myStack));
// 测试出栈
printf("\n--- 出栈测试 ---\n");
Pop(&myStack);
printf("出栈一次后,新的栈顶元素是: %d\n", GetTop(&myStack));
// 测试栈是否为空
printf("\n--- 栈状态测试 ---\n");
if (IsEmpty(&myStack)) {
printf("栈为空,\n");
} else {
printf("栈不为空,\n");
}
// 销毁栈
printf("\n--- 销毁栈 ---\n");
DestroyStack(&myStack);
// 此时再访问 myStack.data 是不安全的,因为它已经被释放了
printf("栈已销毁,\n");
return 0;
}
代码详解
-
typedef struct { ... } Stack;- 这行代码定义了一个名为
Stack的结构体类型,之后我们就可以像使用int,char一样使用Stack来声明变量了。
- 这行代码定义了一个名为
-
*`void InitStack(Stack s)`**
- *参数 `Stack s
**:我们传递的是一个**指针**,而不是Stack s`,为什么?- 效率:结构体可能很大,直接传递整个结构体会产生一份巨大的副本,效率很低,传递指针(通常是一个机器字的大小)效率高得多。
- 修改原对象:C 语言是“值传递”,如果我们传
Stack s,函数内部对s的任何修改(s->top = -1)都只是修改了那份副本,不会影响到main函数中的myStack,只有通过指针,我们才能直接操作main函数中的myStack变量本身,这是 C 语言中修改外部变量的标准做法。
- *参数 `Stack s
-
s->data = (ElemType *)malloc(s->capacity * sizeof(ElemType));malloc是 C 标准库函数,用于在堆上分配一块内存。s->capacity * sizeof(ElemType)计算需要的总字节数。capacity是 10,ElemType是int(4 字节),那么就需要10 * 4 = 40字节。(ElemType *)是将malloc返回的void*类型的指针强制转换为我们的ElemType*类型。- 这行代码执行后,
s->data就指向了一块可以存放s->capacity个ElemType类型数据的内存空间。
-
s->top = -1;- 这是初始化一个空栈的关键。
top是-1表示数组中没有任何元素是有效的,当第一个元素压入时,top会先变为0,然后将元素存入data[0]。
- 这是初始化一个空栈的关键。
-
if (s->data == NULL)- 这是一个非常重要的健壮性检查。
malloc可能会因为内存不足而失败,此时它会返回NULL,如果不检查,直接使用NULL指针(s->data[0] = ...)会导致程序崩溃(段错误)。
- 这是一个非常重要的健壮性检查。
InitStack 是使用 C 语言实现一个栈数据结构时的第一步,也是至关重要的一步,它的主要职责是:
- 分配内存:为存储栈元素的数组在堆上申请空间。
- 设置初始状态:将栈顶指针
top设置为-1,表示栈为空。 - 设置初始容量:为栈设定一个初始的容量,为后续可能的扩容做准备。
- 错误处理:检查内存分配是否成功,确保程序的稳定性。
