c++八股(可能问的多一点)
1.简单说说C++11语法特性
答:
1.auto以及decltype自动类型推导,避免手动声明复杂类型,减少冗长代码提升了可读性和安全性。
2.智能指针 自动释放内存
(具体说说)
有shared和unique 差异主要体现在所有权、内存开销、引用计数以及线程安全上。
(本质和实现原理)
智能指针本质是一个封装了一个原始C++指针的类模板,为了确保动态内存的安全性而产生的。实现原理是通过一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。
(说说shared和unique的区别)
3.nullptr来代替NULL来表示空指针,因为NULL实际上是整数0,而不是一个真正的指针类型,容易产生误导。
4.C++11中的移动语义和右值引用(T&&
)用于避免不必要的深拷贝,提高性能。它们允许对象的资源在右值或即将销毁的对象之间转移,而不是复制。例如,std::move
强制将对象转换为右值引用,从而实现高效的资源转移,减少内存分配和拷贝操作。移动构造函数和移动赋值运算符是实现这一功能的关键。
(为什么要引入右值引用?)右值引用是为一个临时变量取别名,它只能绑定到一个临时变量或表达式(将亡值)上。实际开发中我们可能需要对右值进行修改(实现移动语义时就需要)而右值引用可以对右值进行修改。
(左值引用和指针的区别?)
是否初始化:指针可以不用初始化,引用必须初始化 这里要区分初始化和声明的差异
本质不同:指针是一个变量,拥有自己的内存空间,引用是对被引用的对象取一个别名,引用和被引用对象占同一个空间。
5.lambda表达式
6.std::thread 多线程库的支持
7.范围for循环
2.说说面向对象的三大特性及其意义
封装 隐藏实现细节 实现模块化
继承 不修改类的情况下实现代码重用和扩展
多态 一个接口多种形态 为了代码扩展和重用性 分为静态多态和动态多态 分别用函数重载和虚函数重写实现
虚函数是怎么实现的?它存放在哪里在内存的哪个区?什么时候生成的
虚函数通过虚函数表和虚函数指针来实现的。虚函数表本质上就是一个由指针构成的数组,每个元素中的指针,即虚函数指针,会指向一个虚函数重写的实现代码。
虚函数表存放在静态存储区,在编译期就会生成。虚函数指针存放在实现该虚函数的对象的内存布局中,对象的内存是堆或栈,它就在哪里。
Static关键词的作用?
static 关键字有三个主要作用:
局部变量:在函数内部,static 局部变量只初始化一次,且在函数调用结束后仍然保留其值。
全局变量/函数:在文件内部,static 限制全局变量或函数的作用域,使其仅对当前文件可见。
类成员:在类中,static 成员变量或函数属于类本身而非某个对象,可以不通过对象访问(
int MyClass::count = 0;)。
贴个代码理解第三点:
#include <iostream>
class MyClass {
public:
// 静态成员变量,属于类本身
static int count;
// 构造函数,每创建一个对象,count 就增加1
MyClass() {
count++;
}
// 静态成员函数,用于访问或修改静态成员变量
static void showCount() {
std::cout << "Count: " << count << std::endl;
}
};
// 初始化静态成员变量
int MyClass::count = 0;
int main() {
// 在没有创建对象的情况下,直接通过类名访问静态成员函数
MyClass::showCount(); // 输出: Count: 0
MyClass obj1;
MyClass obj2;
// 可以通过类名或者对象访问静态成员
obj1.showCount(); // 输出: Count: 2
MyClass::showCount(); // 输出: Count: 2
return 0;
}
typedef和define有什么区别?
typedef 用于定义类型别名,遵循编译器检查.#define 是预处理宏替换,直接文本替换,无类型检查。
简单说说final、extern、const、voliate的作用
final 防止继承 保证类不再扩展
extern 跨文件全局变量
const 定义常量
voliate 防止意外修改值 避免编译器优化
说说指针常量和常量指针
指针常量是指针本身是一个常量,指针指向的地址不可变 指向的对象值可以修改
常量指针是一个指向常量的指针,指向可以指向其他对象,指向的对象的值不可以改变
友元是什么?
友元(friend)是一个能够访问类的私有和保护成员的函数或类。
如何解决菱形继承的问题?
菱形继承发生在两个派生类从同一个基类继承,且最终有一个派生类同时继承两个派生类,导致重复继承
用虚基类,这样派生的是最后覆盖的虚函数。
如何将数据定义为寄存器变量?
关键字register register int a
有没有遇到内存泄漏的情况 如何处理的?
为什么不全用mmap分配内存?
mmap将文件映射到内存空间,作为一个系统调用的分配内存的手段,必然伴随着大量用户态和内核态的切换,这对小内存块的分配和释放非常不利,因此mmap知识和大内存块的分配
为什么不全用brk分配内存?
brk所操作的是堆的内存空间。堆内存分配往往会导致内存碎片,尤其是当内存频繁分配和释放时,连续的堆空间难以保持。这会影响性能。
define和const的区别是什么?
编译阶段:define是在编译预处理阶段进行简单的文本替换,const是在编译阶段确定其值
安全性:define定义的宏常量没有数据类型,只是进行简单的替换,不会进行类型安全检查;const定义的常量是有类型的,是要进行类型判断的
内存占用:define定义的宏常量,在程序中使用多少次就会进行多少次替换,内存中有多个备份,占用的是代码段的内存;const定义常量占用静态存储区域的空间,程序运行过程中只有一份
调试:define定义的宏常量不能调试,因为在预编译阶段就已经进行替换了;const定义的常量是可以进行调试的。
程序运行的步骤
预处理 编译 汇编 链接
17.class与struct的区别
默认继承权限不同:class默认继承的是private继承,struct默认是public继承。
C++保留struct关键字,原因是保证与C语言的向下兼容性,为了保证百分百的与C语言中的struct向下兼容,C++把最基本的对象单元规定为class而不是struct,就是为了避免各种兼容性的限制。
锁的底层机制是什么?
锁的底层原理是通过硬件或操作系统提供的原子操作,如CAS,即Compare And Swap,就是每一个线程从主内存复制一个变量副本后,进行操作,然后对其进行修改,修改完后,再刷新回主内存前。再取一次主内存的值,进行比较,是否一样,如果不一样,说明有其他线程修改,本次修改放弃,重试。