颍上人才网
颍上职场资讯
颍上面试技巧
正文:C++面试常见问题解析:strcpy函数实现与代码错误分析
C++面试常见问题解析:strcpy函数实现与代码错误分析
来源:网络整理2025-03-27

面试中常见的C++面试题总结,快来看看,是否对你有帮助!

1、写出完整版的strcpy函数

char* 用于将一个字符串复制到另一个字符串中。其中,第一个参数 strDest 是目标字符串,第二个参数 strSrc 是源字符串。通过这个函数,可以把源字符串的内容复制到目标字符串中,从而实现字符串的复制操作。

断言(strDest 不为空且 strSrc 不为空);

char *address = strDest;

当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自增)时,只要(*strDest 不等于 '\0' )就继续执行;当(*strDest 自增等于 *strSrc 自

return address;

要点:

使用assert断言函数,判断参数是否为NULL;

遇'\0'则停止赋值;

返回新的字符串的首地址。

2、指出代码错误

void Test( void )

分配 100 个字节的内存空间给字符指针 str,str 指向这片内存空间,即 str 是通过 malloc 函数分配得到的一个可用于存储字符的内存区域,其大小为 100 字节。

strcpy( str, "hello" );

free( str );

... //省略的其它语句

错误有二:

使用malloc分配内存后,应判断是否分配成功;

free之后,应置str为NULL,防止变成野指针。

PS:malloc函数

malloc 函数能够分配长度为 num_bytes 字节的内存块。它可以向系统申请分配指定 size 个字节的内存空间。malloc 的全称是 memory allocation,中文名为动态内存分配。当不知道内存具体位置时,若要绑定真正的内存空间,就需用到动态分配内存。

第一、malloc 函数返回的是 void * 类型。

对于 C++而言,若写成 p = malloc (sizeof(int)); 这样的形式,那么程序无法通过编译,并且会报错,报错内容为“不能将 void* 赋值给 int * 类型变量”。

所以需要借助 (int *) 来进行强制转换。而在 C 中,不存在这样的要求。不过,为了能让 C 程序更便于移植到 C++中,建议养成进行强制转换的习惯。

函数的实参是 sizeof(int) 。这个实参的作用是指明一个整型数据所需要的大小。

在 Linux 中存在这样的情况:可以有 malloc(0)。这是因为 Linux 中的 malloc 有一个下限值为 16Bytes。需要注意的是,malloc(-1)是被禁止的。然而,在某些系统中,是不允许进行 malloc(0)操作的。

malloc 只负责分配内存,它无法对所获得的内存进行初始化。因此,在得到一片新的内存时,其内存中的值将是随机的。

给出 BOOL 变量与“零值”比较的 if 语句;给出 int 变量与“零值”比较的 if 语句;给出 float 变量与“零值”比较的 if 语句;给出指针变量与“零值”比较的 if 语句。

BOOL型变量:if(!var)

int型变量: if(var==0)

float型变量:

定义了一个常量 EPSINON,其值为 0.00001。

先找到指针 a 的地址为 0x000m,依据 a 的值 0x000n 以及 i 在类 a 中的偏移 offset,从而得到 a->i 的地址为 0x000n + offset,接着进行*(0x000n + offset) = 10 的赋值操作,意味着内存 0x000n + offset 的值变为 10。

一个类中包含了 static 和 virtual 等内容。现在来探讨一下这个类的内存分布情况。

1、static修饰符

1)static修饰成员变量

所以,静态数据成员的值对每个对象是相同的,且其值可以被更新。

在没有产生类对象前就可以使用它。

2)static修饰成员函数

普通的成员函数和静态成员函数不同。普通成员函数与对象相联系,而静态成员函数不与任何对象相联系。因为不与对象相联系,所以静态成员函数不具有 this 指针。由此可知,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,只能调用其他的静态成员函数。

Static修饰的成员函数,在代码区分配内存。

2、C++继承和虚函数

C++多态包含静态多态与动态多态。静态多态借助重载以及模板技术得以实现,并且是在编译阶段予以确定的。动态多态经由虚函数以及继承关系来实现,会执行动态绑定,是在运行阶段进行确定的。

动态多态实现有几个条件:

(1) 虚函数;

(2) 一个基类的指针或引用指向派生类的对象;

基类指针调用成员函数(虚函数)时,会去查找该对象的虚函数表。该对象的虚函数表地址在其首地址处。然后查找此虚函数表中该函数的指针并进行调用。

每个对象中仅保存一个虚函数表的指针。C++内部会为每一个类维持一个虚函数表,而该类的对象都指向这同一个虚函数表。

虚函数表为何能准确查找相应的函数指针呢?在类设计阶段,虚函数表是直接从基类继承过来的。倘若覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换。所以能够依据指针准确找到该调用哪个函数。

3、virtual修饰符

一个类如果是局部变量,那么该类数据存储在栈区;一个类如果是通过 new/malloc 动态申请的,那么该类数据存储在堆区。

如果某类是通过 virtual 继承而来的子类,那么该类的虚函数表指针会与该类的其他成员一起被存储。这个虚函数表指针指向的是只读数据段中的类虚函数表,而在虚函数表中,存放着一个个的函数指针,这些函数指针又指向代码段中的具体函数。

如果类中成员是virtual属性,会隐藏父类对应的属性。

25、静态变量什么时候初始化?

静态变量存储于虚拟地址空间的数据段和 bss 段。在 C 语言中,它在代码执行之前会被初始化,这属于编译期初始化。而在 C++中,因为引入了对象,对象的生成必须调用构造函数。所以 C++规定,全局或局部静态对象是当且仅当对象首次被使用时才进行构造。

26、TCP怎么保证可靠性?

TCP保证可靠性:

(1)序列号、确认应答、超时重传

数据到达接收方后,接收方需发出确认应答,以表明已收到该数据段。同时,确认序号会说明其下一次需要接收的数据序列号。若发送方长时间未收到确认应答,那么有可能是发送的数据丢失了,也有可能是确认应答丢失了。在这种情况下,发送方会在等待一定时间后进行重传,这个时间通常是 2*RTT(报文段往返时间)加上一个偏差值。

(2)窗口控制与高速重发控制/快速重传(重复确认应答)

TCP 利用窗口控制提升传输速度。即在一个窗口大小范围内,不必非得等到应答才发送下一段数据。窗口大小指的是无需等待确认便可继续发送数据的最大值。若不使用窗口控制,每一个未收到确认应答的数据都需重发。

使用窗口控制时,如果数据段 1001 到 2000 丢失,那么在后面每次传输数据时,确认应答都会持续发送序号为 1001 的应答,这表明接收端要接收从 1001 开始的数据。发送端若收到 3 次相同的应答,就会立即进行重发。然而,还有一种情况,即数据可能都已收到,但有的应答丢失了。在这种情况下,不会进行重发,因为发送端清楚,如果是数据段丢失,接收端不会放过它,会不断向其提醒。

(3)拥塞控制

如果窗口定得很大,发送端会连续发送大量数据。这样做可能会导致网络拥堵,因为大家都在使用网络,而吞吐量是有一定限度的,发送端狂发数据就会造成拥堵。甚至可能会使网络瘫痪。所以 TCP 为了防止这种情况,进行了拥塞控制。

定义拥塞窗口,慢启动时一开始将该窗口大小设为 1 。每次收到确认应答(经过一个 rtt )后,就将拥塞窗口大小*2 。

设置慢启动阈值,通常初始值设为 65536,这就是拥塞避免。当拥塞窗口大小达到该阈值时,拥塞窗口的值不再呈指数上升,而是进行加法增加,即每次确认应答或每个 rtt 时,拥塞窗口大小增加 1,以此来避免拥塞。

如果把报文段的超时重传视为拥塞,那么当发生超时重传时,首先要把阈值设定为当前窗口大小的一半,接着把窗口大小设定为初值 1,之后再重新开始慢启动过程。

遇到 3 次重复确认应答,这意味着收到了 3 个报文段。在此之前有 1 个段丢失了。当遇到这种情况时,便会对丢失的那个段进行立即重传,这就是快速重传(高速重发控制)。

首先把阈值设定为当前窗口的大小的一半,接着把拥塞窗口大小设定为慢启动阈值加上 3 的大小。

在 TCP 通信时,网络吞吐量会逐渐上升;随着拥堵,吞吐量会降低;之后又会进入慢慢上升的过程;网络不会轻易出现瘫痪的情况。

27、红黑树和AVL树的定义,特点,以及二者区别

平衡二叉树(AVL树):

平衡二叉树也被称作 AVL 树,它属于一种特殊的二叉排序树。它的左右子树均为平衡二叉树,并且左右子树高度之差的绝对值不会超过 1。也就是说,以树中所有的结点作为根的树,其左右子树高度之差的绝对值不超过 1。二叉树上结点的左子树深度减去右子树深度的值被称作平衡因子 BF 。平衡二叉树上所有结点的平衡因子只可能是-1、0 和 1 。若二叉树上有一个结点的平衡因子的绝对值大于 1 ,那么该二叉树就是不平衡的。

红黑树:

红黑树是一种二叉查找树。在每个节点上增加一个存储位,这个存储位用于表示节点的颜色。节点的颜色可以是红色或黑色,即非红即黑。红黑树通过对从根到叶子的任何一条路径上各个节点着色方式进行限制,确保没有一条路径会比其他路径长出两倍。所以,红黑树是一种弱平衡二叉树。相较于要求严格的 AVL 树,它的旋转次数少。因此,在搜索、插入、删除操作较多的情况下,通常会使用红黑树。

性质:

1. 每个节点非红即黑

2. 根节点是黑的;

每个叶节点都是黑的,叶节点指的是树尾端的 NULL 指针或 NULL 节点。

4. 如果一个节点是红色的,则它的子节点必须是黑色的。

任意节点到叶子点树 NULL 指针的每条路径,对于该节点来说,都包含相同数目的黑节点。

区别:

AVL 树具有高度平衡的特点。当进行频繁的插入和删除操作时,会引发频繁的 rebalance 情况,进而导致效率下降。而红黑树并非高度平衡,它算是一种折中的方式,在插入操作时最多进行两次旋转,在删除操作时最多进行三次旋转。

28、map和unordered_map优点和缺点

对于map,其底层是基于红黑树实现的,优点如下:

map 结构的最大优点是有序性,在很多应用中,其元素的有序性能够简化很多操作。

map 的查找操作时间复杂度稳定,为 logn;map 的删除操作时间复杂度稳定,为 logn;map 的增加操作时间复杂度稳定,为 logn。

缺点如下:

1)查找、删除、增加等操作平均时间复杂度较慢,与n相关

对于 unordered_map 而言,它的底层是一个哈希表,其优点包含以下这些:

查找、删除、添加的速度快,时间复杂度为常数级O(c)

缺点如下:

由于 unordered_map 内部是基于哈希表的,它是以(key,value)这样的形式来进行存储的,所以其空间占用率比较高。

Unordered_map 的查找时间复杂度不稳定,平均为 O(c),这取决于哈希函数,在极端情况下可能为 O(n);Unordered_map 的删除时间复杂度也不稳定,平均为 O(c),取决于哈希函数,极端情况下可能为 O(n);Unordered_map 的添加时间复杂度同样不稳定,平均为 O(c),取决于哈希函数,极端情况下可能为 O(n)。

29、Top(K)问题

1、直接全部排序(只适用于内存够的情况)

在数据量较小的时候,如果内存能够容纳所有数据。那么最为简单且容易想到的办法就是把数据全部进行排序,接着选取排序后数据里的前 K 个。

这种方法对数据量较为敏感。当数据量较大时,内存无法完全容纳全部数据,此时这种方法就不适用了。即便内存能够满足需求,该方法会对全部数据进行排序,然而题目仅仅要求找出 top K 个数据,所以该方法不是特别高效,不建议使用。

2、快速排序的变形 (只使用于内存够的情况)

这是一种基于快速排序的变形。第一种方法中提到,将所有元素都排序并非十分高效,只需找出前 K 个最大的元素即可。

这种方法跟快速排序相似。首先要挑选出一个划分元。接着把比这个划分元大的元素放置在它的前面,同时把比划分元小的元素放置在它的后面。这样一来,就完成了一趟排序。如果此时这个划分元的序号 index 恰好等于 K,那么这个划分元以及其左边的数,恰好就是前 K 个最大的元素;若 index 大于 K,那么前 K 大的数据在 index 的左边,此时就继续递归地从 index - 1 个数中进行一趟排序;倘若 index< K,那么再从划分元的右边继续进行排序,直到找到序号index刚好等于K为止。再将前K个数进行排序后,返回Top K个元素。这种方法就避免了对除了Top K个元素以外的数据进行排序所带来的不必要的开销。

3、最小堆法

面试要点和技巧_c 面试要点_面试要点有哪些

这是一种局部淘汰的方法。首先读取前面的 K 个数,接着构建一个最小堆。之后把剩余的所有数字逐个与最小堆的堆顶进行对比,要是比堆顶数据小或者等于,就接着比较下一个;要是比堆顶数据大,就删除堆顶元素,并且把新数据插入堆中,再对最小堆进行重新调整。当把全部数据都遍历完后,最小堆里的数据就是最大的 K 个数。

4、分治法

将数据全部进行划分,划分为 N 份。其前提是每一份数据都能够被读取到内存中并进行处理。接着找出每一份数据当中最大的 K 个数。此时会剩下 N*K 个数据。如果内存无法容纳 N*K 个数据,那么就继续进行分治处理,将其分成 M 份。然后找出每一份数据中最大的 K 个数。如果 M*K 个数依然不能被读取到内存中,就继续进行分治处理。如果剩余的数能够读入内存中,那么就可以用快速排序的变形或者归并排序来处理这些数。

5、Hash法

如果这些数据存在较多重复数据,可先借助 hash 法将重复的数去除。倘若重复率较高,便能减少大量的内存用量,进而缩小运算空间。处理后的数据若能读入内存,便可直接进行排序;若不能,则可运用分治法或最小堆法来处理数据。

30、栈和堆的区别,以及为什么栈要快?

堆和栈的区别:

堆是由低地址向高地址扩展;栈是由高地址向低地址扩展

堆中的内存需要通过手动操作来申请和释放;栈中的内存是由操作系统自动进行申请和释放的,它里面存放着参数、局部变量等内存。

堆中会频繁调用 malloc 和 free ,这会导致产生内存碎片,进而降低程序效率;而栈因为具有先进后出的特性,所以不会产生内存碎片。

堆的分配效率较低,而栈的分配效率较高

栈的效率高的原因:

栈是操作系统所提供的数据结构。在计算机底层,对栈提供了一系列的支持:专门分配了寄存器来存储栈的地址,并且有专门的指令用于压栈和入栈操作。而堆是由 C/C++函数库提供的,其机制较为复杂,需要一系列用于分配内存、合并内存以及释放内存的算法,所以效率相对较低。

31、写个函数在main函数执行前先运行

使用了 `__attribute((constructor))` 修饰的函数 `before` ,其作用是在程序开始执行之前进行一些初始化或准备工作。

printf("before main\n");

32、extern“C”的作用?

C++调用 C 函数时需要使用 extern C。这是因为 C 语言本身并不具备函数重载的特性。

33、STL迭代器删除元素

序列容器 vector 和 deque ,使用 erase(itertor) 后,后边每个元素的迭代器会失效。同时,后边每个元素会往前移动一个位置。并且,erase 会返回下一个有效的迭代器。

对于关联容器 map 和 set 而言,使用 erase(iterator) 之后,当前元素的迭代器会失效。然而,它们的结构是红黑树,删除当前元素时,不会对下一个元素的迭代器产生影响。所以,在调用 erase 之前,把下一个元素的迭代器记录下来就可以了。

对于 list 而言,它采用了不连续分配的内存。并且它的 erase 方法能够返回下一个有效的 iterator。

34、vector和list的区别与应用有哪些?

1、概念:

1)Vector

连续存储的容器,动态数组,在堆上分配空间

底层实现:数组

两倍容量增长:

当 vector 增加(插入)新元素时,如果未超过当时的容量且还有剩余空间,就可以直接将新元素添加到最后(或插入指定位置),之后再对迭代器进行调整。

如果不存在剩余空间了,就会重新调配原本元素个数的两倍空间。接着把原空间的元素以复制的形式初始化新空间。之后向新空间添加元素。最后对原空间进行析构并释放。在此过程中,之前的迭代器将会失效。

性能:

访问:O(1)

插入:在最后插入(空间够):很快

在最后插入时(若空间不够):需要进行内存的申请和释放,并且要对之前的数据进行拷贝。

在中间插入(空间够):内存拷贝

需要进行内存的申请和释放操作,同时还要对之前的数据进行拷贝。

删除:在最后删除:很快

在中间删除:内存拷贝

适用场景:经常随机访问,且不经常对非尾节点进行插入删除。

2、List

动态链表是在堆上分配空间的。每当插入一个元素,就会进行空间分配;每当删除一个元素,就会释放空间。

底层:双向链表

性能:

访问:随机访问性能很差,只能快速访问头尾节点。

插入:很快,一般是常数开销

删除:很快,一般是常数开销

适用场景:经常插入删除大量数据

2、区别:

1)vector底层实现是数组;list是双向 链表。

2)vector支持随机访问,list不支持。

3)vector是顺序内存,list不是。

vector 进行中间节点的插入操作会导致内存拷贝,vector 进行中间节点的删除操作也会导致内存拷贝,list 无论进行插入还是删除操作都不会导致内存拷贝。

vector 会一次性把内存分配好,只有在内存不够的时候才会进行 2 倍的扩容;list 每次插入新节点的时候都会进行内存申请。

vector 的随机访问性能较好,而插入删除性能较差;list 的随机访问性能较差,但插入删除性能较好。

3、应用

vector 具有一段连续的内存空间,所以它支持随机访问。如果想要高效的随机访问,并且不在意插入和删除的效率,那就可以使用 vector。

list 拥有一段内存空间,且这段内存空间是不连续的。如果在操作中需要高效地进行插入和删除操作,同时又不关心随机访问的情况,那么就应该使用 list。

35、STL里resize和reserve的区别?

resize()用于改变当前容器内含有元素的数量,即改变 size()的值。例如,对于 vector v ,当执行 v.resize(len) 时,v 的 size 会变为 len 。如果原来 v 的 size 小于 len ,那么容器会新增 (len - size) 个元素,这些新增元素的值默认为 0 。当执行 v.push_back(3) 之后,3 会被放置在 v 的末尾,也就是下标为 len 的位置,此时容器的 size 为 len + 1 。

reserve():改变当前容器的最大容量(capacity)。此操作不会生成元素,仅仅是确定该容器允许容纳的对象数量。若 reserve(len) 的值大于当前的 capacity(),则会重新分配一块能够存储 len 个对象的空间。接着,会把之前 v.size() 个对象通过复制构造函数复制过来,并销毁之前的内存。

36、源码到可执行文件的过程?

1)预编译

主要是对源代码文件里以“#”开头的预编译指令进行处理。处理的规则如下。

1、删除所有的#define,展开所有的宏定义。

处理“#else”这个条件预编译指令。

处理“#include”预编译指令,会将文件内容替换到该指令的位置。这个过程是递归进行的,因为文件中可能包含其他文件。

4、删除所有的注释,“//”和“/**/”。

保留所有的#pragma 编译器指令,因为编译器需要用到它们。例如:#pragma once 是为了防止有文件被重复引用。

添加行号和文件标识,这样在编译时编译器就能产生调试用的行号信息。同时,在编译时产生编译错误或警告时也能够显示行号。

2)编译

预编译之后生成的 xxx.i 或 xxx.ii 文件,先进行词法分析,接着进行语法分析,然后进行语义分析,最后进行优化,之后生成相应的汇编代码文件。

词法分析时,会利用类似“有限状态机”的算法,把源代码程序输入到扫描机里,接着把其中的字符序列分割成一连串的记号。

语法分析器会对扫描器所产生的记号进行语法分析,接着会产生语法树。而由语法分析器输出的语法树,是一种以表达式作为节点的树。

语义分析方面,语法分析器仅仅完成了对表达式在语法层面的分析。而语义分析器会对表达式是否有意义进行判断,它所分析的语义是静态语义,这种语义在编译期能够被分析出来。与之相对应的动态语义是在运行期才能确定的语义。

4、优化:源代码级别的一个优化过程。

目标代码生成这一过程,是由代码生成器来进行的。它将中间代码转换为目标机器代码,从而生成一系列的代码序列,这些代码序列是以汇编语言来表示的。

目标代码进行优化:目标代码优化器对上述的目标机器代码予以优化。它会寻找合适的寻址方式,会使用位移来替代乘法运算,还会删除多余的指令等。

3)汇编

把汇编代码转化为机器能够执行的指令(机器码文件)。汇编器的汇编过程相较于编译器而言较为简单,它不存在复杂的语法,也没有语义方面的内容,更无需进行指令优化,仅仅是依据汇编指令和机器指令的对照表进行逐一翻译,而汇编过程是由汇编器 as 来完成的。汇编之后,产生了目标文件。在 Windows 下是 xxx.o,它与可执行文件格式几乎一样;在 Linux 下是 xxx.obj。

4)链接

把由不同源文件生成的目标文件进行链接,以此来形成一个能够执行的程序。链接包含静态链接以及动态链接这两种情况:

1、静态链接:

函数与数据被编译进一个二进制文件。在使用静态库时,在对可执行文件进行编译链接的过程中,链接器会从库中复制这些函数和数据,然后将它们与应用程序的其他模块组合在一起,从而创建出最终的可执行文件。

空间会被浪费。因为在每个可执行程序里,对于所有需要的目标文件都得有一份副本。所以如果多个程序都依赖同一个目标文件,就会导致同一个目标文件在内存中存在多个副本。

库函数的代码修改后,就需要重新进行编译链接以形成可执行程序,这导致更新较为困难。

静态链接的优点在于,可执行程序中具备了执行该程序所需的一切东西,所以在执行时运行速度快,尽管其运行速度本身就很快。

2、动态链接:

动态链接的基本思想是将程序依据模块进行拆分,拆分成各个相对独立的部分。在程序运行的过程中,才把这些拆分后的部分链接在一起,从而形成一个完整的程序。它不像静态链接那样,把所有的程序模块都链接成一个单独的可执行文件。

共享库是这样一种情况:即便每个程序都需要依赖同一个库,然而这个库不会如同静态链接那样,在内存中存在多份副本。相反,多个程序在执行时会共享同一份该库的副本。

更新便捷:更新时只需替换原先的目标文件,无需对所有程序重新进行链接。程序下次运行时,新版本的目标文件会自动被加载至内存并完成链接,从而使程序达成升级目标。

因为链接被推迟到了程序运行时,所以每次执行程序时都需要进行链接,这就导致性能会有一定损失。

37、tcp握手为什么两次不可以?为什么不用四次?

tcp 是全双工通信。两次握手只能确定单向数据链路能够通信,而无法保证反向的通信是正常的。

不用四次:

握手原本应当如同挥手一样,都需要确认两个方向能够联通。原本的模型应该是:

1.客户端发送syn0给服务器

2.服务器收到syn0,回复ack(syn0+1)

3.服务器发送syn1

4.客户端收到syn1,回复ack(syn1+1)

tcp 是全双工的。上边的四部确认了数据在两个方向上都能正确到达。然而 2、3 步没有上下联系。可以将其合并以加快握手效率。所以就变成了 3 步握手。

面试找工作并非短时间内能够完成。失败的面试经历或许并非坏事,因为积累面试经验也是一种进步。希望这里能对你有所帮助。

学习IT相关内容,找“职坐标在线”

温馨提示:本内容地址http://m.ysjob.cc/article/articledetail-261795.html转载请注明,以上C++面试常见问题解析:strcpy函数实现与代码错误分析资讯信息来自颍上人才网(颍上地区最大的颍上人才网颍上人才网

 
 ©2003-2018 颍上人才网  
客服电话:  QQ: