C++面试问答由浅入深

📅 2026/7/15 3:57:25
C++面试问答由浅入深
1.C中struct和class默认访问权限有什么区别答struct 的默认访问权限是 publicclass 的默认访问权限是 private2.简述C中值传递、引用传递、指针传递三者区别以及各自适用场景答值传递拷贝实参副本函数内修改仅作用于副本原变量不变会产生内存拷贝开销适合小型只读数据。指针传递传递变量内存地址通过解引用可修改原值存在空指针风险语法繁琐需要*解引用、取地址。左值引用变量别名和原变量共享一块内存修改即改原值无空引用语法简洁是修改外部变量首选3.什么是构造函数构造函数能否有返回值、能否被手动调用析构函数有什么特点构造函数定义构造函数是和类名同名的特殊成员函数创建对象时自动调用作用是初始化对象的成员变量保证对象诞生时处于合法可用状态。构造函数核心规则1.函数名类名大小写完全一致2.没有返回值也没有void3.可以重载多个参数列表不同的构造函数4.不能被static、virtual、const修饰5.无参构造、有参构造、拷贝构造、移动构造都属于构造函数。能否手动调用构造函数1.普通栈/堆对象不能直接手动调用2.创建临时对象、初始化列表中构造成员子对象、new在指定内存构造对象、C11委托构造一个构造函数调用同类另一个构造以上情况允许手动调用析构函数定义是对象销毁时自动调用的特殊函数负责释放资源堆内存、文件句柄、网络连接等防止内存泄漏。析构函数核心特点1.名称固定~类名2.无参数、无返回值不能重载一个类最多只能有一个析构3.对象生命周期结束时自动调用1.栈对象出作用域时自动调用2.new创建的堆对象执行delete时调用3.全局/静态对象程序退出时调用4.若不手动写编译器会生成默认析构空函数不释放堆资源5.基类析构建议加virtual多态场景防止派生类资源不释放6.不能手动传参调用仅能通过delete、作用域结束触发7.禁止const、static、传参、返回值、重载。4.区分野指针、空指针、悬空指针分别说明产生场景与危害空指针1.指针明确赋值为0/nullptr代表不指向任何有效内存是合法状态。2.场景指针初始化、函数失败返回空3.危害解引用空指针直接程序崩溃。野指针1.指针未初始化内部存随机脏地址地址完全未知、非法。2.场景局部指针变量定义后没赋值3.危害随机读写未知内存闪退、数据错乱极难复现bug。悬空指针悬挂指针1.指针原本指向一块有效内存这块内存被delete释放后指针没有置空指针本身地址还保留但指向的内存已回收失效2.危害再次解引用、二次delete都会未定义行为。5.什么是左值、右值简单区分并说说引入右值引用的目的是什么左值能取地址、拥有持久内存的是左值右值临时对象、字面量、表达式结果右值引用T两大核心目的1.实现移动语义避免不必要深拷贝提升性能2.支持完美转发封装函数时完整保留参数左/右值属性。6.拷贝构造函数什么时候会被调用写出三种经典场景1.使用同类已有对象初始化新对象Mystr s1; Mystr s2 s1; // 拷贝构造 Mystr s3(s1); // 拷贝构造2.函数值传递类对象作为形参实参拷贝给形参void func(Mystr m) {} func(s1); // 触发拷贝构造3.函数以值方式返回类临时对象Mystr test() { Mystr tmp; return tmp; // 返回局部对象调用拷贝构造编译器可能RVO优化省略 }7.深拷贝与浅拷贝的区别什么时候必须自己实现深拷贝浅拷贝默认拷贝构造/赋值运算符只会逐字节拷贝成员值。若成员是普通内置类型没问题若包含堆指针只会拷贝指针地址两个对象共用同一块堆内存。风险析构时先后两次delete同一块内存程序崩溃深拷贝堆成员不直接拷贝地址而是重新分配一块独立堆内存再把数据复制过去两个对象资源完全独立析构互不干扰。必须手动实现深拷贝的场景类中持有动态分配的堆资源new出来的数组、字符串、自定义堆对象指针默认浅拷贝会引发双重释放的问题。单纯栈数组、普通int/string等无需自己写深拷贝。8.什么是多态实现多态的两个必要条件是什么面向对象运行时多态虚函数多态定义父类指针/引用指向子类对象调用同名函数时实际执行子类重写的版本同一个接口不同实现。实现运行时多态两大必要条件1.基类中函数声明为virtual虚函数2.通过基类指针或引用操作派生类对象且子类重写该虚函数函数名、返回值、参数完全一致。静态多态编译器函数重载、模板动态多态运行期虚函数继承9.纯虚函数是什么包含纯虚函数的类叫什么这种类能不能实例化纯虚函数语法virtual void func() 0;纯虚函数也可以写函数体该类依旧是抽象类不能创建对象子类只有把所有纯虚函数全部重写实现才能称为普通可实例化类。抽象类作用仅用来定义统一接口专门作为父类被继承用来规范子类行为。10.说一说虚析构函数的作用什么场景下基类析构要加virtual虚析构的核心作用通过基类指针销毁子类对象时先调用子类析构再自动调用基类析构完整释放父子两层资源。使用场景只要类会被继承且外部会用基类指针/引用管理堆上子类对象基类析构就必须加virtual单继承、多层继承都需要。11.菱形继承会带来什么两个典型问题如何解决菱形继承两大核心问题1.成员二义性顶层公共基类存在多份副本访问同名成员编译器分不清来自哪一份父类副本直接报错2.数据冗余派生类中存在多份相同公共基类成员变量占用多余内存。解决方案在中间派生类继承时加virtual虚继承让所有派生类共享唯一一份顶层公共基类消除二义性与冗余。12.const关键字分别修饰普通变量、函数形参、类成员函数、类成员变量时各代表什么含义const修饰普通变量变量值只读初始化后不可修改const常量编译期确定值区别于#define宏。const修饰函数形参内置类型防止函数内修改副本意义不大引用/指针禁止修改外部实参推荐const T避免拷贝又只读。const修饰类成员函数函数内不能修改任何类的成员变量也不能调用非const成员函数this指针底层是const 类名* this只能访问只读成员。const修饰类成员变量成员变量必须在构造函数初始化列表初始化构造函数体内赋值无效对象生命周期内该成员值永久不可修改。13.什么是this指针this指针有哪些特性定义this是成员函数隐含的形参指针指向当前调用该函数的对象实例。成员函数访问成员本质都是this-成员。this指针核心特性1.仅存在于非静态成员函数静态成员函数没有this静态函数不属于某个对象2.类型普通成员函数类名* const thisconst成员函数const 类名* const this3.不能手动修改this本身指向指针本身constconst成员函数里不能通过this修改对象成员4.this是函数参数存放在栈/寄存器不属于对象内存不计入类sizeof大小5.可显式使用this-name、return *this实现链式调用。14.静态成员变量与静态成员函数有什么特点两者使用上有什么限制静态成员变量1.存储在全局静态区不属于任何对象所有对象共享同一份程序启动初始化、程序结束销毁生命周期和程序一致不是和类一致。2.必须类外初始化const static 整型可类内初始化3.任意对象修改全部对象可见。静态成员函数1.无this指针不依赖对象可类名::函数名()直接调用也能通过对象调用2.限制只能访问静态成员变量/静态成员函数不能访问普通成员变量、普通成员函数缺少this指针无法定位对象成员不能声明为virtual、const、volatile普通成员每个对象独立一份依赖this静态成员全局唯一无this。15.#define宏定义和const常量的区别1.处理阶段不同#define是预处理阶段文本简单字符串替换无语法校验const是编译阶段处理属于真正变量/常量编译器做类型检查。2.类型安全宏无类型随便替换容易产生运算优先级bugconst有明确数据类型编译器会做类型匹配校验出错直接报错。3.作用域宏全局生效undef才能取消const遵循块作用域、类作用域作用范围可控。4.内存占用宏不分配内存只是文本替换const常量会分配内存。5.可否调试宏无法端点调试无符号信息const常量在调试器中可见支持调试。6.类内静态const整型可类内赋值宏做不到。16.new和malloc区别1.所属语言malloc/free是C库函数new/delete是C运算符。2.内存分配源头malloc从堆分配原始内存仅分配裸内存不会调用构造函数new分配内存后自动调用构造函数初始化对象delete会先调用析构函数再释放内存。3.类型安全malloc返回void*必须强制转型才能使用无类型检查new直接返回对应类型指针自带类型校验。4.失败处理malloc分配失败返回NULL空指针new分配失败默认抛出bad_alloc异常。5.数组支持分配数组要用malloc(n*size)手动计算字节new[]、delete[]专门管理数组对象自动处理元素个数与析构。6.重载支持new/delete可以全局、类内重载自定义内存分配逻辑malloc/free无法重载。17.说说C中explicit关键字的作用举一个反面例子作用修饰单参数构造函数禁止编译器发生隐式类型转换只允许显示调用构造。使用注意事项只能修饰仅有一个参数的构造函数不加explicit的问题class Person { public: Person(int age) { cout构造执行endl; } }; void func(Person p){} int mian() { func(20); return 0; }这里传 int 却能匹配 Person属于意料之外的隐式转换容易产生隐蔽 bug。加上explicit后class Person { public: explicit Person(int age) {} }; void func(Person p) {} int main() { func(20); // 直接编译报错禁止隐式转换 func(Person(20)); // 必须显式构造合法 }18.什么是RAII简单说明原理和用处资源获取即初始化核心原理把资源堆内存、文件句柄、锁、网络连接等的获取放在类的构造函数资源释放放在类的析构函数。对象生命周期绑定资源对象创建-拿资源对象销毁出作用域-自动释放资源。用处不用手动释放资源避免忘记delete、忘记关闭文件、忘记解锁导致泄漏/死锁发生异常时栈上对象会自动析构资源依然能正常释放典型例子std::string\std::vector\std::unique_lock\智能指针unique_ptr/shared_ptr。19.区分unique_ptr、shared_ptr、weak_ptr各自解决什么问题有什么优缺点unique_ptr独占智能指针所有权唯一不允许拷贝只能移动move移动后原指针置空资源所有权转移。优点无引用计数开销性能接近裸指针内存占用小。缺点无法多指针共享同一资源。适用资源只需要单一持有者shared_ptr共享智能指针内置引用计数器每拷贝一次计数1析构/赋值覆盖计数-1计数归0才释放堆资源。优点多个指针共享同一块内存。缺点1.计数带来原子操作性能损耗2.循环引用会导致引用计数永远不为0内存泄漏。weak_ptr弱智能指针不增加shared_ptr的引用计数配套shared_ptr使用不能直接解引用需调用lock()提升为临时shared_ptr。作用专门打破shared_ptr循环引用。特点可以通过expired()判断指向资源是否已释放防止悬空访问。20.C11auto关键字作用auto推导有哪些限制场景auto作用1.定义局部变量时自动推导类型2.配合范围for遍历容器3.函数返回值后置推导c144.简化复杂长类型迭代器、lambda、模板类型auto不能推导/受限场景1.不能用于函数形参c20可以2.声明时必须立刻初始化无初始值无法推导3.无法直接推导数组类型auto会推导成指针4.auto无法推导类的非静态成员变量5.auto无法用于模板参数声明6.区分auto会舍弃顶层const/引用auto、const auto才能保留常量属性21.说一说lambda表达式完整语法结构捕获列表[]常见捕获方式分别是什么含义完整lambda语法[捕获列表](参数列表) mutable - 返回类型 { 函数体 };常见捕获方式1.[]不捕获任何外部变量不能访问外部局部变量2.[]值捕获全部局部变量lambda内拿到拷贝副本默认只读加mutable才可修改副本不影响外部原值mutable关键字只有值捕获才能加用来修改捕获的副本变量3.[]引用捕获全部局部变量直接操作原变量修改会同步到外部注意悬空风险4.[var]仅值捕获变量var其余外部变量不可用5.[var]仅引用捕获变量var6.[, x]默认全部值捕获唯独x采用引用捕获7.[this]捕获当前类对象可访问成员变量/成员函数22.说一说C异常处理机制try-catch-throw以及noexcept关键字作用。完整流程try { // 危险代码 throw 100; // 抛出异常对象 } catch (int e) { // 捕获并处理 }若当前没有匹配catch异常会逐层向上抛出直到main函数全程未捕获程序直接终止。noexcept修饰函数声明该函数不会抛出任何异常。void func() noexcept; // 保证不抛异常 void func() noexcept(false); // 允许抛异常默认状态如果标了noexcept的函数内部却抛出异常程序会直接调用std::terminate强制崩溃不会再走上层catch。noexcept只是承诺编译器不会强制检查函数内部是否真的抛异常运行时才校验。23.简述C11右值引用带来的移动构造、移动赋值函数什么是否会自动生成什么情况编译器不会合成编译器会隐式生成默认移动构造函数和默认移动赋值运算符必须同时满足全部条件1.类没有用户自定义的拷贝构造、拷贝赋值、析构函数2.类没有用户自定义移动构造、移动赋值3.类所有非静态成员、父类都支持移动可被移动构造/移动赋值只要出现任意一种编译器不会自动合成移动构造/移动赋值1.自己写了拷贝构造函数2.自己写了拷贝赋值3.自己写了析构函数4.手动声明了移动构造或移动赋值5.类中存在不可移动的成员如const成员、引用成员、没有移动构造的类成员24.move是做什么的它会真正移动内存资源吗move只是类型转换工具作用是把入参无条件转换成右值引用T仅此而已它本身不分配、不释放、不转移任何内存资源。注意事项1.move不会移动内存只是改变值类别2.移动资源的逻辑写在类的移动构造/移动赋值里3.对一个变量move后原对象进入有效但未定义状态不要再随意使用。25.完美转发forward作用为什么不能只用move代替转发forward核心作用完美转发配合模板万能引用T使用保留传入参数原本的值类别如果传入的是左值forwardT(arg)转换为左值引用如果传入的是右值forwardT(arg)转换为右值引用。为什么move不能替代move会强制把参数转为右值引用不分原本是左值还是右值1.若传入左值用move转发会强行变成右值触发移动语义原对象资源被掏空2.原本只想传递左值、保留原对象结果被意外移动产生严重bug3.forward会根据模板参数T自动判断只在原本是右值才转右值左值维持不变。26.简述C四种强制类型转换static_cast/dynamic_cast/const_cast/reinterpret_cast各自适用场景与限制static_cast静态转换最常用、安全常规转换1.基础类型互转int-double、char-int2.有继承关系父子类指针/引用向上、向下转换不做运行时检查3.void*和普通指针互转4.枚举与整数互转限制不能去掉const不能无关类型指针互转int*-string*不行向下转型无安全校验可能悬空。dynamic_cast动态转换带运行时类型检查仅用于多态前提基类必须有虚函数存在虚表适用场景父子类指针/引用安全向下转型运行时判断实际对象类型。规则转指针失败返回nullptr转引用失败抛bad_cast异常限制只能用于有虚函数的多态继承体系不能转基础类型、无虚函数类。const_cast唯一能增减const/volatile属性的转换唯一作用去掉指针/引用的const、volatile修饰适用场景常量指针临时去掉只读限制修改数据限制不能用来转不同类型只能改常量属性reinterpret_cast底层二进制重解释极不安全适用场景1.完全无关类型指针互转int*-long*、自定义类指针-普通指针2.指针与整数互相转换存储地址值原理只改变编译器对内存字节的解读方式不做任何转换运算。风险无任何安全检查极易越界、内存错乱工程尽量避免