这个面试好像比较着急,也没有自我介绍,面试官直接就问问题了。
一共面了40min左右
epoll
比 select
更快更高效。这是因为 epoll
使用了红黑树(或者哈希表),能够快速检索就绪的文件描述符,而 select
只是简单地轮询所有的文件描述符。epoll
使用更加简洁明了的 API,只需要调用 epoll_create()
创建 epoll 实例,然后使用 epoll_ctl()
向其注册事件和监听的文件描述符,最后使用 epoll_wait()
等待就绪的文件描述符即可。而 select
需要使用 fd_set 结构体进行操作,需要编写较为复杂的代码。select
在不同平台上支持的最大文件描述符数不同,一般情况下为 1024 左右,而 epoll
可以支持非常大的文件描述符数量,受系统资源限制。综上所述,epoll
是更为高效和灵活的 I/O 多路复用机制,在高并发场景下更受欢迎。
是的
这个没准备清楚,没答上来
状态机被广泛应用于描述程序或协议的行为,以及对输入的响应。
状态机的主要优点是:
在 HTTP 请求的解析中,状态机被广泛地使用。HTTP 请求的解析过程是一个典型的输入有序、处理有序的过程,其中每个步骤都是在特定的输入状态下进行的。
从状态机即parse_line
函数,负责读取http报文的第一行,检查读到的一行是否合法,
若合法,则从状态机状态返回LINE_OK
,主状态机即parse_request_line
函数解析请求行,完成后的状态为CHECK_STATE_REQUESTLINE
表示要检查
然后从状态机继续从报文中读取一行,若状态变为LINE_OK
,触发主状态机进行解析请求头(parse_headers
函数),如果是get
请求,就执行请求函数,如果是post
解析后状态变为CHECK_STATE_CONTENT
表示要检查http的消息体,这就又需要从状态机读取数据,当状态变为LINE_OK
时,则可以执行post
请求了
我:一个数据分成多个包连续发包的时候,无法确定包结束。方法有加结尾标识符和TCP包上加packet len
没准备清楚
C++中虚函数是一种特殊的成员函数,它允许在基类和派生类使用同名的函数,实现了多态的特性。虚函数是C++中实现多态(运行时多态)的一种方式。
当基类中的成员函数被声明为虚函数时即函数声明前加上virtual
,派生类可以重写该函数,使用基类指针(引用)指向派生类对象,在运行时,调用该函数时会根据对象的实际类型调用相应的函数。这样就实现了运行时的多态性。
虚函数的机制是通过虚函数表 vftable
和虚函数指针 vfptr
来实现的。每个对象都有一个指向其虚函数表的指针(即虚函数指针),虚函数表是一个数组,包含了该类中所有的虚函数及其地址,最开头是RTTI
指针,标识当前对象的实际类型。vfptr
存放在类的内存空间的最前面。派生类中的虚函数表会继承基类中的虚函数表,如果派生类中重写了基类中的虚函数,就会在派生类的虚函数表中更新该虚函数的地址。当调用虚函数时,通过对象的虚函数指针找到对应的虚函数表,再根据虚函数在虚函数表中的位置找到实际需要调用的函数即vfptr->vftable->func()
一个类里虚函数个数,不影响类大小(都只是存一个vfptr
),影响的只是虚函数表大小
一个类只有一张虚函数表
进程虚拟地址空间的.rodata
只读数据段
这个问题没听懂,应该要问一下面试官的
https://blog.csdn.net/afei__/article/details/82141731
https://blog.csdn.net/wangjian530/article/details/80469625
在C++中,二义性(Ambiguity)指的是在继承关系中出现了歧义,即在调用成员函数时无法确定应该使用哪一个版本的函数。例如,假设有一个基类A和两个派生类B和C,B和C都重载了一个函数foo,如果在另一个类D中以B和C的对象作为参数来调用foo函数,编译器将无法确定应该使用哪一个版本的foo函数,这就是二义性。
class A{
public:
void f();
}
class B{
public:
void f();
void g();
}
class C:public A,public B{
public:
void g();
void h();
}
// 另一种二义性,菱形继承
class A
{
public:
int a; // B1,B2 都将继承一个变量 a
};
class B1 : public A
{
};
class B2 : public A
{
};
class C : public B1, public B2
{
};
int main()
{
C c;
c.a = 10; // ERROR ! 二义性 !!!
return 0;
}
如果声明:C c1,则c1.f();具有二义性,而c1.g();无二义性(同名覆盖)
为了解决二义性问题,C++提供了以下几种解决方案:
使用作用域限定符:使用作用域限定符可以明确指定需要调用的函数版本。例如,可以使用类名限定符来调用基类中的函数,或者使用派生类名限定符来调用派生类中的函数。
int main()
{
C c;
c.A::f();
c.B::f();
return 0;
}
使用虚函数:在基类中定义虚函数,让派生类重写虚函数,通过动态绑定来调用正确的函数版本。
使用virtual继承:如果派生类从多个基类继承,而这些基类中有共同的基类,那么可以使用virtual继承来避免共同基类的重复继承。
使用强制类型转换:使用强制类型转换可以将对象转换为需要的类型,以调用指定的函数版本。但是这种方式不是推荐的做法,因为可能会造成代码可读性下降和运行时错误。
拷贝构造函数用于创建一个新对象,该对象是已有对象的副本。通常,拷贝构造函数采用另一个同类型的对象作为参数,并从该对象中复制数据。拷贝构造函数可以在以下情况下使用:
拷贝赋值函数用于将一个对象的值赋给另一个对象。拷贝赋值函数采用另一个同类型的对象作为参数,并从该对象中复制数据。左边的对象已经初始化过!
RTTI
是C++中的一种运行时类型识别机制,它允许程序在运行时查询一个对象的实际类型信息。RTTI主要用于多态场景中,例如,当一个基类指针指向一个派生类对象时,通过RTTI可以判断出该指针所指向的对象的实际类型。
可以通过使用两个关键字typeid
和dynamic_cast
来实现RTTI:
typeid
关键字:用于查询一个对象的类型信息
dynamic_cast
运算符:用于将一个基类指针或引用转换为派生类指针或引用,如果转换失败则返回nullptr
虚函数表中起始部分存放RTTI指针标识动态类型,从而能实现多态;
RTTI是一种运行时机制,需要在程序运行时才能进行类型信息的查询和转换。由于这种机制会增加程序的运行时开销。使用虚函数实现多态,而不是手动进行类型转换是一种避免额外RTTI开销的方法。
内存泄漏是指在程序运行时,分配的内存空间没有被及时释放(malloc-free, new -delete),导致程序占用的内存越来越多,最终导致程序崩溃或性能下降等问题。
下面是一些防范内存泄漏的方法:
分配内存时,要确保在不需要使用该内存时及时释放。例如,对于动态分配的内存空间,使用delete
或delete[]
进行释放。
注意对象生命周期的管理。在对象不再使用时,应该及时销毁。如果对象被其他对象引用,应该在没有任何引用时再进行销毁。比如在delete
之前抛出异常或者return
了
对于资源对象,应该使用RAII
(Resource Acquisition Is Initialization)技术进行管理。RAII是一种通过在对象的构造函数中获得资源,在析构函数中释放资源的技术,可以避免手动管理资源带来的错误。
尽量避免使用裸指针进行内存管理,使用标准库中的智能指针或其他封装良好的类库进行内存管理。
在编写代码时,可以使用内存泄漏检测工具(如Valgrind、ASAN等)对代码进行检测,及时发现内存泄漏问题。
不知道,还没深入学
RPC(Remote Procedure Call,远程过程调用)是一种分布式计算模型,它允许程序在不同的计算机之间通过网络进行通信和交互,实现像本地调用一样的程序调用和数据传输。RPC通常用于构建分布式系统和微服务架构。
RPC的工作原理可以简单地分为以下几个步骤:
在这个过程中,RPC通常需要解决以下问题:
RPC
框架通常使用网络协议(如HTTP、TCP等)进行数据传输,同时需要将数据序列化成二进制流或其他格式(Protobuf, json)。回溯算法(Backtracking
)是一种暴力搜索算法,用于在所有可能的情况下寻找解决方案。它通过尝试所有可能的解决方案,并在找到满足条件的解决方案后,回溯到之前的状态,继续搜索,直到找到所有可能的解决方案或确定无解。因此,回溯算法通常用于求解组合、排列、集合划分、子集、排列组合和图等问题。
到这里已经紧张到爆炸了,节奏全乱了。
#C++面试##实习#