C++ 基础 常考面试题总结
1. C和C++的区别?
- C是面向过程的语言,核心是函数和过程;C++是多范式语言,支持面向过程、面向对象、泛型编程。
- C++在C基础上扩展了类、继承、多态、模板、STL等特性,C没有这些面向对象和泛型的能力。
- C++支持函数重载、运算符重载、异常处理,C不支持。
- C++的类型检查更严格,C相对更灵活但也更容易出错。
- C++引入了引用、命名空间、智能指针等现代特性,提升了代码安全性和可维护性。
2. const与#define的区别?
- 类型安全:const有明确类型,编译器会做类型检查;#define是简单文本替换,无类型,容易出错。
- 作用域:const遵循C++作用域规则,可限制在局部/类内;#define是全局宏,作用域从定义点到文件结束(或#undef)。
- 调试友好:const变量会进入符号表,调试时可见;#define宏在预处理阶段展开,调试时看不到原始宏名。
- 内存占用:const会分配内存(除非被优化掉);#define不分配内存,只是文本替换。
3. 悬空指针与野指针的区别?
- 悬空指针:指向的内存已经被释放(如delete后未置空),但指针本身非空,访问会导致未定义行为。
- 野指针:未初始化的指针,值是随机垃圾值,指向不可预料的内存地址,访问极其危险。
- 共同点:两者都指向无效内存,访问都会导致程序崩溃或数据破坏。
4. struct与class的区别?
- 默认访问权限:struct默认是public,class默认是private。
- 默认继承权限:struct默认是public继承,class默认是private继承。
- 语义习惯:struct多用于数据聚合(POD类型),class多用于封装和行为定义。
- 语法上,两者几乎等价,可互换使用,只是默认权限不同。
5. sizeof和strlen的区别?
- sizeof是运算符,编译时计算,返回类型/对象占用的内存字节数,包括字符串末尾的'\0'。
- strlen是库函数,运行时计算,返回字符串的实际长度(不包括'\0'),遇到'\0'停止。
- 对数组:sizeof(数组名)返回整个数组大小;strlen(数组名)只计算到第一个'\0'。
- 对指针:sizeof(指针)返回指针本身大小(4/8字节);strlen(指针)计算指向的字符串长度。
6. 32位、64位系统中,各种常用内置数据类型占用的字节数?
7. 深拷贝与浅拷贝的区别?
- 浅拷贝:仅拷贝指针本身,不拷贝指向的内存,多个对象共享同一块内存,释放时会导致double free。
- 深拷贝:拷贝指针指向的内存内容,每个对象拥有独立的内存,互不影响,避免内存泄漏和重复释放。
- 默认拷贝构造/赋值运算符是浅拷贝;当类中有堆内存成员时,必须手动实现深拷贝。
8. 派生类中构造函数、析构函数调用顺序?
- 构造顺序:先调用基类构造函数 → 再调用成员对象构造函数 → 最后调用派生类构造函数。
- 析构顺序:先调用派生类析构函数 → 再调用成员对象析构函数 → 最后调用基类析构函数。
- 多继承时,基类构造顺序按声明顺序,析构顺序相反。
9. C++类中数据成员初始化顺序?
- 初始化顺序与成员在类中的声明顺序一致,与初始化列表中的顺序无关。
- 先初始化基类成员,再初始化本类成员,按声明顺序依次初始化。
- 若初始化列表中顺序与声明顺序不同,仍按声明顺序初始化,可能导致未定义行为。
10. 结构体内存对齐问题?结构体/类大小的计算?
- 内存对齐是为了提升CPU访问效率,编译器会自动填充字节,使成员地址对齐到指定边界(如4/8字节)。
- 对齐规则:每个成员的起始地址是自身大小的整数倍;结构体总大小是最大成员大小的整数倍;嵌套结构体按其最大成员大小对齐。
- 例:struct { char a; int b; },在32位系统中大小为8(char占1,填充3字节,int占4,共8)。
11. static_cast、dynamic_cast、const_cast、reinterpret_cast的区别?
- static_cast:用于基本类型转换、父子类指针/引用转换(无运行时检查),编译期完成。
- dynamic_cast:用于多态类型的父子类指针/引用转换,运行时检查类型,失败返回空指针/抛出异常。
- const_cast:用于移除或添加const/volatile修饰,不能改变类型本身。
- reinterpret_cast:用于任意指针/整数之间的强制转换,直接重解释内存,极其危险,仅用于底层操作。
12. 智能指针是什么?
- 智能指针是封装了原始指针的类模板,通过RAII机制自动管理堆内存,避免内存泄漏。
- 核心类型:unique_ptr:独占所有权,不可拷贝,可移动,离开作用域自动释放。shared_ptr:共享所有权,通过引用计数管理,计数为0时释放,线程安全。weak_ptr:辅助shared_ptr,不增加引用计数,解决循环引用问题。
13. 计算类大小例子
- 空类:大小为1字节(占位,保证不同对象地址不同)。
- 仅含非虚成员函数:大小为0(函数不占对象内存)。
- 含虚函数:大小为指针大小(4/8字节,指向虚函数表)。
- 例:
14. 大端与小端的概念?各自的优势是什么?
- 大端:高位字节存放在低地址,低位字节存放在高地址(符合人类阅读习惯)。
- 小端:低位字节存放在低地址,高位字节存放在高地址(CPU处理更高效)。
- 优势:大端:网络传输常用,数据表示直观,便于调试。小端:CPU在处理多字节数据时,可直接从低地址开始读取,无需字节序转换。
15. C++中*和&同时使用是什么意思?
- int* &p:表示指向int的指针的引用,即指针的别名,可修改指针本身的值。
- int &*p:非法语法,不存在指向引用的指针(引用本身不是对象,无地址)。
- 常见场景:函数参数中传递指针的引用,用于修改指针本身(如动态分配内存)。
16. C++ vector与list区别?
17. 引用和指针之间的区别?
- 定义方式:引用必须在定义时初始化,且一旦绑定到某个对象后就不能再改变;指针可以在定义后重新指向其他对象。
- 空值情况:引用不能为“空”,必须绑定到一个有效的对象;指针可以为nullptr,表示不指向任何对象。
- 内存占用:引用本身不占用额外内存(本质是别名);指针占用内存(32位4字节,64位8字节),存储指向对象的地址。
- 使用方式:引用直接使用对象名访问,无需解引用;指针需要通过*解引用访问对象。
- sizeof:sizeof(引用)返回被引用对象的大小;sizeof(指针)返回指针本身的大小。
18. 栈和堆中的内存分配有何区别?
19. 存在哪些类型的智能指针?
- unique_ptr:独占所有权,不可拷贝,可移动,离开作用域自动释放内存。
- shared_ptr:共享所有权,通过引用计数管理,线程安全,计数为0时释放。
- weak_ptr:辅助shared_ptr,不增加引用计数,可解决循环引用问题,需通过lock()转为shared_ptr使用。
- auto_ptr:C++98引入,C++11后被弃用,独占所有权但拷贝语义不安全。
20. unique_ptr是如何实现的?我们如何强制在unique_ptr中仅存在一个对象所有者?
- 实现核心:封装原始指针,在析构函数中调用delete释放内存;禁用拷贝构造和拷贝赋值运算符,只保留移动构造和移动赋值。
- 强制唯一所有者:显式删除拷贝构造函数和拷贝赋值运算符:unique_ptr(const unique_ptr&) = delete;和unique_ptr& operator=(const unique_ptr&) = delete;。提供移动语义:通过std::move转移所有权,确保同一时间只有一个unique_ptr指向对象。禁止拷贝,只允许移动,从语法层面保证所有权唯一。
21. 插入排序和选择排序
- 插入排序:核心:将未排序元素逐个插入到已排序序列的合适位置。时间复杂度:最好O(n)(已排序),最坏O(n²),平均O(n²)。空间复杂度:O(1),稳定排序。
- 选择排序:核心:每次从未排序部分选择最小/最大元素,放到已排序部分末尾。时间复杂度:最好/最坏/平均均为O(n²)。空间复杂度:O(1),不稳定排序(交换可能破坏相等元素的相对顺序)。
22. 关于静态内存分配和动态内存分配的区别及过程
- 静态内存分配:时机:编译期/链接期确定,程序启动时分配。位置:全局/静态变量存放在数据段/只读数据段。生命周期:整个程序运行期间有效,程序结束时释放。特点:大小固定,分配/释放由编译器自动管理。
- 动态内存分配:时机:运行时根据需求分配。位置:堆区。生命周期:从new/malloc到delete/free,或进程退出。特点:大小可变,需手动管理,易产生内存泄漏/碎片。
23. C++从代码到可执行二进制文件的过程
- 预处理:处理#include、#define等宏,生成.i文件。
- 编译:将预处理后的代码翻译成汇编代码,生成.s文件。
- 汇编:将汇编代码翻译成机器码,生成.o目标文件。
- 链接:将
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++ 常考面试题总结 文章被收录于专栏
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
查看16道真题和解析