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

QQ技术互动交流群:ESP8266&32 物联网开发 群号622368884,不喜勿喷
image

单片机菜鸟博哥CSDN

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集合,然后当做一个缓存来用,不断链表尾部加入数据,然后头部不断取出数据上报,

猜你喜欢

转载自www.cnblogs.com/danpianjicainiao/p/12047426.html