三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

网站的建设与管理自考网站建设客户案例

网站的建设与管理自考网站建设客户案例 网站的建设与管理自考,网站建设客户案例,wordpress商城插件收费,wordpress 简洁 模板奇牛Qt06 线性表容器——奇牛+gemini 1. 线性表与 Qt 容器概述 1.1 什么是线性表 线性表是 $n$ 个具有相同特性的数据元素的有限序列。其结构特点为:存在唯一的开始元素(没有前驱)和唯一的终端元素(没有后继)。 除… 奇牛Qt06 线性表容器——奇牛+gemini 1. 线性表与 Qt 容器概述 1.1 什么是线性表 线性表是 $n$ 个具有相同特性的数据元素的有限序列。其结构特点为:存在唯一的开始元素(没有前驱)和唯一的终端元素(没有后继)。 除了第一个和最后一个数据元素之外,其它数据元素都有且仅有一个直接前驱和一个直接后继。1.2 Qt 容器的设计与选型 Qt 对 C++ 标准模板库(STL)的容器进行了进一步优化和封装,构建了一套更符合 Qt 生态、支持隐式共享(写时复制)的容器类。主流顺序容器:QVector、QList、QLinkedList。 派生特殊容器:QStack(栈)、QQueue(队列)。1.3 存储数据的基本要求 存入 Qt 容器中的元素类型 T 必须能够进行赋值操作。即该类型必须提供:默认构造函数(Default Constructor) 拷贝/复制构造函数(Copy Constructor) 赋值运算符重载(Assignment Operator)支持的类型:基础数据类型(int, double 等)、标准 Qt 类型(QString, QDate, QTime 等)。 不可存储的类型:QObject 及其子类(如 QWidget),因为 QObject 禁用了拷贝构造和赋值运算符。 QVectorQWidget vec; // 错误!编译失败解决方案:存储对应类型的指针。 QVectorQWidget* vec; // 正确!指针允许赋值和拷贝2. 核心顺序容器 2.1 QVector —— 变长数组 QVectorT 在底层实现上对应线性表的顺序存储。 2.1.1 底层内存结构 从其底层数据结构(如图所示)来看: QVectorT - [指针 d] - 指向 QVectorTypedDataT↳ [ ref | alloc | size | flags ] [ array[0] ] ... [ array[alloc-1] ]连续内存:所有元素在物理内存中是连续相邻存储的。 管理开销:包含引用计数(ref)、已分配空间(alloc)、当前元素个数(size)和标志位(flags)。 性能特点:支持 O(1) 随机访问。但在前部或中间位置插入/删除数据时,需要移动后续的所有元素,导致效率极低,时间复杂度为 O(n)。 适用场景:适用于需要频繁通过索引查询,而极少做中间插入、删除操作的业务。2.1.2 常用 API 补充与示例 语法格式与函数原型:void push_back(const T value); —— 尾部插入。 void push_front(const T value); —— 头部插入。 iterator insert(int i, const T value); —— 在指定索引 i 处插入元素。 void remove(int i); —— 删除指定索引 i 处的元素。 T takeAt(int i); —— 删除指定索引 i 处的元素并将其返回。 bool removeOne(const T value); —— 删除第一个匹配到 value 的元素,成功返回 true。 int indexOf(const T value, int from = 0) const; —— 从 from 开始查找元素,返回第一次匹配的索引。 bool contains(const T value) const; —— 判断是否包含某元素。 int count() const; —— 返回元素总数,等价于 size()。 void replace(int i, const T value); —— 将索引 i 处的元素替换为 value。 void swapItemsAt(int i, int j); —— 交换索引 i 和 j 的元素。 void move(int from, int to); —— 将元素从索引 from 移动到索引 to。QVectorQString data;// 1. 插入操作 data "100" "200" "300"; // 流式尾部插入 data.push_back("400"); // 尾部追加 - ["100", "200", "300", "400"] data.push_front("90"); // 头部插入 - ["90", "100", "200", "300", "400"] data.insert(2, "999"); // 指定位置插入 - ["90", "100", "999", "200", "300", "400"]// 2. 删除操作 data.remove(2); // 删除索引2的"999" QString ret = data.takeAt(2); // 删除并返回索引2的"200",此时 ret = "200" data.removeOne("400"); // 匹配并删除 "400"// 3. 查询与修改 QString item = data[1]; // 通过下标访问 int index = data.indexOf("100"); // 获取索引 bool find = data.contains("100");// 是否包含 int total = data.count(); // 元素总数data.replace(1, "Rock"); // 将索引1修改为 "Rock" data[1] = "888"; // 下标法修改 data.swapItemsAt(0, 2); // 交换元素位置 data.move(0, 2); // 将0号元素移动到2号位置,其他顺移2.1.3 二维容器嵌套 Qt 容器支持嵌套,常用于表达矩阵或二维网格: QVectorQVectorint matrix;// 填充 3x4 矩阵 for (int i = 0; i 3; i++) {QVectorint line;for (int j = 0; j 4; j++){line.push_back(i * 4 + j);}matrix.push_back(line); }// 遍历输出 for (int i = 0; i matrix.count(); i++) {for (int j = 0; j matrix[i].count(); j++){std::cout matrix[i][j] " ";}std::cout std::endl; }2.2 QList —— 指针数组(高效的多用途容器) 在 STL 中,std::list 底层通常是双向链表(如结构图所示,节点包含 data、pre 和 next 指针,支持快速的在任意位置 insert、pop 和 push)。 然而,Qt5的QListT 的底层实现并不是传统的链表。Qt6中的QList仅是QVector的别名,不再有实际意义上的QList。2.2.1 Qt5QList底层内存结构 从它的底层设计图可以看出: QListT - [指针 d] - 指向 QListData::Data↳ [ ref | alloc | begin | end | flags ] [ void* array[0] ] ... [ void* array[alloc-1] ]↓ ↓[ 堆区 T ] [ 堆区 T ]实现机制:QList 内部维护了一个指针数组(void* array[]),数组中的每个指针指向在堆(Heap)空间中独立分配的元素 T。 优化特点:由于内部存储的是指针,在头部或尾部添加/删除元素时,只需要移动或扩容指针数组,不需要移动实际的复杂数据对象。因此,它兼具了类似 QVector 的快速索引(O(1))和接近链表的两端高效插入性能。 使用原则:如果需要一个常规的顺序表,且不强制要求元素在内存中必须绝对连续存储,在 Qt 5 中应优先选择 QList。2.2.2 常用 API 与示例 QList 包含与 QVector 高度相似的 API。其特有的头部操作函数原型为:void prepend(const T value); —— 头部插入(等价于 push_front)。 void removeAt(int i); —— 删除指定索引处的元素。QListQString list; list "10" "20"; list.append("30"); list.push_back("40"); // 尾部追加 list.push_front("9"); // 头部追加 list.prepend("8"); // 头部追加 - ["8", "9", "10", "20", "30", "40"]list.insert(2, "111"); // 中间插入int idx = list.indexOf("10"); bool hasTen = list.contains("10");QString deletedStr = list.takeAt(2); // 删除并返回 list.removeAt(0); // 删除0号元素 list.removeOne("30"); // 删除"30"list[1] = "666"; list.replace(1, "999"); list.swapItemsAt(0, 2); list.move(0, 2);// 使用标准迭代器遍历 for (auto it = list.begin(); it != list.end(); it++) {qDebug() *it; }补充:QStringList QStringList 继承自 QListQString。它是专门用于处理字符串列表的子类,扩展了更多针对字符串的操作(如 join() 拼接、filter() 过滤等),比直接使用 QListQString 更具可读性和便利性。2.3 QLinkedList —— 纯正的双向链表 如果在底层需要一个真正的线性表链式存储结构,需使用 QLinkedListT。 2.3.1 底层内存结构 根据双向循环/带头结点的链表图: QLinkedListData 维护着包含了 [ ref | size | flags ] 的头信息, 每个节点为 QLinkedListNodeT,内部拥有前驱指针 p、后继指针 n 以及实际的数据 T t。性能特点:不支持索引随机访问,查找特定元素的时间复杂度为 O(n)。但是,其在任意已知位置的插入和删除操作都是常数级时间,复杂度为 O(1)​。 特有性质:迭代器直接指向表项实体本身。在执行插入或删除操作时,不会导致现有的其他迭代器失效。2.3.2 常用 API 与示例 注意:由于没有索引,QLinkedList 没有重载 [] 运算符,也没有 removeAt(int),只能通过迭代器或专用函数操作。void removeFirst(); —— 删除头节点。 void removeLast(); —— 删除尾节点。// 需要引入头文件 #include QLinkedList QLinkedListQString linkedList; linkedList "100" "200"; linkedList.append("300"); linkedList.push_back("400"); linkedList.push_front("90"); // ["90", "100", "200", "300", "400"]// 必须使用迭代器遍历 for (auto it = linkedList.begin(); it != linkedList.end(); it++) {qDebug() *it; }linkedList.removeFirst(); // 删除 "90" linkedList.removeLast(); // 删除 "400" linkedList.removeOne("300"); // 删除 "300"3. 性能深度对比与选型原则 3.1 性能复杂度对比表容器类 用索引查找 中间任意位置插入 头部插入 尾部插入QLinkedListT O(n) O(1) O(1) O(1)QListT O(1) O(n) Amort. O(1) Amort. O(1)QVectorT O(1) O(n) O(n) Amort. O(1)什么是 Amort. O(1)(均摊常数时间)? 以 QVector 尾部插入为例:如果触发了底层扩容(重新申请大内存、拷贝原数据),单次调用的时间复杂度是 O(n);但在多数情况下不需要扩容,调用复杂度为 O(1)。将扩容成本均摊到每一次常规插入中,平均时间复杂度仍然是 O(1)。4. 衍生顺序容器补充 4.1 QStack —— 栈(堆栈) 栈是一种后进先出(LIFO, Last In First Out)的单端线性表。数据只能从“栈顶”进栈(Push)和出栈(Pop),“栈底”是封闭的。 实现机制:QStackT 继承自 QVectorT,在顺序表的基础上限制并封装了栈的专属接口。 核心函数原型:void push(const T t); —— 元素压入栈顶。 T pop(); —— 弹出并返回栈顶元素。 T top(); —— 查看栈顶元素(不弹出)。QStackint stack; stack.push(10); // 进栈 stack.push(20); stack.push(30); // 此时栈顶为 30while (!stack.isEmpty()) {qDebug() stack.pop(); // 依次输出:30, 20, 10(后进先出) }4.2 QQueue —— 队列 队列是一种先进先出(FIFO, First In First Out)的单向数据结构。数据只能从一个方向(队尾)进入,从另一个方向(队头)出去。 实现机制:QQueueT 继承自 QListT。 核心函数原型:void enqueue(const T t); —— 在队尾入队。 T dequeue(); —— 在队头出队并返回该元素。 T head(); —— 查看队头元素(不出队)。QQueueint queue; queue.enqueue(10); // 入队 queue.enqueue(20); queue.enqueue(30); // 队尾为 30,队头为 10while (!queue.isEmpty()) {qDebug() queue.dequeue(); // 依次输出:10, 20, 30(先进先出) }5. 遍历实例:只读迭代器 在编写容器遍历代码时,应建立起区分“读写”与“只读”的良好习惯。只读迭代器的执行速度比读写迭代器更快,因为其底层不触发潜在的隐式共享深拷贝行为。 QVectorint data = {1, 2, 3, 4, 5};// 1. 读写遍历:需要修改内容时使用标准迭代器类型 for (auto it = data.begin(); it != data.end(); it++) {*it = (*it) * 10; // 修改容器元素的值 }// 2. 只读遍历:仅做查询或打印时,必须坚持使用只读迭代器(性能最优) // 迭代器类型为:QVectorT::const_iterator for (auto it = data.constBegin(); it != data.constEnd(); it++) {qDebug() *it; // 此时不允许进行 *it = xxx 的修改操作 }
← 返回列表