5-1動態內存分配,分配的是堆內存的空間
- 分配內存函數 (都集中在庫函數 stdlib.h 中)
void *malloc (unsigned int num_bytes); //指定分配內存空間大小,大小為 num_bytes字節,其值是隨機值。 void *calloc (unsigned num ,unsigned size); //參數包含元素的數量和每個元素的字節數,內存空間為num*sie void *realloc(void *ptr,size_t size); //調用該函數對內存空間進行重新分配,ptr指向已有的內存空間,size用來指定重新分配后所得整個空間大小
在使用動態分配之前,首先要判斷是否分配成功。
- 內存的釋放函數原型:
void free(void *ptr); //動態分配的內存使用結束后,要及時釋放,
內存釋放后建議把指針指向NULL
5-2隊列(初始化,入隊,出隊,判斷空,判斷滿)
-
單向隊列
- 循環隊列 (隊頭和隊尾有兩種情況會指向同一位置,一是隊列空了,二是隊列滿了)
#define QueueSize_UartLen 8 typedef struct { int front; //隊頭 int rear; //隊尾 int counter; //記錄隊列元素的個數 int uart_data[QueneSize_UartLen]; //定義數組用來存儲隊列的元素 }CIRQUEUE_UART; //定義結構體,用typedef把結構體重新命名為CIRQUEUE_UART void InitQueue(CIRQUEUE_UART *queue) //初始化形參,CIRQUEUE_UART類型的指針變量queue,隊列的初始化 { queue->front=0; //->與指向結構體變量的指針相連,表示指向結構體變量指針的成員(左邊為指針,注意與 . 的區別) queue->rear=0; queue->counter=0; } int Inqueue(CIRQUEUE_UART *queue,int data) //入隊 { if(QueueFull(queue)) //隊滿判斷 { return 0; //輸出隊滿提示 } else { queue->uart_data[queue->rear]=data; //queue->rear指向隊尾待插入數據位置 queue->counter++; //計數器加1 queue->rear=(queue->rear+1)%QueueSize_UartLen; //然后指queue->rear向下一個待插入數據的位置 return 1; } } int OutQueue(CIRQUEUE_UART *queue,int *p_data) //出隊 通過指針p_data取出隊的數據 { if(QueueEmpty(queue)) { return 0; } else { *p_data=queue->data(front); //先把出隊的數據元素取出放在p_data queue->counter--; //計數器減1 queue->front=(queue->front+1)%QueueSize_UartLen; //然后指queue->front向下一個位置 return 1; } } int QueueEmpty(CIRQUEUE_UART *queue) //判斷隊空 { return queue->count==0; } int QueueFull(CIRQUEUE_UART *queue) //判斷隊滿 { return queue->counter==QueueSize_UartLen; }
-
鏈式隊列
構成鏈表中的每一個存儲節點都包括一個值域和一個指針域,而指針域指向的是下一個存儲節點的,則稱該鏈表為單鏈表,在一個單鏈表中第一個節點的指針稱為表頭指針,最后一個節點被稱為表尾節點,表尾節點的指針域的值為空。
在一個單鏈表中,除表頭節點外,每一個每個結點都由前一個結點的指針域所指向,第一個節點只能有另設的表頭指針所指向。當訪問一個單鏈表時候,只能從表頭指針出發,首先訪問第一個節點,然后由第一個節點的指針域指向第二個節點的地址。
空隊列時,頭指針front和尾指針rear都指向頭結點。
單鏈表只要頭節點就可以訪問所有的鏈表元素,融入隊列的特性,一個指針是不不夠的。隊列刪除(出隊)在對頭,插入(入隊)在隊尾。
頭指針front指向鏈隊的頭結點,每個結點對應一個數據;尾指針rear指向終端結點。
typedef struct LinkNode_t //隊列結點結構 { int data; struct LinkNode_t *next; }LinkNode;
typedef struct LinkPoint_t //隊列鏈表結構 { struct LinkNode_t *front; struct LinkNode_t *rear; }LinkQueue; LinkQueue *queue; LinkNode *node; LinkQueue LinkQueueInit() //初始化,建立一個帶有隊頭的空隊列 { queue_t=(LinkQueue)malloc(sizeof(LinkQueue)); //因為在LinkQueue中定義了一個結構體指針,必須對其進行分配內存。 node=(LinkQueue)malloc(sizeof(LinkNode)); //同上 node->next=NULL; queue_t->front=queue->rear=node; return queue_t; } void InlinkQueue(LinkQueue *queue,int data) //入隊 { node=(LinkNode)malloc(sizeof(LinkNode)); node->data=data; node->next=queue->rear->next; //node->next指向data插入前、尾部指針指向的next的位置(queue->rear->next)。 queue->rear->next=node; //新插入的數據或結點成了新的隊尾,隊尾指針queue->rear->next指向新插入的node結點 queue->rear=node; //最后讓新插入的數據或結點成為新的隊尾。 } void OutQueue(LinkQueue *queue) //出隊 { int data; if(!LQEmpty(queue)) //判空 { node=queue->front->next; queue->front->next=node->next; data=node->data; if(node==queue->rear) //如果只有一個元素,當他出隊時隊列就成了空隊,需要修改為尾部指針 { queue->rear=queue->front; } free(node); return data; } } int LQEmpty(LinkQueue *queue) //對空判斷 { if(queue->front==queue->rear) { return 1; } else { return 0; } }
鏈隊沒有判斷隊滿的情況,實際操作沒有必要,因為鏈隊繼承了動態鏈表的操作。入隊時會進行動態分配內存空間,出隊時,則進行了釋放。
補充:結構體中定義另外一個結構體。
5-3堆棧(初始化,進棧,出棧,棧空的判斷,棧滿的判斷,取棧頂元素)
1.采用指向棧頂、棧底兩個指針時: typedef struct { int top; int data[10]; int bottom; }stack_rf; stack_rf *p_rf_data; // p_rf_data->top-p_rf_data->bottom==10; //判斷棧是否為滿 p_rf_data->top-p_rf_data->bottom==0; //判斷棧是否為空 *(p_rf_data->top-1) //棧頂元素 *(p_rf_data->bottom) //棧底元素 2.只采用棧頂一個指針時: #define Len_Uart 10; typedef struct { int top; int uart_data[Len_Uart]; }stack_uart; void InitStack(stack_uart *stack) { stack->top=0; //棧的初始化 } int PushStack(stack_uart *stack,int data) //進棧 { if(StackFull(stack)) //棧滿判斷 { //輸出棧滿提示 return(0); } else { stack->uart_data[stack->top]=data; stack->top++; return(1); } } int PopStack(stack_uart *stack) //出棧 { int data; if(StackEmpty(stack)) //棧空判斷 { return 0; //輸出棧空提示 } else { stack->top--; data=stack->data[stack->top]; return data; } } int StackEmpty(stack_uart *stack) //棧空判斷 { return(stack->top==0); } int StackFull(stack_uart *stack) //棧滿判斷 { return(stack->top==Len_Uart); } int StackTop(stack_uart *stack,int *p_topdata) //取棧頂元素 { if(StackEmpty()) { //輸出棧空提示 return 0; } else { *p_topdata=stack->uart_data[stack->top-1]; return 1; } }
5-4鏈表(鏈表建立,鏈表初始化,鏈表插入,鏈表刪除)
單鏈表:頭結點沒有值域,只有鏈域。專門存放第一個結點的地址。尾結點,有值域也有鏈域,鏈域始終為NULL。
循環鏈表:由單鏈表演變而來,循環鏈表的尾結點的鏈域指向鏈表頭的結點
兩者區別:(1)單鏈表需要專門創建一個頭結點存放第一個節點的地址,尾部節點鏈域為NULL;循環鏈表不需要,只是最后一個鏈域指向表頭結點。
(2)鏈尾判斷,單鏈表只需要判斷鏈域值是否為NULL;循環鏈表只需要判斷該結點的鏈域值是否指向鏈表頭結點。
typedef struct LinkNode //定義鏈表結點類型 { int data; struct LinkNode *next; }*List; List InitList(void) //鏈表初始化 { List linknode_t; linknode_t=(list)malloc(sozeof(list)); //動態分配內存空間給linknode_t if(!linknode_t) { //輸出失敗提示 return 0; } else { linknode_t->next=linknode_t; return linknode_t; } } List g_DebiList; int main(void) //在main中調用InitList()函數 { g_DevList=InitList(); return 0; } 鏈表的操作 typedef struct DEVICE_PROPERTY_t { int data; struct DEVICE_PROPERTY_t *next; }DEVICE_PROPERTY_t; typedef unsigned char uint8_t; #define DEVICELIST_MAX 5; DEVICE_PROPERTY_t g_RegDeviceList[DEVICELIST_MAX]; DEVICE_PROPERTY_t *g_LastRegDevice; uint8_t g_DeviceCnt=0; voide DevListInit(void) //鏈表初始化 { uint8_t i; for(i=0;i<DEVICELIST_MAX;i++) { g_RegDeviceList[i].next=NULL; } g_lastRegDevice=NULL; g_DeviceCnt=0 } DEVICE_PROPERTY_t *FindVacancy(void) //尋找插入點 { uint8_t i; if(g_DeviceCnt<DEVICELIST_MAX) { for(i=0;i<EEVICELIST_MAX;i++) { if(g_RegDeviceList[i].next==NULL) { return(&g_RegDeviceList[i]); } } return 0; } else { return(NULL); } } uint8_t Insert(uint8_t data) //鏈表插入 { DEVICE_PROPERTY_t *tempptr,*ptr; temptr =FindVacancy(); if(tempptr!=null) { tempptr->data=data; if(!g_DeviceCnt) { tempptr->next=tmpptr; g_ListRegDevice=tempptr; } else { ptr=g_LastRegDevice->next; //暫存原來尾結點的鏈域 g_LastRegDevice->next=tempptr; //讓原來尾結點的鏈域指向新插入的結點,即g_LastRegDevice->next=tempptr; tempptr->next=ptr; //讓新插入的結點成為尾結點,即鏈域指向原來尾結點鏈域所指向的位置 } g_DeviceCnt++; return 1; } return 0; } uint8_t DeleteDevicefromlist(uint8_t data) //鏈表刪除(data即為要刪除的結點) { uint8_t i,j; DEVICE_PROPERTY_t *nextdevice; for(i=0;i<g_DeviceCnt;i++) //通過判斷來指定結點的值域是否是要刪除的值域data { if(g_LastRegDevice->data==data) { nextdevce=g_ListRegDevice->next; //將該結點的鏈域指向的結點指針,也就是將要刪除的結點的下一個結點 g_LastRegDevice->next=NULL; //把該節點的鏈域置為NULL g_DeviceCnt--; g_LastRegDevice=nextdevice; //讓刪除結點的下一個結點成為尾結點(刪除是操作的逆過程) if(g_DeviceCnt) { for(j=0;j<g_DeviceCnt-1;j++) { g_ListRegDevice->next=nextdevice; } g_ListRegDevice->next=nextdevice; } return 1; } g_LastRegDevice=g_LastRegDevice->next; } return 0; }
5-5樹
樹為非線性結構,對於二叉樹首先是二叉樹的創建和遍歷
typedef struct tree_node //運用鏈式存儲結構 { char data; //一個值域兩個鏈域,值域存儲結點本身的數值 struct tree_node *lchild,*rchild; //鏈域分別存儲結點左子樹和又子樹的地址 }BT_Node; BT_Node *tree; //定義一個指針tree,用來執行那個即將建立的樹 BT_Node *Creat_BTree(BT_Node *tree) //創建二叉樹 { char ch; ch=getchar(); //從鍵盤輸入一個字符, if(ch=='*') { tree=NULL; } else //如果不是字符,則分配存儲空間 { tree=(BT_Node *)malloc(Tree_NodeLen); tree->data=ch; tree->lchild=Creat_BTree(tree->lchild); //調用創建函數本身,創建結點的左子樹和又子樹(這是函數的遞歸調用) tree->rchild=Creat_BTree(tree->rchild); } return(tree); }
二叉樹分為:前序遍歷,中序遍歷,后序遍歷
示例:二叉樹3種遍歷方式的應用
#include <stdio.h> #include <malloc.h> typedef struct tree_node { char data; struct tree_node *lchild,*rchild; }BT_Node; #define Tree_NodeLen sizeof(BT_Node) BT_Node *tree; BT_Node *Creat_BTree(BT_Node *tree); void Visit_Node(BT_Node *tree); void Pre_Order(BT_Node *tree); void Mid_Order(BT_Node *tree); void After_Order(BT_Node *tree); int main(void) { printf("請輸入樹節點:\n"); tree=Creat_BTree(tree); if(tree) { printf("\n前序遍歷:\n"); Pre_Order(tree); printf("\n"); printf("\n中序遍歷:\n"); Mid_Order(tree); printf("\n"); printf("\n后序遍歷:\n"); After_Order(tree); printf("\n"); } printf("\n"); return 0; } BT_Node *Creat_BTree(BT_Node *tree) { char ch; ch=getchar(); if(ch=='*') { tree=NULL; } else { tree=(BT_Node *)malloc(Tree_NodeLen); tree->data=ch; tree->lchild=Creat_BTree(tree->lchild); tree->rchild =Creat_BTree(tree->rchild ); } return(tree); } void Visit_Node(BT_Node *tree) { printf(" "); putchar(tree->data); printf("\t"); } void Pre_Order(BT_Node *tree) { if(!tree) { return ; } else { Visit_Node(tree); Pre_Order(tree->lchild ); Pre_Order(tree->rchild ); } } void Mid_Order(BT_Node *tree) { if(!tree) { return; } else { Mid_Order(tree->lchild ); Visit_Node(tree); Mid_Order(tree->rchild ); } } void After_Order(BT_Node *tree) { if(!tree) { return; } else { After_Order(tree->lchild ); After_Order(tree->rchild ); Visit_Node(tree); } }