在 qt 中,foreach 是一个 qt 扩展的关键字(宏定义),用于遍历容器类元素,语法简洁、使用方便,底层基于容器的迭代器实现,但屏蔽了迭代器的复杂操作,让遍历逻辑更直观。它支持 qt 容器(如 qlist、qvector、qmap、qhash 等)和 stl 容器(如 std::vector、std::list 等),是 qt 开发中遍历容器的常用方式。
一、核心本质:宏定义而非原生 c++ 语法
foreach 并非 c++ 标准关键字,而是 qt 通过 #define 定义的宏,其底层映射到容器的迭代器遍历逻辑。qt 5 及以上版本默认启用该宏,若需禁用(如避免与其他库冲突),可在项目文件(.pro)中添加:
defines += qt_no_foreach
二、基本语法
1. 遍历“值类型容器”(如 qlist、qvector、std::vector)
语法:
foreach (const 元素类型 &变量名, 容器对象) {
// 遍历逻辑(变量名表示当前元素)
}
// 或(非 const,允许修改元素,仅适用于可修改的容器)
foreach (元素类型 &变量名, 容器对象) {
// 修改元素的逻辑
}
示例:遍历 qlist
qlist<qstring> fruits = {"苹果", "香蕉", "橙子"};
// 只读遍历(推荐用 const &,避免拷贝)
foreach (const qstring &fruit, fruits) {
qdebug() << fruit; // 输出:"苹果" "香蕉" "橙子"
}
// 可修改遍历(需用非 const 引用,容器必须是可修改的)
foreach (qstring &fruit, fruits) {
fruit = "[水果]" + fruit; // 修改元素
}
qdebug() << fruits; // 输出:["[水果]苹果", "[水果]香蕉", "[水果]橙子"]
2. 遍历“键值对容器”(如 qmap、qhash、std::map)
qt 的键值对容器(qmap<k, v>、qhash<k, v>)遍历后,foreach 的“元素类型”是 qpair<k, v>(或 std::pair<k, v> 对于 stl 容器),通过 first 访问键、second 访问值。
语法:
foreach (const qpair<键类型, 值类型> &pair, 键值对容器) {
qdebug() << "键:" << pair.first << ",值:" << pair.second;
}
示例:遍历 qmap<int, qstring>
qmap<int, qstring> studentmap;
studentmap.insert(101, "张三");
studentmap.insert(102, "李四");
studentmap.insert(103, "王五");
// 遍历键值对
foreach (const qpair<int, qstring> &pair, studentmap) {
qdebug() << "学号:" << pair.first << ",姓名:" << pair.second;
}
// 输出(qmap 按键排序):
// 学号: 101 ,姓名: "张三"
// 学号: 102 ,姓名: "李四"
// 学号: 103 ,姓名: "王五"
3. 简化写法:使用auto(qt 5.7+ 支持 c++11 及以上)
若不想显式写元素类型,可结合 auto 关键字(需开启 c++11 支持,.pro 中添加 config += c++11):
qlist<int> nums = {1, 2, 3, 4};
foreach (const auto &num, nums) {
qdebug() << num; // 自动推导类型为 int
}
qhash<qstring, int> scorehash = {{"数学", 90}, {"语文", 85}};
foreach (const auto &pair, scorehash) {
qdebug() << pair.first << ":" << pair.second; // 自动推导为 qpair<qstring, int>
}
三、关键特性与注意事项
1. 遍历的是“容器的拷贝”(重要!)
foreach 遍历的是容器的 临时拷贝,而非容器本身。这意味着:
- 遍历过程中修改容器(如添加/删除元素),不会影响当前遍历的结果(因为遍历的是拷贝);
- 若容器元素是“大对象”(如
qbytearray、自定义结构体),拷贝会带来性能开销,此时推荐用 迭代器 或 qt 5.10+ 提供的q_foreach替代(本质相同,但可通过qt_use_foreach_copy控制是否拷贝,默认仍拷贝)。
反例:遍历中修改容器无效
qlist<int> nums = {1, 2, 3};
foreach (const int &num, nums) {
nums.append(num * 2); // 向原容器添加元素,但遍历的是拷贝,不会遍历到新元素
}
qdebug() << nums; // 输出:[1,2,3,2,4,6](原容器被修改,但遍历未包含新元素)
2. 支持“空容器”和“单元素容器”
foreach 会自动处理空容器(不执行循环体),无需手动判断容器是否为空:
qvector<qstring> emptyvec;
foreach (const auto &str, emptyvec) {
qdebug() << str; // 不会执行
}
3. 与 qt 容器的兼容性
foreach 完美支持所有 qt 容器类:
- 顺序容器:qlist、qvector、qbytearray、qstringlist、qlinkedlist;
- 关联容器:qmap、qmultimap、qhash、qmultihash;
- 其他:qset、qstack、qqueue(本质是顺序容器的封装)。
4. 与 stl 容器的兼容性
qt 5.0+ 支持用 foreach 遍历 stl 容器(如 std::vector、std::list、std::map),语法与 qt 容器一致:
#include <vector>
#include <list>
std::vector<int> stlvec = {10, 20, 30};
foreach (const auto &val, stlvec) {
qdebug() << val; // 输出:10 20 30
}
std::map<std::string, int> stlmap = {{"a", 1}, {"b", 2}};
foreach (const auto &pair, stlmap) {
qdebug() << qstring::fromstdstring(pair.first) << ":" << pair.second; // 输出:"a":1 "b":2
}
5. 避免使用“容器的引用”作为遍历对象
若误将容器的引用传给 foreach,会导致遍历的是“引用的拷贝”,仍无法修改原容器,且语法冗余,不推荐:
qlist<int> nums = {1,2,3};
// 不推荐:&nums 是引用,但 foreach 仍会拷贝引用指向的容器
foreach (const int &num, nums) {
// ...
}
6. 元素为“指针/智能指针”时的注意事项
若容器存储的是指针(如 qlist<qobject*>),foreach 遍历的是指针的拷贝(而非对象的拷贝),此时修改指针指向的对象是有效的,但修改指针本身(如赋值为 nullptr)无效:
qlist<qobject*> objlist;
objlist.append(new qobject);
objlist.append(new qobject);
foreach (qobject *obj, objlist) {
obj->setobjectname("test"); // 有效:修改指针指向的对象的属性
obj = nullptr; // 无效:仅修改拷贝的指针,原容器中的指针不变
}
foreach (qobject *obj, objlist) {
qdebug() << obj->objectname(); // 输出:"test" "test"
delete obj;
}
四、foreach与迭代器、范围 for 的对比
| 特性 | foreach(qt 宏) | 迭代器(qlist::iterator) | 范围 for(c++11+) |
|---|---|---|---|
| 语法简洁性 | 最高(无需手动控制迭代) | 中等(需初始化迭代器) | 高(原生语法,简洁) |
| 遍历对象 | 容器拷贝 | 容器本身 | 容器本身(可通过引用控制) |
| 遍历中修改容器 | 无效(修改拷贝) | 有效(需注意迭代器失效) | 有效(需注意容器类型) |
| 性能(大对象容器) | 较低(拷贝开销) | 较高(无拷贝) | 较高(无拷贝) |
| 兼容性 | qt 容器 + stl 容器 | qt 容器 + stl 容器 | qt 容器 + stl 容器(c++11+) |
| 适用场景 | 简单遍历、无需修改容器 | 复杂遍历(如插入/删除) | 现代 c++ 开发、追求原生语法 |
推荐选择:
- 简单只读遍历(qt 容器):优先用
foreach(语法简洁); - 遍历中需修改容器/大对象容器:用迭代器或范围 for;
- 现代 c++ 项目(c++11+):推荐范围 for(原生支持,无 qt 依赖)。
五、常见错误与解决方案
错误 1:遍历中修改容器,期望影响遍历结果
原因:foreach 遍历的是拷贝,修改原容器不影响遍历。
解决方案:改用迭代器或范围 for:
qlist<int> nums = {1,2,3};
// 用迭代器遍历并修改
for (auto it = nums.begin(); it != nums.end(); ++it) {
*it *= 2; // 直接修改原容器元素
}
qdebug() << nums; // 输出:[2,4,6]
错误 2:遍历大对象容器时性能低下
原因:拷贝大对象带来开销。
解决方案:用 const_iterator 或范围 for(无拷贝):
// 大对象容器(如 qbytearray)
qlist<qbytearray> biglist;
// ... 填充大量 qbytearray ...
// 用范围 for 遍历(无拷贝)
for (const auto &ba : biglist) {
qdebug() << ba.size();
}
错误 3:混淆键值对容器的元素类型
原因:qmap/qhash 的元素是 qpair,而非单独的键或值。
解决方案:通过 pair.first(键)和 pair.second(值)访问:
qhash<qstring, int> scorehash = {{"英语", 95}};
// 错误:元素类型是 qpair,不是 int
// foreach (const int &score, scorehash) { ... }
// 正确:
foreach (const auto &pair, scorehash) {
qdebug() << pair.first << ":" << pair.second; // 英语:95
}
六、总结
foreach 是 qt 提供的简洁遍历工具,核心优势是语法简单、无需关注迭代器细节,适合大多数“只读、不修改容器”的场景。其底层基于容器拷贝,因此需注意:
- 遍历中修改容器无效;
- 大对象容器慎用(拷贝开销);
- 键值对容器需通过
qpair访问键和值。
若需复杂遍历(如插入/删除元素)或追求更高性能,建议使用迭代器或 c++11 范围 for 循环。
到此这篇关于qt中foreach的实现示例的文章就介绍到这了,更多相关qt foreach内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论