跳转到内容
新建笔记

Qt 6 的 QVector 与 QList:常用操作和边界

在 Qt 6 中,QVector<T> 是 QList<T> 的别名,两者使用同一种连续存储的容器实现。Qt 5 中两者的存储差异不能直接套到 Qt 6。下面以 Qt 6.10.2 为实际编译环境,保留 QVector 写法,说明原速查表中的操作及错误名称。Qt:QVector

先分清大小、容量和有效索引

跳转到“先分清大小、容量和有效索引”

size()、无参数 count()、length() 表示元素数,返回 qsizetype;capacity() 是容量。reserve(n) 请求预留容量,不创建第 n 个元素;resize(n) 才改变元素数。容量不一定精确等于请求值。

at(i) 和 operator[](i) 都要求 0 <= i < size()。Debug 断言可以发现部分错误,但不能把越界当作可恢复的异常处理;at() 也不是 std::vector::at() 那种抛出越界异常的接口。如果需要越界时返回默认值,使用 value(i, fallback)。首尾访问、删除首尾、按索引删除也各自要求非空或合法索引。

同一行中的接口用途相近,但签名、返回值及 const 限定仍以实际接口为准。

需要做什么Qt 6 接口与说明
追加、前插append()、prepend();STL 风格为 push_back()、push_front();+= value、<< value 也可追加
插入、替换insert(index, value)、replace(index, value)、operator[];插入位置允许等于 size(),普通访问不允许
按索引读取at() 只读,operator[] 可修改,value() 返回值并可给默认值
首尾读取first() / front()、last() / back();constFirst()、constLast() 明确只读
按值查找contains()、indexOf()、lastIndexOf()、count(value);找不到的索引是 -1
检查首尾与空startsWith()、endsWith()、isEmpty() / empty()
按索引删除remove(index, count)、removeAt();erase(iterator) 或 erase(first, last) 返回后续迭代器
按值删除removeOne() 返回是否删除;removeAll() 返回删除数量
删除首尾removeFirst() / pop_front()、removeLast() / pop_back()
取出并删除takeAt()、takeFirst()、takeLast() 返回被移除的值
改变顺序move(from, to) 移动已有元素;swap() 交换两个容器
填充、截取fill(value) 覆盖已有元素,fill(value, size) 同时设大小;mid(pos, length) 返回子容器
清空、容量clear() 清空元素;reserve()、resize()、capacity();squeeze() / shrink_to_fit() 请求释放多余容量
正向遍历begin()、end();只读用 cbegin()、cend() 或 constBegin()、constEnd()
反向遍历rbegin()、rend();只读用 crbegin()、crend();末尾迭代器是哨兵,不能解引用
连续数据data() 提供可写指针,constData() 提供只读指针;必须同时传递有效元素数量
值操作复制/移动构造、赋值 =、比较 ==、拼接 +;容器析构自动发生
隐式共享detach()、isDetached()、isSharedWith() 与共享状态有关;业务逻辑通常直接使用值语义即可

这些容器通常采用隐式共享:复制容器后修改一个副本,不会把另一个副本的元素值一并改掉。非 const 访问可能触发分离;只读遍历优先使用 const 接口。修改、分离或重新分配后,应重新取得迭代器、元素引用与数据指针,不能拿旧地址继续使用。共享底层存储也不代表可以在多个线程中无同步地修改同一个容器对象。Qt:QList

原表中需要修正的名称

跳转到“原表中需要修正的名称”
原写法或解释修正
popBack、popFront、pushBack、pushFront分别使用 pop_back、pop_front、push_back、push_front;原来的 pop_front 并没有拼错
shrinkToFit()正确名称为 shrink_to_fit();也可用 squeeze()
lastIndex()按值反向查找是 lastIndexOf(value)
if (!vec)没有这种判空转换;使用 vec.isEmpty() 或 vec.empty()
isValidIterator()没有这样的公开检查器;通过操作规则管理有效期,而不是事后检查已失效迭代器
move() 是内部移动构造move(from, to) 是把已有元素移动到另一个索引;C++ 移动构造是另一件事
copyConstruct、defaultConstruct、destruct、freeData、reallocData不把实现细节名当成公开容器 API;使用构造、赋值、RAII 和公开容量操作
setSharable()不作为 Qt 6 的容器接口使用;需要独立副本时使用正常值操作或在有明确目的时 detach()
fromStdVector()、toStdVector()不用于本页 Qt 6 示例;用迭代器范围构造在标准容器间复制
fromList()、toList()Qt 6.10.2 仍保留兼容写法,但 QVector/QList 已是同一类型,直接赋值即可
counta、takeLast/buidcus分别按需要使用 count() / count(value) 和 takeLast();不是新的函数名

完整示例:操作、复制和转换

跳转到“完整示例:操作、复制和转换”

程序同时验证大小与容量、查找失败、越界默认值、删除返回值和副本独立性。不会通过实际越界访问来“测试”未定义行为。

#include <QList>
#include <QVector>
#include <algorithm>
#include <cassert>
#include <type_traits>
#include <vector>
int main() {
static_assert(std::is_same_v<QVector<int>, QList<int>>);
QVector<int> values{3, 1, 3};
assert(values.size() == 3 && values.count() == 3 && values.length() == 3);
values.reserve(32);
assert(values.capacity() >= 32 && values.size() == 3);
assert(values.at(0) == 3 && values.value(99, -1) == -1);
assert(values.contains(3) && values.count(3) == 2);
assert(values.indexOf(3) == 0 && values.lastIndexOf(3) == 2);
assert(values.indexOf(8) == -1);
assert(values.startsWith(3) && values.endsWith(3));
assert(values.first() == values.front() && values.last() == values.back());
assert(values.constFirst() == 3 && values.constLast() == 3);
assert(values.cbegin() == values.constBegin());
assert(values.cend() == values.constEnd());
assert(*values.crbegin() == 3);
assert(std::distance(values.crbegin(), values.crend()) == 3);
const QVector<int> snapshot = values;
values[0] = 9;
assert(snapshot.at(0) == 3 && values.at(0) == 9);
assert(!values.isSharedWith(snapshot));
values.detach();
assert(values.isDetached());
values.data()[1] = 8; // 本次调用得到的新指针,只在当前有效期内使用
assert(values.constData()[1] == 8);
QVector<int> edited;
edited.append(2);
edited.prepend(1);
edited.push_back(3);
edited.push_front(0); // {0, 1, 2, 3}
edited.insert(2, 9);
edited.replace(2, 8);
edited.move(2, 4); // {0, 1, 2, 3, 8}
assert((edited == QVector<int>{0, 1, 2, 3, 8}));
assert((edited.mid(1, 2) == QVector<int>{1, 2}));
assert(edited.takeAt(4) == 8);
assert(edited.takeFirst() == 0 && edited.takeLast() == 3);
assert((edited == QVector<int>{1, 2}));
edited += 2;
edited << 3;
assert(edited.removeOne(2));
assert(!edited.removeOne(99));
assert(edited.removeAll(2) == 1);
edited.pop_front();
edited.pop_back();
assert(edited.isEmpty() && edited.empty());
QVector<int> erased{0, 1, 2, 3, 4, 5};
erased.remove(1, 2); // {0, 3, 4, 5}
erased.removeAt(1); // {0, 4, 5}
const auto next = erased.erase(erased.begin());
assert(*next == 4);
erased.erase(erased.begin(), erased.end());
assert(erased.empty());
erased.fill(7, 4);
erased.resize(6);
assert(erased.at(4) == 0 && erased.at(5) == 0);
erased.fill(2);
erased.removeFirst();
erased.removeLast();
assert(erased.size() == 4 && erased.count(2) == 4);
erased.squeeze();
erased.shrink_to_fit();
assert(erased.size() == 4 && erased.capacity() >= erased.size());
erased.clear();
assert(erased.empty());
QVector<int> sorted{3, 1, 2};
std::sort(sorted.begin(), sorted.end());
std::reverse(sorted.rbegin(), sorted.rend());
assert((sorted == QVector<int>{3, 2, 1}));
const QVector<int> joined = sorted + QVector<int>{0};
assert((joined == QVector<int>{3, 2, 1, 0}));
QVector<int> exchanged{8};
sorted.swap(exchanged);
assert((sorted == QVector<int>{8}));
const std::vector<int> standard{4, 5, 6};
const QVector<int> fromStandard(standard.cbegin(), standard.cend());
const std::vector<int> back(fromStandard.cbegin(), fromStandard.cend());
assert(back == standard);
const QList<int> list = fromStandard;
const QVector<int> vector = list;
assert(vector == fromStandard);
}

构建为 Debug,保留断言:

cmake_minimum_required(VERSION 3.20)
project(QtContainerExample LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core)
add_executable(container_example main.cpp)
target_link_libraries(container_example PRIVATE Qt6::Core)

原表中的大部分查找、插入和删除是按元素逐一处理的操作,不应因为类型名里带 Vector 就推断所有操作均为常数时间。需要大量按键查找时,应根据任务考虑 QHash、QMap 或其他数据结构,而不是单纯替换函数拼写。