深入学习 Arduino LinkedList库(一个变长的集合类数组)

授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。

快速导航
单片机菜鸟的博客快速索引(快速找到你要的)

如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。

1.前言

博主是做Android App开发出身的,所以对Java这门语言算是有点了解。而在Java中,我经常使用到List这个集合类(所以集合,博主理解为一系列同类对象集中在一起的入口)。那么,在Arduino中是否也有这样类似于Java 的List用法呢?这就是本篇内容的重点 —— Arduino LinkedList

2.LinkedList

2.1 LinkedList源码地址

  • 直接点击 这里 下载代码

2.2 LinkedList是什么

  • 一系列 同类对象的集合(这点属性和数组一样)
  • 可以动态添加或者删除对象,长度不固定(这点属性就是和数组非常大的区别,数组都是分配固定大小的内存空间,当你考虑动态长度的时候可以考虑这个库
  • 支持对象为 int, float, objects, Lists

2.3 安装LinkedList

  • 可以通过Arduino IDE库管理器安装LinkedList,搜索“LinkedList”,然后点击下载(适合新手小白,直接上手使用)
    在这里插入图片描述
  • 也可以直接通过github下载源码,放到你的Arduino libraries文件夹

2.4 LinkedList工作原理

  • 工作原理非常简单,就是构造链表,每个节点相连形成一条链,这样可以单独添加节点或者删除节点(请读者自行就查阅链表的数据结构)
    在这里插入图片描述

  • LinkedList源码中定义的节点结构

template<class T> // 泛型
struct ListNode
{
	T data; // 每个节点对应的泛型数据
	ListNode<T> *next;// 每个节点的下一个节点,这样就形成了链表
};

请记住关键的一点,除了最后一个节点,其他节点都会包含下一个节点的地址。

2.5 如何引入 LinkedList库

非常简单,直接引入:

#include <LinkedList.h>

至于怎么样去彻底了解这个库的使用方法,请看博主下面的源码分析。

3.LinkedList源码解析

通过查阅WifiManager的源码,整体代码非常简单,提供出来的方法调用也不多。这里博主就一个个方法详细讲解,并且深入去分析代码。方法分为几类:

  • 初始化方法
  • 添加类方法
  • 删除类方法
  • 其他方法

博主建议:先了解有什么方法,然后在后面例子讲解中去感受方法的使用

3.1 初始化方法

简而言之,就是怎么创建构造函数。

3.1.1 LinkedList —— 初始化构造函数

构造函数有两个,一个不初始化集合中的对象,一个对集合中的对象进行默认初始化。

函数1说明

// 初始化所有的原始默认值,包括头尾节点,集合大小,上一个节点
template<typename T>
LinkedList<T>::LinkedList()
{
	root=NULL;// 头结点
	last=NULL;// 尾结点
	_size=0;// 集合大小

	lastNodeGot = root; // 上一个节点
	lastIndexGot = 0;  // 上一个节点对应的索引
	isCached = false;  //这个暂时没知道怎么用
}

函数2说明

// 初始化集合的数据 从0-(sizeIndex-1)赋值为 _t
template<typename T>
LinkedList<T>::LinkedList(int sizeIndex, T _t){
	for (int i = 0; i < sizeIndex; i++){
		add(_t);
	}
}

个人习惯用函数1.

3.2 添加类方法

添加类方法,也就是往链表添加对象,长度+1。

3.2.1 add —— 添加对象到某个位置

针对添加位置的不同,又区分为两个方法。

函数1说明

template<typename T>
bool LinkedList<T>::add(T _t){

    // 创建一个新的节点
	ListNode<T> *tmp = new ListNode<T>();
	tmp->data = _t;
	tmp->next = NULL;
	// 判断是否已经有对象插入
	if(root){
		// Already have elements inserted
		last->next = tmp;
		last = tmp;
	}else{
		// 第一个对象
		root = tmp;
		last = tmp;
	}
    // 集合大小加1
	_size++;
	isCached = false;

	return true;
}

函数2说明

template<typename T>
bool LinkedList<T>::add(int index, T _t){
    // index超过size,直接添加到表尾
	if(index >= _size)
		return add(_t);
    // index=0 插入表头
	if(index == 0)
		return unshift(_t);
    // 创建一个新的节点
	ListNode<T> *tmp = new ListNode<T>(),
	             // 获取index-1这个节点,也就是插入点的位置
				 *_prev = getNode(index-1);
	// 断开链表 并且把新节点插入对应点 连接链表			 
	tmp->data = _t;
	tmp->next = _prev->next;
	_prev->next = tmp;

	_size++;
	isCached = false;

	return true;
}

这里用到了一个 获取节点 的方法 getNode

template<typename T>
ListNode<T>* LinkedList<T>::getNode(int index){

	int _pos = 0;
	ListNode<T>* current = root;

	// Check if the node trying to get is
	// immediatly AFTER the previous got one
	if(isCached && lastIndexGot <= index){
		_pos = lastIndexGot;
		current = lastNodeGot;
	}

    // 找到对应的索引位置
	while(_pos < index && current){
		current = current->next;

		_pos++;
	}

	// Check if the object index got is the same as the required
	if(_pos == index){
	    // 找到对应的节点内容 返回节点
		isCached = true;
		lastIndexGot = index;
		lastNodeGot = current;

		return current;
	}

	return NULL;
}
3.2.2 unshift —— 添加对象到链表头部位置

函数说明

template<typename T>
bool LinkedList<T>::unshift(T _t){
    // 链表为空直接插入
	if(_size == 0)
		return add(_t);

    // 设置当前节点的下一个节点是原来的root节点,这样就插入到头部了
	ListNode<T> *tmp = new ListNode<T>();
	tmp->next = root;
	tmp->data = _t;
	root = tmp;
	
	_size++;
	isCached = false;
	
	return true;
}

3.3 删除类方法

删除类方法,也就是链表删除对象,长度-1。

3.3.1 remove —— 移除某个位置的对象,并且返回该对象

函数说明

template<typename T>
T LinkedList<T>::remove(int index){
    // 如果index超出了范围 直接返回一个空对象
	if (index < 0 || index >= _size)
	{
		return T();
	}
    // index=0 返回头部对象
	if(index == 0)
		return shift();
	// 返回尾部对象
	if (index == _size-1)
	{
		return pop();
	}

    // 取出对象后 还是重新构建链表
	ListNode<T> *tmp = getNode(index - 1);
	ListNode<T> *toDelete = tmp->next;
	T ret = toDelete->data;
	tmp->next = tmp->next->next;
	// 删除对象 释放内存
	delete(toDelete);
	_size--;
	isCached = false;
	return ret;
}
3.3.2 pop —— 移除链表尾部的对象

函数说明

template<typename T>
T LinkedList<T>::pop(){
    // 空集合 就返回空对象
	if(_size <= 0)
		return T();
	
	isCached = false;
    // 多个元素 就获取最后一个元素
	if(_size >= 2){
		ListNode<T> *tmp = getNode(_size - 2);
		T ret = tmp->next->data;
		// 释放内存
		delete(tmp->next);
		tmp->next = NULL;
		last = tmp;
		_size--;
		return ret;
	}else{
	    //只有一个头元素
		// Only one element left on the list
		T ret = root->data;
		delete(root);
		root = NULL;
		last = NULL;
		_size = 0;
		return ret;
	}
}
3.3.3 shift —— 移除链表头部的对象

函数说明

template<typename T>
T LinkedList<T>::shift(){
    // 空集合 返回空对象
	if(_size <= 0)
		return T();
    // 删除头对象 并且把下一个对象设置为头对象
	if(_size > 1){
		ListNode<T> *_next = root->next;
		T ret = root->data;
		delete(root);
		root = _next;
		_size --;
		isCached = false;

		return ret;
	}else{
		// Only one left, then pop()
		return pop();
	}

}
3.3.4 clear —— 清除整个链表

函数说明

template<typename T>
void LinkedList<T>::clear(){
	while(size() > 0)
		shift();
}

3.4 其他方法

3.4.1 set —— 设置某个位置的对象

函数说明

template<typename T>
bool LinkedList<T>::set(int index, T _t){
	// 判断index是否在有效范围
	if(index < 0 || index >= _size)
		return false;

	getNode(index)->data = _t;
	return true;
}
3.4.2 get —— 获取某个位置的对象

函数说明

template<typename T>
T LinkedList<T>::get(int index){
	ListNode<T> *tmp = getNode(index);

	return (tmp ? tmp->data : T());
}
3.4.3 sort —— 对集合进行排序

函数说明 此方法还没有支持

template<typename T>
void LinkedList<T>::sort(int (*cmp)(T &, T &)){
	if(_size < 2) return; // trivial case;

	for(;;) {	

		ListNode<T> **joinPoint = &root;

		while(*joinPoint) {
			ListNode<T> *a = *joinPoint;
			ListNode<T> *a_end = findEndOfSortedString(a, cmp);
	
			if(!a_end->next	) {
				if(joinPoint == &root) {
					last = a_end;
					isCached = false;
					return;
				}
				else {
					break;
				}
			}

			ListNode<T> *b = a_end->next;
			ListNode<T> *b_end = findEndOfSortedString(b, cmp);

			ListNode<T> *tail = b_end->next;

			a_end->next = NULL;
			b_end->next = NULL;

			while(a && b) {
				if(cmp(a->data, b->data) <= 0) {
					*joinPoint = a;
					joinPoint = &a->next;
					a = a->next;	
				}
				else {
					*joinPoint = b;
					joinPoint = &b->next;
					b = b->next;	
				}
			}

			if(a) {
				*joinPoint = a;
				while(a->next) a = a->next;
				a->next = tail;
				joinPoint = &a->next;
			}
			else {
				*joinPoint = b;
				while(b->next) b = b->next;
				b->next = tail;
				joinPoint = &b->next;
			}
		}
	}
}

4.官方实例操作

4.1 ClassList —— 以动物为例

实例源码

// 以动物为例 创建 cat dog emu 一个个加入list
#include <LinkedList.h>

// Let's define a new class
class Animal {
	public:
		char *name;
		bool isMammal;
};

char  catname[]="kitty";
char  dogname[]="doggie";
char  emuname[]="emu";

LinkedList<Animal*> myAnimalList = LinkedList<Animal*>();

void setup()
{

	Serial.begin(9600);
	Serial.println("Hello!" );

	// Create a Cat
	Animal *cat = new Animal();
	cat->name = catname;
	cat->isMammal = true;

	// Create a dog
	Animal *dog = new Animal();
	dog->name = dogname;
	dog->isMammal = true;

	// Create a emu
	Animal *emu = new Animal();
	emu->name = emuname;
	emu->isMammal = false; // just an example; no offense to pig lovers

	// Add animals to list
	myAnimalList.add(cat);
	myAnimalList.add(emu);
	myAnimalList.add(dog);
}

void loop() {

	Serial.print("There are ");
	Serial.print(myAnimalList.size());
	Serial.print(" animals in the list. The mammals are: ");

	int current = 0;
	Animal *animal;
	for(int i = 0; i < myAnimalList.size(); i++){

		// Get animal from list
		animal = myAnimalList.get(i);

		// If its a mammal, then print it's name
		if(animal->isMammal){

			// Avoid printing spacer on the first element
			if(current++)
				Serial.print(", ");

			// Print animal name
			Serial.print(animal->name);
		}
	}
	Serial.println(".");

	while (true); // nothing else to do, loop forever
}

4.2 SimpleIntegerList——简单int类型集合

实例源码

// int为类型的list集合
#include <LinkedList.h>

LinkedList<int> myList = LinkedList<int>();

void setup()
{
	
	Serial.begin(9600);
	Serial.println("Hello!");

	// Add some stuff to the list
	int k = -240,
		l = 123,
		m = -2,
		n = 222;
	myList.add(n);
	myList.add(0);
	myList.add(l);
	myList.add(17);
	myList.add(k);
	myList.add(m);
}

void loop() {

	int listSize = myList.size();

	Serial.print("There are ");
	Serial.print(listSize);
	Serial.print(" integers in the list. The negative ones are: ");

	// Print Negative numbers
	for (int h = 0; h < listSize; h++) {

		// Get value from list
		int val = myList.get(h);

		// If the value is negative, print it
		if (val < 0) {
			Serial.print(" ");
			Serial.print(val);
		}
	}

	while (true); // nothing else to do, loop forever
}

5.总结

这里只是简单对LinkedList做了介绍,喜欢就麻烦给博主加以点赞关注。我个人觉得可以应用于此类场景:

  • 特别是上传温度之类的需求,不断上传温度,可以构造list集合,然后当做一个缓存来用,不断链表尾部加入数据,然后头部不断取出数据上报,
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

单片机菜鸟哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值