Qt 树形控件 QTreeView QTreeWidget深入剖析

2024-08-26 19:44

本文主要是介绍Qt 树形控件 QTreeView QTreeWidget深入剖析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

众众众所大家们周知,QTreeWidget性能差、QTreeView配合QStandardItemModel性能也差、不够灵活等等,需要自定义Model来配合QTreeView使用。那么为何这么多问题Qt官方却不进行改进?本文结合Qt源码,经过一周的深入分析,对如何设计自定义Model提出了自己的一些想法。

1. QTreeWidget分析

QTreeWidget使用起来比较简单,代码就不在这里罗列了。QTreeWidget使用时所依赖的类:

QTreeWidgetItem 没有从任何类继承
QTreeModel 继承自QAbstractItemModel
QTreeWidget 继承自QTreeView

在QTreeWidget的构造中,调用了QTreeView::setModel(new QTreeModel(1, this));,这样我们在使用时,其实只关心QTreeWidgetQTreeWidgetItem这两个类即可。那么接下来,分析一下这三个类的成员变量,基本就能看出是如何存储我们的数据,也就知道了为何效率比较低。

1.2 QTreeWidget成员

只有一个用Q_DECLARE_PRIVATE(QTreeWidget)修饰的p指针,成员都在QTreeWidgetPrivate中,经过查看里面没什么有用的东西。

1.3 QTreeModel

成员简化如下:

private:QTreeWidgetItem *rootItem;QTreeWidgetItem *headerItem; //这两个和根节点有关private:Q_DECLARE_PRIVATE(QTreeModel) //p指针

存储了根节点的信息,别的也没啥东西。

1.4 QTreeWidgetItem

关键成员如下:

    QList<QList<QWidgetItemData>> values; //关键!QTreeWidget *view = nullptr;QTreeWidgetItemPrivate *d; //d指针,关键!QTreeWidgetItem *par = nullptr; //父ItemQList<QTreeWidgetItem*> children; //子Item列表//d指针的成员:QTreeWidgetItem *q;QVariantList display; //关键!uint disabled : 1;uint selected : 1;uint hidden : 1;int rowGuess;
  • values关键成员,存储了各个Role相关相关的数据,比如背景Qt::BackgroundColorRole、对齐Qt::TextAlignmentRole等等,当然,当你设置的时候,才会存储这些信息。但每个Item都存储了一遍,这个占用了大量空间。
  • QVariantList display关键成员,它会存储你树上要显示的所有内容,也就是说,如果你的原始数据存储在Qt::UserRole中,但显示的数据会存储在这个成员中,相当于存了至少两份内容!所有占用空间大很大一部分原因也在这里。

这两个问题就是占用空间大,效率低的决定性因素,那如何解决这个问题呢?

干掉这两个成员就完事了!重载QAbstractItemModel的data()函数,用到的时候动态返回,这个后面详细说。

2. QTreeView配合QStandardItemModel使用分析

使用时所依赖的类:

QStandardItem 没有从任何类继承
QStandardItemModel 继承自QAbstractItemModel
QTreeView

这里直接看使用示例:

    // 创建一个标准项目模型QStandardItemModel model;// 添加顶级节点QStandardItem* itemRootTop = new QStandardItem("Top");model->appendRow(itemRootTop);// 添加子节点QList<QStandardItem*> items;QStandardItem* item0 = new QStandardItem("item0");QStandardItem* item1 = new QStandardItem("item1");itemRootTop->appendRow(items);// 创建一个QTreeView对象QTreeView treeView;treeView.setModel(&model);treeView.show();

可以看到,这里new了大量个QStandardItem,比treewidget都多,treewidget每个行只对应了一个TreeWidgetItem对象啊!
看一下成员:

QStandardItemPrivate的:

    QStandardItem *parent;QList<QStandardItemData> values; //使用std::pair存储了role相关数据QList<QStandardItem *> children; //存储了当前行所有Item

QStandardItemModelPrivate的:

    QList<QStandardItem *> columnHeaderItems; QList<QStandardItem *> rowHeaderItems; //存储顶级的Item成员QHash<int, QByteArray> roleNames;

由此可见,这个比QTreeWidget还差劲呢。

3.自定义Model/Item

通过上面的分析,用QTreeWidget在数据量不是特别大的时候,用着是没什么问题的。QTreeWidget为了兼容各种情况,已经写的很灵活了,但如何更高效呢?答案就是定制化,这样就减少了一部分灵活性。
如何定制化呢?核心就是重写QAbstractItemModel,Qt已经给了我们一个很好的实例,可以自己看一下editabletreemodel这个例子,笔者这里实现的也是在这个例子基础上改的,添加了TreeItem和TreeModel两个类。

  • TreeItem:对应了树的一行,每一列的数据存储在成员中,同时还存储父和子
  • TreeModel:重新实现关键函数

看这个例子之前,你得先明白QAbstractItemModel的几个关键原理,否则也是一脸懵逼。

3.1 数据刷新机制

qt本身已经实现了一种非常高效的刷新机制,即只刷新在窗口中显示的那部分数据,这个功能是通过在合适的时机调用data()接口来实现的,你自己可以在重写的data函数中加个打印,看看是不是只刷新了窗口显示的那部分数据。
data函数伪代码:

QVariant TreeModel::data(const QModelIndex &index, int role) const
{TreeItem *item = indexToItem(index);if (role == Qt::EditRole){//根据行列提取数据并进行转化return QVariant("编辑时显示的内容")}else if (role == Qt::DisplayRole){//xxxreturn QVariant("正常显示的内容");}else if (role == Qt::TextAlignmentRole){return QVariant(Qt::AlignCenter); //固定居中对齐}else if (role == Qt::BackgroundColorRole){}
}

3.2 如何减少存储容量

答案是在TreeItem中只定义原始数据,显示的内容在data()接口中动态计算填充。
比如笔者这里的成员函数的定义:

private://父节点TreeItem *parentItem;//子节点QList<TreeItem*> childItems;//各个列的原始数据 - 非显示数据QVector<QVariant> itemData;

这里用一个vector存储了每一列的原始数据,如果你想更进一步减少内存,可以用void*指向你本来的原始数据即可,不过这样在更新数据时可能没那么方便。

3.3 QModelIndex怎么算

这个问题折磨了一段时间,来看看创建它的接口:

inline QModelIndex QAbstractItemModel::createIndex(int arow, int acolumn, void *adata) const
{ return QModelIndex(arow, acolumn, adata, this); }

接口很简单,输入行列和要绑定的数据即可,adata我们这里传入对应的TreeItem指针。
但是这里的行有坑,这里高亮一下:

arow行表示TreeItem所在父节点的第几行!
列就是正常的列,无法计算的时候就赋值0。

在笔者的代码中,重写了两个接口来对Item和Index进行互相转换。

3.4 插入数据页面不刷新?

在这个例子中,只需要向模型中更新我们的数据即可,但你会发现插入不会立即刷新,你得切换下页面才刷新,这是因为你得调用beginInsertRowsendInsertRows接口来激发刷新的信号。

3.5 如何写一个更通用的类

不同的树可能显示的display不同,难道每个树都得写个TreeModel?
笔者通过回调函数的方式,通过registerDisplayRuleregisterBackgroundColorRule等接口,将规则lambda注入到TreeModel中,这样就实现了用同一套代码,实现不同类的方式。

3.6 排序、编辑、筛选的支持

编辑应该是可以的,但排序、筛选没有添加支持。

4. 源码如下

TreeModel.h

#ifndef TREEMODEL_H
#define TREEMODEL_H#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include "treeitem.h"class TreeItem;class TreeModel : public QAbstractItemModel
{Q_OBJECTpublic:TreeModel(const QStringList &headers, QObject *parent = 0);~TreeModel();//清空所有数据void clear();//注册显示规则 - 函数对象第二个参数是列下标,返回值是显示的字符串QStringvoid registerDisplayRule(std::function<QString(TreeItem*, int)> funcRule);//注册背景色规则 - 函数对象第二个参数是列下标,返回值是要显示的颜色void registerBackgroundColorRule(std::function<QColor(TreeItem*, int)> funcRule);//添加顶级条目,返回添加的条目TreeItem* addTopLevelItem(QVector<QVariant> vecValue);//添加子条目TreeItem* addChildItem(TreeItem *pParent, QVector<QVariant> vecValue);TreeItem* insertChileItem(TreeItem *pParent, int irow, QVector<QVariant> vecValue); //在指定行row后面插入 TODO未实现//返回顶级条目个数int topLevelItemCount();//返回指定的顶级条目TreeItem *topLevelItem(int index);//删除顶级条目及子条目void deleteToplevelItems(TreeItem *pTopItem);protected:QVariant data(const QModelIndex &index, int role) const override;QVariant headerData(int section, Qt::Orientation orientation,int role = Qt::DisplayRole) const override;QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const override;QModelIndex parent(const QModelIndex &index) const override;int rowCount(const QModelIndex &parent = QModelIndex()) const override;int columnCount(const QModelIndex &parent = QModelIndex()) const override;Qt::ItemFlags flags(const QModelIndex &index) const override;bool setData(const QModelIndex &index, const QVariant &value,int role = Qt::EditRole) override;bool setHeaderData(int section, Qt::Orientation orientation,const QVariant &value, int role = Qt::EditRole) override;bool insertColumns(int position, int columns,const QModelIndex &parent = QModelIndex()) override;bool removeColumns(int position, int columns,const QModelIndex &parent = QModelIndex()) override;bool insertRows(int position, int rows,const QModelIndex &parent = QModelIndex()) override;bool removeRows(int position, int rows,const QModelIndex &parent = QModelIndex()) override;private://数据修改void emitDataChanged(TreeItem *pItem, int iCol);//index转itemTreeItem *indexToItem(const QModelIndex &index) const;//指定item的indexQModelIndex itemToIndex(TreeItem *item, int iCol) const;TreeItem *m_pRootItem;//显示规则std::function<QString(TreeItem*, int)> m_funcDisplayRule;//颜色规则std::function<QColor(TreeItem*, int)> m_funcBackgroundColorRule;friend class TreeItem;
};#endif // TREEMODEL_H

TreeModel.cpp

#include <QtWidgets>
#include <QDebug>
#include <QDateTime>#include "treeitem.h"
#include "treemodel.h"
#include "commonlib/GlogWrapper.h"TreeModel::TreeModel(const QStringList &headers, QObject *parent): QAbstractItemModel(parent)
{m_pRootItem = new TreeItem(headers.size());for(int i=0; i<headers.size(); i++){m_pRootItem->setData(i, headers.at(i));}
}TreeModel::~TreeModel()
{delete m_pRootItem;
}void TreeModel::clear()
{int irowCnt = rowCount();if (irowCnt != 0){removeRows(0, irowCnt);}
}void TreeModel::registerDisplayRule(std::function<QString(TreeItem *, int)> funcRule)
{m_funcDisplayRule = funcRule;
}void TreeModel::registerBackgroundColorRule(std::function<QColor(TreeItem *, int)> funcRule)
{m_funcBackgroundColorRule = funcRule;
}TreeItem *TreeModel::addTopLevelItem(QVector<QVariant> vecValue)
{//检查列数是否一致if (vecValue.size() != m_pRootItem->columnCount()){return nullptr;}if (!insertRow(m_pRootItem->childCount(), QModelIndex())) //会回调insertrowsreturn nullptr;TreeItem *pItem = m_pRootItem->child(m_pRootItem->childCount()-1);pItem->itemData = vecValue;return pItem;//下面这个页面无法及时更新/*m_pRootItem->insertChildren(m_pRootItem->childCount(), 1, m_pRootItem->columnCount(), this);TreeItem *pItem = m_pRootItem->child(m_pRootItem->childCount() - 1);pItem->itemData = vecValue;return pItem;*/}TreeItem* TreeModel::addChildItem(TreeItem *pParent, QVector<QVariant> vecValue)
{if (vecValue.size() != m_pRootItem->columnCount()){return nullptr;}QModelIndex indexParent = itemToIndex(pParent, 0);if (!insertRow(pParent->childCount(), indexParent))return nullptr;TreeItem *pItem = pParent->child(pParent->childCount()-1);pItem->itemData = vecValue;return pItem;//下面这个页面无法及时更新/*pParent->insertChildren(pParent->childCount(), 1, m_pRootItem->columnCount(), this);TreeItem *pItem = pParent->child(pParent->childCount()-1);pItem->itemData = vecValue;return pItem;*/
}int TreeModel::topLevelItemCount()
{return m_pRootItem->childCount();
}TreeItem *TreeModel::topLevelItem(int index)
{if (index > m_pRootItem->childCount()-1){return nullptr;}return m_pRootItem->child(index);
}void TreeModel::deleteToplevelItems(TreeItem *pTopItem)
{if (pTopItem == nullptr){return;}int irow = pTopItem->inParentRow(); //pTopItem所在父的第几行removeRow(irow, QModelIndex()); //会回调removeRows()!第二个参数默认根Item为空,后面会在indexToItem自动转成根
}int TreeModel::columnCount(const QModelIndex & /* parent */) const
{return m_pRootItem->columnCount();
}QVariant TreeModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();TreeItem *item = indexToItem(index);//LOG_INFO_WIN(QString("%1, row:%2, col:%3,dataptr:%4").arg(CToolUtile::GetCurrentTimeSec()).arg(index.row()).arg(index.column()).arg(*(int*)index.internalPointer()));if (role == Qt::EditRole){//TODO}else if (role == Qt::DisplayRole){//根据注入的规则进行显示QString strDisplay = m_funcDisplayRule(item, index.column());return strDisplay;}else if (role == Qt::TextAlignmentRole){return QVariant(Qt::AlignCenter); //居中对齐}else if (role == Qt::BackgroundColorRole){QColor color = m_funcBackgroundColorRule(item, index.column());if (!color.isValid()){return QVariant();}else{return color;}}return QVariant();
}Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{if (!index.isValid())return 0;//return Qt::ItemIsEditable | QAbstractItemModel::flags(index);return QAbstractItemModel::flags(index); //不可编辑
}TreeItem *TreeModel::indexToItem(const QModelIndex &index) const
{if (index.isValid()) {TreeItem *item = static_cast<TreeItem*>(index.internalPointer());if (item)return item;}return m_pRootItem;
}//局部更新中,需要输入行列,这里列已经知道,但是行是多少呢?难道是Item所在整个表格的行?
//答案是非也!经过调试发现,本信号调用完毕后,会激发data()调用去获取pItem的数据,和行的关系不大,所以到底该设置什么数值?
//查看源码(QTreeModel中的settext接口),它这里的行设置的是所在父项子列表中的第几个,照着抄就完了。
void TreeModel::emitDataChanged(TreeItem *pItem, int iCol)
{QModelIndex index = itemToIndex(pItem, iCol);//LOG_INFO_WIN(QString("%1, row:%2, col:%3,dataptr:%4").arg("emitDataChanged").arg(index.row()).arg(index.column()).arg(*(int*)index.internalPointer()));emit dataChanged(index, index, {Qt::DisplayRole});
}//返回指定TreeItem的index,其实返回的是所在父的行以及指定列创建的index
QModelIndex TreeModel::itemToIndex(TreeItem *item, int iCol) const
{//executePendingSort();if (!item || (item == m_pRootItem)){return QModelIndex();}int row = item->inParentRow();return createIndex(row, iCol, item);
}QVariant TreeModel::headerData(int section, Qt::Orientation orientation,int role) const
{if (orientation == Qt::Horizontal && role == Qt::DisplayRole)return m_pRootItem->data(section);return QVariant();
}QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{if (parent.isValid() && parent.column() != 0)return QModelIndex();TreeItem *parentItem = indexToItem(parent);TreeItem *childItem = parentItem->child(row);if (childItem)return createIndex(row, column, childItem);elsereturn QModelIndex();
}bool TreeModel::insertColumns(int position, int columns, const QModelIndex &parent)
{bool success;beginInsertColumns(parent, position, position + columns - 1);success = m_pRootItem->insertColumns(position, columns);endInsertColumns();return success;
}bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{TreeItem *parentItem = indexToItem(parent);bool success;beginInsertRows(parent, position, position + rows - 1);success = parentItem->insertChildren(position, rows, m_pRootItem->columnCount(), this);endInsertRows();return success;
}//当前index在父中所处的行构成的父index
QModelIndex TreeModel::parent(const QModelIndex &index) const
{if (!index.isValid())return QModelIndex();TreeItem *childItem = indexToItem(index);if (childItem == m_pRootItem){return QModelIndex();}TreeItem *parentItem = childItem->parent();return itemToIndex(parentItem, 0);
}bool TreeModel::removeColumns(int position, int columns, const QModelIndex &parent)
{bool success;beginRemoveColumns(parent, position, position + columns - 1);success = m_pRootItem->removeColumns(position, columns);endRemoveColumns();if (m_pRootItem->columnCount() == 0)removeRows(0, rowCount());return success;
}bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent)
{TreeItem *parentItem = indexToItem(parent);bool success = true;beginRemoveRows(parent, position, position + rows - 1);success = parentItem->removeChildren(position, rows);endRemoveRows();return success;
}int TreeModel::rowCount(const QModelIndex &parent) const
{TreeItem *parentItem = indexToItem(parent);return parentItem->childCount();
}bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{TreeItem *item = indexToItem(index);bool result = false;if (role == Qt::EditRole){result = item->setData(index.column(), value);if (result){emit dataChanged(index, index, {role});}}return result;
}bool TreeModel::setHeaderData(int section, Qt::Orientation orientation,const QVariant &value, int role)
{if (role != Qt::EditRole || orientation != Qt::Horizontal)return false;bool result = m_pRootItem->setData(section, value);if (result)emit headerDataChanged(orientation, section, section);return result;
}

TreeItem.h

#ifndef TREEITEM_H
#define TREEITEM_H#include <QList>
#include <QVariant>
#include <QVector>class TreeModel;
class TreeItem
{
public:explicit TreeItem(int iColumns, TreeItem *parent = 0);~TreeItem();//修改指定列原始数据void setColmData(int iColm, QVariant value);//返回指定列的原始数值const QVariant &getColmData(int iColm);//返回所有原始数据const QVector<QVariant> &getAllData();//返回子项个数int childCount() const;//返回子项TreeItem *child(int index);private:void setModel(TreeModel *pModel);int columnCount() const;QVariant data(int column) const;//从第posion位置开始,添加count个bool insertChildren(int position, int count, int columns, TreeModel *pModel);bool insertColumns(int position, int columns);TreeItem *parent();bool removeChildren(int position, int count);bool removeColumns(int position, int columns);int inParentRow() const; //在父的子序列的第几行中bool setData(int column, const QVariant &value);private://父节点TreeItem *parentItem;//子节点QList<TreeItem*> childItems;//各个列的原始数据 - 非显示数据QVector<QVariant> itemData;TreeModel *m_pModel;friend class TreeModel;
};#endif // TREEITEM_H

TreeItem.cpp

#include "treeitem.h"
#include "treemodel.h"#include <QStringList>TreeItem::TreeItem(int iColumns, TreeItem *parent)
{parentItem = parent;itemData.resize(iColumns);m_pModel = nullptr;
}TreeItem::~TreeItem()
{qDeleteAll(childItems);
}void TreeItem::setColmData(int iColm, QVariant value)
{if (iColm > itemData.size()-1){return;}itemData[iColm] = value;if (m_pModel == nullptr){assert(false);return;}//局部更新m_pModel->emitDataChanged(this, iColm);
}const QVariant &TreeItem::getColmData(int iColm)
{if (iColm > itemData.size()-1){return QVariant();}return itemData[iColm];
}const QVector<QVariant> &TreeItem::getAllData()
{return itemData;
}TreeItem *TreeItem::child(int index)
{if (index > childCount()-1){return nullptr;}return childItems.value(index);
}void TreeItem::setModel(TreeModel *pModel)
{m_pModel = pModel;
}int TreeItem::childCount() const
{return childItems.count();
}int TreeItem::inParentRow() const
{if (parentItem)return parentItem->childItems.indexOf(const_cast<TreeItem*>(this));return 0;
}int TreeItem::columnCount() const
{return itemData.count();
}QVariant TreeItem::data(int column) const
{return itemData.value(column);
}bool TreeItem::insertChildren(int position, int count, int columns, TreeModel *pModel)
{if (position < 0 || position > childItems.size())return false;for (int row = 0; row < count; ++row) {QVector<QVariant> data(columns);TreeItem *item = new TreeItem(columns, this);item->setModel(pModel);childItems.insert(position, item);}return true;
}bool TreeItem::insertColumns(int position, int columns)
{if (position < 0 || position > itemData.size())return false;for (int column = 0; column < columns; ++column)itemData.insert(position, QVariant());foreach (TreeItem *child, childItems)child->insertColumns(position, columns);return true;
}TreeItem *TreeItem::parent()
{return parentItem;
}bool TreeItem::removeChildren(int position, int count)
{if (position < 0 || position + count > childItems.size())return false;for (int row = 0; row < count; ++row)delete childItems.takeAt(position);return true;
}bool TreeItem::removeColumns(int position, int columns)
{if (position < 0 || position + columns > itemData.size())return false;for (int column = 0; column < columns; ++column)itemData.remove(position);foreach (TreeItem *child, childItems)child->removeColumns(position, columns);return true;
}bool TreeItem::setData(int column, const QVariant &value)
{if (column < 0 || column >= itemData.size())return false;itemData[column] = value;return true;
}

这篇关于Qt 树形控件 QTreeView QTreeWidget深入剖析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1109508

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口int main(int argc, char *argv[]){// argc是命令行参数个数,argv是

lvgl8.3.6 控件垂直布局 label控件在image控件的下方显示

在使用 LVGL 8.3.6 创建一个垂直布局,其中 label 控件位于 image 控件下方,你可以使用 lv_obj_set_flex_flow 来设置布局为垂直,并确保 label 控件在 image 控件后添加。这里是如何步骤性地实现它的一个基本示例: 创建父容器:首先创建一个容器对象,该对象将作为布局的基础。设置容器为垂直布局:使用 lv_obj_set_flex_flow 设置容器