1.單向鏈表概念以及相應操作:
(1).基本概念:
結構體允許成員類型不相同,解決數組第一個缺陷。鏈表允許大小可變,解決數組第二個缺陷。(該缺點三個解決思路:拆遷,搬遷(c++/java支持),外部擴展(鏈表))。
鏈表就是用來存儲數據的。鏈表用來存數據相對於數組來說優點就是靈活性,需要多少個動態分配多少個,不佔用額外的內存。數組的優勢是使用簡單(簡單粗暴)。
鏈表是由節點組成的,節點中包含:有效數據和指針。指針指向下一節點或者NULL。注意->訪問成員變量,pNext纔是用來連接的指針。
頭指針並不是節點,而是一個普通指針,只佔4字節。頭指針的類型是struct node *類型的,所以它才能指向鏈表的節點。
注意有的鏈表是有頭結點的,和頭指針一起定義;他的有效數據存節點個數或者爲空。
(2).插入:
********在插入一個節點時注意如果新節點先和前面的連接,就無法連接到後續節點了;所以需要先連接後面的結點再連接前一個節點。
(3).遍歷:遍歷是操作順序非常重要。
*********while(NULL !=p->pNext)
{
p=p->pNext;
p->data......
}
注意用這樣遍歷會略過第一個節點(頭節點),最後p指向最後一個節點。
*********while(NULL !=p->pNext)
{
p->data......
p=p->pNext;
}
注意用這樣遍歷不會略過第一個節點(頭節點),會漏掉最後一個節點。
*********while(NULL !=p)
{
p=p->pNext;
p->data......
}
注意用這樣是錯誤用法。
(4).刪除:
*********在找到你要刪除的那個節點後,你就訪問不到他的前一個節點,就不能把他的前一個和後一個連接起來了;此時就需要一個pPrev指針,指向p指向的前一個節點。(在循環內第一句加上pPrev=p即可)
(5).逆序:
採用遍歷+頭插入。把鏈表分爲兩部分:保留一個頭結點和第一個有效節點,用另一個指針指向第二個有效節點依次遍歷並頭插入第一部分(頭結點後,原第一有效節電前)。
#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node * pNext;
};
struct node * creat_node(int data);
void insert_header(struct node *pH,struct node *new);
void insert_tail(struct node *pH,struct node *new);
void link_list_delete(struct node *pH,int data);
void link_list_traverse(struct node *pH);
void link_list_reverse(struct node *pH);
int main(int argc,char **argv[])
{
struct node *pHeader=creat_node(0);
insert_tail(pHeader,creat_node(11));
insert_tail(pHeader,creat_node(22));
insert_tail(pHeader,creat_node(33));
insert_header(pHeader,creat_node(12));
insert_header(pHeader,creat_node(13));
insert_header(pHeader,creat_node(14));
//printf("pHeader->pNext->data is %d\n",pHeader->pNext->data);
link_list_traverse(pHeader);
link_list_delete(pHeader,0);
link_list_reverse(pHeader);
link_list_traverse(pHeader);
return 0;
}
struct node * creat_node(int data)
{
struct node *p=(struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("malloc erro");
return NULL;
}
p->data=data;
p->pNext=NULL;
return p;
}
void insert_header(struct node *pH,struct node *new) //一定要記的pNext纔是用來連接的,->是用來訪問結構體成員的。
{
struct node *p=pH;
new->pNext=p->pNext; //注意在頭部插入需要新節點先和後面的連接,再和前面的連接;要不然先連接前面就訪問不到後面的節點了
p->pNext=new;
pH->data++;
}
void insert_tail(struct node *pH,struct node *new)
{
struct node *p=pH;
while(NULL !=p->pNext) //這樣寫也避免了下面的錯誤。
{
p=p->pNext;
pH->data++;
}
p->pNext=new;
new->pNext=NULL;
}
void link_list_traverse(struct node *pH)
{
struct node *p=pH;
printf("there is %d nodes:\n",pH->data);
/*
while(NULL !=p) //這樣寫,在倒數第二次循環中,當p指向最後一個節點是不爲空;最後一次循環p就是NULL了,data也就沒有了
{
p=p->pNext;
printf("the data is %d\n",p->data);
}
*/
while(NULL !=p->pNext)
{
p=p->pNext; //先前進一步再打印避免打出頭結點。
printf("the data is %d\n",p->data);
} //這裏體現了有頭結點的好處,在這裏這樣寫起來很工整。
}
void link_list_delete(struct node *pH,int data)
{
struct node *p,*pPrev;
p=pH;
pPrev=NULL;
int check_find=0; //把沒找到節點和刪完了節點兩種情況區分開。
while(NULL !=p->pNext)
{
pPrev=p;
p=p->pNext;
if(p->data ==data)
{
if(NULL == p->pNext)
{
pPrev->pNext=NULL;
free(p); //這樣是可以刪除多個data相同的節點,要是隻想刪第一個,加一個return;或break;.
}
else{
pPrev->pNext=p->pNext;
free(p);
}
printf("delete successfully\n");
pH->data--;
check_find=1;
}
}
if(check_find==0)
printf("delete erro ,check if there is the data \n");
}
void link_list_reverse(struct node *pH)
{
struct node *p,*pHold;
p=pH->pNext;
pHold=p;
if(NULL ==p->pNext || NULL == p)
return;
while(NULL !=p->pNext)
{
pHold=pHold->pNext;
if(p==pH->pNext)
p->pNext=NULL;
else
{
p->pNext=pH->pNext;
pH->pNext=p;
}
p=pHold;
}
insert_header(pH,p);//當循環結束時,p指向最後一個節點,還沒出插入前一部分呢。以前都是在循環內先前進一個再處理,所以不會漏掉最後一個,但漏掉第一個。這裏先處理,再前進不會漏第一個,但會漏最後一個。
}
2.雙向鏈表概念及操作:
(1).基本概念:
雙向鏈表的節點 = 有效數據 + 2個指針(一個指向後一個節點,另一個指向前一個節點)
單向鏈表只能由頭指針往後訪問後續節點,雙鏈表就更靈活可以左右移動。
(2).插入:頭部插入,尾部插入:注意還是應該先處理新節點和後續節點的連接,再處理新節點和前節點的連接。
(3).遍歷:
(4).刪除:這裏體現了雙向鏈表的優點,不需要再額外設置一個前向指針。
#include<stdio.h>
#include<stdlib.h>
struct node {
int data;
struct node * pNext;
struct node * pPrev;
};
struct node *create_node(int data);
void insert_header(struct node *pH,struct node *new);
void insert_tail(struct node *pH,struct node *new);
void traverse_forward(struct node *pH);
void traverse_backward(struct node *pT);
int node_delete(struct node *pH,int data);
int main(void)
{
struct node *pHeader=create_node(0);
printf("pHeader->data is %d\n",pHeader->data);
insert_header(pHeader,create_node(23));
insert_header(pHeader,create_node(34));
insert_header(pHeader,create_node(45));
// printf("debug\n:");
/* printf("the node data is %d :\n",pHeader->pNext->data);
printf("the node data is %d :\n",pHeader->pNext->pNext->data);
printf("the node data is %d :\n",pHeader->pNext->pNext->pNext->data);
*/
// traverse_forward(pHeader);
struct node * p1=pHeader->pNext->pNext->pNext;
traverse_backward(p1);
node_delete(pHeader,34);
traverse_backward(p1);
return 0;
}
struct node * create_node(int data)
{
struct node *p=(struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("malloc erro\n");
return NULL;
}
p->data=data;
p->pNext=NULL;
p->pPrev=NULL;
return p;
}
void insert_header(struct node *pH,struct node *new)
{
if(NULL ==pH->pNext)
{
pH->pNext=new;
new->pPrev=pH;
}
new->pNext=pH->pNext; //先鏈接後面的
pH->pNext->pPrev=new;
new->pPrev=pH;
pH->pNext=new; //再連接前面的
pH->data++;
}
void insert_tail(struct node *pH,struct node *new)
{
struct node *p=pH;
while(NULL !=p->pNext)
{
p=p->pNext;
}
pH->data++;
p->pNext=new;
new->pPrev=p;
}
void traverse_forward(struct node *pH)
{
struct node *p=pH;
while(NULL !=p->pNext)
{
p=p->pNext;
printf("the data is %d\n",p->data);
}
}
void traverse_backward(struct node *pT)
{
struct node *p=pT;
while(NULL !=p->pPrev)
{
printf("the node data is %d\n",p->data);
p=p->pPrev;
}
}
int node_delete(struct node *pH,int data)
{
struct node *p=pH;
while(NULL !=p->pNext)
{
p=p->pNext;
if(p->data ==data)
{
if(NULL == p->pNext)
{
p->pPrev->pNext=NULL;
//這了本該有p->pPrev置爲NULL,但是以後free就不用了。但是要有這種想法!!!!
}
else{
p->pNext->pPrev=p->pPrev;
p->pPrev->pNext=p->pNext;
}
free(p);
return 0;
}
}
printf("find erro\n");
return -1;
}
3.linux內核中的鏈表:
(1).實現在include/linux/list.h中,list.h內是一個單純的鏈表封裝,包括節點定義以及相應操作。(函數前有下劃線是說一般不要被別人使用)
(2).在使用鏈表時,我們是把一個鏈表的實例作爲結構體的一個成員,結構體內還有其他的有效信息,以此實現真正的鏈表。
(3).用內核鏈表時實際上通過container_of宏,由結構體成員來實現整個結構體的操作。
4.狀態機:
(1).一般的狀態機是FSM(有限),這個機器會就收外部的信號和輸入,再根據自己的狀態和用戶輸入跳轉到另一狀態。
(2).狀態機分爲
MOORE型:機器狀態只與自己的當前狀態有關(比如當有外部輸入時,就跳到另一狀態)。
MEARLY型:機器狀態和用戶輸入與當前狀態相關。
(3).狀態機的主要用途:電路設計、FPGA程序設計、軟件設計
電路設計中廣泛使用了狀態機思想
FPGA程序設計(就是設計芯片的,比如那些搞時序的)
軟件設計(框架類型的設計,譬如操作系統的GUI系統、消息機制)
(4).狀態機解決了什麼問題
我們平時寫程序都是順序執行的,這種程序有個特點:程序的大體執行流程是既定的,程序的執行是遵照一定的大的方向有跡可尋的。但是偶爾會碰到這樣的程序:外部不一定會按照既定流程來給程序輸入信息,而程序還需要完全能夠接收並響應外部的這些輸入信號,還要能做出符合邏輯的輸出。
C語言實現簡單的狀態機:當自己輸入的密碼爲201417時開鎖,若其中有輸錯則返回初始狀態重新輸入。
#include<stdio.h>
typedef enum{
STATE0,
STATE1,
STATE2,
STATE3,
STATE4,
STATE5,
STATE6,
}state;
int main(void)
{
state current_state=STATE0;
int password=0;
while(1)
{
if(current_state==STATE0)
printf("please enter your password from the beginning:\n");
scanf("%d",&password);
switch(current_state)
{
case STATE0:
if(password==2)
current_state=STATE1;
else
current_state=STATE0;
break;
case STATE1:
if(password==0)
current_state=STATE2;
else
current_state=STATE0;
break;
case STATE2:
if(password==1)
current_state=STATE3;
else
current_state=STATE0;
break;
case STATE3:
if(password==4)
current_state=STATE4;
else
current_state=STATE0;
break;
case STATE4:
if(password==1)
current_state=STATE5;
else
current_state=STATE0;
break;
case STATE5:
if(password==7)
{
current_state=STATE6;
printf("the lock is unlocked");
}
else
current_state=STATE0;
break;
default:
printf("i don't what you are doing,now the lock is locked\n");
current_state=STATE0;
break;
}
if(current_state==STATE6)
break;
}
return 0;
}
到此,C語言專題已結束。。繼續加油吧。。也感謝朱老師的教程,很好懂,但是畢竟因爲只能算入門級了。