[{"data":1,"prerenderedAt":1142},["ShallowReactive",2],{"page-\u002Fcpp\u002Ffeatures\u002Fcpp类的讲解":3},{"id":4,"title":5,"body":6,"description":27,"extension":1136,"meta":1137,"navigation":125,"path":1138,"seo":1139,"stem":1140,"__hash__":1141},"content\u002Fcpp\u002Ffeatures\u002FCpp类的讲解.md","Cpp类的讲解",{"type":7,"value":8,"toc":1120},"minimark",[9,14,17,21,51,75,83,97,108,111,185,189,192,195,204,207,210,219,222,225,234,237,240,247,263,276,279,363,383,386,390,397,490,493,496,542,545,548,598,601,608,653,656,667,676,746,806,809,824,827,830,833,870,888,891,894,897,900,907,953,956,963,978,981,984,999,1002,1022,1025,1039,1059,1066,1086,1089,1092,1116],[10,11,13],"h2",{"id":12},"c-与-c在类上的不同","c++ 与 c#在类上的不同",[10,15,16],{"id":16},"兼容性",[18,19,20],"p",{},"有时候我们编写头文件的时候，我们准备让代码有更好的兼容性，比如说库文件，我们可能会遇到一些宏，类似于如下：",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-cpp shiki shiki-themes github-light github-dark","#ifdef __cplusplus\nextern \"C\" {\n#endif \n","cpp","",[29,30,31,39,45],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"#ifdef __cplusplus\n",[32,40,42],{"class":34,"line":41},2,[32,43,44],{},"extern \"C\" {\n",[32,46,48],{"class":34,"line":47},3,[32,49,50],{},"#endif\n",[18,52,53,54,57,58,61,62,65,66,69,70,74],{},"它们的作用就是确保cpp的编译器在处理戴拿的时候按照c语言的方式来处理函数名，因为C语言是不支持函数重载的，C编译器眼中，函数 ",[29,55,56],{},"void print(int)"," 的符号名就是 ",[29,59,60],{},"print","。C++ 支持函数重载。为了区分 ",[29,63,64],{},"print(int)"," 和 ",[29,67,68],{},"print(double)","，C++ 编译器会偷偷修改函数名（这被称为 ",[71,72,73],"strong",{},"Name Mangling","）。",[22,76,81],{"className":77,"code":79,"language":80},[78],"language-text","print(int)` 可能会变成 `_Z5printi\nprint(double)` 可能会变成 `_Z5printd\n","text",[29,82,79],{"__ignoreMap":27},[18,84,85,86,89,90,92,93,96],{},"如果我们在一个c++中包含了一个使用C编译的库，c++的链接器会去寻找",[29,87,88],{},"_Z5printi","，但 C 库里只有 ",[29,91,60],{},"。结果就是：",[71,94,95],{},"链接错误（LNK2019）","，提示找不到符号。",[18,98,99,100,103,104,107],{},"如果我们用了__cplusplus这个编译器预定义的宏，那么",[71,101,102],{},"c++编译器","编译它的时候，它就存在，如果是用的",[71,105,106],{},"C编译器","，那么它就不存在。",[18,109,110],{},"上面的exern \"C\":{} 就是告诉c++的编译器不使用name mangling，直接使用它们原本的名称。最经典的使用场景会包在整个头文件的内容外面：",[22,112,114],{"className":24,"code":113,"language":26,"meta":27,"style":27},"#pragma once\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n\u002F\u002F 这里写你的 C 风格函数定义\nvoid Logger_Log(const char* message);\nint Calculate_Sum(int a, int b);\n\n#ifdef __cplusplus\n}\n#endif\n",[29,115,116,121,127,131,136,141,146,152,158,164,169,174,180],{"__ignoreMap":27},[32,117,118],{"class":34,"line":35},[32,119,120],{},"#pragma once\n",[32,122,123],{"class":34,"line":41},[32,124,126],{"emptyLinePlaceholder":125},true,"\n",[32,128,129],{"class":34,"line":47},[32,130,38],{},[32,132,134],{"class":34,"line":133},4,[32,135,44],{},[32,137,139],{"class":34,"line":138},5,[32,140,50],{},[32,142,144],{"class":34,"line":143},6,[32,145,126],{"emptyLinePlaceholder":125},[32,147,149],{"class":34,"line":148},7,[32,150,151],{},"\u002F\u002F 这里写你的 C 风格函数定义\n",[32,153,155],{"class":34,"line":154},8,[32,156,157],{},"void Logger_Log(const char* message);\n",[32,159,161],{"class":34,"line":160},9,[32,162,163],{},"int Calculate_Sum(int a, int b);\n",[32,165,167],{"class":34,"line":166},10,[32,168,126],{"emptyLinePlaceholder":125},[32,170,172],{"class":34,"line":171},11,[32,173,38],{},[32,175,177],{"class":34,"line":176},12,[32,178,179],{},"}\n",[32,181,183],{"class":34,"line":182},13,[32,184,50],{},[186,187,188],"h3",{"id":188},"拷贝构造和移动构造",[18,190,191],{},"拷贝是对象被当作值传递或者A a = b的时候触发，移动构造是c++11引入的，用于转移资源所有权，就比如说一个从临时对象拿走内存，来避免昂贵的内存拷贝。",[18,193,194],{},"在CPP中，是否发生了拷贝是由参数传递方式决定的，比如说究竟是值传递还是引用传递。",[22,196,198],{"className":24,"code":197,"language":26,"meta":27,"style":27},"void Func(const std::string s) { ... }\n",[29,199,200],{"__ignoreMap":27},[32,201,202],{"class":34,"line":35},[32,203,197],{},[18,205,206],{},"这种就属于按值传递，编译器回去拷贝一份字符串，const作用就是函数内部，不能修改整个拷贝出来的副本，这种方式少见而且不推荐，因为这种方式限制了修改副本的同时还花费了拷贝开销，很烂。",[18,208,209],{},"我们可以按常量传递，使用类似 const T& x这种方式：",[22,211,213],{"className":24,"code":212,"language":26,"meta":27,"style":27},"void Func(const std::string& s) { ... }\n",[29,214,215],{"__ignoreMap":27},[32,216,217],{"class":34,"line":35},[32,218,212],{},[18,220,221],{},"这种不会发生拷贝，函数会直接指向数据的内存地址，这里是左值引用，const的作用是确保函数内部智能读取原数据而不能修改，这个是最常用的，避免了拷贝，还可以保证原数据的安全，是只读的。类似于c#的in关键字。",[18,223,224],{},"还有一种是普通引用传递T& x{}：",[22,226,228],{"className":24,"code":227,"language":26,"meta":27,"style":27},"void Func(std::string& s) { ... }\n",[29,229,230],{"__ignoreMap":27},[32,231,232],{"class":34,"line":35},[32,233,227],{},[18,235,236],{},"这种不会发生拷贝，并且可以随意的修改原数据，修改会直接反应在调用者那里，类似于ref关键字，这个就是我们常说的这个函数会产生副作用",[186,238,239],{"id":239},"静态类的处理",[18,241,242,243,246],{},"C#中，static class意味着类不能被实例化，且所有成员必须是静态的。 在 ",[71,244,245],{},"C++"," 中：",[18,248,249,255,256,258,259,262],{},[71,250,251,252],{},"类本身不能标记为 ",[29,253,254],{},"static","。",[29,257,254],{}," 关键字在 C++ 中修饰变量或函数时，表示“内部链接”或“类共有成员”，但不能直接修饰 ",[29,260,261],{},"class"," 定义。",[18,264,265,266,272,273,255],{},"如果你想要实现类似 C# 静态类的效果，C++ 通常有两种做法：",[71,267,268,269],{},"使用 ",[29,270,271],{},"namespace"," 或 ",[71,274,275],{},"类内全静态成员",[18,277,278],{},"就比如：",[22,280,282],{"className":24,"code":281,"language":26,"meta":27,"style":27},"\u002F\u002F Logger.h\n#pragma once\n#include \u003Ciostream>\n#include \u003Cstring>\n\nenum class LoggerLevel { \u002F\u002F 建议使用 enum class 避免命名污染\n    Info,\n    Debug,\n    Warning,\n    Error\n};\n\nnamespace Logger {\n    \u002F\u002F 声明函数\n    void Log(LoggerLevel level, const std::string& message);\n}\n",[29,283,284,289,293,298,303,307,312,317,322,327,332,337,341,346,352,358],{"__ignoreMap":27},[32,285,286],{"class":34,"line":35},[32,287,288],{},"\u002F\u002F Logger.h\n",[32,290,291],{"class":34,"line":41},[32,292,120],{},[32,294,295],{"class":34,"line":47},[32,296,297],{},"#include \u003Ciostream>\n",[32,299,300],{"class":34,"line":133},[32,301,302],{},"#include \u003Cstring>\n",[32,304,305],{"class":34,"line":138},[32,306,126],{"emptyLinePlaceholder":125},[32,308,309],{"class":34,"line":143},[32,310,311],{},"enum class LoggerLevel { \u002F\u002F 建议使用 enum class 避免命名污染\n",[32,313,314],{"class":34,"line":148},[32,315,316],{},"    Info,\n",[32,318,319],{"class":34,"line":154},[32,320,321],{},"    Debug,\n",[32,323,324],{"class":34,"line":160},[32,325,326],{},"    Warning,\n",[32,328,329],{"class":34,"line":166},[32,330,331],{},"    Error\n",[32,333,334],{"class":34,"line":171},[32,335,336],{},"};\n",[32,338,339],{"class":34,"line":176},[32,340,126],{"emptyLinePlaceholder":125},[32,342,343],{"class":34,"line":182},[32,344,345],{},"namespace Logger {\n",[32,347,349],{"class":34,"line":348},14,[32,350,351],{},"    \u002F\u002F 声明函数\n",[32,353,355],{"class":34,"line":354},15,[32,356,357],{},"    void Log(LoggerLevel level, const std::string& message);\n",[32,359,361],{"class":34,"line":360},16,[32,362,179],{},[18,364,365,366,376,377,379,380,382],{},"顺带一提， enum class是c++11引入的强类型枚举，也叫有作用域枚举，",[71,367,368,369,372,373],{},"C++ 的 ",[29,370,371],{},"enum class"," 在行为上几乎等同于 C# 的 ",[29,374,375],{},"enum","。而 C++ 传统的 ",[29,378,375],{},"（不带 ",[29,381,261],{}," 的）反而更像是一个“披着名字的整数”。",[18,384,385],{},"普通的enum中，枚举值是之际额暴露在外部，在enum class中，必须通过类名来引用，因此我们才叫做它可以避免命名污染。",[10,387,389],{"id":388},"c的构造函数","C++的构造函数",[18,391,392,393,396],{},"在 C++ 中，构造函数是用于初始化类对象的特殊成员函数。它的名称与类名完全相同，没有返回类型（连 ",[29,394,395],{},"void"," 都没有）。",[22,398,400],{"className":24,"code":399,"language":26,"meta":27,"style":27},"class Logger {\nprivate:\n    int _id;\n    std::string _name;\n\npublic:\n    \u002F\u002F 1. 默认构造函数 (Default Constructor)\n    Logger() {\n        _id = 0;\n        _name = \"Default\";\n    }\n\n    \u002F\u002F 2. 有参构造函数 (Parameterized Constructor)\n    Logger(int id, std::string name) {\n        _id = id;\n        _name = name;\n    }\n};\n",[29,401,402,407,412,417,422,426,431,436,441,446,451,456,460,465,470,475,480,485],{"__ignoreMap":27},[32,403,404],{"class":34,"line":35},[32,405,406],{},"class Logger {\n",[32,408,409],{"class":34,"line":41},[32,410,411],{},"private:\n",[32,413,414],{"class":34,"line":47},[32,415,416],{},"    int _id;\n",[32,418,419],{"class":34,"line":133},[32,420,421],{},"    std::string _name;\n",[32,423,424],{"class":34,"line":138},[32,425,126],{"emptyLinePlaceholder":125},[32,427,428],{"class":34,"line":143},[32,429,430],{},"public:\n",[32,432,433],{"class":34,"line":148},[32,434,435],{},"    \u002F\u002F 1. 默认构造函数 (Default Constructor)\n",[32,437,438],{"class":34,"line":154},[32,439,440],{},"    Logger() {\n",[32,442,443],{"class":34,"line":160},[32,444,445],{},"        _id = 0;\n",[32,447,448],{"class":34,"line":166},[32,449,450],{},"        _name = \"Default\";\n",[32,452,453],{"class":34,"line":171},[32,454,455],{},"    }\n",[32,457,458],{"class":34,"line":176},[32,459,126],{"emptyLinePlaceholder":125},[32,461,462],{"class":34,"line":182},[32,463,464],{},"    \u002F\u002F 2. 有参构造函数 (Parameterized Constructor)\n",[32,466,467],{"class":34,"line":348},[32,468,469],{},"    Logger(int id, std::string name) {\n",[32,471,472],{"class":34,"line":354},[32,473,474],{},"        _id = id;\n",[32,476,477],{"class":34,"line":360},[32,478,479],{},"        _name = name;\n",[32,481,483],{"class":34,"line":482},17,[32,484,455],{},[32,486,488],{"class":34,"line":487},18,[32,489,336],{},[186,491,492],{"id":492},"委托构造",[18,494,495],{},"如果你有多个构造函数，且它们有重复的逻辑，可以让一个构造函数调用另一个。",[22,497,499],{"className":24,"code":498,"language":26,"meta":27,"style":27},"class Device {\npublic:\n    Device(int id, std::string type) {\n        \u002F\u002F 复杂的初始化逻辑\n    }\n\n    \u002F\u002F 调用上面的构造函数\n    Device(int id) : Device(id, \"Unknown\") { } \n};\n",[29,500,501,506,510,515,520,524,528,533,538],{"__ignoreMap":27},[32,502,503],{"class":34,"line":35},[32,504,505],{},"class Device {\n",[32,507,508],{"class":34,"line":41},[32,509,430],{},[32,511,512],{"class":34,"line":47},[32,513,514],{},"    Device(int id, std::string type) {\n",[32,516,517],{"class":34,"line":133},[32,518,519],{},"        \u002F\u002F 复杂的初始化逻辑\n",[32,521,522],{"class":34,"line":138},[32,523,455],{},[32,525,526],{"class":34,"line":143},[32,527,126],{"emptyLinePlaceholder":125},[32,529,530],{"class":34,"line":148},[32,531,532],{},"    \u002F\u002F 调用上面的构造函数\n",[32,534,535],{"class":34,"line":154},[32,536,537],{},"    Device(int id) : Device(id, \"Unknown\") { } \n",[32,539,540],{"class":34,"line":160},[32,541,336],{},[186,543,544],{"id":544},"default和delete",[18,546,547],{},"如果你定义了有参构造函数，编译器就不会自动生成默认构造函数。你可以手动强制它生成，我们需要使用deafult关键字。如果你不希望对象被实例化（比如写单例或静态工具类），可以禁用构造函数，我们使用delete。",[22,549,551],{"className":24,"code":550,"language":26,"meta":27,"style":27},"class Utils {\npublic:\n    Utils() = delete; \u002F\u002F 禁止创建实例\n};\n\nclass Data {\npublic:\n    Data() = default; \u002F\u002F 显式要求编译器生成默认构造函数\n    Data(int x) { }\n};\n",[29,552,553,558,562,567,571,575,580,584,589,594],{"__ignoreMap":27},[32,554,555],{"class":34,"line":35},[32,556,557],{},"class Utils {\n",[32,559,560],{"class":34,"line":41},[32,561,430],{},[32,563,564],{"class":34,"line":47},[32,565,566],{},"    Utils() = delete; \u002F\u002F 禁止创建实例\n",[32,568,569],{"class":34,"line":133},[32,570,336],{},[32,572,573],{"class":34,"line":138},[32,574,126],{"emptyLinePlaceholder":125},[32,576,577],{"class":34,"line":143},[32,578,579],{},"class Data {\n",[32,581,582],{"class":34,"line":148},[32,583,430],{},[32,585,586],{"class":34,"line":154},[32,587,588],{},"    Data() = default; \u002F\u002F 显式要求编译器生成默认构造函数\n",[32,590,591],{"class":34,"line":160},[32,592,593],{},"    Data(int x) { }\n",[32,595,596],{"class":34,"line":166},[32,597,336],{},[186,599,600],{"id":600},"防止隐式转换",[18,602,603,604,607],{},"在 C++ 中，如果",[71,605,606],{},"构造函数只有一个参数","，编译器会尝试进行隐式类型转换，这有时会导致诡异的 Bug，我们可以使用explicit关键字：",[22,609,611],{"className":24,"code":610,"language":26,"meta":27,"style":27},"class Buffer {\npublic:\n    explicit Buffer(int size) { ... }\n};\n\n\u002F\u002F 使用：\nBuffer b1(10);    \u002F\u002F OK\n\u002F\u002F Buffer b2 = 10; \u002F\u002F 报错！因为加了 explicit，禁止将 int 直接“变成” Buffer\n",[29,612,613,618,622,627,631,635,640,645],{"__ignoreMap":27},[32,614,615],{"class":34,"line":35},[32,616,617],{},"class Buffer {\n",[32,619,620],{"class":34,"line":41},[32,621,430],{},[32,623,624],{"class":34,"line":47},[32,625,626],{},"    explicit Buffer(int size) { ... }\n",[32,628,629],{"class":34,"line":133},[32,630,336],{},[32,632,633],{"class":34,"line":138},[32,634,126],{"emptyLinePlaceholder":125},[32,636,637],{"class":34,"line":143},[32,638,639],{},"\u002F\u002F 使用：\n",[32,641,642],{"class":34,"line":148},[32,643,644],{},"Buffer b1(10);    \u002F\u002F OK\n",[32,646,647,650],{"class":34,"line":154},[32,648,649],{},"\u002F\u002F Buffer b2 = 10;",[32,651,652],{}," \u002F\u002F 报错！因为加了 explicit，禁止将 int 直接“变成” Buffer\n",[186,654,655],{"id":655},"初始化",[18,657,658,659,662,663,666],{},"在 C++ 中，上面的赋值写法（在 ",[29,660,661],{},"{}"," 内部赋值）实际上是“先创建变量再赋值”。",[71,664,665],{},"更高效、更推荐","的做法是使用“初始化列表”。",[18,668,669,672,673],{},[71,670,671],{},"语法："," ",[29,674,675],{},"Constructor() : member1(val1), member2(val2) { ... }",[22,677,679],{"className":24,"code":678,"language":26,"meta":27,"style":27},"class Camera {\nprivate:\n    const int _camId; \u002F\u002F 常量必须在初始化列表中初始化\n    int _width;\n    int _height;\n\npublic:\n    \u002F\u002F 推荐写法：在进入构造函数体之前完成初始化\n    Camera(int id, int w, int h) \n        : _camId(id), _width(w), _height(h) \n    {\n        \u002F\u002F 函数体通常为空，或者只写一些逻辑校验\n    }\n};\n",[29,680,681,686,690,695,700,705,709,713,718,723,728,733,738,742],{"__ignoreMap":27},[32,682,683],{"class":34,"line":35},[32,684,685],{},"class Camera {\n",[32,687,688],{"class":34,"line":41},[32,689,411],{},[32,691,692],{"class":34,"line":47},[32,693,694],{},"    const int _camId; \u002F\u002F 常量必须在初始化列表中初始化\n",[32,696,697],{"class":34,"line":133},[32,698,699],{},"    int _width;\n",[32,701,702],{"class":34,"line":138},[32,703,704],{},"    int _height;\n",[32,706,707],{"class":34,"line":143},[32,708,126],{"emptyLinePlaceholder":125},[32,710,711],{"class":34,"line":148},[32,712,430],{},[32,714,715],{"class":34,"line":154},[32,716,717],{},"    \u002F\u002F 推荐写法：在进入构造函数体之前完成初始化\n",[32,719,720],{"class":34,"line":160},[32,721,722],{},"    Camera(int id, int w, int h) \n",[32,724,725],{"class":34,"line":166},[32,726,727],{},"        : _camId(id), _width(w), _height(h) \n",[32,729,730],{"class":34,"line":171},[32,731,732],{},"    {\n",[32,734,735],{"class":34,"line":176},[32,736,737],{},"        \u002F\u002F 函数体通常为空，或者只写一些逻辑校验\n",[32,739,740],{"class":34,"line":182},[32,741,455],{},[32,743,744],{"class":34,"line":348},[32,745,336],{},[22,747,749],{"className":24,"code":748,"language":26,"meta":27,"style":27},"class MyVector {\npublic:\n    \u002F\u002F 拷贝构造：参数必须是常量引用\n    MyVector(const MyVector& other) {\n        \u002F\u002F 执行深拷贝\n    }\n\n    \u002F\u002F 移动构造：参数是右值引用\n    MyVector(MyVector&& other) noexcept {\n        \u002F\u002F 转移指针的所有权\n    }\n};\n",[29,750,751,756,760,765,770,775,779,783,788,793,798,802],{"__ignoreMap":27},[32,752,753],{"class":34,"line":35},[32,754,755],{},"class MyVector {\n",[32,757,758],{"class":34,"line":41},[32,759,430],{},[32,761,762],{"class":34,"line":47},[32,763,764],{},"    \u002F\u002F 拷贝构造：参数必须是常量引用\n",[32,766,767],{"class":34,"line":133},[32,768,769],{},"    MyVector(const MyVector& other) {\n",[32,771,772],{"class":34,"line":138},[32,773,774],{},"        \u002F\u002F 执行深拷贝\n",[32,776,777],{"class":34,"line":143},[32,778,455],{},[32,780,781],{"class":34,"line":148},[32,782,126],{"emptyLinePlaceholder":125},[32,784,785],{"class":34,"line":154},[32,786,787],{},"    \u002F\u002F 移动构造：参数是右值引用\n",[32,789,790],{"class":34,"line":160},[32,791,792],{},"    MyVector(MyVector&& other) noexcept {\n",[32,794,795],{"class":34,"line":166},[32,796,797],{},"        \u002F\u002F 转移指针的所有权\n",[32,799,800],{"class":34,"line":171},[32,801,455],{},[32,803,804],{"class":34,"line":176},[32,805,336],{},[18,807,808],{},"noexcept关键字是 c++11引入的，用来告诉编译器和开发者，这个函数保证是不会抛出异常的，c#的方法默认也是不声明异常的，不强制使用try-catch语法，但是cpp的noexcept不仅仅是一个文档说明，对性能和稳定性也有很重要的影响。先来说说性能优化的地方：",[18,810,811,812,815,816,819,820,823],{},"当 C++ 的标准库容器（比如 ",[29,813,814],{},"std::vector","）需要扩容时，它会将",[71,817,818],{},"旧内存中的对象搬到新内存","中，如果说对象是的移动构造函数是noexcept的话，std::vector就会使用高效的移动操作，如果不是noexcept的话，编译器就会保证移动过程中报错了",[71,821,822],{},"原数据就能不丢失","，会保守选择拷贝方式。",[18,825,826],{},"可能我们比较奇怪的一点，为什么拷贝构造的形参是一个只读左值引用，但是却叫做拷贝。拷贝构造描述的其实是函数的功能，只读的左值引用是实现的最高效、安全的手段：",[186,828,829],{"id":829},"递归死循环",[18,831,832],{},"这个是技术上最根本的原因，假设不使用引用的话，而是真的使用值传递的话：",[22,834,836],{"className":24,"code":835,"language":26,"meta":27,"style":27},"class MyClass {\npublic:\n    \u002F\u002F 假设我们不写引用 &\n    MyClass(MyClass other) { \n        \u002F\u002F ... 实现拷贝逻辑\n    }\n};\n",[29,837,838,843,847,852,857,862,866],{"__ignoreMap":27},[32,839,840],{"class":34,"line":35},[32,841,842],{},"class MyClass {\n",[32,844,845],{"class":34,"line":41},[32,846,430],{},[32,848,849],{"class":34,"line":47},[32,850,851],{},"    \u002F\u002F 假设我们不写引用 &\n",[32,853,854],{"class":34,"line":133},[32,855,856],{},"    MyClass(MyClass other) { \n",[32,858,859],{"class":34,"line":138},[32,860,861],{},"        \u002F\u002F ... 实现拷贝逻辑\n",[32,863,864],{"class":34,"line":143},[32,865,455],{},[32,867,868],{"class":34,"line":148},[32,869,336],{},[18,871,872,873,876,877,880,881,884,885,255],{},"当你执行 ",[29,874,875],{},"MyClass a; MyClass b = a;"," 时：编译器发现我们",[71,878,879],{},"需要使用b的拷贝构造函数","，为了调用这个函数我们必须先把实参a传递给形参other，那么这个时候按值传递就意味着要把a拷贝一份给other，",[71,882,883],{},"然后为了把a拷贝给other，编译器就要调用MyClass的拷贝构造函数","，为了调用这个拷贝构造函数，",[71,886,887],{},"又需要再次拷贝实参",[18,889,890],{},"编译器就会报错！",[18,892,893],{},"或者我们可以从结果来说，拷贝构造定义的是函数执行之后的结果，内存里多了个和原对象一模一样的新对象。",[18,895,896],{},"接下来说说移动构造，为什么必须是右值引用：",[18,898,899],{},"移动构造本质是资源所有权的转移，我们可以脱离拷贝，直接将buffer指针地址拿来赋值给自己，然后将原来的指针设置为nullptr，为什么必须是右值，因为左值是一个持久的对象，它是有名字有地址的对象，如果你把一个正在使用的变量之际额给拿走，原程序就会崩溃，右值由于是临时对象，没有名字会马上销毁，那么它占用的内存资源就可以拿来利用，这个对原程序没有任何影响。",[18,901,902,903,906],{},"如果说",[71,904,905],{},"没有右值引用","，那么编译器就没法在语法层面上面区分是想要拷贝还是移动：",[22,908,910],{"className":24,"code":909,"language":26,"meta":27,"style":27},"class Logger {\npublic:\n    \u002F\u002F 拷贝构造：我承诺不改你，所以我用 const &\n    Logger(const Logger& other); \n\n    \u002F\u002F 移动构造：我需要改你（把你置空），所以我不能用 const\n    \u002F\u002F 我需要专门处理临时变量，所以用 &&\n    Logger(Logger&& other) noexcept; \n};\n",[29,911,912,916,920,925,930,934,939,944,949],{"__ignoreMap":27},[32,913,914],{"class":34,"line":35},[32,915,406],{},[32,917,918],{"class":34,"line":41},[32,919,430],{},[32,921,922],{"class":34,"line":47},[32,923,924],{},"    \u002F\u002F 拷贝构造：我承诺不改你，所以我用 const &\n",[32,926,927],{"class":34,"line":133},[32,928,929],{},"    Logger(const Logger& other); \n",[32,931,932],{"class":34,"line":138},[32,933,126],{"emptyLinePlaceholder":125},[32,935,936],{"class":34,"line":143},[32,937,938],{},"    \u002F\u002F 移动构造：我需要改你（把你置空），所以我不能用 const\n",[32,940,941],{"class":34,"line":148},[32,942,943],{},"    \u002F\u002F 我需要专门处理临时变量，所以用 &&\n",[32,945,946],{"class":34,"line":154},[32,947,948],{},"    Logger(Logger&& other) noexcept; \n",[32,950,951],{"class":34,"line":160},[32,952,336],{},[10,954,955],{"id":955},"访问符号的区别",[18,957,958,959,962],{},"C++区别于C#，他把权限分的很细，我们使用C#的时候一直都是使用.来访问成员，但是CPP不一样。 . 这样的点运算符是对象成员访问，用于直接操作对象实例，左侧必须是一个具体的对象，也就是在栈上定义的变量或者",[71,960,961],{},"解引用后的对象","，对应C#的是访问普通类属性或方法的逻辑：",[22,964,966],{"className":24,"code":965,"language":26,"meta":27,"style":27},"Logger myLogger;         \u002F\u002F 对象在栈上\nmyLogger.Log(level, \"..\"); \u002F\u002F 使用点访问\n",[29,967,968,973],{"__ignoreMap":27},[32,969,970],{"class":34,"line":35},[32,971,972],{},"Logger myLogger;         \u002F\u002F 对象在栈上\n",[32,974,975],{"class":34,"line":41},[32,976,977],{},"myLogger.Log(level, \"..\"); \u002F\u002F 使用点访问\n",[18,979,980],{},"->箭头运算符用于指针访问成员，这是C++处理指针时的专属符号，左侧必须是一个指针，比如说unique_ptr，或者是shared_ptr或者原始指针*。",[18,982,983],{},"它是(*ptr)的缩写，他回去解引用找到对应的对象后，再去访问成员。C#的引用类型变量比如class在底层都是指针，知识c#在帮你自动转换，在cpp中必须手动区分，如果手里是地址，就得用->。",[22,985,987],{"className":24,"code":986,"language":26,"meta":27,"style":27},"auto loggerPtr = std::make_unique\u003CLogger>(); \u002F\u002F loggerPtr 是个智能指针\nloggerPtr->Log(level, \"..\");                  \u002F\u002F 使用箭头访问\n",[29,988,989,994],{"__ignoreMap":27},[32,990,991],{"class":34,"line":35},[32,992,993],{},"auto loggerPtr = std::make_unique\u003CLogger>(); \u002F\u002F loggerPtr 是个智能指针\n",[32,995,996],{"class":34,"line":41},[32,997,998],{},"loggerPtr->Log(level, \"..\");                  \u002F\u002F 使用箭头访问\n",[18,1000,1001],{},"::是作用域解析运算符，用于静态\u002F全局访问，左侧可以是命名空间，也可以是类名或者枚举名称。我们经常用它来访问静态成员变量\u002F方法，访问命名空间里的类或者函数，访问enum class里的成员。对应的是c#里面中的话，访问静态方法比如说Console.WriteLine或者命名空间也用的是. ，但是c++里面必须用::。",[22,1003,1005],{"className":24,"code":1004,"language":26,"meta":27,"style":27},"Loagger::Logger myLogger;        \u002F\u002F 命名空间 :: 类名\nLoggerLevel level = LoggerLevel::info; \u002F\u002F 枚举 :: 成员\nstd::cout \u003C\u003C \"Hi\";               \u002F\u002F 命名空间 :: 对象\n",[29,1006,1007,1012,1017],{"__ignoreMap":27},[32,1008,1009],{"class":34,"line":35},[32,1010,1011],{},"Loagger::Logger myLogger;        \u002F\u002F 命名空间 :: 类名\n",[32,1013,1014],{"class":34,"line":41},[32,1015,1016],{},"LoggerLevel level = LoggerLevel::info; \u002F\u002F 枚举 :: 成员\n",[32,1018,1019],{"class":34,"line":47},[32,1020,1021],{},"std::cout \u003C\u003C \"Hi\";               \u002F\u002F 命名空间 :: 对象\n",[10,1023,1024],{"id":1024},"禁止构造函数隐式转换",[18,1026,1027,1030,1031,1034,1035,1038],{},[29,1028,1029],{},"explicit"," 用于",[71,1032,1033],{},"禁止构造函数或转换运算符的隐式转换","。它强制开发者必须",[71,1036,1037],{},"显式","地调用构造函数，从而避免编译器自动进行不期望的类型转换。",[22,1040,1042],{"className":24,"code":1041,"language":26,"meta":27,"style":27},"    explicit __CLR_OR_THIS_CALL basic_ostream(basic_streambuf\u003C_Elem, _Traits>* _Strbuf, bool _Isstd = false) {\n        _Myios::init(_Strbuf, _Isstd);\n    }\n",[29,1043,1044,1049,1054],{"__ignoreMap":27},[32,1045,1046],{"class":34,"line":35},[32,1047,1048],{},"    explicit __CLR_OR_THIS_CALL basic_ostream(basic_streambuf\u003C_Elem, _Traits>* _Strbuf, bool _Isstd = false) {\n",[32,1050,1051],{"class":34,"line":41},[32,1052,1053],{},"        _Myios::init(_Strbuf, _Isstd);\n",[32,1055,1056],{"class":34,"line":47},[32,1057,1058],{},"    }\n",[18,1060,1061,1062,1065],{},"这里是std::basic_ostream的构造函数，作用是",[71,1063,1064],{},"流缓冲区初始化输出流","，我们平常这样使用：",[22,1067,1069],{"className":24,"code":1068,"language":26,"meta":27,"style":27},"\u002F\u002F 你平时这样用：\nstd::stringstream ss;           \u002F\u002F 内部有自己的 streambuf\nstd::ostream os(&ss);           \u002F\u002F ← 这里调用了这个构造函数\n",[29,1070,1071,1076,1081],{"__ignoreMap":27},[32,1072,1073],{"class":34,"line":35},[32,1074,1075],{},"\u002F\u002F 你平时这样用：\n",[32,1077,1078],{"class":34,"line":41},[32,1079,1080],{},"std::stringstream ss;           \u002F\u002F 内部有自己的 streambuf\n",[32,1082,1083],{"class":34,"line":47},[32,1084,1085],{},"std::ostream os(&ss);           \u002F\u002F ← 这里调用了这个构造函数\n",[18,1087,1088],{},"_Strbuf 是指向流缓冲区的指针，实际数据存储和传输的地方。_Isstd默认false，标记是否为标准流，标准流有cout、cerr、clog等，用于特殊处理。",[18,1090,1091],{},"这里用explicit防止隐式转换：",[22,1093,1095],{"className":24,"code":1094,"language":26,"meta":27,"style":27},"std::stringbuf buf;\nstd::ostream os = &buf;   \u002F\u002F ❌ 如果没有 explicit，这居然能编译！\nstd::stringbuf buf;\nstd::ostream os(&buf);    \u002F\u002F ✅ 加了explicit需要显式构造，清晰明确\n",[29,1096,1097,1102,1107,1111],{"__ignoreMap":27},[32,1098,1099],{"class":34,"line":35},[32,1100,1101],{},"std::stringbuf buf;\n",[32,1103,1104],{"class":34,"line":41},[32,1105,1106],{},"std::ostream os = &buf;   \u002F\u002F ❌ 如果没有 explicit，这居然能编译！\n",[32,1108,1109],{"class":34,"line":47},[32,1110,1101],{},[32,1112,1113],{"class":34,"line":133},[32,1114,1115],{},"std::ostream os(&buf);    \u002F\u002F ✅ 加了explicit需要显式构造，清晰明确\n",[1117,1118,1119],"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":27,"searchDepth":41,"depth":41,"links":1121},[1122,1123,1127,1134,1135],{"id":12,"depth":41,"text":13},{"id":16,"depth":41,"text":16,"children":1124},[1125,1126],{"id":188,"depth":47,"text":188},{"id":239,"depth":47,"text":239},{"id":388,"depth":41,"text":389,"children":1128},[1129,1130,1131,1132,1133],{"id":492,"depth":47,"text":492},{"id":544,"depth":47,"text":544},{"id":600,"depth":47,"text":600},{"id":655,"depth":47,"text":655},{"id":829,"depth":47,"text":829},{"id":955,"depth":41,"text":955},{"id":1024,"depth":41,"text":1024},"md",{},"\u002Fcpp\u002Ffeatures\u002Fcpp类的讲解",{"description":27},"cpp\u002Ffeatures\u002FCpp类的讲解","YjWmtnVtan4hZCzPwwiQtifOhmdALNmRxK86Gq8LOto",1791042462684]