目录
1 栈的定义
2 相关概念
2.1 栈顶(Top)
2.2 栈底(Bottom)
2.3 空栈
2.4 栈的序列
2.5 出栈入栈与栈顶指针移动的关系
2.5.1 初始情况 top = -1
2.5.2 初始情况 top = 0
3 栈的应用:函数调用
4 栈的存储结构
4.1 顺序栈
4.2 链式栈
4.2.1 头插法
4.2.2 尾插法
5 功能定义
6 功能实现
6.1 初始化栈
6.2 返回栈内元素个数
6.3 添加新元素至栈顶
6.4 弹出栈顶元素并返回
6.5 释放栈内存
6.6 遍历栈中的所有元素
6.7 完整代码
1 栈的定义
栈(stack)是一种特殊的线性表,其特点在于只能在表的一端(称为栈顶)进行插入和删除操作。栈的应用范围极为广泛,从算法设计到日常生活场景,如堆叠的盘子、报纸、电梯中人群的进出顺序、邮局的邮筒等,都体现了栈的基本思想。
特点:栈是一种后进先出(LIFO,Last In First Out)或先进后出(FILO,First In Last Out)的线性表。这意味着最后插入的元素会最先被删除,而最先插入的元素会最后被删除。
2 相关概念
2.1 栈顶(Top)
栈顶是栈中允许进行插入和删除操作的一端,也称为表尾。栈顶的位置由一个称为栈顶指针的变量来指示,该变量随着栈中元素的增减而动态变化。栈顶指针通常指向栈顶元素的下一个位置(在数组实现中)或栈顶元素本身(在链表实现中)。
2.2 栈底(Bottom)
栈底是栈中固定不变的一端,不允许进行插入和删除操作,也称为表头。栈底通常位于栈的起始位置,其元素是栈中最早插入的元素。
2.3 空栈
空栈是指不包含任何元素的栈。在空栈中,栈顶指针通常指向栈的起始位置或某个特定的空值标记。
2.4 栈的序列
设栈 S = (a1, a2, ..., an),则 a1 称为栈底元素,an 为栈顶元素。栈中元素按 a1, a2, ..., an 的次序依次进栈(压栈、push),而出栈(弹栈、pop)时,第一个被删除的元素应为栈顶元素 an,随后是 an-1, ..., a2, a1,即按照后进先出的顺序进行。
2.5 出栈入栈与栈顶指针移动的关系
不同的参考书对栈的操作有不同的描述,以下是两种常见的描述:
2.5.1 初始情况 top = -1
在这种情况下,栈顶指针 (top) 初始值设为 -1,表示栈为空。当有新元素入栈时,先将栈顶指针加 1(即 top++),然后将元素放入栈顶位置。出栈时,先取出栈顶元素,再将栈顶指针减 1(即 top--)。
- 初始状态:栈为空,栈顶指针(top)为 -1。
- 元素 A 入栈:首先将栈顶指针加 1(即 top++),使得 top 变为 0,然后将元素 A 放入新的栈顶位置。
- 元素 BCD 依次入栈:同样遵循上述步骤,每次新元素进入栈时,先更新栈顶指针(top++),然后再放置元素。此时栈内顺序为 A->B->C->D,栈顶指针指向 D,top 等于 3。
- 元素 D 出栈:执行 pop 操作时,首先弹出栈顶元素 D,然后将栈顶指针下移一位(top--),指向原来的次栈顶元素 C,top 等于 2。
- 最终栈的状态变为 A->B->C,栈顶指针指向 C,top 等于 2。
2.5.2 初始情况 top = 0
在这种情况下,栈顶指针 (top) 初始值设为 0 ,表示栈为空。当有新元素入栈时,先将元素放入栈顶位置,然后将栈顶指针加 1(即 top++)。出栈时,先将栈顶指针减 1(即 top--),再取出栈顶元素。
- 初始状态:栈为空,栈顶指针(top)为 0。
- 元素 A 入栈:首先将元素 A 放入栈顶位置,然后将栈顶指针加 1(即 top++),使得 top 变为 1。
- 元素 BCD 依次入栈:同样遵循上述步骤,每次新元素进入栈时,先放置元素,然后再更新栈顶指针(top++)。此时栈内顺序为 A->B->C->D,栈顶指针指向 D 后面一个位置(但不存储元素), top 等于 4。
- 元素 D 出栈:执行 pop 操作时,首先将栈顶指针下移一位,指向 C 后面一个位置(但不存储元素),即 top 变为 3,然后弹出栈顶元素 D。
- 最终栈的状态变为 A->B->C,栈顶指针指向 C 后面一个位置(但不存储元素),top 等于 3。
3 栈的应用:函数调用
栈在算法和数据结构中有广泛的应用,如函数调用栈、深度优先搜索(DFS)、表达式求值、括号匹配等。此外,栈还可以用于实现队列、优先队列等其他数据结构的功能。
栈最常用的地方就是计算机的函数调用,不管何种语言,最先被调用的函数将会最后返回,而最后被调用的函数则会最先返回其结果。举例:
void funcA() {
funcB();
}
void funcB() {
funcC();
}
void funcC() {
// 执行某些操作
}
在这个例子中,函数调用的顺序是 funcA() -> funcB() -> funcC()。当程序执行到 funcA() 时,它会调用 funcB(),而 funcB() 又会调用 funcC()。每次函数调用时,都会在调用栈上创建一个新的栈帧(或称为活动记录),用于存储该函数的局部变量、参数以及返回地址等信息。
当 funcC() 执行完毕后,它会将控制权返回给 funcB(),后者继续执行剩余的部分,完成后也将控制权返回给 funcA()。因此,返回的顺序是 funcC() -> funcB() -> funcA()。这个过程清楚地展示了栈如何管理函数调用的顺序,确保每个函数都能正确地返回到调用它的函数处。
通过这种方式,即使是在复杂的程序中,栈也能有效地管理和跟踪大量的函数调用,保证程序的正常运行。
4 栈的存储结构
栈可以使用不同的数据结构来实现,最常见的两种方法是使用顺序表(数组)和链表。根据所使用的存储结构,栈可以分为两种类型:顺序栈和链式栈。在实际应用中,选择哪种实现方式取决于具体的需求和场景。
4.1 顺序栈
顺序栈是使用数组来实现的栈。在这种实现方式中,栈中的元素存储在一个固定大小的数组中,通常使用一个整型变量(如 top)来指示栈顶的位置。具有以下特点:
- 存储效率高:由于使用数组,内存分配连续,访问速度快。
- 空间利用率有限:数组的大小是固定的,如果栈中元素数量超过数组的容量,需要进行扩容操作,这可能会导致额外的时间开销。
- 实现简单:数组的索引操作简单直观,易于理解和实现。
4.2 链式栈
链式栈是使用链表来实现的栈。在这种实现方式中,栈中的元素存储在一个链表中,每个节点包含一个数据元素和一个指向下一个节点的指针。通常使用一个指针(如 top)来指示栈顶的位置。具有以下特点:
- 动态分配内存:链表的节点可以在需要时动态分配和释放,不受固定大小的限制。
- 空间利用率高:可以根据需要动态扩展和收缩,避免了空间浪费。
- 实现稍微复杂:链表的插入和删除操作涉及指针操作,相对数组来说稍微复杂一些。
4.2.1 头插法
优点:
- 插入和删除操作高效:在链表头部插入和删除节点的时间复杂度为 O(1),因为只需要修改头指针和新节点的指针。
- 栈顶元素访问方便:栈顶元素始终位于链表的头部,可以直接通过头指针访问,无需遍历整个链表。
缺点:
- 插入和删除操作会影响其他节点:虽然头插法的时间复杂度为 O(1),但在某些情况下,频繁的插入和删除操作可能会影响链表的稳定性,尤其是在多线程环境中。
4.2.2 尾插法
优点:
- 逻辑直观:对于习惯于从尾部操作的人来说,尾插法可能更符合直觉。
缺点:
- 插入和删除操作效率低:在链表尾部插入和删除节点的时间复杂度为 O(n),因为每次操作都需要遍历整个链表找到最后一个节点。
- 栈顶元素访问不便:栈顶元素位于链表的尾部,访问栈顶元素需要遍历整个链表,效率较低。
总结:
顺序栈适用于栈的大小已知且变化不大,或者对访问速度有较高要求的场景。
链式栈适用于栈的大小不确定或频繁变化,或者需要灵活管理内存的场景。由于栈的主要操作是入栈(push)和出栈(pop),而这些操作都涉及到栈顶元素,因此使用头插法可以显著提高效率。头插法不仅插入和删除操作的时间复杂度为 O(1),而且访问栈顶元素也非常方便。
5 功能定义
前文提到过,栈的底层实现既可以使用数组也可以使用链表。本节基于动态数组实现一个栈,并需要实现如下函数方法:
功能 | 函数签名 | 描述 |
---|---|---|
初始化栈 | void initStack(Stack *stack, size_t capacity) | 初始化一个栈,并设置初始容量。 |
返回栈内元素个数 | size_t getSize(const Stack *stack) | 返回栈中当前的元素个数。 |
添加新元素至栈顶 | void push(Stack *stack, int element) | 将一个新元素压入栈顶。如果栈满,则自动扩容。 |
弹出栈顶元素并返回 | int pop(Stack *stack) | 移除并返回栈顶元素(假设是 int 类型)。如果栈为空,返回错误码(如 -1)。 |
释放栈内存 | void destroyStack(Stack *stack) | 释放栈占用的所有内存资源。 |
遍历栈中的所有元素 |
| 方法一:使用循环和一个临时栈,通过 push 和 pop 操作,遍历栈中的所有元素,遍历后栈的状态与遍历前相同。 方法二:直接通过循环动态数组的操作遍历栈中的所有元素。 |
6 功能实现
本节基于动态数组来实现一个栈。
6.1 初始化栈
#include <stdio.h>
#include <stdlib.h>
// 定义栈结构体
// 使用顺序存储结构(动态数组)来实现 —> 顺序栈
typedef struct
{
int *data; // 动态数组存储栈元素,以 int 类型为例
size_t size; // 当前栈内元素个数
size_t capacity; // 动态数组的容量
} Stack;
/**
* 初始化栈
*
* 此函数用于初始化一个栈结构体,分配初始容量的内存,并设置栈的初始状态。
*
* @param stack 指向栈结构体的指针
* @param initialCapacity 栈的初始容量
*/
void initStack(Stack *stack, size_t initialCapacity)
{
if (stack == NULL) // 检查传入的指针是否为空
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// size_t 是 C 标准库中定义的一种无符号整数类型,不能表示负数
// 如果传入的 initialCapacity 是负数,可能会表示为一个很大的数(补码、数据回绕处理)
// 函数内部不方便处理负数行为,只能在函数调用之前,约束传入的参数
// 所以这里只是检查是否为 0
if (initialCapacity == 0)
{
puts("Warning: 初始容量为 0,请使用一个正数来表示容量!");
return; // 如果容量为 0,退出函数
}
// 使用 malloc 分配初始容量的内存
stack->data = (int *)malloc(initialCapacity * sizeof(int));
if (stack->data == NULL)
{
puts("Error: 内存分配失败,初始化失败!");
return; // 如果分配失败,退出函数
}
stack->size = 0; // 初始元素个数为 0
stack->capacity = initialCapacity; // 设置初始容量
printf("Success:成功初始化动态数组,容量为:%zu\n", initialCapacity);
}
int main()
{
// 声明结构体变量
Stack myStack;
puts("----------------------1.初始化栈----------------------");
initStack(&myStack, 2); // 初始化栈,初始容量为 2
initStack(NULL, 2); // 错误测试
initStack(&myStack, 0); // 错误测试
return 0;
}
输出结果如下所示:
6.2 返回栈内元素个数
/**
* 获取栈内元素个数
*
* 此函数用于获取栈中当前的元素个数。
*
* @param stack 指向栈结构体的指针
* @return 栈内元素个数,如果传入的指针为 NULL,则返回 0
*/
size_t getSize(const Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return 0; // 如果是空指针,返回 0
}
// 返回栈内元素个数
return stack->size;
}
6.3 添加新元素至栈顶
/**
* 添加新元素到栈顶
*
* 此函数用于将新元素添加到栈顶。如果栈已满,会自动扩展栈的容量。
* 新元素只能添加到栈顶(即动态数组的尾部),所以不用传入索引。
*
* @param stack 指向栈结构体的指针
* @param element 要添加的新元素
*/
void push(Stack *stack, int element)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// 检查顺序表(动态数组)是否存满了
if (stack->size == stack->capacity)
{
printf("栈空间已满,当前容量为 %zu ,需要扩容,正在扩容中~\n", stack->capacity);
// 如果栈已满,需要扩展容量
size_t newCapacity = stack->capacity * 2; // 扩容到原来的 2 倍
// 重新分配内存
int *newData = (int *)realloc(stack->data, newCapacity * sizeof(int));
if (newData == NULL)
{
puts("Error: 扩容失败!");
return; // 如果内存分配失败,退出函数
}
stack->data = newData;
stack->capacity = newCapacity;
printf("扩容成功(容量扩大一倍),现在的容量为 %zu\n", newCapacity);
}
stack->data[stack->size] = element; // 将元素压入栈顶
stack->size++; // 更新元素个数
printf("Success:新元素 %d 成功进栈(栈顶)\n", element);
}
6.4 弹出栈顶元素并返回
/**
* 弹出栈顶元素并返回
*
* 此函数用于从栈顶弹出一个元素,并返回该元素。如果栈为空,会输出错误信息并返回一个无效值。
*
* @param stack 指向栈结构体的指针
* @return 栈顶元素,如果栈为空则返回 -1
*/
int pop(Stack *stack)
{
// 检查栈是否为空
if (stack->size == 0)
{
puts("当前栈空,弹栈失败");
return -1; // 栈为空,返回无效值
}
// 方法 1
// int popElement = stack->data[stack->size - 1]; // 使用数组加下标返回,注意需要 -1 ,不然会越界访问
// stack->size--; // 更新元素个数
// return popElement;
// 方法 2
// stack->size--; // 更新元素个数
// return stack->data[stack->size]; // 返回栈顶元素
// 方法 3
return stack->data[--stack->size]; // 返回栈顶元素并更新元素个数(自减操作)
}
6.5 释放栈内存
/**
* 释放栈内存
*
* 此函数用于释放栈所占用的内存,并将栈结构体的成员重置为初始状态。
*
* @param stack 指向栈结构体的指针
*/
void destroyStack(Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// 释放动态数组内存
free(stack->data); // 释放动态数组内存
stack->data = NULL; // 防止悬挂指针
// 重置栈结构体的成员
stack->size = 0;
stack->capacity = 0;
puts("Success: 栈内存已成功释放");
}
6.6 遍历栈中的所有元素
/**
* 遍历栈中的所有元素
*
* 此函数用于遍历栈中的所有元素,打印每个元素的值。遍历后栈的状态与遍历前相同。
*
* @param stack 指向栈结构体的指针
*/
void traverseStack(Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
Stack tempStack; // 创建一个临时栈用于保存弹出的元素
initStack(&tempStack, stack->capacity); // 初始化临时栈,容量与原栈相同
puts("使用临时栈的方式遍历栈栈中的所有元素:");
// 循环直到原栈为空
while (stack->size > 0)
{
int element = pop(stack); // 从原栈弹出元素
printf("%d\t", element); // 处理元素(这里是打印)
push(&tempStack, element); // 将元素压入临时栈
}
printf("\n");
// 将所有元素从临时栈重新压入原栈
while (tempStack.size > 0)
{
int element = pop(&tempStack); // 从临时栈弹出元素
push(stack, element); // 将元素压回原栈
}
// 释放临时栈占用的资源
destroyStack(&tempStack);
}
对于顺序栈,可以直接使用循环遍历动态数组进行栈元素的访问,如下所示:
/**
* 打印栈中的所有元素
*
* 此函数用于打印栈中的所有元素。通过直接访问栈的内部数组,从栈底到栈顶依次打印每个元素。
*
* @param stack 指向栈结构体的指针
*/
void printfStack(Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// 检查栈是否为空
if (stack->size == 0)
{
puts("Warning:当前栈为空,没有元素可以打印");
return; // 如果栈为空,直接返回
}
for (size_t i = 0; i < stack->size; i++)
{
printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
}
// 注意,倒着遍历的时候,就不能使用 size_t 定义循环变量
// size_t 无法取到负数,会将负数(计算机中存储的是补码)解释成一个很大的数
// 所以得用 int 定义循环变量
// for (int i = stack->size - 1; i >= 0; i--)
// {
// printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
// }
printf("\n"); // 打印一个换行,结束一行
}
6.7 完整代码
#include <stdio.h>
#include <stdlib.h>
// 定义栈结构体
// 使用顺序存储结构(动态数组)来实现 —> 顺序栈
typedef struct
{
int *data; // 动态数组存储栈元素,以 int 类型为例
size_t size; // 当前栈内元素个数
size_t capacity; // 动态数组的容量
} Stack;
/**
* 初始化栈
*
* 此函数用于初始化一个栈结构体,分配初始容量的内存,并设置栈的初始状态。
*
* @param stack 指向栈结构体的指针
* @param initialCapacity 栈的初始容量
*/
void initStack(Stack *stack, size_t initialCapacity)
{
if (stack == NULL) // 检查传入的指针是否为空
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// size_t 是 C 标准库中定义的一种无符号整数类型,不能表示负数
// 如果传入的 initialCapacity 是负数,可能会表示为一个很大的数(补码、数据回绕处理)
// 函数内部不方便处理负数行为,只能在函数调用之前,约束传入的参数
// 所以这里只是检查是否为 0
if (initialCapacity == 0)
{
puts("Warning: 初始容量为 0,请使用一个正数来表示容量!");
return; // 如果容量为 0,退出函数
}
// 使用 malloc 分配初始容量的内存
stack->data = (int *)malloc(initialCapacity * sizeof(int));
if (stack->data == NULL)
{
puts("Error: 内存分配失败,初始化失败!");
return; // 如果分配失败,退出函数
}
stack->size = 0; // 初始元素个数为 0
stack->capacity = initialCapacity; // 设置初始容量
printf("Success:成功初始化动态数组,容量为:%zu\n", initialCapacity);
}
/**
* 获取栈内元素个数
*
* 此函数用于获取栈中当前的元素个数。
*
* @param stack 指向栈结构体的指针
* @return 栈内元素个数,如果传入的指针为 NULL,则返回 0
*/
size_t getSize(const Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return 0; // 如果是空指针,返回 0
}
// 返回栈内元素个数
return stack->size;
}
/**
* 添加新元素到栈顶
*
* 此函数用于将新元素添加到栈顶。如果栈已满,会自动扩展栈的容量。
* 新元素只能添加到栈顶(即动态数组的尾部),所以不用传入索引。
*
* @param stack 指向栈结构体的指针
* @param element 要添加的新元素
*/
void push(Stack *stack, int element)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// 检查顺序表(动态数组)是否存满了
if (stack->size == stack->capacity)
{
printf("栈空间已满,当前容量为 %zu ,需要扩容,正在扩容中~\n", stack->capacity);
// 如果栈已满,需要扩展容量
size_t newCapacity = stack->capacity * 2; // 扩容到原来的 2 倍
// 重新分配内存
int *newData = (int *)realloc(stack->data, newCapacity * sizeof(int));
if (newData == NULL)
{
puts("Error: 扩容失败!");
return; // 如果内存分配失败,退出函数
}
stack->data = newData;
stack->capacity = newCapacity;
printf("扩容成功(容量扩大一倍),现在的容量为 %zu\n", newCapacity);
}
stack->data[stack->size] = element; // 将元素压入栈顶
stack->size++; // 更新元素个数
printf("Success:新元素 %d 成功进栈(栈顶)\n", element);
}
/**
* 弹出栈顶元素并返回
*
* 此函数用于从栈顶弹出一个元素,并返回该元素。如果栈为空,会输出错误信息并返回一个无效值。
*
* @param stack 指向栈结构体的指针
* @return 栈顶元素,如果栈为空则返回 -1
*/
int pop(Stack *stack)
{
// 检查栈是否为空
if (stack->size == 0)
{
puts("当前栈空,弹栈失败");
return -1; // 栈为空,返回无效值
}
// 方法 1
// int popElement = stack->data[stack->size - 1]; // 使用数组加下标返回,注意需要 -1 ,不然会越界访问
// stack->size--; // 更新元素个数
// return popElement;
// 方法 2
// stack->size--; // 更新元素个数
// return stack->data[stack->size]; // 返回栈顶元素
// 方法 3
return stack->data[--stack->size]; // 返回栈顶元素并更新元素个数(自减操作)
}
/**
* 释放栈内存
*
* 此函数用于释放栈所占用的内存,并将栈结构体的成员重置为初始状态。
*
* @param stack 指向栈结构体的指针
*/
void destroyStack(Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// 释放动态数组内存
free(stack->data); // 释放动态数组内存
stack->data = NULL; // 防止悬挂指针
// 重置栈结构体的成员
stack->size = 0;
stack->capacity = 0;
puts("Success: 栈内存已成功释放");
}
/**
* 遍历栈中的所有元素
*
* 此函数用于遍历栈中的所有元素,打印每个元素的值。遍历后栈的状态与遍历前相同。
*
* @param stack 指向栈结构体的指针
*/
void traverseStack(Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
Stack tempStack; // 创建一个临时栈用于保存弹出的元素
initStack(&tempStack, stack->capacity); // 初始化临时栈,容量与原栈相同
puts("使用临时栈的方式遍历栈栈中的所有元素:");
// 循环直到原栈为空
while (stack->size > 0)
{
int element = pop(stack); // 从原栈弹出元素
printf("%d\t", element); // 处理元素(这里是打印)
push(&tempStack, element); // 将元素压入临时栈
}
printf("\n");
// 将所有元素从临时栈重新压入原栈
while (tempStack.size > 0)
{
int element = pop(&tempStack); // 从临时栈弹出元素
push(stack, element); // 将元素压回原栈
}
// 释放临时栈占用的资源
destroyStack(&tempStack);
}
/**
* 打印栈中的所有元素
*
* 此函数用于打印栈中的所有元素。通过直接访问栈的内部数组,从栈底到栈顶依次打印每个元素。
*
* @param stack 指向栈结构体的指针
*/
void printfStack(Stack *stack)
{
// 检查传入的指针是否为空
if (stack == NULL)
{
puts("Error: 传入的栈为 NULL");
return; // 如果是空指针,则直接返回,退出函数
}
// 检查栈是否为空
if (stack->size == 0)
{
puts("Warning:当前栈为空,没有元素可以打印");
return; // 如果栈为空,直接返回
}
for (size_t i = 0; i < stack->size; i++)
{
printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
}
// 注意,倒着遍历的时候,就不能使用 size_t 定义循环变量
// size_t 无法取到负数,会将负数(计算机中存储的是补码)解释成一个很大的数
// 所以得用 int 定义循环变量
// for (int i = stack->size - 1; i >= 0; i--)
// {
// printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
// }
printf("\n"); // 打印一个换行,结束一行
}
int main()
{
// 声明结构体变量
Stack myStack;
puts("----------------------1.初始化栈----------------------");
initStack(&myStack, 2); // 初始化栈,初始容量为 2
initStack(NULL, 2); // 错误测试
initStack(&myStack, 0); // 错误测试
puts("-------------------2.新元素进栈(栈顶)------------------");
push(NULL, 1); // 错误测试
push(&myStack, 1); // 元素 1 压栈
push(&myStack, 2); // 元素 2 压栈
push(&myStack, 3); // 元素 3 压栈(需要扩容)
printf("栈内元素个数为:%zu\n", getSize(&myStack));
printf("栈内元素个数为:%zu\n", getSize(NULL)); // 错误测试 返回 0
puts("---------------------3.循环遍历栈元素--------------------");
// traverseStack(&myStack);
printfStack(&myStack);
printfStack(NULL); // 错误测试
puts("---------------------4.栈顶元素出栈--------------------");
printf("弹出的栈顶元素是:%d\n", pop(&myStack));
printf("弹出的栈顶元素是:%d\n", pop(&myStack));
printf("弹出的栈顶元素是:%d\n", pop(&myStack));
printf("栈内元素个数为:%zu\n", getSize(&myStack));
printfStack(&myStack); // 没有元素可打印,警告
puts("---------------------4.释放栈内存--------------------");
destroyStack(&myStack);
return 0;
}
输出结果如下所示: