c++面向对象编程 struct和class选择如果你的类型主要是一堆公开的数据用 struct。如果你的类型有私有状态和公有接口用 class。移动语义c11引入拷贝构造函数拷贝构造函数是一种特殊的构造函数它在创建对象时是使用同一类中之前创建的对象来初始化新创建的对象**他能确保新对象与原对象内容相同但内存独立修改新对象不会影响原对象。**拷贝构造函数通常用于通过使用另一个同类型的对象来初始化新创建的对象。复制对象把它作为参数传递给函数。复制对象并从函数返回这个对象。ClassName(constClassNameother);当你的类包含动态资源指针、文件句柄等时必须自己写拷贝构造函数实现深拷贝否则默认的浅拷贝会导致内存泄漏或双重释放。和构造函数的区别构造函数、析构函数调用链机制构造函数调用链普通继承非虚继承当创建一个派生类对象时构造函数的调用顺序是调用基类的构造函数按继承声明顺序从左到右。调用成员对象的构造函数按在类中声明的顺序从上到下。执行派生类构造函数体。classBase{public:Base(){std::coutBase constructor\n;}};classMember{public:Member(){std::coutMember constructor\n;}};classDerived:publicBase{private:Member m;public:Derived(){std::coutDerived constructor\n;}};intmain(){Derived d;// 输出顺序// Base constructor// Member constructor// Derived constructor}为什么这样设计​派生类可能依赖于基类已经初始化好的成员比如基类提供了 protected 数据成员所以基类必须先构造。成员对象同理派生类构造函数体可能用到成员对象所以成员对象必须在派生类构造函数体之前构造完毕。虚拟继承菱形继承当存在虚拟继承时构造顺序更复杂。C 规定首先构造所有的虚基类按深度优先、从左到右的顺序且只构造一次即使多个派生类共享同一个虚基类。然后构造非虚基类按继承声明顺序。再构造成员对象。最后执行派生类构造函数体。classVBase{public:VBase(){std::coutVBase constructor\n;}};classMiddle1:virtualpublicVBase{public:Middle1(){std::coutMiddle1 constructor\n;}};classMiddle2:virtualpublicVBase{public:Middle2(){std::coutMiddle2 constructor\n;}};classDerived:publicMiddle1,publicMiddle2{public:Derived(){std::coutDerived constructor\n;}};intmain(){Derived d;// 输出顺序// VBase constructor (虚基类最先构造且只一次)// Middle1 constructor// Middle2 constructor// Derived constructor}为什么这样设计​虚拟继承的目的是解决菱形继承中基类被重复构造的问题。通过将虚基类的构造责任交给最派生的类并在所有直接基类之前构造保证了虚基类子对象唯一且在所有路径上共享。析构函数调用链析构函数的调用顺序与构造函数完全相反执行派生类析构函数体。调用成员对象的析构函数按声明顺序的逆序。调用非虚基类的析构函数按继承顺序的逆序。调用虚基类的析构函数按构造顺序的逆序。classBase{public:~Base(){std::coutBase destructor\n;}};classMember{public:~Member(){std::coutMember destructor\n;}};classDerived:publicBase{private:Member m;public:~Derived(){std::coutDerived destructor\n;}};intmain(){Derived d;// 析构输出顺序在 main 结束 d 销毁时// Derived destructor// Member destructor// Base destructor}为什么这样设计​派生类可能拥有需要先释放的资源比如动态分配的内存这些资源可能在基类析构函数中被访问所以派生类必须先清理。成员对象可能被派生类析构函数体使用所以成员对象应在派生类析构函数体之后、基类析构之前销毁。基类析构函数负责清理基类自己的资源应该在最后执行。构造函数为何不能抛出异常构造函数中抛出异常会导致当前正在构造的对象不会被完整创建因此该对象的析构函数不会被调用。但已经构造完成的基类子对象和成员对象会被正确析构。这是 C 异常安全机制的重要设计。抛出异常会发什么#includeiostreamclassMember{public:Member(){std::coutMember constructed\n;}~Member(){std::coutMember destructed\n;}};classBase{public:Base(){std::coutBase constructed\n;}~Base(){std::coutBase destructed\n;}};classDerived:publicBase{private:Member m;public:Derived():Base(),m(){std::coutDerived constructor body\n;throwstd::runtime_error(error);// 在构造函数体中抛出异常}~Derived(){std::coutDerived destructed\n;}};intmain(){try{Derived d;}catch(...){std::coutException caught\n;}}结果分析先构造 Base基类子对象→ 成功。再构造 Member成员对象→ 成功。进入 Derived 构造函数体 → 抛出异常。Derived 对象尚未构造完成所以不会调用 ~Derived()。但已经构造好的 Member 和 Base 会被自动析构按构造逆序先 Member后 Base。异常继续传播到 catch 块。为什么 ~Derived() 不被调用C 标准规定如果一个对象的构造函数因异常而终止则该对象的析构函数不会被调用。原因很直观析构函数负责清理已经完全构造的对象。如果构造函数没有执行完毕中途抛异常对象处于“部分构造”状态其成员可能未初始化或资源未分配调用析构函数可能会导致未定义行为比如访问未初始化的指针。因此C 选择不调用析构函数而是由编译器自动销毁已经构造完成的子对象基类和成员。中断了什么环节异常中断了构造函数体的执行以及后续的成员/基类构造如果有的话。具体来说中断了当前对象的构造函数体Derived 构造函数体中的代码在异常点之后不再执行。中断了当前对象的完整构造对象没有被完全创建其生命周期从未开始。中断了当前对象析构函数的调用如前所述~Derived() 不会被调用。但没有中断已经成功的子对象构造的析构过程——那些子对象会被正常析构。资源泄漏的风险如果构造函数中已经分配了动态资源如 new、fopen、malloc然后在后续步骤抛出异常这些资源将无法被释放因为析构函数不会被调用。classRisky{private:int*data;FILE*file;public:Risky(){datanewint[100];// 分配堆内存filefopen(test.txt,w);// 打开文件throwstd::runtime_error();// 异常data 和 file 永远不会被释放}~Risky(){delete[]data;fclose(file);}};解决方案使用 RAII 包装类如 std::vector、std::unique_ptr、std::fstream让资源的释放由成员对象的析构函数自动完成。这样即使构造函数抛出异常已构造的 RAII 成员也会正确析构释放资源。classSafe{private:std::vectorintdata;// RAII 容器std::ofstream file;// RAII 文件流public:Safe():data(100),file(test.txt){throwstd::runtime_error();// 异常但 data 和 file 的析构函数会被调用}};多态#includeiostreamusingnamespacestd;// 基类 AnimalclassAnimal{public:// 虚函数 sound为不同的动物发声提供接口virtualvoidsound()const{coutAnimal makes a soundendl;}// 虚析构函数确保子类对象被正确析构virtual~Animal(){coutAnimal destroyedendl;}};// 派生类 Dog继承自 AnimalclassDog:publicAnimal{public:// 重写 sound 方法voidsound()constoverride{coutDog barksendl;}~Dog(){coutDog destroyedendl;}};// 测试多态intmain(){Animal*animalPtr;// 基类指针// 创建 Dog 对象并指向 Animal 指针animalPtrnewDog();animalPtr-sound();// 调用 Dog 的 sound 方法deleteanimalPtr;// 释放内存调用 Dog 和 Animal 的析构函数return0;}只有通过基类的指针或引用调用虚函数时才会发生多态。如果直接使用派生类的对象调用函数那么调用的是派生类中的版本而不是基类中的版本。多态性需要运行时类型信息RTTI这可能会增加程序的开销。如何更有效率的写法智能指针摆脱手动new/deleteincludememory// 必须包含头文件intmain(){// 使用 std::unique_ptr 表示独占所有权std::unique_ptrAnimalanimalPtrstd::make_uniqueDog();animalPtr-sound();// 依然完美支持多态调用 Dog::sound()// 重新赋值animalPtrstd::make_uniqueCat();animalPtr-sound();// 依然支持多态调用 Cat::sound()// 离开作用域时智能指针会自动调用 delete你完全不需要写return0;}变化点不用写 delete智能指针在超出作用域时会自动释放内存彻底告别内存泄漏。不用写 new推荐使用 std::make_uniqueC14来创建对象它不仅更高效而且能提供更好的异常安全保障。告别手动创建工厂模式enumclassAnimalType{DOG,CAT};// 工厂函数隐藏创建细节std::unique_ptrAnimalcreateAnimal(AnimalType type){switch(type){caseAnimalType::DOG:returnstd::make_uniqueDog();caseAnimalType::CAT:returnstd::make_uniqueCat();default:returnnullptr;}}intmain(){// 业务层只和基类指针打交道完全不知道具体创建了哪个子类autoanimalcreateAnimal(AnimalType::DOG);animal-sound();// 业务代码极其清爽没有 new没有 deletereturn0;}对于虚函数和虚析构函数的误解我将虚析构函数会被派生类执行的机制误理解成虚函数也如此。但实际上虚析构函数会被派生类执行是析构函数的调用链规则导致的。虚函数如果被派生类重载就只会执行派生类的函数。c规定构造函数从基类到派生类依次调用。析构函数从派生类到基类依次调用。这个规则是独立于虚函数机制的。内联函数内联函数是 C 中一种请求编译器在调用处直接展开函数体的机制目的是消除函数调用的开销压栈、跳转、返回等从而提升性能。工作原理普通函数普通函数调用时CPU 需要将参数压栈保存当前指令地址返回地址跳转到函数代码处执行执行完毕后再跳转回来内联函数则不同编译器在编译阶段将函数体的代码直接替换到每个调用点就像把函数体“复制粘贴”过去一样从而避免了上述开销。内联函数的特点编译期行为inline 只是对编译器的请求编译器可以选择忽略比如函数体过大、递归函数等。定义通常放在头文件中因为内联展开需要看到函数定义所以内联函数通常定义在头文件中而不是源文件中。没有额外的调用开销但也可能增加代码体积如果多次调用每次都会复制一份代码即“以空间换时间”。使用场景小型、频繁调用的函数性能热点例如简单的 getter/setter、数学计算、比较函数等这些函数体很小调用开销占比大内联能明显提升性能。classPoint{intx,y;public:intgetX()const{returnx;}// 适合内联voidsetX(intval){xval;}// 适合内联};替代宏#defineC 语言中常用宏定义实现“函数”但宏有很多缺陷没有类型检查、参数多次求值、难以调试。内联函数可以完全替代宏且更安全。// 宏有隐患#defineSQUARE(x)((x)*(x))// 内联函数类型安全无副作用inlineintsquare(intx){returnx*x;}性能敏感的库函数标准库中的许多函数如 std::max、std::min、std::swap 等都被声明为内联以便在用户代码中直接展开。短小的模板函数模板函数通常定义在头文件中编译器会隐式将其视为内联候选即使不加 inline 关键字。