《朱老师物联网大讲堂》学习笔记
学习地址:www.zhulaoshi.org
(1).
链表的引入,
数组是最简单的数据结构,
还有很多种数据结构,用于解决不同的问题,
数组,缺陷,元素类型一致,数据个数事先指定且不能改变,
结构体可以存储不同类型元素,
链表,可以这样理解,它是一个元素个数可以实时改变的数组,
大学校区扩展的例子,不错~
拆迁,外部扩展,搬迁,
链表,外部扩展,动态,
表之间用锁链连接起来,
表,节点,存储的数据,链表的各个节点是完全类似的,
链,指针,
时刻记住,链表是用来解决数组的大小不能动态扩展的问题,
链表能完成的任务,数组也能完成,
数组能完成的任务,链表也能完成,
不同的是,实现的灵活性不一样,特点不一样,
(2).
struct node
{
int data;
struct node *pNext;
};
//这是一个模板,
堆内存的申请和使用,
why 是堆?
不能用栈,它是FILO,不能实现链表灵活增删的特点,
不能用data数据段,它是确定的,更谈不上链表灵活性的实现,
最终我们选择使用堆内存,
先申请堆内存,
然后清理空间,
把申请到的内存当作新节点,
填充新节点数据和指针域,
struct node *p1 = (struct node *)malloc(sizeof(struct node));
bzero( p, sizeof( struct node ) );
(*p).data = 10;
(*p).pNext = NULL;
链表的头指针,其本身不是节点,只是一个普通指针,指向链表的节点,
struct node *pHeader = NULL;
如何将头指针指向第一个节点呢?
pHeader = p1;
如果再弄多个节点,
(*p1).pNext = p2;
依此类推即可,
(3)(4).
上节是为了学习才有p1,p2...
后面就只有pHeader,依次访问各个节点的pNext,
封装成函数,关键是函数的接口,也就是参数和返回值的设计,
struct node * creat_node( int d );
{
struct node *p = (struct node *)malloc(sizeof(struct node));
if( NULL == p )
{
printf("malloc error\n");
return -1;
}
bzero( p, sizeof( struct node ) );
(*p).data = d;
(*p).pNext = NULL;
return p;
};
void insert_tail(struct node *pH, struct node *new )
{
struct node *p = pH;
while( NULL != p->pNext )
{
p = p->pNext;
}
p->pNext = new ;
}
(5).
(6).
(7).
(8).
(9).
(10).
(11).
(12).
(13).
linux内核链表,
单链表移动方向的局限性,所以发明双向链表,
双向链表的数据区域有局限性,所以怎么办呢?
实际项目中,链表节点中的数据是一个结构体,这个结构体中的数据类型是各种各样的,
因为各种各样,所以我们无法通过一个普遍适用的操作函数来访问所有的链表,
如果是这样,就意味着我们设计一个链表,就得为其写一套链表的基本操作函数,
不过你有没发现,这些不同链表的操作方法思路是一样的,只是涉及到数据部分的操作不同,
于是,我们可以这样,把链表里面共同的部分提取出来成为一套标准的实现方法,把不同的部分留着让具体链表的实现者自己去处理,
这也是linux内核链表的设计思路,
内核中实现了一个纯链表的封装,包括了各种操作函数,但是又不涉及数据区,
实现在include/linux/list.h中,纯链表的完整封装,包含节点定义和各种链表操作方法,
(14).
__list_add,
_list_add,
list_add,
的区别,给你个关键词提醒你,内核,
以下是朱老师编写的用于讲解内核链表实现及使用的实例代码,
#include <linux/list.h>
struct driver_info
{
int data;
};
// driver结构体用来管理内核中的驱动
struct driver
{
char name[20]; // 驱动名称
int id; // 驱动id编号
struct driver_info info; // 驱动信息
struct list_head head; // 内嵌的内核链表成员
};
struct driver2
{
char name[20]; // 驱动名称
int id; // 驱动id编号
struct driver_info info; // 驱动信息
//struct list_head head; // 内嵌的内核链表成员
struct driver *prev;
struct driver *next;
};
// 分析driver结构体,可知:前三个成员都是数据区域成员(就是我们之前简化为int data的东西),第4个成员是一个struct list_head类型的变量,这就是一个纯链表。
// 本来driver结构体是没有链表的,也无法用链表来管理。但是我们driver内嵌的head成员本身就是一个纯链表,所以driver通过head成员给自己扩展了链表的功能。
// driver通过内嵌的方式扩展链表成员,本身不只是有了一个链表成员,关键是可以通过利用list_head本身事先实现的链表的各种操作方法来操作head。
// 最终效果:我们可以通过遍历head来实现driver的遍历;遍历head的函数在list.h中已经事先写好了,所以我们内核中去遍历driver时就不用重复去写了。
// 通过操作head来操作driver,实质上就是通过操作结构体的某个成员变量来操作整个结构体变量。这里面要借助container_of宏,
(15).
状态机,一套机制,
有限状态机FSM,指的是有有限个状态,状态变量对应一个值,
这个机器能够从外部接受信号和信息输入,机器在接受到外部输入的信号后会综合考虑当前自己的状态和用户输入的信息,然后机器做出动作,跳转到另一个状态,
外部输入触发状态机的改变,
数学是原理上的指引,
Moore摩尔型状态机,输出只与当前状态有关,
Mealy型状态机,和当前状态和输入条件都有关,进而决定跳转到哪个状态,
状态机主要用于,电路设计,FPGA程序设计,
软件设计中用的少,甚至你都没有机会去看到,比如GUI系统可能会用到,
状态机解决了什么问题,
我们平时写程序都是顺序执行的,顶多小范围内循环判断,这些都是有迹可循的,
常规程序是1,2,3,4,5,而非常规程序是5,3,2,4,1,
(16).
密码输错,删除或重置,来重新输入,
下面是朱老师实现的状态机程序,
#include <stdio.h>
// 给状态机定义状态集
typedef enum
{
STATE1,
STATE2,
STATE3,
STATE4,
STATE5,
STATE6,
STATE7,
}STATE;
int main(void)
{
int num = 0;
// current_state记录状态机的当前状态,初始为STATE1,用户每输入一个正确的
// 密码STATE就走一步,一直到STATE为STATE7后锁就开了;其中只要有一次用户
// 输入对不上就回到STATE1.
STATE current_state = STATE1; // 状态机初始状态为STATE1
// 第一步:实现一个用户循环输入密码的循环
printf("请输入密码,密码正确开锁.\n");
while (1)
{
scanf("%d", &num);
printf("num = %d.\n", num);
// 在这里处理用户的本次输入
switch (current_state)
{
case STATE1:
if (num == 1)
{
current_state = STATE2; // 用户输入对了一步,STATE走一步
}
else
{
current_state = STATE1;
}
break;
case STATE2:
if (num == 2)
{
current_state = STATE3; // 用户输入对了一步,STATE走一步
}
else
{
current_state = STATE1;
}
break;
case STATE3:
if (num == 3)
{
current_state = STATE4; // 用户输入对了一步,STATE走一步
}
else
{
current_state = STATE1;
}
break;
case STATE4:
if (num == 4)
{
current_state = STATE5; // 用户输入对了一步,STATE走一步
}
else
{
current_state = STATE1;
}
break;
case STATE5:
if (num == 5)
{
current_state = STATE6; // 用户输入对了一步,STATE走一步
}
else
{
current_state = STATE1;
}
break;
case STATE6:
if (num == 6)
{
current_state = STATE7; // 用户输入对了一步,STATE走一步
}
else
{
current_state = STATE1;
}
break;
default:
current_state = STATE1;
}
if (current_state == STATE7)
{
printf("锁开了.\n");
break;
}
}
return 0;
}
(17).
多线程,并行,
单核CPU微观上一定是串行的,不可能是并行的,
多核CPU微观上才能实现并行,
进程和线程都是为了实现宏观上的并行,
linux中进程和线程差异不大,linux中的线程就是轻量级的进程,
windows中差异比较大,
现在多线程用的多些,
这与现在多核心cpu的设计有关,操作系统会优先将多个线程放在多个核心中分别单独运行,
线程同步和锁,