当前位置: 首页 > news >正文

STL进阶:手写forEach与map操作技巧

引言

在前面的文章中,我们已经学习了setmapunordered_map的基本操作。本文将聚焦三个进阶主题:

  1. 手写my_forEach模板函数——理解 STL 算法如何通过函数指针和仿函数操作迭代器

  2. map[]at()的本质区别——一个会自动插入,一个会抛异常

  3. 自定义类型存入unordered_map——值或键是自定义类时如何处理

第一部分:手写 forEach 模板函数

一、STL for_each 回顾

#include <iostream> #include <set> #include <algorithm> // for_each 所在位置 using namespace std; void show(int item) { cout << item << " "; } int main() { set<int> s = {8, 7, 2, 4, 5, 0, 3}; // STL 的 for_each:起始迭代器、结束迭代器、回调函数 for_each(s.begin(), s.end(), show); // 输出:0 2 3 4 5 7 8 }

二、回调函数传递的本质

for_each第三个参数接受的是可调用对象show是一个函数名,函数名在 C++ 中会被隐式转换为函数指针

// show 的类型是:void (*)(int) // 函数名 show 就是函数地址,即函数指针 void (*fp)(int) = show; // fp 是指向 show 的函数指针 fp(42); // 等价于 show(42)

三、手写 my_forEach 模板

// 模板参数: // Itr — 迭代器类型(set::iterator / map::iterator / ...) // Call — 可调用对象的类型(函数指针 / 仿函数 / Lambda) template<typename Itr, typename Call> void my_forEach(Itr start, Itr end, Call fp) { while (start != end) { fp(*start); // 解引用迭代器,传给回调函数 start++; // 移动到下一个元素 } }

为什么用模板?

如果用具体类型如果用模板
只能用于set<int>可用于任何容器
只能接受void(*)(int)回调可接受任何可调用对象
写一个容器就得重写一个一劳永逸

四、配合 set 和 map 使用

// 用于 set set<int, greater<int>> s({8, 7, 2, 4, 5, 6, 0, 3}); my_forEach(s.begin(), s.end(), show); // 输出:8 7 6 5 4 3 2 0 // 用于 map(需要适配回调函数) // map 迭代时,元素是 pair<const K, V>,键不允许修改 void show2(pair<const int, char> item) { cout << item.first << ":" << item.second << " "; } map<int, char> m = {{5, '9'}, {4, 'a'}, {5, '6'}}; my_forEach(m.begin(), m.end(), show2); // 输出:4:a 5:9(自动按键排序,key=5 重复的不插入)

五、本质理解

第二部分:map 的 [] vs at() —— 安全访问关键

一、问题场景

map<int, char> m = {{5, '9'}, {4, 'a'}}; // [] 操作符:若 key 不存在,会自动插入! cout << m[5] << endl; // '9' → 存在,正常返回 cout << m[10] << endl; // 0 → 不存在,自动插入 {10, 0}! // 此时 m.size() 变成了 3! // 这不是 bug,是设计行为

二、本质区别

三、代码验证

#include <iostream> #include <map> #include <stdexcept> using namespace std; int main() { map<int, char> m = {{5, '9'}, {4, 'a'}}; cout << "初始化 size: " << m.size() << endl; // 2 // [] 访问不存在的 key cout << "m[10] = " << m[10] << endl; // 空字符 cout << "访问后 size: " << m.size() << endl; // 3!被插入了 // at() 访问不存在的 key try { cout << m.at(15) << endl; } catch (out_of_range& e) { cout << "异常:" << e.what() << endl; // 输出:异常:map::at } cout << "at()访问后 size: " << m.size() << endl; // 仍然是 3 return 0; }

四、最佳实践

场景推荐方式原因
确定 key 存在m[k]简洁
不确定 key 是否存在m.find(k)m.at(k)+ try-catch安全,不会意外插入
需要插入或更新m[k] = v最方便
只读查询m.at(k)不存在时明确报错

第三部分:自定义类型存入 unordered_map

一、Person 类定义

#include <iostream> #include <string> using namespace std; class Person { private: int sid; string name; int age; public: Person(int sid, string name, int age) : sid(sid), name(name), age(age) {} // 将对象信息格式化为字符串 string tostring() const { char buf[128] = ""; sprintf(buf, "%d,%s,%d", sid, name.c_str(), age); return string(buf); } };

二、存入 unordered_map 的几种方式

#include <unordered_map> int main() { unordered_map<int, Person> ms; // 方式1:make_pair ms.insert(make_pair(1, Person(1, "李明", 17))); // 方式2:初始化列表 ms.insert({2, Person(2, "刘明", 21)}); // 方式3:emplace 原地构造(最高效,减少一次拷贝) ms.emplace(4, Person(4, "王明", 18)); cout << "size: " << ms.size() << endl; // 3 return 0; }

emplace vs insert 的区别

三、遍历与查找

int main() { unordered_map<int, Person> ms; ms.insert(make_pair(1, Person(1, "李明", 17))); ms.insert(make_pair(2, Person(2, "刘明", 21))); ms.insert(make_pair(4, Person(4, "王明", 18))); ms.insert(make_pair(3, Person(3, "赵明", 22))); ms.insert(make_pair(6, Person(6, "孙明", 21))); // 遍历(无序!) for (auto it = ms.begin(); it != ms.end(); ++it) { cout << it->second.tostring() << endl; } // 查找姓"刘"的学生 for (auto it = ms.begin(); it != ms.end(); ++it) { if (it->second.tostring().find("刘") != string::npos) { cout << "找到:" << it->second.tostring() << endl; break; } } // 用 find() 按键快速查找(O(1)) auto it = ms.find(6); if (it != ms.end()) { ms.erase(it); // 删除孙明 } return 0; }

四、string::find 的使用

string s = "1,刘明,21"; // find() 返回查找内容首次出现的字节位置(从 0 开始) // 注意:一个中文字符在 UTF-8 中占 3 个字节 cout << s.find("刘") << endl; // 输出 2('1' ',' 之后) // 未找到返回 string::npos if (s.find("李") == string::npos) { cout << "未找到" << endl; }
查找方式返回值未找到
s.find("str")首次出现的位置(从0开始)string::npos
s.rfind("str")最后一次出现的位置string::npos
s.find_first_of("abc")任意字符首次出现位置string::npos

第四部分:完整示例

#include <iostream> #include <map> #include <unordered_map> #include <string> using namespace std; template<typename Itr, typename Call> void my_forEach(Itr start, Itr end, Call fp) { while (start != end) { fp(*start); start++; } } class Person { private: int sid; string name; int age; public: Person(int sid, string name, int age) : sid(sid), name(name), age(age) {} string tostring() const { char buf[128] = ""; sprintf(buf, "%d,%s,%d", sid, name.c_str(), age); return string(buf); } }; int main() { // ========== 测试 my_forEach + map ========== cout << "===== map 遍历 =====\n"; map<int, char> m = {{5, '9'}, {4, 'a'}}; my_forEach(m.begin(), m.end(), [](pair<const int, char> item) { cout << item.first << ":" << item.second << " "; }); cout << endl; // ========== 测试 at() 异常 ========== cout << "\n===== at() 异常测试 =====\n"; cout << "m[10] = " << m[10] << " (size=" << m.size() << ")" << endl; try { cout << m.at(15) << endl; } catch (out_of_range& e) { cout << "m.at(15) 抛出异常!" << endl; } cout << "at() 后 size = " << m.size() << " (未变化)" << endl; // ========== 测试自定义类型 ========== cout << "\n===== 自定义类型存储 =====\n"; unordered_map<int, Person> students; students.emplace(1, Person(1, "李明", 17)); students.emplace(2, Person(2, "刘明", 21)); students.emplace(3, Person(3, "王明", 18)); for (auto it = students.begin(); it != students.end(); ++it) { cout << it->second.tostring() << endl; } return 0; }

总结

一、核心要点

主题核心内容
my_forEach 模板模板函数接收迭代器 + 可调用对象,体现 STL 统一接口思想
map::[]key 不存在时自动插入默认值
map::at()key 不存在时抛出 out_of_range 异常
emplace原地构造,减少一次临时对象拷贝
string::find()返回字节位置,未找到返回string::npos

二、安全访问原则

三、一句话记忆

my_forEach用模板统一所有容器的遍历;map::[]找不到就插入,at()找不到就抛异常;emplace原地构造最省拷贝;string::find返回位置,npos表示未找到。

http://www.cnnetsun.cn/news/2495415.html

相关文章:

  • ElevenLabs印地文语音API接入全攻略:从零配置到生产级SSML控制,3小时内上线高保真语音服务
  • element-plus主题换色
  • Shiro反序列化漏洞深度解析:从Padding Oracle到TemplatesImpl链
  • 3分钟搞定百度网盘提取码:新手也能快速上手的终极解决方案
  • 5步终极指南:如何让四足机器人像猎豹一样奔跑
  • 【C++ AI 大模型接入 SDK】 - DeepSeek 模型接入(下)
  • AI教材写作大揭秘!低查重工具,为教材编写保驾护航!
  • AI教材生成秘籍!揭秘低查重的AI教材编写工具,高效产出优质教材
  • Tidal-Media-Downloader:3分钟掌握终极Tidal音乐下载方案
  • 华为OD机试真题 新系统【小学英语老师批改作文】
  • 每天节省25分钟:淘宝淘金币自动化脚本全攻略
  • USBIPD-Win终极指南:3步实现Windows与WSL 2的USB设备无缝共享
  • 如何在IDEA中高效编辑JAR文件:JarEditor插件完整指南
  • AhabAssistantLimbusCompany:PC端《Limbus Company》自动化助手终极指南
  • 快速制作系统启动盘:Rufus实战指南与高级配置技巧
  • AI编程助手的现状与未来:Copilot、CodeLlama与GPT-4
  • Cursor Free VIP技术架构深度解析:设备标识重置与多平台兼容实现
  • 如何在GTA V中安全使用YimMenu:新手完全指南与防封技巧
  • ElevenLabs东北话语音API调用失败率高达41%?一线工程师紧急封存的6个底层HTTP头配置方案
  • Obsidian Full Calendar插件完整指南:在笔记中高效管理你的日程
  • 告别数据锁定:用youdaonote-pull实现有道云笔记的本地化自由
  • 5分钟搞定歌词管理:LDDC免费歌词下载工具完全指南
  • cann/asc-devkit:内置数据类型
  • 如何通过CDCS项目快速提升数据科学实战能力:中国数据竞赛优胜解集锦的终极指南 [特殊字符]
  • TextShot多语言OCR配置指南:如何轻松识别中文、英文、法文等100+语言
  • requests-oauthlib实战:构建完整的第三方应用集成方案
  • fltk-rs主题定制技巧:打造个性化GUI界面的10个实用方法
  • 如何在Windows上快速运行安卓应用:APK Installer终极指南
  • 如何高效使用Mihon漫画阅读器:Android平台上的开源漫画管理解决方案
  • 如何为老款Mac安装最新macOS?OCLP-Mod技术深度解析