…
文章目录
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据
这是百度百科对于 单链表 的解释,通俗来说,单链表 就是一种数据结构,它包含了一个 数据域 data 和一个 指针域 next ,最大的特点就是 链式存储 。链表有很多种,其中 单链表 是最基本、最简单的一种结构,很多OJ题都会利用 单链表 出题,后面的部分高阶数据结构也会用到 单链表 ,因此学好 单链表 很重要。除了 单链表 外,还有 双向带头循环链表 (后面会介绍)等链表类型,先来进入 单链表 的学习吧!

单链表 创建时是一个结构体类型的指针,一开始指向空,只有在经过插入数据后才会有自己的指向 ,因此我们可以根据这一特点,遍历 整个 单链表 ,并输出其中的 数据域 data。

void SLTPrint(const SLT** pphead) //单链表的打印
{
assert(pphead); //不需要断言 *pphead ,因为存在空链表打印的情况,是合法的
printf("\n\n当前链表为: ");
const SLT* cur = *pphead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next; //cur要向后移动
}
printf("NULL\n\n\n"); //链表最后为空
}
销毁 单链表 也需要将其 遍历 一遍,因为链表中的元素不是连续存放的,只能逐个节点销毁 ,销毁思想为:利用 *pphead 遍历整个 单链表 ,保存头节点 *pphead 的下一个节点信息至 cur,释放原头节点,更新头节点信息(把 cur 的值赋给头节点 *pphead )如此重复,直到释放完所有节点即可。

//不带哨兵位的单链表不需要初始化
void SLTDestroy(SLT** pphead) //单链表的销毁
{
assert(pphead); //一二级指针都不能为空
//空表不走销毁程序,但不能报错
if (*pphead)
{
while (*pphead)
{
SLT* cur = (*pphead)->next; //记录头的下一个位置
free(*pphead);
*pphead = cur; //向后移动,不断销毁
}
}
}
单链表 是由一个一个独立存在的节点组成的结构,当涉及插入操作时,向内存申请节点,涉及删除操作时,就要把对应的节点销毁
static SLT* buyNode(const SLTDataType x) //买节点
{
SLT* newnode = (SLT*)malloc(sizeof(SLT));
assert(newnode); //防止申请失败的情况
newnode->data = x;
newnode->next = NULL;
return newnode; //返回买好的节点
}
单链表 尾插是比较费劲的,因为得先通过头节点指针向后 遍历 找到尾节点,然后将尾节点与新节点之间建立链接关系,其中还得分情况尾插
关于
单链表中函数用二级指针的问题:
插入或删除时,如果是第一次操作,需要对头节点本身造成改变,且头节点是一个一级指针,因此需要通过二级指针的方式来在函数中改变头节点的值。至于后续的操作,都只是改变了结构体中的next值,因此使用一级指针就够了,但是为了函数设计时的普适性,单链表中的函数参数都设计成了二级指针的形式。

void SLTPushBack(SLT** pphead, const SLTDataType x) //尾插
{
assert(pphead);
SLT* newnode = buyNode(x);
SLT* tail = *pphead;
//尾插分情况,链表为空,直接赋值,不为空,找到尾,再链接
if (tail == NULL)
{
*pphead = tail = newnode; //直接赋值
}
else
{
while (tail->next != NULL)
{
tail = tail->next; //找到尾节点
}
tail->next = newnode; //链接
}
//SLTInsert(pphead, NULL, x); //可以复用任意位置前插
}
尾删操作与尾插基本一致,同样是需要找到尾节点,不过每次 tail 指针在向后移动前,会先使用一个 prev 指针保存 tail 的信息,当 tail 为尾节点时,释放 tail ,并将 prev->next 置空,此时的 prev 就是新的尾节点,因为原理都差不多,这里就不用动图展示了,值得注意的是尾删也分两种情况


void SLTPopBack(SLT** pphead) //尾删
{
assert(pphead);
assert(*pphead); //如果链表为空,是删不了的
SLT* tail = *pphead;
SLT* prev = NULL;
while (tail->next)
{
prev = tail; //保存上一个节点信息
tail = tail->next; //找到尾节点
}
free(tail);
//分情况,如果prev是空,说明删除的尾节点同时也是头节点
if (prev)
prev->next = tail = NULL; //把尾节点的上一个节点指向空
else
*pphead = NULL; //此时直接把prev置空
/*SLT* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
SLTErase(pphead, tail);*/ //可以复用任意位置删除
}
对于头部操作来说,单链表 是很轻松的,比如 单链表 头插的本质就是将 新节点 newnode 与 头节点 *pphead 链接,然后更新头节点信息就行了,即 *pphead = newnode ,三行代码就解决了。


void SLTPushFront(SLT** pphead, const SLTDataType x) //头插
{
assert(pphead);
SLT* newhead = buyNode(x);
newhead->next = *pphead; //直接链接
*pphead = newhead; //更新头节点信息
//代码简洁之道
//SLTInsert(pphead, *pphead, x); //可以复用任意位置前插
}
头删也是比较简单的,先用 cur 指向头节点,先保存 头节点 cur 的下一个节点信息至 节点 newhead,释放 原头节点 cur,更新 newhead 为链表的新头,即 *pphead = newnode 当然涉及删除的操作,都需要进行表空检查,如果链表为空,是不能执行删除的。


void SLTPopFront(SLT** pphead) //头删
{
assert(pphead);
assert(*pphead); //头删同样存在空不能删的情况
//先找到头的下一个节点,然后赋值新头
SLT* cur = *pphead; //指向头节点
SLT* newhead = cur->next; //保存头节点的下一个节点信息
free(cur);
cur = NULL;
*pphead = newhead; //赋值新头
//SLTErase(pphead, *pphead); //可以复用任意位置删除
}
查找函数很简单,遍历+比较 就行了,找到目标元素值后,返回相关节点信息,其实查找这个函数主要是为了配合后面任意插入删除函数的 ,所以比较简单。
SLT* SLTFind(const SLT** pphead, const SLTDataType x) //查找值为x的节点(第一次出现)
{
assert(pphead);
SLT* cur = (SLT*)*pphead; //指向头节点
while (cur)
{
if (cur->data == x)
return cur; //找到了,返回相关节点信息
cur = cur->next;
}
return NULL; //没有找到的情况
}
修改函数是在查找函数基础上进行的:当我们输入元素值交给查找函数,找到目标节点后返回其节点信息,然后直接通过返回的节点改变 data 值就行了,在调用查找函数的前提下,一行代码就解决了。
void SLTModify(SLT* node, const SLTDataType val) //修改 node 节点处的值
{
//注意:在调用函数时,可以通过链式访问,将查找函数的返回值作为形参一传入就行了
assert(node); //断言,如果节点node是空指针,是不能做修改的
node->data = val;
}
简单版是在任意位置后插入元素,删除任意位置后的节点,根据 单链表 的特性,对后面节点进行操作是比较简单,无非就是改变链接关系。但是这种对后操作存在缺陷:不适合实现头插、头删
插入(后插)主要分两步
获取信息:有三个关键信息:被插入节点
cur、待插入节点newnode和cur的下一个节点tail
改变链接关系:很简单,
cur->next = newnode,后插嘛,先把待插入节点链接到cur后面,然后再把newnode和tail链接起来就行了
这里的 nodeAfter 是需要通过查找函数找出来的,它是一个 一级指针
//这两个有缺陷,不能对头节点进行操作,但实现起来比较简单
void SLTInsertAfter(SLT* nodeAfter, const SLTDataType x) //任意插,后插法
{
assert(nodeAfter);
SLT* cur = nodeAfter;
SLT* newnode = buyNode(x);
SLT* tail = cur->next; //先保存被插入节点的下一个节点信息
//更改链接关系,后插完成
cur->next = newnode;
newnode->next = tail;
}
删除思路,和头删差不多
curcur 的下一个节点 tailtail 的下一个节点 newtail接下来就很简单了,释放 tail 节点,更改链接关系,当然断言是少不了


void SLTEraseAfter(SLT* nodeAfter) //任意删,删除后面节点
{
assert(nodeAfter);
assert(nodeAfter->next); //如果目标节点的下一个为空,是后删不了的
SLT* cur = nodeAfter;
SLT* tail = cur->next; //待删除的节点
SLT* newtail = tail->next; //新尾
free(tail);
tail = NULL;
cur->next = newtail; //更改链接关系
}
困难版就比较麻烦了,因为它要实现的是任意位置前插元素、删除任意位置的节点,单链表 的最大缺点是不能回退,这就意味着即使我们得到了待删除节点,也是很难求出它的上一个节点的 ,对于这种尴尬情况,只能老老实实从头节点处开始向后 遍历 寻找,直到找到待删除节点的上一个节点。
其实这个也没有多困难,无非就是比上一种多个参数,然后多了一步遍历操作而已,内核思想任然不变

//这两个实现起来比较麻烦,但是很全能
void SLTInsert(SLT** pphead, SLT* node, const SLTDataType x) //任意插,前插法
{
assert(pphead);
SLT* newnode = buyNode(x);
SLT* cur = *pphead;
SLT* prev = NULL;
while (cur != node)
{
prev = cur; //要找到目标节点的上一个节点
cur = cur->next;
}
//判断一下,是否为空表插入
//走的是尾插的那一套思想
if (prev)
{
prev->next = newnode;
newnode->next = node;
}
else
{
newnode->next = node;
*pphead = newnode; //空表需要更新头节点信息
}
}
删除是一样的逻辑,不过多了一个 tail 而已,指向的位置是 node 的下一个节点,其余步骤跟插入基本一致,之后也是一样的更改链接关系,一样需要判断是否为空表,如果为空表需要更新头节点信息。
其实现在看来,困难版的插入删除,就像是尾部插入删除的升级版,有些麻烦,但很可靠,困难版的任意位置插入删除可以完全替代头尾插入删除,也就是说写这一对函数就够了。
void SLTErase(SLT** pphead, SLT* node) //任意删,删除当前节点
{
assert(pphead);
assert(node); //不必检查*pphead的合法性,查验node就行了
SLT* cur = *pphead; //走的是尾删的那一套思想
SLT* prev = NULL;
SLT* tail = node->next;
while (cur != node)
{
prev = cur;
cur = cur->next;
}
free(node);
//跟尾插一样,需要判断一下
if (prev)
prev->next = tail;
else
*pphead = tail;
}
代码放一起看,会更好理解一些~
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<windows.h>
typedef int SLTDataType; //单链表的数据类型
typedef struct SListNode
{
SLTDataType data; //数据域
struct SListNode* next; //指针域
}SLT; //重命名为 SLT
void SLTDestroy(SLT** pphead); //单链表的销毁
void SLTPrint(const SLT** pphead); //单链表的打印
void SLTPushBack(SLT** pphead, const SLTDataType x); //尾插
void SLTPopBack(SLT** pphead); //尾删
void SLTPushFront(SLT** pphead, const SLTDataType x); //头插
void SLTPopFront(SLT** pphead); //头删
//这两个有缺陷,不能对头节点进行操作,但实现起来比较简单
void SLTInsertAfter(SLT* nodeAfter, const SLTDataType x); //任意插,后插法
void SLTEraseAfter(SLT* nodeAfter); //任意删,删除后面节点
//这两个实现起来比较麻烦,但是很全能
void SLTInsert(SLT** pphead, SLT* node, const SLTDataType x); //任意插,前插法
void SLTErase(SLT** pphead, SLT* node); //任意删,删除当前节点
SLT* SLTFind(const SLT** pphead, const SLTDataType x); //查找值为x的节点(第一次出现)
void SLTModify(SLT* node, const SLTDataType val); //修改 node 节点处的值
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//不带哨兵位的单链表不需要初始化
void SLTDestroy(SLT** pphead) //单链表的销毁
{
assert(pphead); //一二级指针都不能为空
//空表不走销毁程序,但不能报错
if (*pphead)
{
while (*pphead)
{
SLT* cur = (*pphead)->next; //记录头的下一个位置
free(*pphead);
*pphead = cur; //向后移动,不断销毁
}
}
}
void SLTPrint(const SLT** pphead) //单链表的打印
{
assert(pphead); //不需要断言 *pphead ,因为存在空链表打印的情况,是合法的
printf("\n\n当前链表为: ");
const SLT* cur = *pphead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next; //cur要向后移动
}
printf("NULL\n\n\n"); //链表最后为空
}
static SLT* buyNode(const SLTDataType x) //买节点
{
SLT* newnode = (SLT*)malloc(sizeof(SLT));
assert(newnode); //防止申请失败的情况
newnode->data = x;
newnode->next = NULL;
return newnode; //返回买好的节点
}
void SLTPushBack(SLT** pphead, const SLTDataType x) //尾插
{
assert(pphead);
SLT* newnode = buyNode(x);
SLT* tail = *pphead;
//尾插分情况,链表为空,直接赋值,不为空,找到尾,再链接
if (tail == NULL)
{
*pphead = tail = newnode; //直接赋值
}
else
{
while (tail->next != NULL)
{
tail = tail->next; //找到尾节点
}
tail->next = newnode; //链接
}
//SLTInsert(pphead, NULL, x); //可以复用任意位置前插
}
void SLTPopBack(SLT** pphead) //尾删
{
assert(pphead);
assert(*pphead); //如果链表为空,是删不了的
SLT* tail = *pphead;
SLT* prev = NULL;
while (tail->next)
{
prev = tail; //保存上一个节点信息
tail = tail->next; //找到尾节点
}
free(tail);
//分情况,如果prev是空,说明删除的尾节点同时也是头节点
if (prev)
prev->next = tail = NULL; //把尾节点的上一个节点指向空
else
*pphead = NULL; //此时直接把prev置空
/*SLT* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
SLTErase(pphead, tail);*/ //可以复用任意位置删除
}
void SLTPushFront(SLT** pphead, const SLTDataType x) //头插
{
assert(pphead);
SLT* newhead = buyNode(x);
newhead->next = *pphead; //直接链接
*pphead = newhead; //更新头节点信息
//代码简洁之道
//SLTInsert(pphead, *pphead, x); //可以复用任意位置前插
}
void SLTPopFront(SLT** pphead) //头删
{
assert(pphead);
assert(*pphead); //头删同样存在空不能删的情况
//先找到头的下一个节点,然后赋值新头
SLT* cur = *pphead; //指向头节点
SLT* newhead = cur->next; //保存头节点的下一个节点信息
free(cur);
cur = NULL;
*pphead = newhead; //赋值新头
//SLTErase(pphead, *pphead); //可以复用任意位置删除
}
//这两个有缺陷,不能对头节点进行操作,但实现起来比较简单
void SLTInsertAfter(SLT* nodeAfter, const SLTDataType x) //任意插,后插法
{
assert(nodeAfter);
SLT* cur = nodeAfter;
SLT* newnode = buyNode(x);
SLT* tail = cur->next; //先保存被插入节点的下一个节点信息
//更改链接关系,后插完成
cur->next = newnode;
newnode->next = tail;
}
void SLTEraseAfter(SLT* nodeAfter) //任意删,删除后面节点
{
assert(nodeAfter);
assert(nodeAfter->next); //如果目标节点的下一个为空,是后删不了的
SLT* cur = nodeAfter;
SLT* tail = cur->next; //待删除的节点
SLT* newtail = tail->next; //新尾
free(tail);
tail = NULL;
cur->next = newtail; //更改链接关系
}
//这两个实现起来比较麻烦,但是很全能
void SLTInsert(SLT** pphead, SLT* node, const SLTDataType x) //任意插,前插法
{
assert(pphead);
SLT* newnode = buyNode(x);
SLT* cur = *pphead;
SLT* prev = NULL;
while (cur != node)
{
prev = cur; //要找到目标节点的上一个节点
cur = cur->next;
}
//判断一下,是否为空表插入
//走的是尾插的那一套思想
if (prev)
{
prev->next = newnode;
newnode->next = node;
}
else
{
newnode->next = node;
*pphead = newnode; //空表需要更新头节点信息
}
}
void SLTErase(SLT** pphead, SLT* node) //任意删,删除当前节点
{
assert(pphead);
assert(node); //不必检查*pphead的合法性,查验node就行了
SLT* cur = *pphead; //走的是尾删的那一套思想
SLT* prev = NULL;
SLT* tail = node->next;
while (cur != node)
{
prev = cur;
cur = cur->next;
}
free(node);
//跟尾插一样,需要判断一下
if (prev)
prev->next = tail;
else
*pphead = tail;
}
SLT* SLTFind(const SLT** pphead, const SLTDataType x) //查找值为x的节点(第一次出现)
{
assert(pphead);
SLT* cur = (SLT*)*pphead; //指向头节点
while (cur)
{
if (cur->data == x)
return cur; //找到了,返回相关节点信息
cur = cur->next;
}
return NULL; //没有找到的情况
}
void SLTModify(SLT* node, const SLTDataType val) //修改 node 节点处的值
{
//注意:在调用函数时,可以通过链式访问,将查找函数的返回值作为形参一传入就行了
assert(node); //断言,如果节点node是空指针,是不能做修改的
node->data = val;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void menu()
{
printf("0.退出 1.打印\n");
printf("2.尾插 3.尾删\n");
printf("4.头插 5.头删\n");
printf("6.任意插(后插) 7.任意删(后删)\n");
printf("8.任意插(前插) 9.任意删(当前)\n");
printf("10.查找 11.修改\n");
}
int main()
{
SLT* s = NULL;
int input = 1;
while (input)
{
menu();
printf("请选择:>");
scanf("%d", &input);
system("cls"); //清屏函数,让显示效果更好
int val = 0; //待插入值
int pos = 0; //待查找节点值
switch (input)
{
case 0:
printf("成功退出\n");
break;
case 1:
SLTPrint(&s);
break;
case 2:
printf("请输入一个数:>");
scanf("%d", &val);
SLTPushBack(&s, val);
break;
case 3:
SLTPopBack(&s);
break;
case 4:
printf("请输入一个数:>");
scanf("%d", &val);
SLTPushFront(&s, val);
break;
case 5:
SLTPopFront(&s);
break;
case 6:
printf("请输入被插入的节点值:>");
scanf("%d", &pos);
printf("请输入一个数:>");
scanf("%d", &val);
SLTInsertAfter(SLTFind(&s, pos), val);
break;
case 7:
printf("请输入被删除的节点值:>");
scanf("%d", &pos);
SLTEraseAfter(SLTFind(&s, pos));
break;
case 8:
printf("请输入被插入的节点值:>");
scanf("%d", &pos);
printf("请输入一个数:>");
scanf("%d", &val);
SLTInsert(&s, SLTFind(&s, pos), val);
break;
case 9:
printf("请输入被删除的节点值:>");
scanf("%d", &pos);
SLTErase(&s, SLTFind(&s, pos));
break;
case 10:
printf("请输入被查找的节点值:>");
scanf("%d", &pos);
SLT* tmp = SLTFind(&s, pos);
printf("当前节点的地址为:%p\n", tmp);
break;
case 11:
printf("请输入被修改的节点值:>");
scanf("%d", &pos);
printf("请输入一个数:>");
scanf("%d", &val);
SLTModify(SLTFind(&s, pos), val);
break;
default :
printf("选择错误,重新选择!\n");
break;
}
}
SLTDestroy(&s); //每次程序运行结束,都会执行销毁函数
return 0;
}
下面是几道关于 单链表 的OJ试题,可以试着解决一下,加强对链表的认识
以上就是关于 单链表 的全部内容了,单链表 中用到了 二级指针 这个东西,如果使用带哨兵位的 单链表 就可以不用 二级指针 ,但是这种结构用的比较少,单纯的学好 单链表 ,能快速提高我们对链表的认识,毕竟链表这个工具后续还会用到。从文中可以看出,单链表 相对于 顺序表 ,不用考虑空间问题,且头插头删效率很高,可惜 单链表 不支持下标的随机访问。总之,顺序表 和 单链表 各有各的用途,二者相辅相成,都是很不错的数据结构。
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正

…
相关文章推荐
数据结构 | 顺序表
数据结构 | 时间复杂度与空间复杂度
C语言进阶——动态内存管理
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,