C++面试题总结
在 C++ 中,模板的定义通常需要放在头文件中,而非源文件中。因为模板是在编译期根据使用的类型进行实例化的,若放在源文件中,其他源文件无法看到模板的完整定义,会导致链接错误。为例,在 64 位机中,类的大小计算需考虑成员变量的大小、内存对齐以及是否有虚函数(虚函数会带来虚函数表指针,占 8 字节)。2.当线程被notifiy_one或者notify_all唤醒,会立即重新获取锁,且wait的解锁、
1. 不能重载的运算符
在 C++ 里,并非所有运算符都能被重载,不能重载的运算符及其原因如下:
.(成员访问运算符):用于访问对象的成员,若重载可能导致对象成员访问方式混乱,破坏封装性,编译器也无法确保访问的正确性。.*(成员指针访问运算符):用于通过成员指针访问对象成员,逻辑复杂且与对象内存布局紧密相关,重载可能引发内存访问错误。::(作用域运算符):用于区分不同作用域的标识符(如类、命名空间),其功能是编译器的作用域解析,不涉及运行时操作,重载无实际意义。? :(条件运算符):是三目运算符,语法结构特殊(需要三个操作数),且运算逻辑与流程控制相关,重载会导致语法解析困难,破坏代码可读性。sizeof(长度运算符):用于计算类型或变量的大小,其结果在编译期确定,与具体对象无关,重载会改变基本类型长度的计算规则,导致系统级错误。typeid(类型识别运算符):用于获取变量或表达式的类型信息,是 C++ 运行时类型识别(RTTI)的核心机制,重载会破坏类型系统的一致性。
2. 函数重载判断依据
在 C++ 中,能作为函数重载判断依据的有:
- 参数类型:例如
void func(int x)和void func(double x),因参数类型不同可构成重载。 const:例如void func(int* ptr)和void func(const int* ptr),因const修饰符不同可构成重载。- 参数个数:例如
void func(int x)和void func(int x, int y),因参数个数不同可构成重载。
而返回类型不能作为函数重载的判断依据,因为仅返回类型不同的函数,编译器无法在调用时区分。
3. list 删除元素与迭代器失效
在 C++ 中,list删除任意一个元素不会导致其他迭代器失效。因为list是双向链表结构,被删除元素两边的元素的迭代器指针会自动更新,其他迭代器不受影响。
4. C++ 中的类型转换
static_cast(基础转换):用于基础类型之间的转换,如double d = static_cast<double>(42);(整数转双精度浮点数)。dynamic_cast(多态下行转换):用于多态场景下的下行转换,需类有虚函数,例如:class Base { virtual void f() {} }; class Derived : public Base {}; Base* b = new Derived; Derived* d = dynamic_cast<Derived*>(b);const_cast(去除const):用于去除指针或引用的const属性,例如const int* cptr = new int(10); int* ptr = const_cast<int*>(cptr);。reinterpret_cast(底层二进制转换):用于底层二进制位的重新解释转换,例如int* p = new int(5); long addr = reinterpret_cast<long>(p);(指针转数值表示存储地址)。
5. 引用的本质
引用的本质是对指针的封装,底层是通过const的指针实现的,因此本身引用具有const语义,不能重新绑定。引用本质如int &ref = a;等价于int * const ptr = &a;。
6. 非静态成员函数与this指针
在 C++ 中每个非静态成员函数都隐含一个指向当前对象的this指针,其类型由成员函数是否为const决定。非const成员函数的this指针类型为类类型* const,可以修改类的成员变量;而const成员函数的this指针类型为const 类类型* const,不能修改非mutable修饰的类内变量,只能修改用mutable修饰的变量(const必须放在函数的后面,且只有类内的函数可以有这个const)。例如:
class MyClass {
private:
int a; // 非mutable成员(受const函数限制)
mutable int b; // mutable成员(不受const函数限制)
public:
MyClass(int a_, int b_) : a(a_), b(b_) {}
// 非const成员函数,this指针类型为 MyClass* const
void modifyNonConst() {
a = 100; // 合法,通过非const this修改非mutable成员
b = 200; // 合法,修改mutable成员
}
// const成员函数,this指针类型为 const MyClass* const
void modifyConst() const {
// a = 300; // 非法,const this不能修改非mutable成员
b = 400; // 合法,mutable成员不受const限制
}
void print() const {
std::cout << "a = " << a << ", b = " << b << std::endl;
}
};
7. 万能引用与完美转发
万能引用是指用&&从模板参数推导T&&创造的右值引用或左值引用的类型。当使用完美转发时,T&&会折叠为左值引用(当传入左值时)或右值引用(当传入右值时)。例如:
#include <iostream>
using namespace std;
// 模板函数中的万能引用(能推导的类型)
template <typename T>
void func(T&& x) { // x 是万能引用
cout << "确定成功" << endl;
}
int main() {
int a = 30;
func(a); // 万能引用绑定左值a,正确
func(30); // 万能引用绑定右值30(临时对象),正确
func(a + 7); // 万能引用绑定右值(表达式结果是临时对象),正确
return 0;
}
8. 类的大小计算
以class A为例,在 64 位机中,类的大小计算需考虑成员变量的大小、内存对齐以及是否有虚函数(虚函数会带来虚函数表指针,占 8 字节)。
- 不含
virtual的类:- 类中成员变量按声明顺序,遵循内存对齐规则排列,填充字节用于满足对齐要求。静态成员变量和非虚成员函数不占类的实例内存。
- 以示例类为例,
int64_t A占 8 字节(地址 0 - 7),short B占 2 字节(地址 8 - 9),char C占 1 字节(地址 10),之后为满足void* p(占 8 字节,需 8 字节对齐),填充 5 字节(地址 11 - 15),void* p占 8 字节(地址 16 - 23),最终类大小需根据各成员及填充情况,按最大对齐数(此处为 8)调整为整数倍。
- 含
virtual的类:- 会额外增加一个
vptr(虚函数表指针,8 字节),通常位于类内存最开始位置。 - 同样按成员声明顺序和内存对齐规则计算,
vptr占 8 字节(地址 0 - 7),之后int64_t A占 8 字节(地址 8 - 15),short B占 2 字节(地址 16 - 17),char C占 1 字节(地址 18),为满足void* p(8 字节对齐),填充 5 字节(地址 19 - 23),void* p占 8 字节(地址 24 - 31),总大小按最大对齐数(8)调整,最终为 32 字节。
- 会额外增加一个
9. 多线程相关说法判断
下面的说法是否正确以及为什么?
1.使用condition_variable必须搭配互斥锁来保持临界区,通常配合lock_guard
2.当线程被notifiy_one或者notify_all唤醒,会立即重新获取锁,且wait的解锁、阻塞、唤醒、加锁是原子性的
3.调用wait(lock,predicate)时,必须先持有锁。该函数会检查predicate的条件,日光返回true则继续执行,否则就释放锁并阻塞线程直到被唤醒。
4.wait函数底层实现是一个if
-
错误。
condition_variable需搭配unique_lock<mutex>,而非lock_guard。因wait需临时释放锁再重新获取,lock_guard不支持手动解锁 / 加锁,无法满足需求。 -
正确。
wait的 “解锁→阻塞” 是原子操作,避免通知丢失;被唤醒后会立即重新获取锁,保证线程安全。 -
正确。调用
wait(lock,predicate)需先持有锁,若条件为true则继续;否则释放锁并阻塞,直到被唤醒后重新检查。 -
错误。
wait底层用while循环而非if,以处理虚假唤醒,确保条件满足才继续执行。例如: -
while (!predicate()) { cv.wait(lock); }
16. 模板的定义位置
在 C++ 中,模板的定义通常需要放在头文件中,而非源文件中。因为模板是在编译期根据使用的类型进行实例化的,若放在源文件中,其他源文件无法看到模板的完整定义,会导致链接错误。例如将模板add的定义放在头文件template_example.h中:
// template_example.h
#ifndef TEMPLATE_EXAMPLE_H
#define TEMPLATE_EXAMPLE_H
template <typename T>
T add(T a, T b) {
return a + b;
}
#endif
在main.cpp中包含该头文件并实例化模板:
#include "template_example.h"
#include <iostream>
int main() {
std::cout << add(2, 3) << std::endl; // 实例化int版本
std::cout << add(2.5, 3.5) << std::endl; // 实例化double版本
return 0;
}
17. vector的reserve与resize
reserve(100)的作用:reserve(n)的功能是预先分配至少能容纳n个元素的内存空间,执行后v.capacity()的值必然≥100(若原容量小于 100,则扩容至 100;若原容量已≥100,则不改变)。resize(50)的作用:resize(n)的功能是调整容器中实际元素的数量为n(若n小于当前元素数,则删除多余元素;若n大于当前元素数,则添加默认构造的元素)。但resize不会改变容器的容量(capacity),更不会减小容量。
更多推荐

所有评论(0)