面试真题 | 美团校招面经
@[toc]
一面
TCP和UDP的区别?
以下是TCP和UDP之间几个关键的区别:
-
连接与无连接:
- TCP(传输控制协议)是面向连接的协议。在数据交换之前,TCP需要在通信双方之间建立一个连接。这个连接过程包括三次握手,确保双方都已准备好接收数据。
- UDP(用户数据报协议)是无连接的协议。UDP发送数据之前不需要建立连接,因此它是面向事务的简单不可靠信息传送服务。
-
可靠性:
- TCP提供了可靠的数据传输服务。它通过序列号、确认应答、超时重传、流量控制、拥塞控制等机制确保数据的完整性和顺序性。
- UDP则不保证数据传输的可靠性。如果因为网络问题导致数据丢失或损坏,UDP不会进行任何恢复操作,它仅仅是将数据报尽最大努力交付给接收方。
-
头部开销:
- TCP的头部较大(通常20字节,可选字段会增加其大小),包含了序列号、确认号、窗口大小等控制信息,这些信息用于确保数据的可靠传输。
- UDP的头部较小(只有8字节),只包含必要的端口号、长度和校验和等信息,因此UDP的传输效率更高,但牺牲了可靠性。
-
应用场景:
- TCP适用于需要可靠传输的应用场景,如文件传输(FTP)、网页浏览(HTTP)、电子邮件(SMTP)等。
- UDP适用于对实时性要求较高,但可以容忍少量数据丢失的应用场景,如视频流、实时音频传输(VoIP)、DNS查询等。
-
流量控制和拥塞控制:
- TCP通过滑动窗口协议进行流量控制,防止发送方发送数据过快导致接收方处理不过来。同时,TCP还通过慢启动、拥塞避免、快重传、快恢复等算法进行拥塞控制,避免网络拥塞。
- UDP不提供流量控制和拥塞控制机制,它假设网络状况良好,数据传输的可靠性由应用层来保障。
I/O复用解决什么问题?为什么使用I/O复用?
这个问题考的是对网络编程中资源管理、效率提升以及并发处理的理解。
I/O复用解决什么问题?
I/O复用主要解决的是在高并发网络编程中,单个进程或线程同时处理多个网络连接时的效率问题。在传统的网络编程模型中,如果服务器需要同时监听多个socket,它可能会为每个socket创建一个新的进程或线程。然而,这种方法在连接数增多时会带来显著的资源消耗(如内存和CPU),导致系统性能下降,甚至崩溃。
I/O复用技术允许单个进程或线程同时监听多个文件描述符(socket),并能够在这些文件描述符中的任何一个准备好I/O操作(如读、写)时得到通知。这样,进程或线程就可以非阻塞地等待多个I/O操作,从而提高了资源利用率和程序的吞吐量。
为什么使用I/O复用?
-
提高资源利用率:通过复用单个进程或线程来处理多个I/O操作,减少了系统资源的消耗(如创建和销毁进程/线程的开销)。
-
提升系统吞吐量:由于减少了因等待I/O操作而阻塞的进程/线程数量,系统能够同时处理更多的网络连接,从而提高了整体的吞吐量。
-
简化编程模型:I/O复用使得开发者能够编写出更简洁、更易于维护的代码,因为不再需要为每个连接单独创建一个进程或线程。
-
更好的可伸缩性:随着网络连接的增加,传统的多进程/多线程模型可能会遇到资源瓶颈。而I/O复用模型则能够更好地适应高并发场景,具有更好的可伸缩性。
-
支持异步I/O:虽然I/O复用本身并不直接等同于异步I/O,但它为实现异步I/O提供了基础。通过使用非阻塞I/O和事件通知机制,开发者可以构建出高效的异步I/O系统。
常见的I/O复用技术
-
select:是最早的I/O复用技术之一,但它有一些限制,比如单个进程能够监视的文件描述符的数量有限(通常在1024个左右),并且当文件描述符数量很大时,效率较低。
-
poll:是select的改进版本,它不受文件描述符数量的限制,但在处理大量文件描述符时效率仍然不高。
-
epoll(Linux特有):是select和poll的进一步增强,它提供了更高的效率和更好的可伸缩性。epoll使用基于事件的通知机制,当文件描述符就绪时,它会通知进程进行处理,而不是让进程不断地轮询检查。这使得epoll在处理大量并发连接时具有非常出色的性能。
问项目中CPP语法问题?
项目中算法?
项目问题。
- 你写在简历上的项目一定要事无巨细,搞清楚哦。
二面 一周后 (1h)
进行自我介绍
详细讲下对你提高很大的项目?
有逻辑,金字塔原理。
哪个项目?
为什么?
是因为让你学到了什么?
升华一下!
写一下类的拷贝构造
在C++中,拷贝构造函数是一个特殊的构造函数,它用于创建一个新对象作为另一个同类型对象的副本。拷贝构造函数在以下几种情况下被自动调用:
- 当通过值传递对象给函数时。
- 当从一个对象初始化另一个同类型的对象时(使用等号
=
)。 - 当函数返回对象时(包括临时对象)。
拷贝构造函数的声明通常如下:
class ClassName {
public:
// 默认构造函数
ClassName() {
// 初始化代码
}
// 拷贝构造函数
ClassName(const ClassName& other) {
// 拷贝other到当前对象
}
// 其他成员函数...
};
这里是一个简单的例子,演示了如何为一个包含动态分配内存的类编写拷贝构造函数:
#include <iostream>
using namespace std;
class String {
private:
char* data;
public:
// 默认构造函数
String(const char* str = "") {
if (str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
} else {
data = new char[1];
*data = '\0';
}
}
// 拷贝构造函数
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
// 析构函数
~String() {
delete[] data;
}
// 输出字符串
void print() const {
cout << data << endl;
}
// 其他成员函数...
};
int main() {
String s1("Hello");
String s2 = s1; // 调用拷贝构造函数
s1.print(); // 输出: Hello
s2.print(); // 输出: Hello
return 0;
}
在这个例子中,String
类包含了一个指向字符数组的指针data
,用于存储字符串。拷贝构造函数String(const String& other)
接收一个对同类型对象的常量引用作为参数,并使用strlen
和strcpy
函数来复制other
对象的字符串到当前对象的data
指针所指向的内存中。注意,这里使用了new
来分配内存,因此必须在析构函数中使用delete[]
来释放内存,以避免内存泄漏。
记住,如果你没有为你的类提供拷贝构造函数,编译器会为你生成一个默认的拷贝构造函数,该构造函数会进行浅拷贝(shallow copy)。然而,在包含动态分配内存或资源管理(如文件句柄、网络连接等)的类中,浅拷贝通常是不安全的,因为它会导致多个对象共享相同的资源,并在析构时多次释放这些资源,从而引发错误。因此,在这些情况下,你需要自己编写一个拷贝构造函数来执行深拷贝(deep copy)。
虚函数的构造,能否有delete函数,构造函数能否有虚函数?
在C++中,关于虚函数、构造函数和delete
函数(这里我假设你指的是析构函数,因为C++标准中并没有直接称为delete
的函数,但析构函数在对象销毁时自动调用,其效果类似于"删除"对象)有一些重要的规则和限制。
虚析构函数
可以有虚析构函数。虚析构函数主要用于在通过基类指针删除派生类对象时,确保能够正确地调用派生类的析构函数,以避免资源泄漏或未定义行为。当基类指针指向派生类对象,且你希望通过该基类指针删除该对象时,必须在基类中声明虚析构函数。
class Base {
public:
virtual ~Base() {} // 虚析构函数
};
class Derived : public Base {
// ...
};
Base* ptr = new Derived();
delete ptr; // 正确调用Derived的析构函数
构造函数不能是虚函数
构造函数不能是虚函数。这是因为构造函数是在对象创建时调用的,而虚函数的机制依赖于对象的vptr(虚函数表指针),该指针在对象构造期间还未被初始化。因此,在构造函数调用时,编译器无法确定应该调用哪个类的构造函数。此外,构造函数的目的就是初始化对象,此时对象的具体类型(基类还是派生类)是已知的,因此不需要虚函数机制来确定调用哪个构造函数。
是否有“dele
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
让实战与真题助你offer满天飞!!! 每周更新!!! 励志做最全ARM/Linux嵌入式面试必考必会的题库。 励志讲清每一个知识点,找到每个问题最好的答案。 让你学懂,掌握,融会贯通。 因为技术知识工作中也会用到,所以踏实学习哦!!!