单向循环链表与双向链表

单向循环链表的原理与应用

思考:对于单向链表而言,想要遍历链表,则必须从链表的首结点开始进行遍历,请问有没有更简单的方案实现链表中的数据的增删改查?

回答:是有的,可以使用单向循环的链表进行设计,单向循环的链表的使用规则和普通的单向链表没有较大的区别,需要注意:单向循环链表的尾结点的指针域中必须指向链表的首结点的地址,由于带头结点的单向循环链表更加容易进行管理,所以教学以带头结点的为例:

上图所示的就是一个典型的单向循环链表的结构,可以发现单向循环链表的结构属于环形结构,链表中的最后一个结点的指针域中存储的是链表的第一个结点的地址。

为了管理单向循环链表,需要构造头结点的数据类型以及构造有效结点的数据类型,如下:

//指的是单向循环链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int  DataType_t;//构造单向循环链表的结点,链表中所有结点的数据类型应该是相同的
typedef struct CircularLinkedList
{DataType_t  		 data; //结点的数据域struct LinkedList	*next; //结点的指针域}CircLList_t;

创建一个空链表,由于是使用头结点,所以就需要申请头结点的堆内存并初始化即可!

//创建一个空单向循环链表,空链表应该有一个头结点,对链表进行初始化
CircLList_t * CircLList_Create(void)
{//1.创建一个头结点并对头结点申请内存CircLList_t *Head = (CircLList_t *)calloc(1,sizeof(CircLList_t));if (NULL == Head){perror("Calloc memory for Head is Failed");exit(-1);}//2.对头结点进行初始化,头结点是不存储数据域,指针域指向自身,体现“循环”思想Head->next = Head;//3.把头结点的地址返回即可return Head;
}

 

创建新结点,为新结点申请堆内存并对新结点的数据域和指针域进行初始化,操作如下:

//创建新的结点,并对新结点进行初始化(数据域 + 指针域)
CircLList_t * CircLList_NewNode(DataType_t data)
{//1.创建一个新结点并对新结点申请内存CircLList_t *New = (CircLList_t *)calloc(1,sizeof(CircLList_t));if (NULL == New){perror("Calloc memory for NewNode is Failed");return NULL;}//2.对新结点的数据域和指针域进行初始化New->data = data;New->next = NULL;return New;
}

 遍历整个链表

//遍历链表
bool CircLList_Print(CircLList_t *Head)
{//对单向循环链表的头结点的地址进行备份CircLList_t *Phead = Head;//判断当前链表是否为空,为空则直接退出if (Head->next == Head){printf("current linkeflist is empty!\n");return false;}//从首结点开始遍历while(Phead->next){//把头结点的直接后继作为新的头结点Phead = Phead->next;//输出头结点的直接后继的数据域printf("data = %d\n",Phead->data);//判断是否到达尾结点,尾结点的next指针是指向首结点的地址if (Phead->next == Head->next){break;}	}return true;
}

 根据情况把新结点插入到链表中,此时可以分为尾部插入、头部插入、指定位置插入:

头插

bool CircLList_HeadInsert(CircLList_t *Head, DataType_t data)
{CircLList_t *Phead = Head; //备份头结点的地址//1.创建新的结点,并对新结点进行初始化CircLList_t *New = CircLList_NewNode(data);if (NULL == New){printf("can not insert new node\n");return false;}//2.判断链表是否为空,如果为空,则把新结点作为首结点,体现“循环”if (Head->next == Head){Head->next = New;New->next = New;return true;}//3.如果链表是非空的,则需要对尾结点的next指针进行处理,指向首结点while(Phead->next){Phead = Phead->next;if ( Phead->next == Head->next ){break;}}Phead->next = New;        //尾结点的next指针指向新的首结点New->next = Head->next;   //新结点的next指针指向原本的首结点Head->next = New;         //更新首结点地址,让头结点的next指针指向新结点return true;
}

 尾插

bool CircLList_TailInsert(CircLList_t *Head, DataType_t data)
{CircLList_t *Phead = Head; //备份头结点的地址//1.创建新的结点,并对新结点进行初始化CircLList_t *New = CircLList_NewNode(data);if (NULL == New){printf("can not insert new node\n");return false;}//2.判断链表是否为空,如果为空,则把新结点作为首结点,体现“循环”if (Head->next == Head){Head->next = New;New->next = New;return true;}//3.如果链表是非空的,则需要对尾结点的next指针进行处理,指向首结点while(Phead->next){Phead = Phead->next;if ( Phead->next == Head->next ){break;}}Phead->next = New;      //旧的尾结点的next指针指向新结点New->next = Head->next; //新结点的next指针指向首结点地址return true;
}

指定位置后插入

bool CircLList_TailInsert(CircLList_t *Head, DataType_t data)
{CircLList_t *Phead = Head; //备份头结点的地址//1.创建新的结点,并对新结点进行初始化CircLList_t *New = CircLList_NewNode(data);if (NULL == New){printf("can not insert new node\n");return false;}//2.判断链表是否为空,如果为空,则把新结点作为首结点,体现“循环”if (Head->next == Head){Head->next = New;New->next = New;return true;}//3.如果链表是非空的,则需要对指定位置结点的指针进行处理while (Phead->next){if (Phead->data == Data) {New->next = Phead->next;Phead->next = New;return true;}Phead = Phead->nextif(Phead == Head->next)break;Phead = Phead->next;}
}

根据情况可以从链表中删除某结点,此时可以分为尾部删除、头部删除、指定元素删除:

头删

//头删
bool LList_HeadDel(LList_t *Head)
{//对单向循环链表的头结点的地址进行备份CircLList_t *Phead = Head;//对单向循环链表的首结点的地址进行备份CircLList_t *Temp = Head->next;//2.判断链表是否为空,如果为空,则退出if (Head->next == Head){printf("Linkedlist is Empty\n");return false;}//3.判断链表中是否只有首结点if ( Head->next == Head->next->next ){Temp->next = NULL;           //首结点的next指针指向NULLHead->next = Head;           //头结点的next指针指向头结点,体现“循环”free(Temp);                  //释放结点内存return true;}    //4.如果链表是非空的,则需要对尾结点的next指针进行处理,指向新的首结点while(Phead->next){Phead = Phead->next;if ( Phead->next == Temp )   //遍历到尾结点{break;}}Phead->next = Temp->next ;       //让尾结点的next指针指向新的首结点Head->next = Phead->next;        //更新首结点,让头结点的next指针指向新的首结点Temp->next = NULL;               //旧的首结点的next指针指向NULL,从链表中断开free(Temp);                      //释放待删除结点的内存return true;
}

尾删

//头删
bool LList_HeadDel(LList_t *Head)
{//对单向循环链表的头结点的地址进行备份CircLList_t *Phead = Head;//对单向循环链表的首结点的地址进行备份CircLList_t *Temp = Head->next;//2.判断链表是否为空,如果为空,则退出if (Head->next == Head){printf("Linkedlist is Empty\n");return false;}//3.判断链表中是否只有尾结点if ( Head->next == Head->next->next ){Temp->next = NULL;           //首结点的next指针指向NULLHead->next = Head;           //头结点的next指针指向头结点,体现“循环”free(Temp);                  //释放结点内存return true;}    //4.如果链表是非空的,则需要对尾结点的next指针进行处理,指向新的首结点while(Phead->next){Phead_prv = Phead            //保存尾结点直接前驱的地址Phead = Phead->next;if ( Phead->next == Temp )   //遍历到尾结点{break;}}Phead_prv->next = Temp ;          //让尾结点的直接前驱指向首节点Phead->next = NULL;               //尾结点的next指针指向NULL,从链表中断开free(Phead);                      //释放待删除结点的内存return true;
}

指定删

双向链表的原理与应用

如果想要提高单向链表或者单向循环链表的访问速度,则可以在链表中的结点中再添加一个指针域,让新添加的指针域指向当前结点的直接前驱的地址,也就意味着一个结点中有两个指针域(prev + next),也被称为双向链表(Double Linked List)。 

由于带头结点更加方便用户进行数据访问,所以本次创建一条带头结点的双向不循环的链表。

//指的是双向链表中的结点有效数据类型,用户可以根据需要进行修改
typedef int  DataType_t;//构造双向链表的结点,链表中所有结点的数据类型应该是相同的
typedef struct DoubleLinkedList
{DataType_t  		     data; //结点的数据域struct DoubleLinkedList	*prev; //直接前驱的指针域struct DoubleLinkedList	*next; //直接后继的指针域}DoubleLList_t;

创建一个空链表,由于是使用头结点,所以就需要申请头结点的堆内存并初始化即可!

//创建一个空双向链表,空链表应该有一个头结点,对链表进行初始化
DoubleLList_t * DoubleLList_Create(void)
{//1.创建一个头结点并对头结点申请内存DoubleLList_t *Head = (DoubleLList_t *)calloc(1,sizeof(DoubleLList_t));if (NULL == Head){perror("Calloc memory for Head is Failed");exit(-1);}//2.对头结点进行初始化,头结点是不存储数据域,指针域指向NULLHead->prev = NULL;Head->next = NULL;//3.把头结点的地址返回即可return Head;
}

创建新结点,为新结点申请堆内存并对新结点的数据域和指针域进行初始化,操作如下:

DoubleLList_t * DoubleLList_NewNode(DataType_t data)
{//1.创建一个新结点并对新结点申请内存DoubleLList_t *New = (DoubleLList_t *)calloc(1,sizeof(DoubleLList_t));if (NULL == New){perror("Calloc memory for NewNode is Failed");return NULL;}//2.对新结点的数据域和指针域(2个)进行初始化New->data = data;New->prev = NULL;New->next = NULL;return New;
}

根据情况可以从链表中插入新结点,此时可以分为尾部插入、头部插入、指定位置插入:

头插

//头插
bool DoubleList_HeadInsert(DoubleList_t * Head, DataType_t data)
{//1.创建新结点并对新结点进行初始化DoubleList_t * New = DoubleList_NewNode(data);if (NULL == New){printf("can not insert new node\n");return false;}//2.判断双向链表是否为空,如果为空,则直接插入到头结点之后if (NULL == Head->next){Head->next = New;  //让头结点的next指针指向新结点return true;}//3.如果双向链表为非空,则把新结点插入到链表的头部New->next = Head->next; //新结点的next指针指向原本的首结点地址Head->next->prev = New; //原本的首结点的prev指针指向新结点的地址Head->next = New; //更新头结点的next指针,让next指针指向新结点的地址return true;
}

尾插

//尾插
bool DoubleList_TailInsert(DoubleList_t * Head, DataType_t data)
{DoubleList_t * Phead = Head; //备份头结点地址,防止头结点丢失//1.创建新结点并对新结点进行初始化DoubleList_t * New = DoubleList_NewNode(data);if (NULL == New){printf("can not insert new node\n");return false;}//2.判断双向链表是否为空,如果为空,则直接插入到头结点之后if (NULL == Head->next){Head->next = New; //让头结点的next指针指向新结点return true;}//3.如果双向链表为非空,则把新结点插入到链表的尾部while(Phead->next){Phead = Phead->next;}Phead->next = New; //尾结点的next指针指向新结点地址New->prev = Phead; //新结点的prev指针指向原本的尾结点地址return true;
}

指定插

根据情况可以从链表中删除某结点,此时可以分为尾部删除、头部删除、指定结点删除:

//指定位置插入 插入目标结点之后
bool DoubleList_DestInsert(DoubleList_t *Head, DataType_t destval, DataType_t data)
{DoubleList_t * Phead = Head; //备份头结点地址,防止头结点丢失//1.创建新结点并对新结点进行初始化DoubleList_t * New = DoubleList_NewNode(data);if (NULL == New){printf("can not insert new node\n");return false;}//2.判断双向链表是否为空,如果为空,则直接插入到头结点之后if (NULL == Head->next){Head->next = New; //让头结点的next指针指向新结点return true;}//3.如果双向链表为非空,此时分为3种情况(尾部 or 中间)while(Phead->next){Phead = Phead->next;if (Phead->data == destval){break;}}//如果遍历链表之后发现没有目标结点,则退出即可if (Phead->next == NULL && Phead->data != destval){printf("dest node is not found\n");return false;}//如果遍历链表找到目标结点,则分为(尾部 or 中间)if(Phead->next == NULL)      //尾插{New->prev   = Phead;     //新结点的prev指针指向尾结点的地址Phead->next = New;       //尾结点的next指针指向新结点}else                         //中间{New->next = Phead->next;     // 新结点的next指针指向目标结点的直接后继结点New->prev = Phead;           // 新结点的prev指针指向目标结点的地址Phead->next->prev = New;     // 目标结点的直接后继结点的prev指针指向新结点Phead->next = New;           // 目标结点的next指针指向新结点}return true;
}

头删

//头删
bool DoubleList_HeadDel(DoubleList_t *Head)
{//对双向链表的首结点的地址进行备份DoubleList_t *Temp = Head->next;//2.判断链表是否为空,如果为空,则退出if (Head->next == NULL){printf("DoubleList is Empty\n");return false;}//3.判断链表中是否只有首结点if (Temp->next != NULL)          // 如果链表不止一个节点{         Temp->next->prev = NULL;     // 新首节点的prev置为NULL}//4.如果链表是非空的,则需要对头结点的next指针进行处理,指向新的首结点Head->next = Temp->next;         //头结点的next指针进行处理,指向新的首结点Temp->next = NULL;               //旧的首节点next指向NULLfree(Temp);                      //释放待删除结点的内存return true;
}

 如果没有 if (Thead->next != NULL) 这个条件判断,直接写 Thead->next->prev = NULL ,会产生以下严重后果:当只有一个结点时, Thead->next 的值为 NULL 。因为尾节点的 next 指针指向 NULL ,表示链表结束。如果直接执行Thead->next->prev = NULL ,就相当于对一个空指针进行解引用操作,去访问它的成员 prev 。在 C 语言中,这是不允许的,会导致程序崩溃,抛出类似 “Segmentation fault(段错误)” 这样的运行时错误。 

尾删

// 尾删
bool DoubleList_TailDel(DoubleList_t *Head)
{// 备份头结点的地址DoubleList_t *Phead = Head;// 判断链表是否为空,如果为空则退出if (Head->next == NULL){printf("DoubleList is Empty\n");return false;}// 遍历到尾结点while (Phead->next != NULL){Phead = Phead->next;}// 获取尾结点的前驱结点DoubleList_t *Prev = Phead->prev;// 如果存在前驱结点(即链表不止一个节点)if (Prev != NULL){Prev->next = NULL;  // 前驱结点的next指向NULL}else{Head->next = NULL;  // 如果无前驱,说明删除后链表为空}// 释放尾结点内存free(Phead);return true;
}

指定删

//指定删除
bool DoubleList_DestDel(DoubleList_t * Head, DataType_t destval)
{DoubleList_t * Phead = Head; //备份头结点地址,防止头结点丢失//1.判断双向链表是否为空,如果为空,则删除失败if (NULL == Head->next){printf("linked list is empty\n");return false;}//2.如果双向链表为非空,此时遍历链表查找没有目标结点while(Phead->next){Phead = Phead->next;if (Phead->data == destval){break;}}//如果链表中没有目标结点,此时直接退出即可if (Phead->next == NULL && Phead->data != destval){printf("dest node is not found\n");return false;}//如果链表中发现目标结点,此时分为(头部 or 尾部 or 中间)if(Phead == Head->next)                 //头部{Head->next = Phead->next;           //更新头结点,让头结点的next指针指向首结点的直接后继if (Phead->next != NULL)            //如果待删除节点的下一个节点存在,则将其 prev 置为 NULL,确保新头节点的前驱正确。{Phead->next->prev = NULL;}Phead->next = NULL;free(Phead);                        //释放待删除结点内存}else if(Phead->next == NULL)            //尾部{Phead->prev->next = NULL;           //尾结点的直接前驱结点的next指针指向NULLPhead->prev = NULL;                 //尾结点的prev指针指向NULLfree(Phead);                        //释放待删除结点内存}else                                    //中间{Phead->prev->next = Phead->next;    //让待删除结点的直接前驱结点的next指针指向待删除结点的直接后继Phead->next->prev = Phead->prev;    //让待删除结点的直接后继结点的prev指针指向待删除结点的直接前驱地址Phead->next = NULL;                 //让待删除结点的next指针指向NULLPhead->prev = NULL;                 //让待删除结点的prev指针指向NULLfree(Phead);                        //释放待删除结点内存}return true;
}

练习题

 

A :双链表在插入和删除时,需要同时调整前驱和后继指针,操作并不比单链表简单,所以 A 选项错误。

B:单链表和双链表都不支持随机访问

C:双链表无论是表头指针还是表尾指针都很重要,都不能省略,因为双链表的遍历、插入和删除等操作往往需要借助表头指针或表尾指针来进行定位起始位置等操作\

D:单链表中每个结点只有一个指向后继的指针,若要访问前驱结点,只能从头开始遍历链表。而双链表的每个结点既有指向后继的指针,又有指向前驱的指针 ,可以方便灵活地访问前后相邻结点,所以 D 选项正确。

A:p->next=q; 这一步执行后,p 原来的后继结点就丢失了,后续再执行 q->next=p->next; 无法正确设置 q 的后继

B:操作顺序符合上述在双向链表中 p 结点后插入 q 结点的步骤,要记住插入动作要先连接在断开
C:同 A
D:同 A

A:p->prior=q; 这一步直接改变 p 的前驱,后续 p->prior->next=q; 操作时,由于 p 的前驱已经改变,会导致逻辑错误

B:p->prior=q->next; 这里逻辑混乱,q->next 指向的是 p ,将其赋给 p->prior 不符合在 p 前插入 q 的指针调整逻辑

C:q->next=p; p->next=q; 这两步操作错误,在双向链表中在 p 前插入 q ,不应该改变 p 的后继,且后续指针调整逻辑也混乱

D:对

A:对

B:p->prior = p->prior->prior; 这一步将 p 的前驱指向了 p 前驱的前驱,逻辑混乱;p->prior->prior = p; 也不符合双向链表删除结点的指针调整规则,会导致链表结构错误
C:p->next->prior = p; 这一步错误地将 p 后继的前驱又指向了 p ,没有达到删除 p 的目的;p->next = p->next->next; 也不能正确调整指针
D:p->next = p->prior->prior; 和 p->prior = p->prior->prior; 这两步操作不符合双向链表删除结点时调整指针的逻辑,会使链表结构出错

A:顺序表:它可以通过数组下标直接随机存取任一指定序号的元素,时间复杂度为O(1) 。在顺序表最后进行插入和删除运算时,若不涉及扩容等操作 ,插入和删除的时间复杂度也较低,插入平均时间复杂度为 O(1)(不考虑扩容) ,删除平均时间复杂度为 O(1)(不考虑元素移动后的整理等额外操作),所以顺序表能满足最常用操作的高效需求。

B:双链表:双链表不支持随机存取,要存取指定序号的元素,需要从表头或表尾开始遍历,时间复杂度为 O(n)

C:双循环链表:和双链表类似,不支持随机存取,存取指定序号元素需遍历,时间复杂度为 O(n) 

D:单循环链表:同样不支持随机存取,存取指定序号元素要遍历链表,时间复杂度为 O(n)

A:单链表:在单链表中,在最后一个元素之后插入元素,需要遍历整个链表找到尾节点,时间复杂度为 O(n) ;删除第一个元素,虽然时间复杂度为 O(1),但插入操作效率低,不满足最常用操作高效的要求。

B:不带头结点的单循环链表:插入最后一个元素需要遍历链表找尾节点,时间复杂度 O(n) ;删除第一个元素时,因为没有头结点辅助,还需要先找到尾节点使其 next 指向第二个节点,操作相对复杂,时间复杂度也较高,整体不满足要求。

C:双链表:在双链表中,删除第一个元素时间复杂度为 O(1),但在最后一个元素之后插入元素,需要遍历链表找到尾节点(虽然可以从表头或表尾双向遍历,但仍需一定时间),时间复杂度为 O(n) ,不是最节省时间的。

D:不带头结点且有尾指针的单循环链表:有尾指针,在最后一个元素之后插入元素时,可直接利用尾指针,时间复杂度为 O(1);删除第一个元素时,可通过尾指针找到第一个元素(尾指针的 next 指向第一个元素),然后调整指针完成删除,时间复杂度也为 O(1) ,能高效满足最常用的两种操作,最节省运算时间。

A:在顺序表中,由于元素是连续存储的,可通过数组下标直接访问第i个元素,时间复杂度为O(1)。

B:效率一样
C:效率一样
​​​​​​​D:效率一样

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/82475.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows鼠标掉帧测试与修复

前言 这两天突然发现鼠标似乎有掉帧,但是掉的又不太明显,用着感觉似乎快速移动的时候会有一瞬间卡一下,但是眼睛又看不清楚,不太确定是不是自己的心理作用,非常难受。 如何判断鼠标是否掉帧 根据我的经验&#xff0…

U 盘数据恢复全攻略

目录 💾 U盘数据误删怎么办?两款实用工具助你找回丢失文件!1️⃣ Recover My Files:数据恢复的得力助手📌 主要特点🛠 使用步骤详解1. 下载与安装2. 启动软件并选择恢复类型3. 选择U盘所在分区4. 选择文件恢…

HarmonyOS NEXT~鸿蒙系统运维:全面解析与最佳实践

HarmonyOS NEXT~鸿蒙系统运维:全面解析与最佳实践 摘要 本文深入探讨鸿蒙(HarmonyOS)系统的运维管理,从架构特点到日常维护操作,全面分析这一全场景分布式操作系统的运维要点。文章将介绍鸿蒙系统特有的分布式能力运维管理、性能…

基于 STM32 的智慧农业温室控制系统设计与实现

摘要 本文提出一种基于 STM32 微控制器的智慧农业温室控制系统设计方案,通过集成多类型环境传感器、执行机构及无线通信模块,实现对温室内温湿度、光照、土壤湿度等参数的实时监测与自动调控。文中详细阐述硬件选型、电路连接及软件实现流程,并附关键代码示例,为智慧农业领…

Appium+python自动化(五)- 模拟器

简介 Appium是做安卓自动化的一个比较流行的工具,对于想要学习该工具但是又局限于没 android 手机来说,可以通过安卓模拟器来解决该问题,下面就讲解使用appium连接安卓模拟器的操作步骤。而是由于手机数据线问题,也只好先用模拟器…

汽车充电桩专用ASCP210系列电气防火限流式保护器

1.概述汽车充电桩专用电气防火限流式保护器 电气防火限流式保护器可有效克服传统断路器、空气开关和监控设备存在的短路电流大、切断短路电流时间长、短路时产生的电弧火花大,以及使用寿命短等弊端,发生短路故障时,能以微秒级速度快速限制短…

Linux中磁盘分区与挂载

一、磁盘划分 1.1 了解磁盘 硬盘的接口类型 接口类型发展方向应用场景IDESATA I/II/III个人PC机SCSISAS服务器上 磁盘命名规则 OSIDE(并口)SATA(串口)SCSIRHEL5/dev/hda/dev/sda/dev/sdaRHEL6/dev/sda/dev/sda/dev/sdaRHEL7/dev/sda/dev/sda/dev/sda 1.2 磁盘划分 磁盘划…

【数据分析】什么是特征蒸馏?

引言 —— “ 在数据洪流中提炼真金——解密特征蒸馏的艺术。” 在数据爆炸的时代,我们每天产生的信息量已远超人类处理能力的极限。当企业拥有百万维的用户行为数据,医疗研究者面对TB级的基因测序记录,工程师试图从千万张图像中识别关键模式…

机器学习笔记【Week4】

一、 为什么要用神经网络? 逻辑回归只能处理线性可分问题。例如,经典的 XOR 异或问题无法用单层逻辑回归准确分类。神经网络通过多层结构和非线性激活函数,能学习复杂的决策边界,解决非线性问题。 二、神经网络的基本组成 神经网…

java交易所,多语言,外汇,黄金,区块链,dapp类型的,支持授权,划转,挖矿(源码下载)

目前这套主要是运营交易所类型的,授权的会贵点,编译后的是可以直接跑的,图片也修复了,后门也扫了 都是在跑的项目支持测,全开源 源码下载:https://download.csdn.net/download/m0_66047725/90887047 更多…

2025CCPC河北省赛题解

题目区分度不错&#xff0c;不过两题手快铜确实没想到。 Attention is all you need&#xff01; H - What is all you need? 签到题 #include <bits/stdc.h> #define x first #define y second #define int long long #define double long doubleusing namespace st…

【IOS】【OC】【应用内打印功能的实现】如何在APP内实现打印功能,连接本地打印机,把想要打印的界面打印成图片

【IOS】【OC】【应用内打印功能的实现】如何在APP内实现打印功能&#xff0c;连接本地打印机&#xff0c;打印想打印的界面 设备/引擎&#xff1a;Mac&#xff08;14.1.1&#xff09;/cocos 开发工具&#xff1a;Xcode 开发语言&#xff1a;OC/C 开发需求&#xff1a;工程中…

AWS WebRTC:获取信令服务节点和ICE服务节点

建立WebRTC的第一步是获取信令服务节点和ICE服务节点。 前提条件是有访问AWS的密钥&#xff0c;主要是ak&#xff0c;sk&#xff0c;token&#xff0c;我这边是业务云有接口可以返回这些信息&#xff0c;所以我直接从业务云获取。 先介绍一下什么是ak&#xff0c;sk&#xff…

C++23 新成员函数与字符串类型的改动

文章目录 引言std::basic_string::contains 与 std::basic_string_view::contains (P1679R3)功能介绍示例代码优势 禁止从 nullptr 构造 std::basic_string 和 std::basic_string_view (P2166R1)背景改动影响 std::basic_string_view 的显式范围构造函数 (P1989R2)功能介绍示例…

VMware-MySQL主从

MySQL主从 服务器信息 服务器类型角色主机地址主机名称虚拟机master192.168.40.128test-1虚拟机slave192.168.40.129test-2 Master 配置&#xff08;192.168.40.128&#xff09; 删除自动生成的配置 /var/lib/mysql/auto.cnf [roottest-1 ~]# rm -rf /var/lib/mysql/auto.…

Java组合、聚合与关联:核心区别解析

在Java中&#xff0c;组合、聚合和关联是描述类之间关系的三种不同方式&#xff0c;它们的核心区别在于对象间的依赖强度和生命周期管理。以下是它们的详细对比&#xff1a; 1. 关联&#xff08;Association&#xff09; 定义&#xff1a;最基本的类间关系&#xff0c;表示一个…

如何保护网络免受零日漏洞攻击?

零日漏洞&#xff08;Zero-Day Vulnerability&#xff09;是指软件或系统中尚未被厂商发现或修补的安全漏洞。这个名称中的“零日”意味着&#xff0c;从漏洞被发现到厂商发布修复补丁的时间是零天&#xff0c;也就是说&#xff0c;黑客可以利用这个漏洞进行攻击&#xff0c;而…

linux快速入门-VMware安装linux,配置静态ip,使用服务器连接工具连接,快照和克隆以及修改相关配置信息

安装VMWare 省略&#xff0c;自己检索 安装操作系统-linux 注意&#xff1a;需要修改的我会给出标题&#xff0c;不要修改的直接点击下一步就可以 选择自定义配置 选择稍后安装操作系统 选择合适的内存 选择NAT模式 仅主机模式 虚拟机只能和主机通信&#xff0c;不能上网…

Nest全栈到失业(一):Nest基础知识扫盲

Nest 是什么? 问你一个问题,node是不是把js拉出来浏览器环境运行了?当然,他使用了v8引擎加上自己的底层模块从而实现了,在外部编辑处理文件等;然后它使用很多方式来发送请求是吧,你知道的什么http.request 或 https.request; 我们浏览器中,使用AJAX以及封装AJAX和http的Axios…

Streamlit 项目知识点总结

目录 1. 单选框、下拉框格式化 2. 多媒体资源的引用 2.1 搭建一个简易的http服务器 2.2 约定多媒体资源的输入格式 2.3 解析多媒体资源 3. 设置页面的全局背景图片 4. 输出流式文本&#xff08;类似打字效果&#xff09; 4.1 使用内置的 st.write_stream 方法实现 4.2…