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