校招 未知部门 / 硬件工程师 / 一面/二面

[面试日期] 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&amp; other) { // 拷贝other到当前对象 } // 其他成员函数... }; </code></pre> <p>这里是一个简单的例子,演示了如何为一个包含动态分配内存的类编写拷贝构造函数:</p> <pre><code>#include &lt;iostream&gt; using namespace std; class String { private: char* data; public: // 默认构造函数 String(const char* str = &quot;&quot;) { if (str) { data = new char[strlen(str) + 1]; strcpy(data, str); } else { data = new char[1]; *data = &#x27;\0&#x27;; } } // 拷贝构造函数 String(const String&amp; other) { data = new char[strlen(other.data) + 1]; strcpy(data, other.data); } // 析构函数 ~String() { delete[] data; } // 输出字符串 void print() const { cout &lt;&lt; data &lt;&lt; endl; } // 其他成员函数... }; int main() { String s1(&quot;Hello&quot;); String s2 = s1; // 调用拷贝构造函数 s1.print(); // 输出: Hello s2.print(); // 输出: Hello return 0; } </code></pre> <p>在这个例子中,<code>String</code>类包含了一个指向字符数组的指针<code>data</code>,用于存储字符串。拷贝构造函数<code>String(const String&amp; 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>的函数,但析构函数在对象销毁时自动调用,其效果类似于&quot;删除&quot;对象)有一些重要的规则和限制。</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 推荐面试题