前言
本篇文章继续带大家来刷题。
一、纯虚函数和虚函数的区别
1.实现:纯虚函数没有具体的实现代码,只有函数原型,通过在函数声明的末尾添加= 0来指示。虚函数具有默认的实现代码,但可以在派生类中进行重写。
2.抽象类:包含纯虚函数的类是抽象类(Abstract Class),不能直接实例化对象,只能被用作其他具体类的基类。而包含虚函数的类可以实例化对象,但如果包含了至少一个纯虚函数,则其仍然是抽象类。
3.继承和实现要求:派生类必须实现基类中的纯虚函数,否则派生类仍然是抽象类。对于虚函数,派生类可以选择性地重写或继承基类中的实现。
4.调用方式:虚函数通过动态绑定来调用,根据对象的实际类型来确定调用哪个版本的函数。纯虚函数没有具体的实现,不能直接调用,只能在派生类中进行重写后调用。
二、结构体和类的区别
1.默认访问级别:在类中,成员的默认访问级别为私有(private),而在结构体中,默认为公有(public)。这意味着在结构体中定义的成员变量和成员函数默认对外是可访问的,而在类中,默认情况下是私有的,需要使用访问修饰符来设置访问级别。
2.继承:类可以使用继承来派生新的类,以实现代码重用和层次化设计。而结构体不能直接继承其他结构体或类,它们主要用于组织相关的数据字段。
3.成员函数:类可以定义成员函数,用于封装操作数据的行为。而结构体通常用于存储数据,并不包含行为。然而,在C++中,结构体也可以具有成员函数,与类的成员函数没有本质区别。
4.默认构造函数和析构函数:在类中,如果没有显式定义构造函数和析构函数,编译器会为类生成默认构造函数和析构函数。而在结构体中,如果没有显式定义构造函数和析构函数,编译器也会为结构体生成默认构造函数和析构函数。
三、自旋锁和信号量的区别
自旋锁在等待获取锁的过程中,线程会一直忙等待,不会主动进入休眠状态。它会不断地检查锁的状态,直到获得锁为止。
信号量在无法获取资源时,会导致线程或进程进入休眠状态,从而让出CPU资源给其他线程或进程。当其他线程或进程释放了资源并增加了信号量的计数值时,进入休眠状态的线程或进程会被唤醒,继续执行。
四、怎么取消结构体对齐
1.使用预处理指令:可以使用预处理指令 #pragma pack 或 #pragma pack() 来修改结构体的对齐方式。例如,设置为按照字节对齐:
#pragma pack(1) struct MyStruct { // 结构体成员... }; #pragma pack()
在上述代码中,#pragma pack(1) 指定了结构体按照1字节对齐,#pragma pack() 用于恢复默认对齐方式(通常是按照编译器默认的对齐规则)。
2.使用编译器属性:有些编译器提供了特定的属性或修饰符,可以在结构体定义时使用,用于控制对齐方式。例如,使用GCC编译器的 attribute((packed)) 属性:
struct __attribute__((packed)) MyStruct { // 结构体成员... };
上述代码中,attribute((packed)) 属性指定了按照字节对齐(取消对齐)。
五、链表相交
1.什么是链表相交
链表相交指的是两个链表在某个节点处出现了重合,形成了共享部分。具体地说,两个链表相交意味着它们中至少有一个节点是相同的,而且这个节点之后的所有节点都是相同的。
相交链表的示意图如下:
List A: a1 → a2 ↘ c1 → c2 → c3 ↗ List B: b1 → b2 → b3
在上面的示意图中,链表A和链表B在节点c1处相交,之后的节点c2和c3都是相同的。
注意,链表的相交不仅仅限于两个链表长度相等的情况。相交节点可以在较短链表的任意位置出现,而且两个链表在相交之前的部分可以是不同长度的。
判断两个链表是否相交的目标就是找到它们的相交节点。如果两个链表相交,则从相交节点开始之后的所有节点都是相同的。可以通过遍历链表,比较节点是否相等来判断链表是否相交,并找到相交的节点位置。
2.怎么判断链表是否相交
1.首先,遍历两个链表,分别获取它们的长度。可以使用两个指针分别指向两个链表的头节点,通过不断向后移动指针并计数,可以得到两个链表的长度。
2.如果两个链表的尾节点不相等,即最后一个节点不同,那么这两个链表肯定不相交,可以直接返回不相交的结果。
3.如果两个链表的尾节点相等,说明它们有相同的结尾部分,可能相交。此时,计算两个链表的长度差(长度较长的链表减去长度较短的链表),然后将较长链表的指针向后移动与长度差相同的步数,使得两个链表剩余的长度相同。
4.此时,同时遍历两个链表,比较节点是否相等。如果找到了相等的节点,说明两个链表相交。如果遍历完两个链表都没有找到相等的节点,说明两个链表不相交。
#include <stdbool.h> // 定义链表节点结构 struct ListNode { int val; struct ListNode *next; }; // 获取链表的长度 int getLinkedListLength(struct ListNode* head) { int length = 0; while (head != NULL) { length++; head = head->next; } return length; } // 判断两个链表是否相交 bool isLinkedListIntersect(struct ListNode* headA, struct ListNode* headB) { if (headA == NULL || headB == NULL) { return false; } // 获取链表的长度 int lengthA = getLinkedListLength(headA); int lengthB = getLinkedListLength(headB); // 将指针移动到相同长度的起始位置 int diff = lengthA - lengthB; if (diff > 0) { while (diff > 0) { headA = headA->next; diff--; } } else { while (diff < 0) { headB = headB->next; diff++; } } // 比较节点是否相等 while (headA != NULL && headB != NULL) { if (headA == headB) { return true; // 相交 } headA = headA->next; headB = headB->next; } return false; // 不相交 }
六、为什么是三次握手和四次挥手
1.三次握手
三次握手的目的是确保双方都能收到对方的确认信息,并且双方都同意建立连接。通过这种握手机制,可以防止已失效的连接请求报文段再次传到服务器端,造成资源的浪费。同时,通过每次握手都交换序列号,可以防止过期的连接请求被误认为是新的连接。这样可以保证 TCP 连接的可靠性和有效性。
2.四次挥手
四次挥手的目的是保证双方都能完成数据的传输并且确认关闭请求。通过四次挥手,可以确保已发送的数据都能被接收方接收到,并且在关闭连接之前完成可能存在的后续数据传输。同时,通过等待一段时间的 TIME-WAIT 状态,可以避免在关闭连接后可能残留的延迟数据包干扰到下一个连接。
总结
本篇文章就讲解到这里。