c++面试题整理(一)
全网最全c++面试题
1、const关键字的作用?(变量,参数,返回值)
- 定义常量值:const 可以用于定义常量变量,其值在初始化后不能被修改。
const int MAX_SIZE = 100;
- 修饰指针:const 可以修饰指针,表示指针指向的内容是常量,或者指针本身是常量。
- 指向常量的指针:指针指向的内容不能被修改。
const int* p = &some_int;
*p = 5; // 错误,不能修改指向的内容`
- 常量指针:指针本身不能被修改,即不能指向其他地址。
int value = 10;
int* const p = &value;
p = &another_value; // 错误,不能修改指针本身`
- 指向常量的常量指针:指针本身和指向的内容都不能被修改。
const int* const p = &some_int;
*p = 5; // 错误
p = &another_int; // 错误`
- 修饰函数参数:当const 用于修饰函数参数时,它告诉调用者该参数在函数内部不会被修改。
void foo(const int& x) {
// x 不能被修改
}
- 修饰函数返回值:当const 用于修饰函数返回值时,它告诉调用者返回的对象不应被修改(如果返回的是引用或指针)。
const int& getValue() {
static int value = 42;
return value;
}
int main() {
const int& ref = getValue();
ref = 5; // 错误,不能修改返回值
}
- 修饰成员函数:当const 用于修饰成员函数时,它表示该函数不会修改调用它的对象的状态(即不会修改对象的任何非静态成员变量)。
class MyClass {
public:
int value;
int getValue() const {
return value;
}
void setValue(int v) {
value = v;
}
};
int main() {
MyClass obj;
const MyClass& constObj = obj;
int val = constObj.getValue(); // 正确
constObj.setValue(5); // 错误,不能调用非const成员函数
}
通过使用const,你可以提高代码的可读性和安全性,确保某些值或对象状态在程序的某些部分中不被意外修改。
2、什么是死锁?造成死锁的必要条件?如何避免死锁?
在C++(以及许多其他并发编程环境中)中,死锁是一个严重的问题,它发生在两个或更多的进程或线程无限期地等待一个资源,而该资源又被另一个等待其他资源的进程或线程所持有。这导致了一个循环等待条件,使得所有涉及的进程或线程都无法继续执行。
以下是死锁发生的四个必要条件(也称为死锁的四个C’s):
- 互斥条件(Mutual Exclusion):至少有一个资源必须处于非共享模式,即一次只有一个进程可以使用。如果其他进程请求该资源,则请求者只能等待,直到当前占有者释放该资源。
- 持有和等待条件(Hold and Wait):至少有一个进程必须持有至少一个资源,并等待获取一个当前被其他进程持有的资源。
- 非抢占条件(No Preemption):资源只能被持有它的进程自愿地释放。系统不能强制进程释放资源。
- 循环等待条件(Circular Wait):必须存在一个进程-资源的循环链,其中每个进程都在等待下一个进程所持有的资源。
- 要解决死锁问题,可以设计算法来预防或避免死锁,或者可以在死锁发生时进行检测并恢复。一些预防策略包括:
- 资源顺序分配法:给每个资源编号,并要求所有进程按照编号顺序请求资源。
- 一次性分配:在进程开始前,为其所需的所有资源一次性分配完毕。
- 线程死锁等待超时则自动放弃请求并且释放自己占有的资源
检测策略则是定期检查系统状态,以判断是否存在死锁。如果存在,则采取措施来打破死锁,如撤销某些进程并释放其资源。
在C++中,使用多线程编程时,开发者需要特别注意避免死锁,尤其是在使用互斥锁(如std::mutex)、条件变量(如std::condition_variable)或其他同步原语时。正确的同步和互斥策略是避免死锁的关键。
3、static关键字作用?
- static修饰局部变量,局部变量的生命周期变长,函数执行结束不会立即释放内存
- static修饰全局变量,则该变量作用域变小,只能在当前文件使用,其它文件禁止用
- static修饰函数,函数作用域变小,只能在当前文件使用
- 修饰类的成员,成为静态成员,静态成员为所有类对象共享,属于类,不属于具体的某个对象,可以使用类名+作用域限定符访问
- 修饰类的成员变量:在类外初始化,没有初始化的话编译器会自动将其初始化为0。
- 修饰类的成员函数
- 注意:普通成员函数能调用静态成员函数,静态成员函数不能调用普通成员函数
4、c/c++中内存可以划分为几个部分?
在C/C++中,内存可以大致划分为以下几个部分:
- 栈区(Stack):栈区由编译器自动分配和释放,主要存放函数的参数值、局部变量的值等。函数的调用过程也是通过栈来完成的。其操作方式类似于数据结构中的栈,由高地址向低地址增长。
- 堆区(Heap):堆区一般由程序员手动申请以及释放,若程序员不释放,程序结束时可能由操作系统(OS)回收。堆区的内存分配类似于链表,由低地址向高地址增长。
- 全局/静态存储区:全局变量和静态变量的存储被放在同一块内存中。初始化的全局变量和静态变量存储在一个区域,而未初始化的全局变量和未初始化的静态变量存储在另一个相邻的区域。程序结束后,这些变量所占用的空间由系统释放。
- 文字常量区:常量字符串就是存储在这个区域中。程序结束后,这部分内存也由系统释放。
- 程序代码区:存放函数体的二进制代码。
此外,在某些描述中,还提到了自由存储区,这是由malloc等函数分配的内存块,与堆十分相似,但释放方式是通过free函数。
另外,从虚拟内存的角度来看,程序的内存分布可以进一步细分为预留内存地址(不可访问)、程序代码区(只读,存放代码和其他内容)、data段(存放初始化的全局变量和静态变量以及文字常量区)、bss段(存放未初始化的全局变量和静态变量)、堆、共享库文件(调用的库文件,位于堆和栈之间)、栈,以及操作系统和内核调用的内存地址。
需要注意的是,不同的操作系统和编译器可能对内存的管理和划分有所不同,但上述划分方式是比较常见和基础的。同时,程序员在编写代码时应当注意内存的管理,避免内存泄漏和野指针等问题。