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语言专题已结束。。继续加油吧。。也感谢朱老师的教程,很好懂,但是毕竟因为只能算入门级了。