[{"data":1,"prerenderedAt":1216},["ShallowReactive",2],{"page-\u002Fcpp\u002Fcpp的学习笔记":3},{"id":4,"title":5,"body":6,"description":43,"extension":1210,"meta":1211,"navigation":84,"path":1212,"seo":1213,"stem":1214,"__hash__":1215},"content\u002Fcpp\u002FCpp的学习笔记.md","Cpp的学习笔记",{"type":7,"value":8,"toc":1183},"minimark",[9,26,30,34,37,139,158,161,184,187,190,203,216,219,222,236,251,254,258,264,285,295,298,301,304,322,326,340,365,368,371,374,378,381,384,387,401,419,438,486,489,492,498,527,541,559,571,579,593,596,607,625,628,635,638,654,657,660,667,672,695,705,716,719,722,725,728,731,734,745,752,755,758,763,769,795,798,801,804,807,814,846,855,871,874,877,880,883,886,889,895,915,925,928,931,949,964,982,985,988,991,994,1001,1004,1011,1014,1033,1036,1148,1152,1158,1166,1171,1179],[10,11,12,20],"ul",{},[13,14,15,19],"li",{},[16,17,18],"strong",{},"2.2 结构 & 2.3 类","：理解如何构建数据结构是底层设计的基础。",[13,21,22,25],{},[16,23,24],{},"2.4 构造 & 2.5 析构","：掌握对象的生命周期管理，尤其是资源分配与释放。\n《C++语言导学 第二版》",[27,28,29],"h2",{"id":29},"struct和class的深度对比",[31,32,33],"p",{},"C++中，struct和class在语法上几乎一样，但是有一些区别，除此以外访问成员的方式完全相同。\nstruct默认成员是Public，但是class默认成员是private。",[31,35,36],{},"举个例子：",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-cpp shiki shiki-themes github-light github-dark","struct Point {\n    int x;\n    int y;\n    void print() { std::cout \u003C\u003C x \u003C\u003C \", \" \u003C\u003C y \u003C\u003C std::endl; }\n};\n\nint main() {\n    Point p{1, 2};     \u002F\u002F 这是一个对象\n    p.print();         \u002F\u002F 用 . 访问成员函数\n    std::cout \u003C\u003C p.x;  \u002F\u002F 用 . 访问数据成员\n\n    Point* ptr = &p;   \u002F\u002F 这是一个指针\n    ptr->print();      \u002F\u002F 用 -> 访问成员函数\n    std::cout \u003C\u003C ptr->y; \u002F\u002F 用 -> 访问数据成员\n}\n","cpp","",[45,46,47,55,61,67,73,79,86,92,98,104,110,115,121,127,133],"code",{"__ignoreMap":43},[48,49,52],"span",{"class":50,"line":51},"line",1,[48,53,54],{},"struct Point {\n",[48,56,58],{"class":50,"line":57},2,[48,59,60],{},"    int x;\n",[48,62,64],{"class":50,"line":63},3,[48,65,66],{},"    int y;\n",[48,68,70],{"class":50,"line":69},4,[48,71,72],{},"    void print() { std::cout \u003C\u003C x \u003C\u003C \", \" \u003C\u003C y \u003C\u003C std::endl; }\n",[48,74,76],{"class":50,"line":75},5,[48,77,78],{},"};\n",[48,80,82],{"class":50,"line":81},6,[48,83,85],{"emptyLinePlaceholder":84},true,"\n",[48,87,89],{"class":50,"line":88},7,[48,90,91],{},"int main() {\n",[48,93,95],{"class":50,"line":94},8,[48,96,97],{},"    Point p{1, 2};     \u002F\u002F 这是一个对象\n",[48,99,101],{"class":50,"line":100},9,[48,102,103],{},"    p.print();         \u002F\u002F 用 . 访问成员函数\n",[48,105,107],{"class":50,"line":106},10,[48,108,109],{},"    std::cout \u003C\u003C p.x;  \u002F\u002F 用 . 访问数据成员\n",[48,111,113],{"class":50,"line":112},11,[48,114,85],{"emptyLinePlaceholder":84},[48,116,118],{"class":50,"line":117},12,[48,119,120],{},"    Point* ptr = &p;   \u002F\u002F 这是一个指针\n",[48,122,124],{"class":50,"line":123},13,[48,125,126],{},"    ptr->print();      \u002F\u002F 用 -> 访问成员函数\n",[48,128,130],{"class":50,"line":129},14,[48,131,132],{},"    std::cout \u003C\u003C ptr->y; \u002F\u002F 用 -> 访问数据成员\n",[48,134,136],{"class":50,"line":135},15,[48,137,138],{},"}\n",[31,140,141,142,145,146,149,150,153,154,157],{},"深入理解底层思维，可以把这个规则和 ",[16,143,144],{},"内存模型"," 联系起来：",[45,147,148],{},"."," 实际上是直接访问对象的内存，而 ",[45,151,152],{},"->"," 是先",[16,155,156],{},"解引用指针","，再访问对象的内存。",[31,159,160],{},"在现代C++中他们的区别已经非常小了，但是在模板操作的时候，可以使用class或者typename但是无法使用struct。",[162,163,164,170],"blockquote",{},[31,165,166,169],{},[48,167,168],{},"!tip"," 对比",[10,171,172,178],{},[13,173,174,177],{},[45,175,176],{},"template \u003Cclass T> void func();"," \u002F\u002F ✅ 正确",[13,179,180,183],{},[45,181,182],{},"template \u003Cstruct T> void func();"," \u002F\u002F ❌ 错误",[31,185,186],{},"在细节的业务开发的时候我们会根据意图来选择使用哪个，比如说POD，我们需要把一堆数据打包在一起没有复杂逻辑封装的时候，可以用struct。 DTO之类的。",[31,188,189],{},"而在需要有封装和抽象的时候，对象具有内部状态的时候，有复杂生命周期管理的时候，我们就最好用class。",[31,191,192,193],{},"在内存构造上，",[16,194,195,198,199,202],{},[45,196,197],{},"struct"," 和 ",[45,200,201],{},"class"," 完全没有区别。",[31,204,205,206,208,209,211,212,215],{},"如果你定义了一个 ",[45,207,197],{}," 和一个 ",[45,210,201],{},"，且它们的成员、顺序和访问权限完全一致，那么它们在内存中的布局（Memory Layout）是 ",[16,213,214],{},"100% 相同","的。",[27,217,218],{"id":218},"union-特殊的类类型",[31,220,221],{},"union被称为共同体，所有的程序都占据同一块内存地址，现代c++中使用的比较少，因为有了更安全的std::variant(c++ 17以后)，传统的union不能支持POD类型，因为union不能轻易存放std::string或者std::vector，union不能自动调用它们的构造和析构函数。",[31,223,224,225,198,228,231,232,235],{},"这里提醒一句，",[45,226,227],{},"std::string",[45,229,230],{},"std::vector"," 的构造与析构函数正是 C++ ",[16,233,234],{},"RAII（资源获取即初始化）"," 机制的灵魂所在。\n![[Pasted image 20260312011144.png]]",[31,237,238,239,242,243,246,247,250],{},"这里我们必须来说一下为什么有这个必要性，当你写 ",[45,240,241],{},"std::vector\u003Cint> v(100);"," 时，构造函数会去操作系统那里申请存放 100 个整数的内存空间，并把这些空间初始化，当 ",[45,244,245],{},"v"," 超出作用域（比如函数结束）时，它的",[16,248,249],{},"析构函数会被自动调用","，释放刚才申请的那块内存。",[31,252,253],{},"所以说，主要的原因是它们管理着堆内存，如果没有析构，那么会发生内存泄漏。",[255,256,257],"h3",{"id":257},"典型的vector组成",[31,259,260,261,263],{},"一个典型的 ",[45,262,230],{}," 在内存中通常由三个指针组成：",[265,266,267,273,279],"ol",{},[13,268,269,272],{},[45,270,271],{},"T* _First",": 指向堆内存中数据的开头。",[13,274,275,278],{},[45,276,277],{},"T* _Last",": 指向最后一个有效元素的末尾。",[13,280,281,284],{},[45,282,283],{},"T* _End",": 指向申请的总空间的末尾（Capacity）。",[31,286,287,288,291,292,294],{},"这里可以解释为什么我们之前说传统的 ",[45,289,290],{},"union"," 很难处理 ",[45,293,227],{},"。",[31,296,297],{},"在里面编译器不知道当前存的是int还是std::string，并且union结束了生命周期的话，编译器不会调用析构。但是std::variant不一样，内部有一个类型标签，他会去检查和调用析构。",[31,299,300],{},"同样，在性能方面也有很大的差异，普通的struct或者in 在拷贝构造的时候，是简单的位拷贝，但是std::vector或者std::string会去申请一块同样大小的内存，并且把数据一个一个复制过去。两份的数据有各自的独立内存，修改一个不会影响另一个。",[255,302,303],{"id":303},"小字符串优化",[31,305,306,307,309,310,313,314,317,318,321],{},"有趣的是，",[45,308,227],{}," 的构造函数非常聪明。 如果你只存一个很短的字符串（比如 ",[45,311,312],{},"\"Hi\"","），它",[16,315,316],{},"不会","去堆上申请内存，而是直接存在自己对象内部的固定数组里（通常是 15-22 个字节）。这就是 ",[16,319,320],{},"Small String Optimization (SSO)","，旨在减少小数据的内存申请开销。",[27,323,325],{"id":324},"enum枚举-不是容器","enum枚举 不是容器！",[31,327,328,329,332,333,332,336,339],{},"当我们创建一个枚举的时候，不是在内存中创建了一个数组，在编译器的视角中，在编译后，",[45,330,331],{},"Red","、",[45,334,335],{},"Green",[45,337,338],{},"Blue"," 这些名字大多都消失了，变成了常数（0, 1, 2。。。从0开始）。",[31,341,342,345,346,349,350,353,354,357,358,332,360,332,362,364],{},[16,343,344],{},"内存视角","：如果你定义了一个变量 ",[45,347,348],{},"Color myColor = Red;","，在",[16,351,352],{},"内存中","只占用",[16,355,356],{},"一个","整数的空间（通常是 4 字节）。它并没有把 ",[45,359,331],{},[45,361,335],{},[45,363,338],{}," 全都排在内存里。",[31,366,367],{},"相当于存的是页码不是内容，有效地减少内存消耗。",[255,369,370],{"id":370},"强类型的枚举",[31,372,373],{},"在现代c++中，我们优先使用enum class，这个和传统的enum不一样的地方在于，它不会进行隐式的转换，避免把Color::Red错误地加到一个int变量上。并且限制了作用域，必须使用Color::Red来访问。",[27,375,377],{"id":376},"c的容器的思维","C++的容器的思维",[31,379,380],{},"在 C 语言或基础阶段，我们用的是“裸数组”（Raw Array），那就像是一排简陋的货架，你自己得记着哪格放了东西、货架够不够大、搬家时得一个一个手动搬。",[31,382,383],{},"C++ 做了一件伟大的事情，将货架变成了不同功能的仓库。 现代C++中，容器是一个类模板，它的任务是管理内存和组织数据。  具体表现为可以在堆上申请空间，并且在析构的时候自动释放内存。 规定了数据的排布。",[255,385,386],{"id":386},"层次漫游",[31,388,389,390,393,394,397,398,294],{},"层次漫游是C++ 面向对象编程的一个思维，指的是",[16,391,392],{},"在继承层次结构中","，通过基类指针或引用操作对象时，",[16,395,396],{},"安全地向下转换到派生类","，以便",[16,399,400],{},"调用派生类特有的方法或访问其特有成员的过程",[31,402,403,404,332,407,410,411,414,415,418],{},"基类指针，我们只能调用基类中声明的虚函数（如 ",[45,405,406],{},"draw()",[45,408,409],{},"rotate()","），派生类定义了",[16,412,413],{},"自己独有的成员函数","（如 ",[45,416,417],{},"Smiley::wink()","），通过基类指针无法直接调用。",[31,420,421,422,425,426,429,430,433,434,437],{},"解决方案是 将基类指针",[16,423,424],{},"向下转换","为派生类指针，才能访问派生类特有的功能。\n",[45,427,428],{},"dynamic_cast"," 是 C++ 提供的",[16,431,432],{},"安全向下转换","运算符，用于在继承层次中进行",[16,435,436],{},"运行时类型识别","（RTTI）。",[38,439,441],{"className":40,"code":440,"language":42,"meta":43,"style":43},"Shape* ps = read_shape(cin);\n\n\u002F\u002F 尝试将 Shape* 转换为 Smiley*\nif (Smiley* p = dynamic_cast\u003CSmiley*>(ps)) {\n    \u002F\u002F 转换成功，ps 指向的是 Smiley 对象或其派生对象\n    p->wink();  \u002F\u002F 调用 Smiley 特有的函数\n} else {\n    \u002F\u002F 转换失败，ps 不是 Smiley 类型\n}\n",[45,442,443,448,452,457,462,467,472,477,482],{"__ignoreMap":43},[48,444,445],{"class":50,"line":51},[48,446,447],{},"Shape* ps = read_shape(cin);\n",[48,449,450],{"class":50,"line":57},[48,451,85],{"emptyLinePlaceholder":84},[48,453,454],{"class":50,"line":63},[48,455,456],{},"\u002F\u002F 尝试将 Shape* 转换为 Smiley*\n",[48,458,459],{"class":50,"line":69},[48,460,461],{},"if (Smiley* p = dynamic_cast\u003CSmiley*>(ps)) {\n",[48,463,464],{"class":50,"line":75},[48,465,466],{},"    \u002F\u002F 转换成功，ps 指向的是 Smiley 对象或其派生对象\n",[48,468,469],{"class":50,"line":81},[48,470,471],{},"    p->wink();  \u002F\u002F 调用 Smiley 特有的函数\n",[48,473,474],{"class":50,"line":88},[48,475,476],{},"} else {\n",[48,478,479],{"class":50,"line":94},[48,480,481],{},"    \u002F\u002F 转换失败，ps 不是 Smiley 类型\n",[48,483,484],{"class":50,"line":100},[48,485,138],{},[255,487,488],{"id":488},"容器三大家族",[31,490,491],{},"C++提供了不同类型的容器，序列容器、关联容器和无序关联容器。",[31,493,494,497],{},[16,495,496],{},"序列容器","的含义就是按先后顺序排队，比如说：",[10,499,500,511,519],{},[13,501,502,506,507,510],{},[16,503,504],{},[45,505,230],{},"：",[16,508,509],{},"最常用的容器。"," 像一个可以自动变长的数组。内存是连续的，随机访问（比如看第 100 个）极快。",[13,512,513,518],{},[16,514,515],{},[45,516,517],{},"std::list","：双向链表。内存不连续。你想在中间插队很容易，但想找第 100 个就得从头数。",[13,520,521,526],{},[16,522,523],{},[45,524,525],{},"std::deque","：双向队列。头尾都能高效进出。",[31,528,529,532,533,536,537,540],{},[16,530,531],{},"关联容器","，可以按逻辑查找：\n这些容器关注的是元素的",[16,534,535],{},"值","或",[16,538,539],{},"关系","，会自动帮你排好序。",[10,542,543,551],{},[13,544,545,550],{},[16,546,547],{},[45,548,549],{},"std::set","：集合。里面不能有重复元素，且永远是排好序的。",[13,552,553,558],{},[16,554,555],{},[45,556,557],{},"std::map","：键值对（Key-Value）。就像字典，通过“单词”找“解释”。",[162,560,561],{},[31,562,563],{},[564,565,566,567,570],"em",{},"底层通常是",[16,568,569],{},"红黑树","，查找速度非常稳（O(logn)）。",[31,572,573,574,506],{},"无序关联容器通常使用哈希算法，",[16,575,576],{},[45,577,578],{},"std::unordered_map \u002F set",[10,580,581,584],{},[13,582,583],{},"它们不排序，而是通过“哈希算法”把东西乱序塞进桶里。",[13,585,586,589,590,294],{},[16,587,588],{},"优点","：查找极其快（接近常数时间），适合",[16,591,592],{},"处理海量数据",[31,594,595],{},"除了容器以外还有其他几个概念很重要，这些概念在其他语言也会有体现。比如说是迭代器，它连接了算法和容器。",[31,597,598,599,602,603,606],{},"如果没有迭代器，你写一个排序算法得为 ",[45,600,601],{},"vector"," 写一套，为 ",[45,604,605],{},"list"," 写一套。有了迭代器，算法只需要知道“怎么从一个元素挪到下一个”，而不需要管容器底层是连续内存还是链表。",[31,608,609,610,332,613,616,617,620,621,624],{},"容器就是帮你",[16,611,612],{},"管内存",[16,614,615],{},"定结构","的工具。你不需要再用 ",[45,618,619],{},"malloc"," 去手动申请空间，也不用担心 ",[45,622,623],{},"free"," 漏了。",[27,626,627],{"id":627},"纯虚函数和抽象类",[31,629,630,631,634],{},"![[Pasted image 20260312011906.png]]\n这个类就表示一个纯接口，这里的=0很奇怪，这说明这个函数是纯虚函数，",[16,632,633],{},"Container的派生类必须去定义这个函数","，你必须去重载这个纯虚函数，提供具体逻辑，这样类可以变成具体类，可以创建对象。",[255,636,637],{"id":637},"基类的析构",[31,639,640,643,644,647,648,536,651,294],{},[16,641,642],{},"编译器确实会自动生成析构函数，但它生成的默认析构函数是非虚（Non-virtual）的。"," 如果基类析构函数不是 ",[45,645,646],{},"virtual","，在多态使用场景下会导致",[16,649,650],{},"内存泄漏",[16,652,653],{},"未定义行为",[31,655,656],{},"如果通过一个基类指针去删除一个派生类对象，那么如果析构函数不是虚的，编译器会进行静态绑定这个行为，它看不到对象的实际类型（派生类）。",[31,658,659],{},"编译器检查ptr类型，发现是Base*，编译器查看Base的析构函数，发现不是虚函数，那么只调用了Base的析构，跳过了派生类的析构函数，派生类里的一些资源没有被销毁，内存发生泄漏。",[31,661,662,663,666],{},"既然 ",[45,664,665],{},"non-virtual"," 这么危险，为什么 C++ 不像 Java 或 C# 那样默认所有函数都是虚的？",[31,668,669],{},[16,670,671],{},"原因还是为了性能（C++ 的执念）：",[10,673,674,680,686],{},[13,675,676,679],{},[16,677,678],{},"虚函数表指针 (vptr)","：我们在之前聊过，一旦有了虚函数，每个对象都要多占 8 字节（64位系统）来存虚表指针。",[13,681,682,685],{},[16,683,684],{},"性能开销","：虚函数调用需要查表，比直接调用慢。",[13,687,688,691,692,694],{},[16,689,690],{},"零开销原则 (Zero-overhead Principle)","：如果你只是写一个简单的 ",[45,693,197],{}," 存数据，不需要多态，C++ 就不想让你为“你没用到的功能”付出内存和速度的代价。",[31,696,697,698,701,702,704],{},"只要你的类满足以下",[16,699,700],{},"任何一个条件","，就必须手动写一个 ",[45,703,646],{}," 析构函数：",[265,706,707,713],{},[13,708,709,710,712],{},"这个类里有任何一个其他的 ",[45,711,646],{}," 函数。",[13,714,715],{},"这个类会被其他类继承（即它作为基类）。",[27,717,718],{"id":718},"基本操作",[31,720,721],{},"在类中，有几种常见的构造、析构和拷贝操作。\n![[Pasted image 20260312013300.png]]",[31,723,724],{},"![[Pasted image 20260312013412.png]]",[255,726,727],{"id":727},"拷贝消除",[31,729,730],{},"这是C++编译器的一种优化手段，他可以让拷贝不发生，在现代 C++（特别是 C++17 之后），这已经从一种“可选的优化”变成了“语言的强制要求”。",[31,732,733],{},"要理解它的实现，我们需要看一看函数调用的**栈帧（Stack Frame）是如何工作的。",[31,735,736,737,740,741,744],{},"传统做法中，调用者会给变量x预留空间，函数会在自己的栈帧",[16,738,739],{},"构造一个临时对象","，编译器会",[16,742,743],{},"调用拷贝构造函数，把临时对象的数据复制给x","，函数返回，销毁临时对象。",[31,746,747,748,751],{},"拷贝消除的原理就是，编译器通过修改调用约定来优化，当我们去",[16,749,750],{},"调用一个返回对象的函数时候","，编译器会给函数传递一个指针，指向调用者中存放结果的地址（x变量的地址）。",[31,753,754],{},"这样函数内部就不会创建局部临时对象，而是在指针指向的内存位置上执行构造函数，这样构造的对象就在最终的位置。",[31,756,757],{},"因此准确来说，它取消了拷贝构造操作。",[31,759,760],{},[16,761,762],{},"与“移动语义”（Move）的关系",[31,764,765,766,506],{},"很多开发者会混淆这两者。其实它们是",[16,767,768],{},"互补关系",[265,770,771,780,789],{},[13,772,773,776,777,294],{},[16,774,775],{},"第一优先级：拷贝消除","。如果能直接在目的地构造，就完全不调用构造函数。性能开销：",[16,778,779],{},"0",[13,781,782,785,786,294],{},[16,783,784],{},"第二优先级：移动语义","。如果不能消除（比如对象必须先在别处存在），就调用“移动构造函数”，把资源所有权偷过来。性能开销：",[16,787,788],{},"极低（通常只是几个指针赋值）",[13,790,791,794],{},[16,792,793],{},"第三优先级：拷贝语义","。如果以上都不行，才老老实实调用拷贝构造函数，进行昂贵的深拷贝。",[255,796,797],{"id":797},"拷贝和移动",[31,799,800],{},"![[Pasted image 20260312014533.png]]",[255,802,803],{"id":803},"拷贝容器",[31,805,806],{},"![[Pasted image 20260312014700.png]]",[31,808,809,810,813],{},"这里说明了浅拷贝可能导致资源竞争和程序崩溃。 这里v1内部有一个指针指向堆内存里的数组，当我们执行Vector v2 =v1的时候，编译器会 ",[16,811,812],{},"逐成员拷贝"," 。",[10,815,816,833],{},[13,817,818,821,822,825,826,829,830,294],{},[16,819,820],{},"编译器的逻辑","：把 ",[45,823,824],{},"v1"," 里的指针地址（比如 ",[45,827,828],{},"0x0012","）原封不动地复制给 ",[45,831,832],{},"v2",[13,834,835,506,838,198,840,842,843,294],{},[16,836,837],{},"结果",[45,839,824],{},[45,841,832],{}," 变成了两个独立的变量，但它们内部的指针却指向了",[16,844,845],{},"同一块内存地址",[31,847,848,849,851,852,854],{},"当我们修改v1",[48,850,779],{},"的时候，v2",[48,853,779],{},"也跟着变了。修复的方法也很简单，需要手动定义拷贝行为，而不是复制地址。",[10,856,857,864],{},[13,858,859,860,863],{},"申请一块",[16,861,862],{},"新","的、同样大小的内存。",[13,865,866,867,870],{},"把原内存里的内容",[16,868,869],{},"复制","过去。",[31,872,873],{},"![[Pasted image 20260312015115.png]]",[31,875,876],{},"之后v2 = v1结果就是图下：",[31,878,879],{},"![[Pasted image 20260312015212.png]]",[31,881,882],{},"除去拷贝构造函数，还需要一个拷贝赋值操作：\n![[Pasted image 20260312015302.png]]",[255,884,885],{"id":885},"移动容器",[31,887,888],{},"移动是C++ 11 之后性能飞跃的分水岭，经常用来处理大型容器。",[31,890,891,892,506],{},"在没有“移动”概念的老派 C++ 中，如果你从函数返回一个大型 ",[45,893,894],{},"Vector",[265,896,897,903,909],{},[13,898,899,902],{},[16,900,901],{},"拷贝构造","：系统会申请一块同样大的新内存。",[13,904,905,908],{},[16,906,907],{},"数据搬运","：把旧内存里的 10,000 个数据一个个复制到新内存。",[13,910,911,914],{},[16,912,913],{},"销毁旧物","：把函数里的局部对象删掉，释放原来的内存。",[31,916,917,918,921,922,294],{},"可以看上面的",[16,919,920],{},"拷贝消除版块","。\n“移动”的逻辑非常流氓但极其高效：它不复制数据，而是直接",[16,923,924],{},"偷走指针",[31,926,927],{},"![[Pasted image 20260312015621.png]]\n这里是一个vector加法的例子，如果是在传统c++中，计算r = x+y+z的时候，x+y 中，operator+ 返回的时候，res的内容会被拷贝到一个临时对象，然后res被销毁。 +z的时候又会拷贝到一个临时对象，然后又一次将结果复制给r。 这里就是不合理的地方。",[31,929,930],{},"res会在堆上申请空间，存好数据。既然res会被销毁，那么我们可以直接将内存指针转移给调用者。因此我们会使用&& 右值引用。",[10,932,933,941],{},[13,934,935,940],{},[16,936,937],{},[45,938,939],{},"Vector(Vector&& a)","：移动构造函数。",[13,942,943,948],{},[16,944,945],{},[45,946,947],{},"Vector& operator=(Vector&& a)","：移动赋值运算符。",[31,950,951,952,955,956,959,960,963],{},"当编译器看到 ",[45,953,954],{},"r = x + y + z"," 时，它知道 ",[45,957,958],{},"x + y"," 产生的是一个",[16,961,962],{},"临时的、即将销毁的","对象（右值）。",[265,965,966,969,976],{},[13,967,968],{},"它不再调用“拷贝”函数。",[13,970,971,972,975],{},"它调用“移动”函数：直接把临时对象里的指针",[16,973,974],{},"偷","过来，赋值给 r。",[13,977,978,979,294],{},"把临时对象里的指针设为 ",[45,980,981],{},"nullptr",[31,983,984],{},"![[Pasted image 20260312020356.png]]",[31,986,987],{},"被移动的对象的引用不会变，但是对象内部的资源通常会变成nullptr或者初始状态。",[27,989,990],{"id":990},"资源管理",[31,992,993],{},"![[Pasted image 20260312020647.png]]",[31,995,996,997,1000],{},"在C++中，",[16,998,999],{},"资源","是指任何必须获取并在使用后（显式或隐式）释放的东西",[31,1002,1003],{},"就比如内存，new分配的内存，释放是通过delete，线程也必须释放，通过Join()或者析构。\n文件句柄也一样，但是释放的方式是close()或者析构。锁用unlock()，网络连接用close()，数据库是disconnect()。",[31,1005,1006,1007,1010],{},"资源句柄是一个对象，它在构造函数中获取资源，在析构函数中释放资源，这就是C++中的资源管理的核心模式——",[16,1008,1009],{},"RAII","（Resource Acquisition Is Initialization，资源获取即初始化）。",[255,1012,1013],{"id":1013},"资源安全的三级",[31,1015,1016,1019,1020,1023,1026,1027,1029,1032],{},[16,1017,1018],{},"弱资源安全","：资源最终会被释放（但可能延迟）",[1021,1022],"br",{},[16,1024,1025],{},"强资源安全","：资源在不再需要时立即释放",[1021,1028],{},[16,1030,1031],{},"无泄漏","：所有资源都被正确管理",[31,1034,1035],{},"C++标准库提供了多种资源句柄：",[1037,1038,1039,1055],"table",{},[1040,1041,1042],"thead",{},[1043,1044,1045,1049,1052],"tr",{},[1046,1047,1048],"th",{},"资源句柄",[1046,1050,1051],{},"管理的资源",[1046,1053,1054],{},"特性",[1056,1057,1058,1071,1083,1096,1109,1122,1135],"tbody",{},[1043,1059,1060,1065,1068],{},[1061,1062,1063],"td",{},[45,1064,230],{},[1061,1066,1067],{},"动态数组",[1061,1069,1070],{},"可拷贝（深拷贝）或移动",[1043,1072,1073,1077,1080],{},[1061,1074,1075],{},[45,1076,227],{},[1061,1078,1079],{},"字符串",[1061,1081,1082],{},"可拷贝或移动",[1043,1084,1085,1090,1093],{},[1061,1086,1087],{},[45,1088,1089],{},"std::thread",[1061,1091,1092],{},"线程",[1061,1094,1095],{},"只能移动，不能拷贝",[1043,1097,1098,1103,1106],{},[1061,1099,1100],{},[45,1101,1102],{},"std::unique_ptr",[1061,1104,1105],{},"单个对象\u002F数组",[1061,1107,1108],{},"独占所有权，只能移动",[1043,1110,1111,1116,1119],{},[1061,1112,1113],{},[45,1114,1115],{},"std::shared_ptr",[1061,1117,1118],{},"单个对象",[1061,1120,1121],{},"共享所有权，可拷贝",[1043,1123,1124,1129,1132],{},[1061,1125,1126],{},[45,1127,1128],{},"std::fstream",[1061,1130,1131],{},"文件",[1061,1133,1134],{},"可移动",[1043,1136,1137,1142,1145],{},[1061,1138,1139],{},[45,1140,1141],{},"std::lock_guard",[1061,1143,1144],{},"互斥锁",[1061,1146,1147],{},"自动解锁",[255,1149,1151],{"id":1150},"gc模式和raii模式的优劣","GC模式和RAII模式的优劣",[31,1153,1154,1157],{},[16,1155,1156],{},"垃圾回收","（GC）：",[10,1159,1160,1163],{},[13,1161,1162],{},"优点：自动管理内存，减少程序员负担",[13,1164,1165],{},"缺点：只管理内存，不管理其他资源；全局性，破坏局部性；不确定的释放时机",[31,1167,1168,506],{},[16,1169,1170],{},"RAII\u002F资源句柄",[10,1172,1173,1176],{},[13,1174,1175],{},"优点：管理所有资源；确定性释放；局部性更好；零开销抽象",[13,1177,1178],{},"缺点：需要程序员理解生命周期",[1180,1181,1182],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":43,"searchDepth":57,"depth":57,"links":1184},[1185,1186,1190,1193,1197,1200,1206],{"id":29,"depth":57,"text":29},{"id":218,"depth":57,"text":218,"children":1187},[1188,1189],{"id":257,"depth":63,"text":257},{"id":303,"depth":63,"text":303},{"id":324,"depth":57,"text":325,"children":1191},[1192],{"id":370,"depth":63,"text":370},{"id":376,"depth":57,"text":377,"children":1194},[1195,1196],{"id":386,"depth":63,"text":386},{"id":488,"depth":63,"text":488},{"id":627,"depth":57,"text":627,"children":1198},[1199],{"id":637,"depth":63,"text":637},{"id":718,"depth":57,"text":718,"children":1201},[1202,1203,1204,1205],{"id":727,"depth":63,"text":727},{"id":797,"depth":63,"text":797},{"id":803,"depth":63,"text":803},{"id":885,"depth":63,"text":885},{"id":990,"depth":57,"text":990,"children":1207},[1208,1209],{"id":1013,"depth":63,"text":1013},{"id":1150,"depth":63,"text":1151},"md",{},"\u002Fcpp\u002Fcpp的学习笔记",{"description":43},"cpp\u002FCpp的学习笔记","6wKuvqFWtcJmUvsdaV1tuAgpT5UteTy7AW5dCLDq5uM",1791042461420]