1. 三道编程
(1)ip地址转整数
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
int main()
{
char ip[] = "192.168.1.0";
int i = 0;
unsigned int ip_int = 0;
char* ptr;
char* p = strtok_s(ip, ".",&ptr);
int a[4] = { 0 };
for (; i < 4 && p != nullptr; i++)
{
a[i] = atoi(p);
p = strtok_s(NULL, ".", &ptr);
}
for (i = 0; i < 4; i++)
{
if (a[i] > 0 && a[i] < 255)
{
ip_int += a[i] * pow(256, 3-i);
}
}
cout << ip_int << endl;
}
运行结果:
(2)有一链表的头节点head,请将所给链表按照val变量的大小升序排列并返回排序后的链表。 结构如下:
struct ListNode
{
int val;
ListNode* next;
}
思路: 对链表自顶向下归并排序的过程如下。
-
找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 22 步,慢指针每次移动 11 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。
-
对两个子链表分别排序。
-
将两个排序后的子链表合并,得到完整的排序后的链表。
上述过程可以通过递归实现。递归的终止条件是链表的节点个数小于或等于1,即当链表为空或者链表只包含 1个节点时,不需要对链表进行拆分和排序。
代码实现:
//合并
ListNode* Merge(ListNode* l1,ListNode* l2)
{
ListNode* l = new ListNode(0);
ListNode* head = l;
while(l1 != nullptr && l2 != nullptr)
{
if(l1->val < l2->val)
{
l->next = l1;
l1 = l1->next;
}
else
{
l->next = l2;
l2 = l2->next;
}
l = l->next;
}
l->next = l1 ? l1 : l2;
return head->next;
}
//递归
ListNode* MergeSort(ListNode* head,ListNode* tail)
{
//递归终止条件
if(head == nullptr)
{
return head;
}
if(head->next == tail)
{
head->next = nullptr;
return head;
}
ListNode* slow = head;
ListNode* fast = head;
while(fast != tail && fast->next != tail)
{
slow = slow->next;
fast = fast->next->next;
}
return Merge(MergeSort(head,slow),MergeSort(slow,tail));
}
ListNode* sortList(ListNode* head) {
return MergeSort(head,nullptr);
}
(3)多线程交替输出数字
给定n个线程,不用任何原子类型或者锁,使其交替输出0-m的数字
打印一定要先打印,修改flag条件一定要最后修改。防止一修改flag,其他线程就通过了if条件
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
// num_thread个线程输出
// 1-n 0-m
int flag = 1;
int num = 0;
const int num_thread = 3;
const int num_max = 7;
// 线程函数
void print(int tid) {
while (true) {
if (tid == flag) {
// 打印一定要先打印,修改flag条件一定要最后修改。防止一修改flag,其他线程就通过了if条件
cout << tid << " " << num << endl;
num = (num + 1) % (num_max + 1); // 0 ~ num_max
flag = flag % num_thread + 1; // 1 ~ num_thread
}
}
}
int main() {
vector<thread> vec;
for (int i = 1; i <= num_thread; i++) {
vec.push_back(thread(print, i));
}
for (thread& t : vec) {
t.join();
}
return 0;
}
只打印一次 0~num_max
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
// num_thread个线程输出
// 1-n 0-m
int flag = 1;
int num = 0;
const int num_thread = 4;
const int num_max = 7;
// 线程函数
void print(int tid) {
while (true) {
if (num == num_max + 1) {
return;
}
if (tid == flag) {
if (num == num_max) {
cout << tid << " " << num << endl;
// 这里修改num,让num超过num_max,表示可以退出程序了,让其他两个线程退出
num++;
// 不修改flag,其他两个线程就不会进入if(tid == flag)
return;
}
cout << tid << " " << num << endl;
num = (num + 1) % (num_max + 1); // 0 ~ num_max
flag = flag % num_thread + 1; // 1 ~ num_thread
}
}
}
int main() {
vector<thread> vec;
for (int i = 1; i <= num_thread; i++) {
vec.push_back(thread(print, i));
}
for (thread& t : vec) {
t.join();
}
return 0;
}
2. I/O复用有几种,说下优缺点?
参考博客:blog.csdn.net/qq_41721746/article/details/124234462
3. 虚函数的优缺点
- 优点:在有可能成为父类时,虚函数可以被同名子类函数覆盖,安全;基类的析构函数有时必须实现成虚函数,就是在基类的指针(引用)指向堆上new出来的派生类对象的时候,delete pb(基类的指针),它调用析构函数的时候,必须发生动态绑定,否则会导致派生类的析构函数无法调用。
- 缺点:一个类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要储存的内容是RTTI指针和虚函数地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区,一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable,需要额外的一点点运行时间(不过现在机子很快,这些时间可以无视)
4. 介绍单例模式
一个类不管创建多少次对象,都只能得到一个该对象的实例。常用到的比如日志模块、数据库模块。实现方法:
- 构造函数私有化
- 获取类的唯一实例对象的接口方法(static方法)
- 删除默认的拷贝构造和赋值重载(单例模式只允许一个实例)
单例模式分为饿汉式单例模式和懒汉式单例模式
- 饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了。线程安全的,但是获取在软件启动的时候,并没有使用到这个对象,然而这个对象已经产生,启动时间长,程序启动就得加载,比较浪费资源。
- 懒汉式单例模式:唯一的实例对象,直到第一次获取它的时候才产生。将唯一的实例对象定义为指针,该指针初始化为nullptr(静态变量在类外初始化),当第一次调用时,new()一个对象给指针,后续调用时,直接返回该指针,是线程安全的,也不浪费资源。
5. 介绍malloc、new和delete、free区别(问的是底层实现还是区别忘了,就说下区别把)
malloc和free,称作C的库函数;new和delete,称作运算符
(1)malloc和new的区别
- malloc按字节开辟内存的;new开辟内存时需要指定类型 new int[10],所以malloc开辟内存返回的是void* operator new->int*
- malloc只负责开辟空间,new不仅仅有malloc的功能,还可以进行数据初始化
- malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
(2)free和delete的区别
delete:是先调用析构函数,再free,单个不加[],数组加[]。
free:函数调用,传起始地址。
-
delete 是操作符,而 free 是函数;
-
delete 用于释放 new 分配的空间,free 有用释放 malloc 分配的空间;
-
free 不会调用对象的析构函数,而 delete 会调用对象的析构函数;
-
调用 free 之前需要检查要释放的指针是否为 NULL,使用 delete 释放内存则不需要检查指针是否为 NULL;
6. 进程、线程和协程的区别
进程:一个正在运行的程序,是一个动态的概念。是操作系统进行分配的基本单位,有自己独立的运行空间,进程创建先消耗资源大,切换开销大
线程:进程内部的一条执行路径(序列)。是CPU调度的基本单位,共享进程中的地址空间,线程创建消耗资源较小,切换开销小
协程:协程是一种比线程更加轻量级的存在。协程完全由程序所控制(在用户态执行),带来的好处是性能大幅度的提升。
一个操作系统中可以有多个进程;一个进程可以有多个线程;同理,一个线程可以有多个协程。
线程和进程的区别
◼ 进程是资源分配的最小单位,线程是 CPU 调度的最小单位
◼ 进程有自己的独立地址空间,线程共享进程中的地址空间
◼ 进程的创建消耗资源大,线程的创建相对较小
◼ 进程的切换开销大,线程的切换开销相对较小
◼ 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
◼ 进程的并发性较低,线程的并发性较高
线程和协程的区别如下:
1. 线程是操作系统的资源,线程的创建、切换、停止等都非常消耗资源,而创建协程不需要调用操作系统的功能,编程语言自身就能完成,所以协程也被称为用户态线程,协程比线程轻量很多;
2. 线程在多核环境下是能做到真正意义上的并行,而协程是为并发而产生的;
3. 一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行;
4. 线程进程都是同步机制,而协程则是异步;
5. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力;
6. 操作系统对于线程开辟数量限制在千的级别,而协程可以达到上万的级别。