這次我們一起看看一種特殊的線性表——棧的順序存儲結構實現。
還是老規矩:
程序在碼雲上可以下載。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
棧是一個被限制了插入和刪除操作發生位置的線性表。棧的插入和刪除操作只能發生在線性表一端,這個位置稱之爲棧頂,與之相反的另一端稱之爲棧底。棧不允許從棧頂和棧底中間的任意位置插入和刪除元素。這樣的操作限制導致棧具備了“先進後出,後進先出”(FIFO)的特性。這也使得棧的可以執行的操作不像線性表那麼多,那麼隨意了。
還要向大家強調一點:棧和隊列是非常重要的兩種數據結構,我們在後期的程序中會經常把棧和隊列引入,作爲一種工具來使用。比如:二叉樹先序、中序、後續的非遞歸遍歷,圖的深度優先和廣度優先遍歷都會涉及到這兩種數據結構中的一種,所以請大家一定親自動手做一下棧和隊列的程序,這對後期程序的理解和實現是有幫助的。
在計算機中,函數調用的過程必然會涉及到棧。細心的童鞋會發現,無論是嵌套調用還是遞歸調用,函數調用的順序非常符合“先進後出”的特徵。每調用一個函數,就要在運行時棧中壓入這個函數的信息(包括實參和返回值)(壓棧),函數運行結束後,這個函數的信息會從棧中彈出(彈棧)。非常像計算機中的一個概念“中斷”。理解運行時棧的這一特性有利於理解函數遞歸調用的整個過程,也就會明白遞歸算法的效率爲什麼會比非遞歸的差了。這對於後期理解二叉樹先序、中序、後續的遞歸遍歷算法的執行過程有好處。好多老師講課根本不會講到這一知識點,這個知識點在書上56頁有詳細說明。很多時候程序運行發生了棧溢出(Java語言中的StackOverflowException),你就該明白那是什麼意思了。
首先來看看棧的抽象數據類型定義:
ADT Stack{
數據對象:D ={ai | ai∈Elemset,(i=1,2,…,n, n≥0)}
數據關係:R1 = {<ai-1,ai>|ai-1,ai ∈ D,(i=2,3,…,n)}
約定an爲棧頂, a1爲棧底。
基本操作:
InitStack(&S)
操作結果:構造一個空的棧S。
DestroyStack(&S)
初始條件: 棧S已經存在。
操作結果: 銷燬棧S。
ClearStack(&S)
初始條件: 棧S已經存在。
操作結果: 將棧S重置爲空棧。
StackIsEmpty(S)
初始條件: 棧S已經存在。
操作結果: 若棧S爲空棧,則返回TURE;否則返回FALSE。
StackLength(S)
初始條件: 棧S已經存在。
操作結果: 返回棧S中的數據元素個數。
GetTop(S, &e)
初始條件: 棧S已經存在且非空。
操作結果: 用e返回棧S中棧頂元素的值。
Push(&S, e) //入棧操作
初始條件: 棧S已經存在。
操作結果: 插入元素e爲新的棧頂元素。
Pop(&S, &e) //出棧操作
初始條件: 棧S已經存在且非空。
操作結果: 刪除S的棧頂元素並用e返回其值。
StackTraverse(S, visit ())
初始條件: 棧S已經存在且非空。
操作結果: 從棧底到棧頂依次對S的每個元素調用函數visit ()。一旦visit ()失敗,則操作失敗。
}ADT Stack
一起來看看程序的實現。
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>引入頭文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#include <stdio.h> //使用了標準庫函數
#include <stdlib.h> //使用了動態內存分配函數
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>自定義符號常量<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#define STACK_INIT_SIZE 100 //存儲空間初始分配量
#define STACKINCREMENT 10 //存儲空間分配增量
#define OVERFLOW -2 //內存溢出錯誤常量
#define OK 1 //表示操作正確的常量
#define ERROR 0 //表示操作錯誤的常量
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>自定義數據類型<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
typedef int Status; //用typedef給int起個別名,也便於程序的維護
typedef int SElemType; //用typedef給int起個別名,也便於程序的維護
typedef struct { //棧的順序存儲表示
SElemType *base; //棧底指針,在棧構造之前和銷燬之後,base的值爲NULL
SElemType *top; //棧頂指針
int stacksize; //當前已分配的存儲空間,以元素爲單位
}SqStack;
//-------------------------------------------順序棧的主要操作-----------------------------------------
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>1.初始化順序棧<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:InitStack_Sq
參數:SqStack &S 順序棧引用
返回值:狀態碼,OK表示操作成功
作用:構造一個空的順序棧
*/
Status InitStack_Sq(SqStack &S){
//動態申請順序棧的內存空間,並檢查內存空間是否成功分配
//if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))))
//這句代碼相當於以下兩行代碼:
//S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
//if(!S.base) <=> if(S.base == NULL)
if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)))){
printf("內存分配失敗,程序即將退出!\n");
exit(OVERFLOW);
}//if
//由於剛動態分配完的棧是空棧,所以棧頂指針和棧底指針都指向棧底
S.top = S.base;
//棧的大小就是棧的初始化大小參數STACK_INIT_SIZE
S.stacksize = STACK_INIT_SIZE;
//操作成功
printf("順序棧S所需內存已分配成功!\n");
return OK;
}//InitStack_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>2.銷燬順序棧<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:DestoryStack_Sq
參數:SqStack &S 順序棧引用
返回值:狀態碼,OK表示操作成功
作用:釋放順序棧S所佔內存空間
*/
Status DestoryStack_Sq(SqStack &S){
//棧底指針保存的是順序棧內存空間的首地址
free(S.base);
//操作成功
printf("順序棧內存釋放成功!\n");
return OK;
}//DestoryStack_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>3.置空順序棧<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:ClearStack_Sq
參數:SqStack &S 順序棧引用
返回值:狀態碼,OK表示操作成功
作用:將順序棧S中的元素清空
*/
Status ClearStack_Sq(SqStack &S){
//只需要重新設置棧頂指針到初始位置,保留現有空間
//棧頂指針和棧底指針都指向棧底表示此棧是空棧
S.top = S.base;
//操作成功
return OK;
}//ClearStack_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>4.判斷順序棧是否爲空<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:StackIsEmpty_Sq
參數:SqStack S 順序棧S
返回值:若順序棧S是空棧返回1,否返回0
作用:判斷順序棧S是否爲空棧
*/
Status StackIsEmpty_Sq(SqStack S){
//棧頂指針和棧底指針都指向棧底表示此棧是空棧
return S.top == S.base;
}//StackIsEmpty_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>5.獲取順序棧的長度<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:StackLength_Sq
參數:SqStack S 順序棧S
返回值:若順序棧S是空棧返回1,否返回0
作用:判斷順序棧S是否爲空棧
*/
Status StackLength_Sq(SqStack S){
//棧的長度就是棧頂指針和棧底指針之間的元素個數
return (S.top - S.base);
}//StackLength_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>6.獲得棧頂元素的值<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:GetTop
參數:SqStack S 順序棧S
返回值:成功獲取順序棧S棧頂元素的值後返回OK,否則返回ERRROR
作用:用e返回棧頂元素的值,但是棧頂元素不做出棧操作
*/
Status GetTop(SqStack S, SElemType &e){
//空棧沒有棧頂元素,所以要先判斷棧是否爲空
//注意棧是否爲空和棧是否存在不是一個概念,所以不可以用
//S.base != NULL判斷棧是否爲空
if(StackIsEmpty_Sq(S)) {
return ERROR;
}//if
//注意:棧頂指針指向棧頂元素的下一個位置
e = *(S.top - 1);
/* 注意:此處不能使用“e = *(--S.top); ”的原因
1. --S.top自減操作改變了棧頂指針本身的指向,使得該指針向前移動一位,相當於刪除了原來棧中的最後一個元素(最後一個元素出棧);
2. S.top-1 僅僅表示棧頂指針的上一個位置,並沒有改變S.top的值,*(S.top-1)表示取棧頂指針前一個位置的值,即棧頂元素的值
3. 這兩種寫法造成的結果是不同的,如果是純代數運算,兩者沒有差別,但在指向數組
(順序結構在C語言中是用一維數組描述的)的指針變量運算中,這兩個表達式有特殊含義
在指針運算中,“指針變量-1 ”表示該指針變量所指位置的前一個位置,
這種做法並不改變指針變量本身的值。
--指針變量 不僅使得該指針指向原來所指位置的上一個位置, 還修改了指針變量本身的值
在棧中,棧頂指針和棧底指針所指向的位置有特殊的含義,故兩者不等價。
*/
//操作成功
return OK;
}//GetTop_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>7.在順序棧中插入元素(入棧)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:ReallocStack_Sq
參數:SqStack &S 順序棧S引用
返回值:狀態碼,操作成功返回OK,否則返回ERRROR
作用:將棧S擴容,每擴容一次,棧的大小增加STACKINCREMENT
*/
Status ReallocStack_Sq(SqStack &S){
//爲順序棧重新分配內存(擴容),擴展的空間大小是STACKINCREMENT
/*if(!(S.base = (SElemType *)realloc(S.base,
(STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType))))
這句代碼相當於:
S.base = (SElemType *)realloc(S.base,
(STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType));
if(!S.base) <=> if(S.base == NULL)
*/
if(!(S.base = (SElemType *)realloc(S.base,
(STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType)))){
printf("內存分配失敗,程序即將退出!\n");
exit(OVERFLOW);
}//if
//由於擴容前棧已經滿了,所以棧頂指針位置就是棧底指針+原來棧的大小
S.top = S.base + S.stacksize;
//擴容後,棧的大小增加了STACKINCREMENT
S.stacksize += STACKINCREMENT;
//操作成功
printf("順序棧S所需內存已重新分配成功!\n");
return OK;
}//ReallocStack_Sq
/*
函數:Push_Sq
參數:SqStack &S 順序棧引用
SElemType e 被插入的元素e
返回值:成功獲取順序棧S棧頂元素的值後返回OK,否則返回ERRROR
作用:(入棧、壓棧)插入元素e爲新的棧頂元素
*/
Status Push_Sq(SqStack &S, SElemType e){
//入棧時發現棧滿了,就要追加存儲空間(擴容)
if(S.top - S.base >= S.stacksize) {
//調用擴容函數
ReallocStack_Sq(S);
}//if
//插入前,棧頂指針指向當前棧頂元素的下一個位置
//將e賦值給棧頂指針所指存儲空間(插入元素e),棧頂指針後移
//*S.top++ = e; <=> *(S.top) = e; S.top++;
*S.top++ = e;
}//Push_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>8.在順序棧中刪除元素(出棧)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:Pop_Sq
參數:SqStack &S 順序棧引用
SElemType &e 帶回被刪除的元素值e
返回值:刪除成功返回OK,否則返回ERRROR
作用:(出棧,彈棧)若棧不空,則刪除S的棧頂元素,用e返回其值
*/
Status Pop_Sq(SqStack &S, SElemType &e){
//在空棧中執行出棧操作沒有意義,所以要判斷棧是否爲空
//注意棧是否爲空和棧是否存在不是一個概念,所以不可以用
//S.base != NULL判斷棧是否爲空
if(StackIsEmpty_Sq(S)) {
return ERROR;
}//if
//刪除前,棧頂指針指向當前棧頂元素的下一個位置
//--S.top;之後,棧頂指針剛好指向被刪除元素
//棧頂指針前移,保存被刪除的元素值到e
//e=*--S.top; <=> --S.top; e=*(S.top);
e = *--S.top;
//操作成功
return OK;
}//Pop_Sq
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>9.遍歷整個順序棧<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
函數:Print
參數:ElemType e 被訪問的元素
返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR
作用:訪問元素e的函數,通過修改該函數可以修改元素訪問方式,
該函數使用時需要配合遍歷函數一起使用。
*/
Status Print(SElemType e){
printf("%5d ", e);
return OK;
}//Print
/*
函數:StackTraverse_Sq
參數:SqStack S 順序棧S
Status(* visit)(SElemType) 函數指針,指向元素訪問函數。
返回值:狀態碼,操作成功返回OK,操作失敗返回ERROR
作用:調用元素訪問函數按出棧順序完成順序棧的遍歷,但並未真正執行出棧操作
*/
Status StackTraverse_Sq(SqStack S, Status(* visit)(SElemType)) {
//在空棧中執行遍歷操作沒有意義,所以要判斷棧是否爲空
//注意棧是否爲空和棧是否存在不是一個概念,所以不可以用
//S.base != NULL判斷棧是否爲空
if(StackIsEmpty_Sq(S)) {
printf("此棧是空棧");
return ERROR;
}//if
//調用元素訪問函數依次訪問棧中的每個元素
for(int i = 0; i < StackLength_Sq(S); ++i){
//調用元素訪問函數,一旦訪問失敗則退出
if(!visit(S.base[i])) {
return ERROR;
}//if
}//for
//輸出換行,是控制檯顯示美觀
printf("\n");
//操作成功
return OK;
}//StackTraverse_Sq
//-------------------------------------------主函數---------------------------------------------------
int main(int argc,char *argv[]){
SqStack S;
SElemType e;
int tmp,tmp1=0,i,n;
while(1){
printf("\n*****************************順序棧完整版測試程序******************************\n");
printf("->1.初始化順序棧S\n");
printf("->2.清空順序棧S\n");
printf("->3.輸出順序棧S的所有元素\n");
printf("->4.查看棧的長度(元素個數)\n");
printf("->5.獲得棧頂元素的值\n");
printf("->6.在順序棧中插入元素(入棧)\n");
printf("->7.在順序棧中刪除元素(出棧)\n");
printf("->8.進位制轉換程序\n");
printf("->9.銷燬順序棧S\n");
printf("->10.退出程序\n");
while(1){
printf("請輸入您想要操作的代號:");
scanf("%d",&tmp);
switch(tmp){
case 1:{
if(tmp1){//若棧已經被初始化並且棧中含有元素
printf("順序棧已經被創建,正在銷燬...\n");
DestoryStack_Sq(S);
}//if
InitStack_Sq(S);
printf("您想爲順序棧S輸入幾個元素?\n");
scanf("%d",&n);
printf("請輸入順序棧S的所有元素,每個元素用空格隔開:\n");
for(i=0;i<n;i++){
scanf("%d",&e);
Push_Sq(S,e);
}//for
printf("創建後的順序棧S的結果爲:\n");
StackTraverse_Sq(S, Print);
printf("順序棧S創建成功!\n");
tmp1=1;
break;
}//case 1
case 2:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
ClearStack_Sq(S);
printf("棧已清空,長度爲%d\n",StackLength_Sq(S));
break;
}//case 2
case 3:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
if(!StackLength_Sq(S)){
printf("棧可能已被清空或刪光,沒有元素可供輸出,請初始化後執行操作!\n");
break;
}//if
printf("順序棧S內的全部元素爲:\n");
StackTraverse_Sq(S, Print);
break;
}//case 3
case 4:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
printf("順序棧S的長度爲%d\n",StackLength_Sq(S));
break;
}//case 4
case 5:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
if(!StackLength_Sq(S)){
printf("棧可能已被清空或刪光,沒有元素可供輸出,請初始化後執行操作!\n");
break;
}//if
if(GetTop(S,e))
printf("棧頂元素的值爲%5d\n",e);
break;
}//case 5
case 6:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
printf("您想爲順序棧S插入幾個元素?\n");
scanf("%d",&n);
printf("插入前棧S內的全部元素爲:\n");
StackTraverse_Sq(S, Print);
printf("請依次輸入您想插入的所有元素,每個元素用空格隔開:\n");
for(i=0;i<n;i++){
scanf("%d",&e);
Push_Sq(S,e);
}//for
printf("插入後棧S內的全部元素爲:\n");
StackTraverse_Sq(S, Print);
break;
}//case 6
case 7:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
if(!StackLength_Sq(S)){
printf("棧可能已被清空或刪光,沒有元素可供輸出,請初始化後執行操作!\n");
break;
}//if
printf("您想在棧中刪除幾個元素?\n");
scanf("%d",&n);
printf("刪除前棧S內的全部元素爲:\n");
StackTraverse_Sq(S, Print);
for(i=0;i<n;i++){
Pop_Sq(S,e);
}//for
printf("刪除後棧S內的全部元素爲:\n");
StackTraverse_Sq(S, Print);
break;
}//case 7
case 8:{
if(!tmp1){
free(S.base);
break;
}//if
int i;
InitStack_Sq(S);
printf("請輸入一個十進制整數:\n");
scanf("%d",&n);
printf("您想把它轉化成幾進制,請輸入一個不超過16的正整數:\n");
scanf("%d",&i);
if(i<=0||i>16){
printf("您輸入的數字不合法\n");
break;
}//if
while(n){
Push_Sq(S,n%i);
n=n/i;
}//while
printf("您想要的%d進制數爲:",i);
while(!StackIsEmpty_Sq(S)){
Pop_Sq(S,e);
switch(e){
case 1: case 2: case 3: case 4: case 5:
case 6: case 7: case 8: case 9:
{ printf("%d",e); break; }
case 10:{ printf("A"); break; }
case 11:{ printf("B"); break; }
case 12:{ printf("C"); break; }
case 13:{ printf("D"); break; }
case 14:{ printf("E"); break; }
case 15:{ printf("F"); break; }
default :{ printf("程序出現錯誤!\n"); break; }
}//switch
}//while
printf("\n");
DestoryStack_Sq(S);
break;
}//case 8
case 9:{
if(!tmp1){
printf("順序棧S不存在,您可能還未創建,請在創建(執行操作1)後執行該操作!\n\n");
break;
}//if
printf("->銷燬順序棧S:");
DestoryStack_Sq(S);
tmp1=0;
break;
}//case 9
case 10:{
printf("程序退出,再見!\n\n");
exit(0);
break;
}//case 10
default:{
printf("您的輸入非法,請重新輸入!\n\n");
break;
}//default
}//switch
break;
}//while
system("pause");
system("cls"); //清屏
fflush(stdin); //清空標準輸入緩衝區,避免多輸入字符(如回車、空格)造成影響
}//while
return 0;
}
以下是程序運行時的部分輸出和我輸入的測試數據:
注:程序中涉及到菜單刷新,這裏不再重複截取菜單,只保留輸入數據和程序的結果輸出
*****************************順序棧完整版測試程序******************************
->1.初始化順序棧S
->2.清空順序棧S
->3.輸出順序棧S的所有元素
->4.查看棧的長度(元素個數)
->5.獲得棧頂元素的值
->6.在順序棧中插入元素(入棧)
->7.在順序棧中刪除元素(出棧)
->8.進位制轉換程序
->9.銷燬順序棧S
->10.退出程序
請輸入您想要操作的代號:1
順序棧S所需內存已分配成功!
您想爲順序棧S輸入幾個元素?
5
請輸入順序棧S的所有元素,每個元素用空格隔開:
1 4 5 9 2
創建後的順序棧S的結果爲:
1 4 5 9 2
順序棧S創建成功!
請按任意鍵繼續. . .
請輸入您想要操作的代號:3
順序棧S內的全部元素爲:
1 4 5 9 2
請按任意鍵繼續. . .
請輸入您想要操作的代號:4
順序棧S的長度爲5
請按任意鍵繼續. . .
請輸入您想要操作的代號:5
棧頂元素的值爲 2
請按任意鍵繼續. . .
請輸入您想要操作的代號:6
您想爲順序棧S插入幾個元素?
2
插入前棧S內的全部元素爲:
1 4 5 9 2
請依次輸入您想插入的所有元素,每個元素用空格隔開:
45 67
插入後棧S內的全部元素爲:
1 4 5 9 2 45 67
請按任意鍵繼續. . .
請輸入您想要操作的代號:7
您想在棧中刪除幾個元素?
3
刪除前棧S內的全部元素爲:
1 4 5 9 2 45 67
刪除後棧S內的全部元素爲:
1 4 5 9
請按任意鍵繼續. . .
請輸入您想要操作的代號:8
順序棧S所需內存已分配成功!
請輸入一個十進制整數:
45
您想把它轉化成幾進制,請輸入一個不超過16的正整數:
16
您想要的16進制數爲:2D
順序棧內存釋放成功!
請按任意鍵繼續. . .
請輸入您想要操作的代號:2
棧已清空,長度爲0
請按任意鍵繼續. . .
請輸入您想要操作的代號:3
棧可能已被清空或刪光,沒有元素可供輸出,請初始化後執行操作!
請按任意鍵繼續. . .
請輸入您想要操作的代號:9
->銷燬順序棧S:順序棧內存釋放成功!
請按任意鍵繼續. . .
請輸入您想要操作的代號:10
程序退出,再見!
--------------------------------
Process exited with return value 0
Press any key to continue . . .
總結:
本次的程序涉及到很多的編程技巧,尤其是對指針變量做++和–操作要格外小心,因爲它們與普通的變量做++和–操作的含義和後果都不一樣,稍有不慎就會惹出麻煩。
下次的文章會介紹另一種特殊的線性表——隊列的鏈式存儲實現。請大家繼續關注我的博客,期待下次再見!