lin通信学习总结

核心内容摘要

微信小程序自定义tabBar实战:从零到一打造个性化底部导航栏(附完整代码)
无需Steam客户端的Steam创意工坊模组下载解决方案:WorkshopDL完全指南

如何永久保存数字记忆?揭秘QQ空间备份神器

std::variantstd::variant是 C17 æ ‡å‡†åº“ä¸­åŠ å…¥çš„ä¸€ä¸ªç±»æ¨¡æ�¿å®ƒä»£è¡¨ä¸€ä¸ªç±»å�‹å®‰å…¨çš„è�”å�ˆä½“union。它å�¯ä»¥æŒ�有其模æ�¿å�‚数列表中指定的任何一ç§�ç±»å�‹çš„值。我们也ä¸�å�«ä»–è�”å�ˆä½“了常说的便是“å�˜ä½“â€� å�‚考https://en.cppreference.com/w/cpp/header/variant.htmlä¼ ç»Ÿçš„ Cunionä¸�是类å�‹å®‰å…¨çš„。我们需è¦�自己记ä½�当å‰�存储的是哪ç§�ç±»å�‹å¦‚æ�œè®¿é—®é”™äº†æ¯”如在一个存储intçš„union上读å�–float会导致未定义行为就好比内存里å®�际是int 10的二进制数æ�®ä½†ä½ è¦�求编译器按照floatçš„æ ¼å¼�è§£æ��这段二进制 ——floatå’Œint的二进制编ç �规则完全ä¸�å�Œæ¯”如float是 IEEE 754 æµ®ç‚¹æ ¼å¼�int是补ç �è§£æ��出æ�¥çš„结æ�œæ˜¯æ— æ„�义的 “å�ƒåœ¾å€¼â€�è€Œä¸”å®ƒæ— æ³•å¤„ç�†é��平凡类å�‹å¦‚std::string其中é��平凡类å�‹æŒ‡é‚£äº›æœ‰è‡ªå®šä¹‰æ�„é€ å‡½æ•° / æ��æ�„函数ã€�自定义拷è´� / 移动语义ã€�虚函数的类å�‹æ¯”如std::stringã€�std::vectorã€�std::map等这类类å�‹çš„对象需è¦�编译器自动管ç�†èµ„æº�比如std::stringä¼šåœ¨å †ä¸Šåˆ†é…�内存存储字符æ��æ�„时释放。详细的对比就是C11 之å‰�union 完全ç¦�止包å�«é��平凡类å�‹ —— å› ä¸º union çš„æ�„é€ / æ��æ�„函数是编译器自动生æˆ�的它å�ªä¼šåˆ†é…�内存但ä¸�会调用æˆ�员的æ�„é€ / æ��æ�„函数。比如// C11 å‰�编译报错 union BadUnion { int i; std::string s; // std::string 有自定义æ�„é€ /æ��æ�„/æ‹·è´� };如æ�œå…�许BadUnionçš„æ��æ�„函数ä¸�知é�“该调用int还是std::stringçš„æ��æ�„函数std::stringçš„å †å†…å­˜ä¼šæ³„æ¼�导致资æº�管ç�†å´©æºƒã€‚C11 å�Šä¹‹å��å…�许包å�«é��平凡类å�‹ä½†éœ€è¦�手动管ç�†æ�„é€ / æ��æ�„æ��å…¶ç¹�ç��且容易出错#include iostream #include string union StringUnion { int i; std::string s; // 空æ�„é€ ä¸�åˆ�始化任何æˆ�员必须手动æ�„é€ StringUnion() {} // 空æ��æ�„ä¸�销æ¯�任何æˆ�员必须手动销æ¯� ~StringUnion() {} // 手动æ�„é€ std::string æˆ�员 void init_string(const std::string str) { // 定ä½� new在 s 对应的内存地å�€ä¸Šæ�„é€ std::string 对象 new (s) std::string(str); } // 手动销æ¯� std::string æˆ�员 void destroy_string() { // 显å¼�调用 std::string çš„æ��æ�„å‡½æ•°é‡Šæ”¾å †å†…å­˜ s.~basic_string(); } }; int main() { // 场景1正确使用先æ�„é€ ã€�å†�使用ã€�最å��销æ¯� StringUnion u1; //

手动æ�„é€ string æˆ�员必须先æ�„é€ æ‰�能访问 u

init_string(Hello Union); //

使用 string �员 std::cout u

s u

s std::endl; // 输出Hello Union //

手动销� string �员必须销��则内存泄� u

destroy_string(); // 场景2切æ�¢æˆ�员先销æ¯�æ—§æˆ�员å†�æ�„é€ æ–°æˆ�员 StringUnion u2; // 先使用 int æˆ�员int 是平凡类å�‹æ— 需手动æ�„é€ /销æ¯� u

i 100; std::cout u

i u

i std::endl; // 输出100 // 切æ�¢åˆ° string æˆ�员int æ— éœ€é”€æ¯�但必须先æ�„é€ string u

init_string(Switch to string); std::cout u

s u

s std::endl; // 输出Switch to string u

destroy_string(); // 用完必须销� // 场景3错误示例忘记销�导致内存泄� // StringUnion u3; // u

init_string(Memory Leak); // // 忘记调用 destroy_string()std::string çš„å †å†…å­˜æ°¸è¿œä¸�会释放 // 场景4错误示例未æ�„é€ å°±è®¿é—®æœªå®šä¹‰è¡Œä¸º // StringUnion u4; // std::cout u

s std::endl; // 未æ�„é€ å°±è®¿é—®ç¨‹åº�崩溃/å�ƒåœ¾å€¼ return 0; }细节点new (s) std::string(str)调用定ä½� newä¸�åœ¨å †ä¸Šåˆ†é…�新内存而是直æ�¥åœ¨s这个地å�€ä¸Šæ�„é€ std::string对象执行std::stringçš„æ�„é€ å‡½æ•°åˆ�始化其内部的指针ã€�长度等æˆ�员。这ç§�写法需è¦�ä½ æ‰‹åŠ¨è®°ä½� “当å‰�活跃的是哪个æˆ�员â€�手动调用æ�„é€ / æ��æ�„ —— ä¸�仅代ç �å¤�æ�‚还å›�到了 “记类å�‹â€� 的问题一旦æ¼�调用æ��æ�„就会导致内存泄æ¼�调用错了å�ˆæ˜¯æœªå®šä¹‰è¡Œä¸ºã€‚std::variant的优势是它解决了所有这些问题它知é�“当å‰�存储的是哪ç§�ç±»å�‹å¹¶ç¡®ä¿�对象被正确æ�„é€ å’Œæ��æ�„我们å�¯ä»¥æŠŠå®ƒæƒ³è±¡æˆ�一个 “智能的â€�ã€�“类å�‹ä¸°å¯Œçš„â€�union。ï¸� 定义和赋值修改#include variant #include string #include iostream // 示例定义和赋值 int main() { // 定义一个 variant它å�¯ä»¥å­˜å‚¨ä¸€ä¸ª int一个 double或一个 std::string std::variantint, double, std::string v; v 42; // ç�°åœ¨æŒ�有 int std::cout int: std::getint(v) std::endl; v

14; // ç�°åœ¨æŒ�有 double std::cout double: std::getdouble(v) std::endl; v hello; // ç�°åœ¨æŒ�有 std::string std::cout string: std::getstd::string(v) std::endl; // 赋值时如æ�œæ‰¾ä¸�到对应类å�‹çš„值则报错 // v std::pairint, int{}; // Error // 使用index()è�·å�–当å‰�æŒ�有的类å�‹ç´¢å¼• std::cout Current index: v.index() std::endl; std::variantstd::string, std::string v2; // v2 abc; // Error }std::variant是 C17 引入的类å�‹å®‰å…¨è�”å�ˆä½“在定义时必须指定它能存储的所有类å�‹åˆ—表且这些类å�‹ä¼šæŒ‰é¡ºåº�分é…�索引ä»� 0 开始。// æ ¼å¼�std::variantç±»å�‹1, ç±»å�‹2, ç±»å�‹3, ... å�˜é‡�å��; std::variantint, double, std::string v; // å�¯å­˜å‚¨int(索引

�double(索引

�string(索引

定义时至少è¦�指定一ç§�ç±»å�‹ç©ºçš„std::variant是é��法的ä¸�å…�许é‡�å¤�定义相å�Œç±»å�‹å¦‚std::variantstd::string, std::stringè¿™ç±»å®šä¹‰æ— æ„�义且会导致编译错误未显å¼�赋值时std::variant会默认åˆ�始化第一个类å�‹å¦‚上é�¢çš„våˆ�å§‹æŒ�有值为0çš„int。所以我们就å�•纯定义的è¯�也会调用æ�„é€ æ‰€ä»¥è¦�求第一个å�‚æ•°å¿…é¡»è¦�有默认æ�„é€ ç»†èŠ‚ä¸�è¦�错如æ�œéƒ½æ²¡æœ‰é»˜è®¤æ�„é€ æˆ‘ä»¬å�¯ä»¥ç¬¬ä¸€ä¸ªä¼ 入一个该类æ��供的一个空类 --- std::monostatestd::variant支æŒ�ç›´æ�¥èµ‹å€¼ä½†å�ªèƒ½èµ‹å€¼ä¸ºå®šä¹‰æ—¶æŒ‡å®šçš„ç±»å�‹èµ‹å€¼å��会自动切æ�¢å†…部存储的类å�‹ã€‚std::variantint, double, std::string v; //

赋值为int类�索引0 v 42; std::cout int值: std::getint(v) std::endl; // 输出int值: 42 //

赋值为double类�索引1 v

14; std::cout double值: std::getdouble(v) std::endl; // 输出double值:

14 //

赋值为std::stringç±»å�‹ç´¢å¼•2 v hello; // å­—é�¢é‡�自动转æ�¢ä¸ºstd::string std::cout string值: std::getstd::string(v) std::endl; // 输出string值: helloâ�Œ ä¸�能赋值为定义时未指定的类å�‹å¦‚v std::pairint, int{}会直æ�¥ç¼–译报错â�Œ é‡�å¤�ç±»å�‹çš„std::variant如std::variantstd::string, std::stringæ— æ³•èµ‹å€¼å› ä¸ºç¼–è¯‘å™¨æ— æ³•åŒºåˆ†é‡�å¤�ç±»å�‹âœ… 赋值时会自动处ç�†ç±»å�‹è½¬æ�¢å¦‚const char*å­—é�¢é‡�å�¯èµ‹å€¼ç»™std::stringç±»å�‹çš„å�˜ä½“。通过index()æˆ�员函数å�¯è�·å�–当å‰�存储类å�‹çš„索引验è¯�赋值是å�¦æˆ�功切æ�¢ç±»å�‹std::variantint, double, std::string v; v hello; // 切æ�¢ä¸ºstringç±»å�‹ç´¢å¼•2 std::cout 当å‰�ç±»å�‹ç´¢å¼•: v.index() std::endl; // 输出当å‰�ç±»å�‹ç´¢å¼•: 2索引ä»� 0 开始ä¸�定义时的类å�‹é¡ºåº�ä¸¥æ ¼å¯¹åº”è‹¥std::variant处äº� “空状æ€�â€�如异常情况下index()会返å›�std::variant_npos通常是size_t最大值。 访问值

使用std::getT或std::getN我们�以通过类�或索引�类�模��数/�类�模��数直���值。但如�当�variant存储的�是我们请求的类� / 索引它会抛出std::bad_variant_access异常。int main() { std::variantint, double v 42; try { std::cout std::getint(v) std::endl; std::cout std::getdouble(v) std::endl; // 抛出异常 } catch (const std::bad_variant_access e) { std::cout Error: e.what() std::endl; } }

使用std::get_ifTstd::get_if�会抛出异常。它��一个指针�数如�variant当�存储的是指定类�则返�一个指�该值的指针�则返�nullptr。int main() { std::variantint, double, std::string v hello; // 使用std::get_if�试��值 if (auto pval std::get_ifint(v)) { std::cout int value: *pval std::endl; } else if (auto pval std::get_ifdouble(v)) { std::cout double value: *pval std::endl; } else if (auto pval std::get_ifstd::string(v)) { std::cout string value: *pval std::endl; } }

使用std::visitæ�¨è��最安全强大std::visit类模æ�¿å…�è®¸ä½ æ��供一个 “访问者â€�visitoræ�¥æ ¹æ�®å½“å‰�存储的类å�‹æ‰§è¡Œç›¸åº”çš„æ“�作这是最类å�‹å®‰å…¨ã€�最清晰的方å¼�。第一个å�‚数访问者是一个å�¯è°ƒç”¨å¯¹è±¡é€šå¸¸æ˜¯ä¸€ä¸ªé‡�载了operator()的类或者使用 lambda 表达å¼�结å�ˆoverloaded技巧std::visit会把std::variant对象中存储的值å�–出æ�¥ä½œä¸ºå�‚æ•°ä¼ ç»™ visitor å�¯è°ƒç”¨å¯¹è±¡ã€‚所以在处ç�†std::variantæ—¶std::visit是更ç�°ä»£ã€�æ›´å®‰å…¨ä¹Ÿæ›´å¼ºå¤§çš„é€‰æ‹©ç›¸æ¯”ä¼ ç»Ÿçš„std::getå’Œstd::get_if它在代ç �çš„å�¥å£®æ€§ã€�å�¯ç»´æŠ¤æ€§å’Œè¡¨è¾¾åŠ›ä¸Šéƒ½æœ‰æ˜�显优势。#include iomanip #include iostream #include string #include type_traits #include variant #include vector // the variant to visit using value_t std::variantint, double, std::string; struct VisitorOP { void operator()(int i) const { std::cout int: i \n; } void operator()(double d) const { std::cout double: d \n; } void operator()(const std::string s) const { std::cout string: s \n; } }; // helper type for the visitor #4 templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C

templateclass... Ts overloaded(Ts...) - overloadedTs...; int main() { std::vectorvalue_t vec { 10,

5, hello }; for (auto v : vec) { std::visit(VisitorOP(), v); } std::cout \n; for (auto v : vec) { //

void visitor, only called for side-effects (here, for I/O) std::visit([](auto arg) { std::cout arg; }, v); //

value-returning visitor, demonstrates the idiom of returning // another variant value_t w std::visit([](auto arg) - value_t { return arg arg; }, v); //

type-matching visitor: a lambda that handles each type // differently std::cout . After doubling, variant holds ; std::visit([](auto arg) { using T std::decay_tdecltype(arg); if constexpr (std::is_same_vT, int) std::cout int with value arg \n; else if constexpr (std::is_same_vT, double) std::cout double with value arg \n; else if constexpr (std::is_same_vT, std::string) std::cout string with value \ std::quoted(arg) \\n; else static_assert(false, non-exhaustive visitor!); }, w); } std::cout \n; for (auto v : vec) { //

another type-matching visitor: a class with 3 overloaded // operator()s std::visit(overloaded{ [](int arg) { std::cout int: arg ; }, [](double arg) { std::cout double: arg ; }, [](const std::string arg) { std::cout string: std::quoted(arg) ; } }, v); } }std::get/std::get_if需è¦�ä½ æ‰‹åŠ¨ä¿�è¯�访问的类å�‹æˆ–索引ä¸�variant当å‰�存储的类å�‹ä¸€è‡´ã€‚如æ�œç±»å�‹ä¸�匹é…�std::get会抛出异常std::get_if会返å›�nullptr但这些错误都å�‘生在è¿�行时。std::visitç¼–è¯‘å™¨ä¼šå¼ºåˆ¶ä½ å¤„ç�†variant中所有å�¯èƒ½çš„ç±»å�‹ã€‚如æ�œæ¼�æ�‰äº†ä»»ä½•一ç§�ç±»å�‹ä»£ç �会在编译期就报错比如代ç �里的static_assert(false, non-exhaustive visitor!)ä»�æ ¹æº�上æ�œç»�了è¿�行时错误。而且性能开销visit 是比较å°�的没有è¿�行时的检查类å�‹

基础背景代ç �çš„æ ¸å¿ƒç›®æ ‡è¿™æ®µä»£ç �先定义了一个能存储int/double/stringçš„variantç±»å�‹value_tç„¶å��创建了包å�«è¿™ä¸‰ç§�ç±»å�‹å€¼çš„å�‘é‡�vec。整个程åº�çš„æ ¸å¿ƒå°±æ˜¯ç”¨std::visité��å�†è¿™ä¸ªå�‘é‡�对æ¯�个variant里的ä¸�å�Œç±»å�‹å€¼æ‰§è¡Œä¸�å�Œæ“�作 —— 本质是 â€œæ ¹æ�®variant存储的å®�é™…ç±»å�‹è‡ªåŠ¨è°ƒç”¨å¯¹åº”é€»è¾‘â€�。

第一ç§�用法用自定义结æ�„体å�š “访问者â€�代ç �里的VisitorOP是一个结æ�„体它é‡�载了 3 次operator()分别处ç�†int/double/stringç±»å�‹ã€‚std::visit(VisitorOP(), v)就是把vé‡Œå­˜å‚¨çš„å€¼ä¼ ç»™VisitorOP的对象编译器会自动匹é…�值的类å�‹è°ƒç”¨å¯¹åº”çš„operator()比如v存的是int就调用处ç�†int的那个函数最终打å�°å¯¹åº”ç±»å�‹å’Œå€¼ã€‚这是std::visit最基础的用法用 “é‡�载函数调用è¿�算符的类â€� å°�装所有类å�‹çš„处ç�†é€»è¾‘。

第二ç§� 第三ç§�用法用 lambda å�šè®¿é—®è€…进阶第二段循ç�¯é‡Œç¬¬ä¸€ä¸ªstd::visitç›´æ�¥ä¼ 了一个泛å�‹ lambda[](auto arg) { std::cout arg; }å› ä¸º lambda 是泛å�‹çš„能æ�¥æ”¶ä»»æ„�ç±»å�‹çš„å�‚数所以å�¯ä»¥ç›´æ�¥å¤„ç�†variant里的所有类å�‹ç¬¬äºŒä¸ªstd::visit更巧妙它先返å›�一个新的variant把å�Ÿå€¼ç¿»å€�比如 int 10 å�˜ 20string hello å�˜ hellohelloç„¶å��å�ˆç”¨ä¸€ä¸ªå¸¦if constexpr的泛å�‹ lambda在编译期判断å�‚æ•°ç±»å�‹åˆ†åˆ«æ‰“å�°ä¸�å�Œçš„æ��示语 —— æ ¸å¿ƒæ˜¯ “用泛å�‹ lambda 编译期判断替代结æ�„体é‡�è½½â€�。

第四ç§�用法用overloaded组å�ˆå¤šä¸ª lambda最优å®�践代ç �里的overloaded是一个模æ�¿æŠ€å·§å®ƒèƒ½æŠŠå¤šä¸ªä¸�å�Œçš„ lambda “å�ˆå¹¶â€� æˆ�一个对象æ¯�个 lambda 处ç�†ä¸€ç§�ç±»å�‹ã€‚std::visit(overloaded{处ç�†intçš„lambda, 处ç�†doubleçš„lambda, 处ç�†stringçš„lambda}, v)å°±ä¼šæ ¹æ�®vçš„å®�é™…ç±»å�‹è°ƒç”¨å¯¹åº”çš„ lambda—— 这是å®�é™…å¼€å�‘中最常用的写法ä¸�用写结æ�„体直æ�¥ç”¨ lambda 组å�ˆä»£ç �更简æ´�。ä¸�过部分的大家呢这个会比较看ä¸�懂这里解释一下其å®�它是 C 里一个æ��å…¶å·§å¦™ä½†æ ¸å¿ƒç®€å�•的模æ�¿æŠ€å·§è¿™æ®µä»£ç �的目的是把多个ä¸�å�Œçš„ lambda或函数对象“å�ˆå¹¶â€� æˆ�一个对象让这个对象拥有所有 lambda çš„operator()é‡�è½½ç‰ˆæœ¬è¿™æ ·å°±èƒ½ç”¨å®ƒä½œä¸ºstd::visit的访问者匹é…�variantçš„ä¸�å�Œç±»å�‹ã€‚å…¶å®�就是上é�¢çš„æ–¹æ³•就是写法的差别而已templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; };拆解æˆ� 3 个关键部分templateclass... Ts这是 C11 çš„å�˜å�‚模æ�¿Ts...表示 “任æ„�æ•°é‡�ã€�ä»»æ„�ç±»å�‹çš„æ¨¡æ�¿å�‚æ•°â€�æ¯”å¦‚ä¼ 3 个 lambdaTs就是这 3 个 lambda 的类å�‹ã€‚struct overloaded : Ts...overloaded结æ�„体公有继承了所有Ts里的类å�‹ä¹Ÿå°±æ˜¯ç»§æ‰¿äº†æ‰€æœ‰ä¼ 入的 lambda。lambda 本质是匿å��的函数对象æ¯�个 lambda 都有自己的operator()继承å��overloadedå°± “拥有â€� 了这些operator()。using Ts::operator()...;这是 C17 的包展开语法作用是 “把所有基类Tsçš„operator()都引入到overloaded的作用域中â€�。â�Œ ä¸�åŠ è¿™è¡Œçš„é—®é¢˜C 中å­�类继承多个基类的å�Œå��函数这里都是operator()时基类的函数会被 “éš�è—�â€�编译器ä¸�知é�“该调用哪个✅ åŠ è¿™è¡Œçš„ä½œç”¨æ˜¾å¼�把所有基类的operator()暴露出æ�¥è®©ç¼–è¯‘å™¨èƒ½æ ¹æ�®å�‚æ•°ç±»å�‹åŒ¹é…�对应的é‡�载版本。这æ‰�是é‡�点templateclass... Ts overloaded(Ts...) - overloadedTs...;这是 C17 的类模æ�¿æ�¨å¯¼æŒ‡å�—作用是当我们使用overloaded{lambda1, lambda2, lambda3}è¿™ç§�æ–¹å¼�创建对象时编译器能自动æ�¨å¯¼æ¨¡æ�¿å�‚æ•°Ts就是这 3 个 lambda 的类å�‹æ¯”如我们写的overloaded{[](int){}, [](double){}}编译器会æ�¨å¯¼Ts是 “处ç�† int çš„ lambda ç±»å�‹ 处ç�† double çš„ lambda ç±»å�‹â€�自动生æˆ�overloadedlambda1_type, lambda2_type的对象备注C20 起编译器能自动æ�¨å¯¼è¿™è¡Œå�¯ä»¥çœ�略但为了兼容通常会ä¿�留。overloaded是一个继承了多个类å�‹Ts...çš„å�˜å�‚模æ�¿ç»“æ�„ä½“å½“ä½ ç”¨overloaded{lambda1, lambda2}è¿™ç§� “è�šå�ˆåˆ�始化â€� 的方å¼�创建对象时C17 的编译器默认å�ªä¼š “ä»�结æ�„体的æˆ�员å�˜é‡�â€� æ�¨å¯¼æ¨¡æ�¿å�‚æ•°ä¸�会ä»� “基类列表Ts...â€� æ�¨å¯¼è€Œoverloaded结æ�„体本身没有任何æˆ�员å�˜é‡�å�ªæœ‰ç»§æ‰¿çš„基类所以编译器会直æ�¥æŠ¥é”™â€œæ— 法æ�¨å¯¼ overloaded 的模æ�¿å�‚æ•°â€�。第一步先看 “有æˆ�员å�˜é‡�â€� 的正常情况编译器能æ�¨å¯¼å�‡è®¾æˆ‘们写一个简å�•的模æ�¿ç»“æ�„体里é�¢æœ‰æˆ�员å�˜é‡�// 模æ�¿ç»“æ�„体有一个æˆ�员å�˜é‡�ç±»å�‹æ˜¯ T templateclass T struct MyStruct { T value; // æˆ�员å�˜é‡� }; int main() { // 用 {10} åˆ�始化编译器能æ�¨å¯¼ //

看到�员�� value 被赋值为 10int类� //

所以模æ�¿å�‚æ•° T int自动生æˆ� MyStructint MyStruct s{10}; return 0; }这个场景编译器能正常æ�¨å¯¼å› 为它能ä»�æˆ�员å�˜é‡�的赋值里找到模æ�¿å�‚数的匹é…�关系。第二步å†�看 overloaded 的情况编译器æ�¨å¯¼å¤±è´¥å›�到我们的overloaded结æ�„体它的定义是templateclass... Ts struct overloaded : Ts... { // å�ªæœ‰åŸºç±» Ts...没有任何æˆ�员å�˜é‡� using Ts::operator()...; };å½“ä½ å†™overloaded{lambda1, lambda2}时问题就æ�¥äº†ç¼–译器的æ€�考过程C17“我è¦�æ�¨å¯¼ overloaded 的模æ�¿å�‚æ•° Ts...首先找它的æˆ�员å�˜é‡�…… 哦它没有æˆ�员å�˜é‡�â€�“那我该ä»�哪找 Ts... 的类å�‹åŸºç±»åˆ—表ä¸�行规则说我å�ªçœ‹æˆ�员å�˜é‡�ä¸�看基类â€�“完了找ä¸�到匹é…�的模æ�¿å�‚数报错â€�通俗比喻这就åƒ�ä½ å�»ä¹°å¥¶èŒ¶åº—员å�ªè®¤ “è�œå�•上的选项â€�æˆ�员å�˜é‡�ä¸�认 â€œèµ å“�â€�åŸºç±»ã€‚ä½ æŒ‡ç�€èµ å“�说 “我è¦�这个â€�店员会说 “我ä¸�知é�“这是什么没法下å�•â€�—— 编译器就是这个店员它å�ªçœ‹æˆ�员å�˜é‡�ä¸�认基类所以æ�¨å¯¼å¤±è´¥ã€‚第三步æ�¨å¯¼æŒ‡å�—的作用给编译器 “开特例â€�我们写的æ�¨å¯¼æŒ‡å�—templateclass... Ts overloaded(Ts...) - overloadedTs...;æœ¬è´¨æ˜¯ç»™ç¼–è¯‘å™¨åŠ äº†ä¸€æ�¡ “特例规则â€�“当有人用overloaded{å�‚æ•°1, å�‚æ•°2,...}创建对象时ä¸�ç®¡ä½ æœ‰æ²¡æœ‰æˆ�员å�˜é‡�ç›´æ�¥æŠŠè¿™äº›å�‚数的类å�‹å½“æˆ�模æ�¿å�‚æ•° Ts...â€�åŠ ä¸Šè¿™æ�¡è§„则å��编译器å†�看到overloaded{lambda1, lambda2}“哦有æ�¨å¯¼æŒ‡å�—ä¸�用看æˆ�员å�˜é‡�了。â€�“å�‚æ•° 1 是 lambda1ç±»å�‹ L1å�‚æ•° 2 是 lambda2ç±»å�‹ L2。â€�“所以 Ts... L1, L2模æ�¿å�‚数就定了生æˆ� overloadedL1, L2â€�所以C17 编译器æ�¨å¯¼æ¨¡æ�¿å�‚æ•°æ—¶ “眼里å�ªæœ‰æˆ�员å�˜é‡�â€�而overloaded没有æˆ�员å�˜é‡�ã€�å�ªæœ‰ç»§æ‰¿çš„基类所以编译器猜ä¸�到模æ�¿å�‚æ•°æ�¨å¯¼æŒ‡å�—的作用就是 “绕开æˆ�员å�˜é‡�规则â€�ç›´æ�¥å‘Šè¯‰ç¼–译器用åˆ�始化å�‚数的类å�‹ä½œä¸ºæ¨¡æ�¿å�‚æ•°ã€‚ä½ å�¯ä»¥æŠŠè¿™ä¸ªè¿‡ç¨‹è®°æˆ�正常情况æˆ�员å�˜é‡�ç±»å�‹ → 模æ�¿å�‚数编译器会overloaded 情况åˆ�始化å�‚æ•°ç±»å�‹ → 模æ�¿å�‚数需è¦�æ�¨å¯¼æŒ‡å�—教编译器 综å�ˆæ¡ˆä¾‹ç®€åŒ–#include iostream #include list #include set #include string #include type_traits #include variant #include vector templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; }; templateclass... Ts overloaded(Ts...) - overloadedTs...; // å®�ç�°ä¸€ä¸ªå“ˆå¸Œè¡¨æ¡¶å�¯ä»¥æ˜¯ä¸€ä¸ªé“¾è¡¨ä¹Ÿå�¯ä»¥æ˜¯ä¸€ä¸ªçº¢é»‘æ ‘ class HashTable { private: using Value std::variantstd::listint, std::setint; std::vectorValue _tables; public: HashTable(size_t len) : _tables(len) {} void insert(const int key) { size_t hash key % _tables.size(); // 扩容 if (std::holds_alternativestd::listint(_tables[hash])) { auto list std::getstd::listint(_tables[hash]); // å°�äº�则æ�’入到链表 if (list.size()

{ list.push_back(key); } else { // 大äº�则转æ�¢åˆ°çº¢é»‘æ ‘ std::setint s(list.begin(), list.end()); s.insert(key); _tables[hash] move(s); } } else { auto set std::getstd::setint(_tables[hash]); set.insert(key); } } bool find(const int key) { size_t hash key % _tables.size(); // 查找 auto findInList [key](std::listint list) - bool { return std::find(list.begin(), list.end(), key) ! list.end(); }; auto findInSet [key](std::setint set) - bool { return set.count(key); }; return std::visit(overloaded{ findInList, findInSet }, _tables[hash]); } }; int main() { HashTable ht(

; for (int i 0; i 10; i) { ht.insert(i *

; } std::cout ht.find(

std::endl; std::cout ht.find(

std::endl; return 0; }这个哈希表案例中std::variantstd::listint, std::setint被用æ�¥å®šä¹‰å“ˆå¸Œæ¡¶çš„ç±»å�‹è®©æ¯�个桶既能存储链表std::listä¹Ÿèƒ½å­˜å‚¨çº¢é»‘æ ‘std::set—— æ�’å…¥å…ƒç´ æ—¶å…ˆé€šè¿‡std::holds_alternative判断当å‰�æ¡¶æ˜¯é“¾è¡¨è¿˜æ˜¯çº¢é»‘æ ‘è‹¥é“¾è¡¨å…ƒç´ æ•°è¶…è¿‡ 8 åˆ™è‡ªåŠ¨è½¬ä¸ºçº¢é»‘æ ‘æŸ¥æ‰¾å…ƒç´ æ—¶åˆ©ç”¨std::visit结å�ˆoverloadedæŠ€å·§æ ¹æ�®æ¡¶çš„å®�é™…ç±»å�‹é“¾è¡¨ / çº¢é»‘æ ‘è‡ªåŠ¨è°ƒç”¨å¯¹åº”çš„æŸ¥æ‰¾é€»è¾‘é“¾è¡¨ç”¨std::findã€�çº¢é»‘æ ‘ç”¨countstd::variantåœ¨è¿™é‡Œçš„æ ¸å¿ƒä»·å€¼æ˜¯ç”¨ç±»å�‹å®‰å…¨çš„æ–¹å¼�æ›¿ä»£ä¼ ç»Ÿ union既能ç�µæ´»å­˜å‚¨ä¸¤ç§�ä¸�å�Œçš„容器类å�‹å�ˆèƒ½é€šè¿‡é…�套的std::holds_alternative/std::get/std::visit安全地处ç�†ä¸�å�Œç±»å�‹çš„逻辑é�¿å…�了手动管ç�†ç±»å�‹æ ‡è¯†çš„ç¹�ç��和出错é£�险å®�ç�°äº† “一个容器ä½�置存储多ç§�ç±»å�‹ã€�且æ¯�ç§�ç±»å�‹æ‰§è¡Œä¸“å±�逻辑â€� 的需求。所以在这里std::variantæ‰¿è½½çš„æ ¸å¿ƒä»·å€¼å°±æ˜¯é€šè¿‡ “链表 / çº¢é»‘æ ‘çš„åˆ‡æ�¢è§„åˆ™å…ƒç´ æ•°â‰¥8â€� 这个数学阈值平衡哈希表的时间 / 空间消耗—— 链表std::list的优势是æ�’入快ã€�空间开销å°�但查找慢O (n)适å�ˆå…ƒç´ å°‘çš„åœºæ™¯çº¢é»‘æ ‘std::set的优势是查找快O (logn)但æ�’å…¥ / 空间开销大适å�ˆå…ƒç´ 多的场景。开å�‘时通过 “8 ä¸ªå…ƒç´ â€� 这个数学阈值作为切æ�¢æ�¡ä»¶ç”¨std::variant让æ¯�ä¸ªå“ˆå¸Œæ¡¶æ ¹æ�®å…ƒç´ æ•°é‡�动æ€�切æ�¢å­˜å‚¨ç±»å�‹å…ƒç´ 少的时候用链表çœ�空间ã€�å¿«æ�’å…¥å…ƒç´ å¤šçš„æ—¶å€™ç”¨çº¢é»‘æ ‘æ��查找效ç�‡æœ€ç»ˆå®�ç�° “ä½�å…ƒç´ é‡�æ—¶æ�§ç©ºé—´æ¶ˆè€—é«˜å…ƒç´ é‡�æ—¶æ�§æ—¶é—´æ¶ˆè€—â€� 的平衡而std::variant则是å®�ç�°è¿™ç§� “动æ€�ç±»å�‹åˆ‡æ�¢â€� 的类å�‹å®‰å…¨è½½ä½“é�¿å…�äº†ä¼ ç»Ÿæ–¹å¼�å¦‚æ‰‹åŠ¨æ ‡è®°ç±»å�‹ã€�强制类å�‹è½¬æ�¢çš„出错é£�险。简å�•å®�ç�°å�Ÿç�†std::visit本质是 “编译期生æˆ�ç±»å�‹åˆ†å�‘表 è¿�行时查表调用â€�把variant的类å�‹ç´¢å¼•æ˜ å°„åˆ°å¯¹åº”çš„å¤„ç�†å‡½æ•°ã€‚下é�¢æˆ‘用 “通俗å�Ÿç�† 简化å®�ç�°â€� 的方å¼�ç»™ä½ è®²é€�它的底层逻辑一ã€�std::visitæ ¸å¿ƒå®�ç�°å�Ÿç�†å¤§ç™½è¯�版编译期准备生æˆ� “类å�‹ - 函数â€� æ˜ å°„è¡¨ç¼–è¯‘å™¨ä¼šå…ˆåˆ†æ��variant的类å�‹åˆ—表比如本例中是listintå’Œsetint以å�Šä½ ä¼ å…¥çš„è®¿é—®è€…overloaded组å�ˆçš„两个 lambda为æ¯�个类å�‹ç”Ÿæˆ�对应的 “处ç�†å‡½æ•°åœ°å�€â€�并按类å�‹ç´¢å¼•0 对应 listã€�1 对应 setæ•´ç�†æˆ�ä¸€å¼ â€œåˆ†å�‘表â€�。è¿�行时执行查表 调用程åº�è¿�行时std::visitå…ˆè�·å�–variant当å‰�存储类å�‹çš„索引通过variant.index()ç„¶å��到 “分å�‘表â€� 里找到该索引对应的处ç�†å‡½æ•°æœ€å��把variant里的å®�é™…å€¼ä¼ ç»™è¿™ä¸ªå‡½æ•°æ‰§è¡Œ —— 整个过程就åƒ� â€œæ ¹æ�®ç±»å�‹ç¼–å�·æ‰¾å¯¹åº”的工具干活â€�。二ã€�简化版å®�ç�°å¸®ä½ ç�†è§£æ ¸å¿ƒé€»è¾‘我们用伪代ç �模拟std::visitçš„æ ¸å¿ƒé€»è¾‘ä½ ä¸€çœ‹å°±æ‡‚// 模拟 std::variant çš„æ ¸å¿ƒç»“æ�„ templateclass... Ts struct MyVariant { size_t index; // 存储当å‰�ç±»å�‹çš„索引 // 存储å®�际值的内存简化版å®�际是对é½�的内存å�— alignas(Ts...) char data[max_sizeof(Ts...)]; // è�·å�–当å‰�ç±»å�‹ç´¢å¼• size_t get_index() const { return index; } // 按索引è�·å�–值的指针简化版 void* get_data() { return data; } }; // 模拟 std::visit çš„æ ¸å¿ƒå®�ç�° templateclass Visitor, class... Ts auto my_visit(Visitor visitor, MyVariantTs... var) { // 编译期生æˆ�ç±»å�‹ç´¢å¼• → 处ç�†å‡½æ•° çš„æ˜ å°„è¡¨ using FuncTable void* (*)[]; static FuncTable table { // 对æ¯�个类å�‹ Ts生æˆ�“把 var çš„å€¼ä¼ ç»™ visitorâ€�的函数 [](MyVariantTs... v) { return visitor(*static_castTs*(v.get_data())); }... }; // è¿�è¡Œæ—¶æ ¹æ�®ç´¢å¼•查表调用对应函数 size_t idx var.get_index(); return table[idx](var); }这个简化版里编译期table数组会被编译器生æˆ�æ¯�ä¸ªå…ƒç´ å¯¹åº”ä¸€ä¸ªç±»å�‹çš„处ç�†å‡½æ•°è¿�行时å�ªéœ€è¦�æ ¹æ�®indexå�–æ•°ç»„å…ƒç´ è°ƒç”¨å‡½æ•°å�³å�¯æ²¡æœ‰å¤šä½™çš„if-else分支。三ã€�结å�ˆå“ˆå¸Œè¡¨æ¡ˆä¾‹çš„具体执行æµ�ç¨‹åœ¨ä½ çš„å“ˆå¸Œè¡¨find函数中return std::visit(overloaded{findInList, findInSet}, _tables[hash]);编译期编译器生æˆ�ä¸€å¼ è¡¨ç´¢å¼• 0 对应findInList处ç�† listã€�索引 1 对应findInSet处ç�† setè¿�行时先è�·å�–_tables[hash]的索引0 或 1若索引是 0 → 调用findInList把variant里的 list ä¼ ç»™å®ƒè‹¥ç´¢å¼•æ˜¯ 1 → 调用findInSet把variant里的 set ä¼ ç»™å®ƒæœ€ç»ˆè¿”å›�查找结æ�œã€‚总结std::visitçš„æ ¸å¿ƒæ˜¯ç¼–è¯‘æœŸç”Ÿæˆ�分å�‘表è¿�行时快速查表调用比手动写if-else get_if更高效它的å�Ÿç�†æœ¬è´¨æ˜¯ “把类å�‹åˆ¤æ–­ä»�è¿�行时的分支æ��å‰�到编译期的表生æˆ�â€�æ—¢ä¿�è¯�ç±»å�‹å®‰å…¨å�ˆä¸�æ�Ÿå¤±æ€§èƒ½åœ¨å“ˆå¸Œè¡¨æ¡ˆä¾‹ä¸­å®ƒçš„作用就是 â€œæ ¹æ�® variant çš„å®�é™…ç±»å�‹list/set自动调用对应的查找函数â€�ä¸�用手动写分支判断。总结std::variant作为 C17 的类å�‹å®‰å…¨è�”å�ˆä½“æ ¸å¿ƒå�ªå…�许存放å�¯æ��æ�„ã€�å�¯ç§»åЍ / æ‹·è´�ã€�å�¯å®�例化的é��引用值类å�‹ç»�对ä¸�能存放引用类å�‹éœ€ç”¨std::reference_wrapper包装ã€�voidç±»å�‹ã€�ä¸�完整类å�‹æœªå®šä¹‰çš„结æ�„体ã€�抽象类å�«çº¯è™šå‡½æ•°å�Œæ—¶ä¸�建议存放é‡�å¤�ç±»å�‹å¦‚variantint, int会导致std::get编译报错ã€�æ— å�ˆæ³•移动 / æ‹·è´�语义的å¤�æ�‚ç±»å�‹æ˜“资æº�泄æ¼�ã€�超大内存类å�‹å¾’å¢�variant内存开销而 C17 中还è¦�求其第一个类å�‹å¿…须有默认æ�„é€ å‡½æ•°C20 放宽此é™�åˆ¶ä½ çš„å“ˆå¸Œè¡¨æ¡ˆä¾‹ä¸­listintå’Œsetintå› æ»¡è¶³ “å�¯é»˜è®¤æ�„é€ ã€�å�¯ç§»åЍã€�尺寸适中â€� çš„è¦�求是variant的典å�‹å�ˆç�†ç”¨æ³•。所以std::variantç¦�存引用ã€�voidã€�ä¸�完整类å�‹ã€�抽象类ä¸�建议存é‡�å¤�ç±»å�‹ã€�æ— å�ˆæ³•移动 / æ‹·è´�的类å�‹ã€�超大类å�‹æ ¸å¿ƒè¦�求存放类å�‹éœ€æ˜¯å�¯æ��æ�„ã€�å�¯ç§»åЍ / æ‹·è´�ã€�å�¯å®�例化的值类å�‹ã€‚

成品人直播APP下载官网-成品人直播APP下载官网应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123