• Qt通过Doc模式读取XML并设计一个增删改查方便的一个操作类


    前言

    如果对开源库TinyXml有兴趣的可以去看看这篇文章。
    C++使用TinyXml(开源库)读取*.XMl文件

    DOC 文档对象模型

    文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。它是一种与平台和语言无关的应用程序接口(API),它可以动态地访问程序和脚本,更新其内容、结构和www文档的风格(HTML和XML文档是通过说明部分定义的)。文档可以进一步被处理,处理的结果可以加入到当前的页面。

    DOM是一种基于树的API文档,它要求在处理过程中整个文档都表示在存储器中。另外一种简单的API是基于事件的SAX,它可以用于处理很大的XML文档,由于大,所以不适合全部放在存储器中处理。

    先来了解一下xml基本要素:
    在这里插入图片描述

    QtXML基本结构

    下面列出了Qt处理xml的一些类以及说明,加粗表示是常用。

    类名说明
    QDomAttrThe QDomAttr class represents one attribute of a QDomElement
    QDomNodeThe QDomNode class is the base class for all the nodes in a DOM tree.
    QDomTextThe QDomText class represents text data in the parsed XML document.
    QDomElementThe QDomElement class represents one element in the DOM tree.
    QDomCDATASectionThe QDomCDATASection class represents an XML CDATA section
    QDomCharacterDataThe QDomCharacterData class represents a generic string in the DOM
    QDomCommentThe QDomComment class represents an XML comment
    QDomDocumentThe QDomDocument class represents an XML document
    QDomDocumentFragmentThe QDomDocumentFragment class is a tree of QDomNodes which is not usually a complete QDomDocument.
    QDomEntityThe QDomEntity class represents an XML entity.

    操作XML

    部署环境

    通过Qt Create创建一个工程。然后在*.pro配置文件中添加xml model。
    在这里插入图片描述
    在添加Qt解析xml相关的头文件

    #include 
    #include 
    
    • 1
    • 2

    添加信息头

    	// xml文档
    	QDomDocument* m_Doc = new QDomDocument(); 
    	
        // 创建XML处理类,通常用于处理第一行描述信息
        QDomProcessingInstruction instruction;
    
        std::string note =  "version=1.0 encoding=utf-8";
    
        // 创建XML头部格式
        instruction = m_Doc->createProcessingInstruction(QString::fromStdString(type), QString::fromStdString(note));
    
        // 添加到XML文件中
        m_Doc->appendChild(instruction);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    读取XML文件

        // 打开文件
        QFile m_File = new QFile("./1.xml");
    
        if (false == m_File->open(mode))
        {
            QMessageBox::information(NULL, u8"提示", u8"文件打开失败!");
            return;
        }
    
        // 将doc与file关联起来
        // 这个函数从IO设备dev中读取XML文档,如果内容被成功解析,则返回true;否则返回false。
        if (false == m_Doc->setContent(m_File))
        {
            QMessageBox::information(NULL, u8"提示", u8"操作的文件不是XML文件!");
            m_File->close();
            return ;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    添加根节点

    通过createElement方法创建的第一个子节点就是根节点。然后通过appendChild方法将创建的节点添加到doc中。

        // 创建根节点
        QDomElement rootNode = m_Doc->createElement(rootName);
    
        // 添加到XML文件中
        m_Doc->appendChild(rootNode);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    添加一个没有属性的节点

    添加一个没有属性的节点的方法和添加根节点的方法一致。

        // 创建根节点
        QDomElement rootNode = m_Doc->createElement(rootName);
    
        // 添加到XML文件中
        m_Doc->appendChild(rootNode);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    添加一个有属性的节点

        std::list<std::pair<std::string, std::string>> Attribute{{"ID","2"},{"name","张三"}};
        QDomElement node = m_Doc->createElement(nodeName);
    
        // 给节点创建属性
        for(const auto& elem : Attribute)
        {
            // 参数一是字符串,参数二可以是任何类型
            node.setAttribute(QString::fromStdString(elem.first), QString::fromStdString(elem.second));
        }
    
        rootNode.appendChild(node);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    添加一个元素节点

        // 创建子元素
        QDomElement node = m_Doc->createElement(nodeName);
    
        // 设置尖括号中的值
        QDomText text = m_Doc->createTextNode(value);
    
        // 添加关系
        node.appendChild(text);
        root.appendChild(node);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    给节点单独设置属性

    setAttribute方法设置节点的属性名和属性值。

        QDomElement elemNode = m_Doc->createElement(QString::fromStdString(name));
    
        elemNode.setAttribute(QString::fromStdString(name), QString::fromStdString(value));
    
        node.appendChild(elemNode);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    删除所有同名节点

    removeChild方法使用需要注意

    看下面官方文档说明

    Removes oldChild from the list of children. oldChild must be a direct child of this node.
    Returns a new reference to oldChild on success or a null node on failure.
    
    • 1
    • 2

    类似于C++容器存在迭代器失效问题。所以我这里就从后向前删除了。

        // 获取节点名字为Book的所有节点
        QDomNodeList nodes = m_Doc->elementsByTagName(nodeName);
    
        for (int i = nodes.size() - 1; i >= 0; i--)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
            
             //   所以 要么从后向前删除 要么就需要重新指定新节点的位置
            root.removeChild(element);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    删除所有同名带属性的节点

        QDomNodeList nodes = m_Doc->elementsByTagName(nodeName);
    
        for (int i = 0; i < nodes.count(); i++)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
    
            bool isFind {true};
            for(const auto& elem : attribute)
            {
                // 进行判断(返回属性的值进行判断)
                if (element.attribute(QString::fromStdString(elem.first)) != QString::fromStdString(elem.second))
                {
                    isFind = false;
                }
            }
            if(true == isFind)
            {
                root.removeChild(nodes.at(i));
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    递归删除所有同名的节点

        // 获取节点名字为Book的所有节点
        QDomNodeList nodes = m_Doc->elementsByTagName(QString::fromStdString(nodename));
    
        // QDomElement root = GetRootNode();
    
        for (int i = nodes.size() - 1; i >= 0; i--)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
            
            // 获取父节点
            QDomNode fNode = element.parentNode();
            
            fNode.removeChild(element);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    保存XML

        QFile* m_File= new QFile(fileName);
        if (false == m_File->open(QFileDevice::WriteOnly | QFileDevice::Truncate))
        {
            QMessageBox::information(NULL, "提示", "文件打开失败!");
            return;
        }
        QTextStream stream(m_File);
        m_Doc->save(stream, retract);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    获取节点元素

    if (node.toElement().tagName() == name)
    {
        return node;
    }
    
    • 1
    • 2
    • 3
    • 4

    获取节点元素的值

    if (node.toElement().tagName() == name)
    {
        return node.toElement().text().toStdString();
    }
    
    • 1
    • 2
    • 3
    • 4

    获取该节点的属性值

    key为属性名

    if (false == node.isNull() && true == node.toElement().hasAttribute(key))
    {
    value = node.toElement().attributeNode(key).value();
    }
    
    • 1
    • 2
    • 3
    • 4

    完整代码

    注:必须支持C++17

    OperateXML.h

    #ifndef OPERATEXML_H
    #define OPERATEXML_H
    
    // 读取xml必须要包含的
    #include 
    #include 
    
    #include 
    #include 
    //#include 
    
    #include 
    #include 
    
    class OperateXML
    {
    public:
    
        explicit OperateXML(/*std::string filename = std::string("")*/);
        ~OperateXML();
    
        // 创建一个xml文件
        bool CreateXmlFile(std::string filename = std::string(""), QIODevice::OpenMode mode = QFileDevice::WriteOnly | QFileDevice::Truncate);
    
        // 添加一个信息头
        void CreateXmlHeader(std::string type = std::string("xml"), std::string version = "1.0", std::string encoding = std::string("UTF-8"));
    
        // 加载已有的xml文件
        bool LoadXml(std::string filename, QIODevice::OpenMode mode = QFileDevice::ReadOnly);
    
        // 获取根节点 Stu
        QDomElement GetRootNode();
    
        // 创建一个根节点  Stu
        QDomElement CreateXmlRootNode(std::string rootName = std::string("root"));
    
        // 添加一个没有属性的节点    Stu StuInfo
        QDomElement AddNoAttributesNode(QDomElement fNode, std::string nodeName);
    
        // 添加一个有属性的节点    Stu StuInfo  ID 1  Name 张三
        QDomElement AddAttributesNode(QDomElement fNode, std::string nodeName,
                                      std::list<std::pair<std::string, std::string>> Attribute);
    
        // 添加元素节点
        QDomElement  AddElementNode(QDomElement fNode, std::string nodeName, std::string value);
    
        // 设置节点属性
        QDomElement SetNodeAttribute(QDomElement node, std::string name, std::string value);
    
        // 删除节点 删除当前节点下的所有名为nodename的节点
        void DeleteNodes(QDomElement fNode, std::string nodename);
    
        // 删除节点 删除当前节点下的所有名为nodename的节点 且属性值相同
        void DeleteNode(QDomElement fNode, std::string nodename,
                        std::list<std::pair<std::string, std::string>> attribute);
    
        // 删除节点 删除所有节点下名为nodename的节点
        void DeleteNodes(std::string nodename);
    
         // 删除节点 删除所有节点下名为nodename的节点 且属性值相同
        void DeleteNode(std::string nodename, std::list<std::pair<std::string, std::string>> attribute);
    
        // 保存Xml  进位空格数
        void SaveXml(int retract = 4);
    
        // 获取当前节点以及同级下的兄弟节点 且名为name的节点  如果为第一个孩子节点记得传入第三个参数为true
        QDomElement GetElem(QDomElement& node, std::string name, bool first = false);
        QDomElement GetElem(QDomElement& node, QString name, bool first = false);
    
    
        // 获取当前节点以及同级下的兄弟节点 且名为name的节点的值
        std::string GetElemValue(QDomElement& node, std::string name, bool first = false);
        QString GetElemValue(QDomElement& node, QString name, bool first = false);
    
    
        // 获取 该节点指定的 属性节点值
        std::string GetElemAttributeValue(QDomElement node, std::string key);
        QString GetElemAttributeValue(QDomElement node, QString key);
    private:
        QDomDocument* m_Doc;
        QFile* m_File;
        std::string m_FileName;
    
    };
    
    #endif // OPERATEXML_H 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    OperateXML.cpp

    #include "OperateXML.h"
    
    OperateXML::OperateXML(/*std::string filename*/)
        : m_Doc(new QDomDocument())
        , m_File{nullptr}
    {
    }
    
    OperateXML::~OperateXML()
    {
        if(nullptr != m_Doc)
        {
            delete m_Doc;
            m_Doc = nullptr;
        }
        if(nullptr != m_File)
        {
            m_File->close();
            delete m_File;
            m_File = nullptr;
        }
    }
    
    bool OperateXML::CreateXmlFile(std::string filename, QIODevice::OpenMode mode)
    {
        if(true == filename.empty())
        {
            QMessageBox::information(NULL, u8"提示", u8"文件名为空!");
            return false;
        }
    
        if(nullptr != m_File)
        {
            delete m_File;
            m_File = nullptr;
        }
    
        m_File = new QFile(QString::fromStdString(filename));
    
        if(false == m_File->open(mode))
        {
            QMessageBox::information(NULL, u8"提示", u8"文件打开或创建失败!");
            return false;
        }
    
        if(false == m_File->isOpen())
        {
            QMessageBox::information(NULL, u8"提示", u8"文件打开或创建失败!");
            return false;
        }
    
        return true;
    }
    
    void OperateXML::CreateXmlHeader(std::string type, std::string version, std::string encoding)
    {
        // 创建XML处理类,通常用于处理第一行描述信息
        QDomProcessingInstruction instruction;
    
        std::string note =  std::string("version=\"") + version + std::string("\"  ") +
                std::string("encoding=\"") + encoding + std::string("\"");
    
        // 创建XML头部格式
        instruction = m_Doc->createProcessingInstruction(QString::fromStdString(type), QString::fromStdString(note));
    
        // 添加到XML文件中
        m_Doc->appendChild(instruction);
    }
    
    bool OperateXML::LoadXml(std::string filename, QIODevice::OpenMode mode)
    {
        // 打开文件
        m_File = new QFile(QString::fromStdString(filename));
    
        if (false == m_File->open(mode))
        {
            QMessageBox::information(NULL, u8"提示", u8"文件打开失败!");
    
            return false;
        }
    
        // 将doc与file关联起来
        // 这个函数从IO设备dev中读取XML文档,如果内容被成功解析,则返回true;否则返回false。
        if (false == m_Doc->setContent(m_File))
        {
            QMessageBox::information(NULL, u8"提示", u8"操作的文件不是XML文件!");
            m_File->close();
            return false;
        }
    
        return true;
    }
    
    QDomElement OperateXML::GetRootNode()
    {
        QDomElement root = m_Doc->documentElement();
    
        return root;
    }
    
    QDomElement OperateXML::CreateXmlRootNode(std::string rootName)
    {
        // 创建根节点
        QDomElement rootNode = m_Doc->createElement(QString::fromStdString(rootName));
    
        // 添加到XML文件中
        m_Doc->appendChild(rootNode);
    
    
        return rootNode;
    }
    
    QDomElement OperateXML::AddNoAttributesNode(QDomElement fNode, std::string nodeName)
    {
        if(true == fNode.isNull())
        {
            return QDomElement();
        }
        QDomElement node = m_Doc->createElement(QString::fromStdString(nodeName));
    
        fNode.appendChild(node);
    
    
    
        return node;
    }
    
    QDomElement OperateXML::AddAttributesNode(QDomElement fNode, std::string nodeName,
                                              std::list<std::pair<std::string, std::string>> Attribute)
    {
        QDomElement node = m_Doc->createElement(QString::fromStdString(nodeName));
    
        // 给节点创建属性
        for(const auto& elem : Attribute)
        {
            // 参数一是字符串,参数二可以是任何类型
            node.setAttribute(QString::fromStdString(elem.first), QString::fromStdString(elem.second));
        }
    
        fNode.appendChild(node);
    
        return node;
    }
    
    QDomElement OperateXML::AddElementNode(QDomElement fNode, std::string nodeName, std::string value)
    {
        // 创建子元素
        QDomElement node = m_Doc->createElement(QString::fromStdString(nodeName));
    
        // 设置尖括号中的值
        QDomText text = m_Doc->createTextNode(QString::fromStdString(value));
    
        // 添加关系
        node.appendChild(text);
        fNode.appendChild(node);
    
        return node;
    }
    
    QDomElement OperateXML::SetNodeAttribute(QDomElement node, std::string name, std::string value)
    {
        QDomElement elemNode = m_Doc->createElement(QString::fromStdString(name));
    
        elemNode.setAttribute(QString::fromStdString(name), QString::fromStdString(value));
    
        node.appendChild(elemNode);
    
        return node;
    }
    
    void OperateXML::DeleteNodes(QDomElement fNode, std::string nodename)
    {
        // 获取节点名字为Book的所有节点
        QDomNodeList nodes = m_Doc->elementsByTagName(QString::fromStdString(nodename));
    
        for (int i = nodes.size() - 1; i >= 0; i--)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
            /*
             * Removes oldChild from the list of children. oldChild must be a direct child of this node.
                Returns a new reference to oldChild on success or a null node on failure.
    
                所以 要么从后向前删除 要么就需要重新指定新节点的位置
             */
            fNode.removeChild(element);
        }
    
    }
    
    void OperateXML::DeleteNode(QDomElement fNode, std::string nodename, std::list<std::pair<std::string, std::string>> attribute)
    {
        QDomNodeList nodes = m_Doc->elementsByTagName(QString::fromStdString(nodename));
    
        for (int i = 0; i < nodes.count(); i++)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
    
            bool isFind {true};
            for(const auto& elem : attribute)
            {
                // 进行判断(返回属性的值进行判断)
                if (element.attribute(QString::fromStdString(elem.first)) != QString::fromStdString(elem.second))
                {
                    isFind = false;
                }
            }
            if(true == isFind)
            {
                fNode.removeChild(nodes.at(i));
            }
        }
    }
    
    void OperateXML::DeleteNodes(std::string nodename)
    {
        // 获取节点名字为Book的所有节点
        QDomNodeList nodes = m_Doc->elementsByTagName(QString::fromStdString(nodename));
    
        // QDomElement root = GetRootNode();
    
        for (int i = nodes.size() - 1; i >= 0; i--)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
            /*
             * Removes oldChild from the list of children. oldChild must be a direct child of this node.
                Returns a new reference to oldChild on success or a null node on failure.
    
                所以 要么从后向前删除 要么就需要重新指定新节点的位置
             */
            QDomNode fNode = element.parentNode();
            fNode.removeChild(element);
        }
    }
    
    void OperateXML::DeleteNode(std::string nodename, std::list<std::pair<std::string, std::string> > attribute)
    {
        QDomNodeList nodes = m_Doc->elementsByTagName(QString::fromStdString(nodename));
    
        for (int i = 0; i < nodes.count(); i++)
        {
            // 获取节点
            QDomElement element = nodes.at(i).toElement();
    
            bool isFind {true};
            for(const auto& elem : attribute)
            {
                // 进行判断(返回属性的值进行判断)
                if (element.attribute(QString::fromStdString(elem.first)) != QString::fromStdString(elem.second))
                {
                    isFind = false;
                }
            }
            if(true == isFind)
            {
                QDomNode fNode = element.parentNode();
                fNode.removeChild(nodes.at(i));
            }
        }
    }
    
    void OperateXML::SaveXml(int retract)
    {
        m_File->close();
        if (false == m_File->open(QFileDevice::WriteOnly | QFileDevice::Truncate))
        {
            QMessageBox::information(NULL, "提示", "文件打开失败!");
            return;
        }
        QTextStream stream(m_File);
        m_Doc->save(stream, retract);
    }
    
    QDomElement OperateXML::GetElem(QDomElement& node, std::string name, bool first)
    {
        if(true == first)
        {
            node = node.firstChild().toElement();
        }
        while(false == node.isNull())
        {
            if (node.toElement().tagName() == QString::fromStdString(name))
            {
                return node;
            }
            node = node.nextSibling().toElement();
        }
        return QDomElement();
    }
    
    QDomElement OperateXML::GetElem(QDomElement &node, QString name, bool first)
    {
        if(true == first)
        {
            node = node.firstChild().toElement();
        }
        while(false == node.isNull())
        {
            // qDebug() << node.toElement().tagName();
            if (node.toElement().tagName() == name)
            {
                return node;
            }
            node = node.nextSibling().toElement();
        }
        return QDomElement();
    }
    
    std::string OperateXML::GetElemValue(QDomElement& node, std::string name, bool first)
    {
        if(true == first)
        {
            node = node.firstChild().toElement();
        }
        while(false == node.isNull())
        {
            // qDebug() << node.toElement().tagName();
            if (node.toElement().tagName() == QString::fromStdString(name))
            {
                return node.toElement().text().toStdString();
            }
            node = node.nextSibling().toElement();
        }
        return std::string();
    }
    
    QString OperateXML::GetElemValue(QDomElement &node, QString name, bool first)
    {
        if(true == first)
        {
            node = node.firstChild().toElement();
        }
        while(false == node.isNull())
        {
            // qDebug() << node.toElement().tagName();
            if (node.toElement().tagName() == name)
            {
                return node.toElement().text();
            }
            node = node.nextSibling().toElement();
        }
        return QString();
    }
    std::string OperateXML::GetElemAttributeValue(QDomElement node, std::string key)
    {
        std::string value;
    
        if (false == node.isNull() && true == node.toElement().hasAttribute(QString::fromStdString(key)))
        {
            value = node.toElement().attributeNode(QString::fromStdString(key)).value().toStdString();
        }
        return value;
    }
    
    QString OperateXML::GetElemAttributeValue(QDomElement node, QString key)
    {
        QString value;
    
        if (false == node.isNull() && true == node.toElement().hasAttribute(key))
        {
            value = node.toElement().attributeNode(key).value();
        }
        return value;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
  • 相关阅读:
    【玩转CSS】这些高级技巧,你都会吗
    【Flutter】三个Channel(Android-java / Ios-swift)
    django项目实战基于Python实现的衣物捐赠系统
    网络安全(黑客)-0基础小白自学
    数据链路层协议
    【ES6知识】ESModule 模块化
    ​力扣解法汇总1260-二维网格迁移
    基于Python实现的CNN for OxFlowers17实验
    03 循环、字符串、列表、元祖、字典
    深入探讨负载均衡的原理及算法
  • 原文地址:https://blog.csdn.net/qq_45254369/article/details/128039637