双向链表的初步练习

news2024/11/29 13:32:43

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:  Solitary-walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互赞互关一下,看到必回
👑👑👑💎👑👑👑   

目录

 


1. 双向链表的初始化

2. 双向链表的销毁

3. 双向链表的尾插

4. 双向链表的头插

5. 双向链表的尾删

6.双向链表的头删

7. 双向链表的指定位置之后的插入

8. 双向链表的指定位置的删除
9. 双向链表的按值查找

10.链表打印


首先在我们进行各项具体任务之前,咱还是需要把前期工作准备好的 

1.先进行自定义链表的声明

2.一些函数的声明

3.必要头文件的引入

首先我们要知道什么是双向链表,顾名思义,对于一个结点而言既有指向自己的指针,也有指向其他结点的指针域

如图所示:

链表结构体类型的定义代码如下:

typedef struct ListNode 
{
	DataType data;//数据域
	struct ListNode* pre;//前驱域
	struct ListNode*next;//后继域
}ListNode;

 1.初始化

 注意这里的带头双向链表不同于前面单链表的头节点

为了好区分,这里我们称之为"哨兵位"

哨兵位只是占一个位置,并不存储任何有效的数据

 当我们只有哨兵位这个结点,思考一下,如何变成一个双向循环链表

你想对了吗??

	phead->pre = phead->next = phead;//让他变成双向循环链表

初始化代码之不采用 传参的方法

ListNode* Init()//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
{
	//自己需要创建一个哨兵位
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	assert(phead);//可能开辟失败
	phead->data = -1;
	phead->pre = phead->next = phead;//记住这里要让他变成双向循环链表
	return phead;
}

初始化代码之采用 传参的方法

注意这里我们形参是用二级指针来接收的

因为实参我们需要传链表的地址,否则我们是无法完成初始化的

因为函数形参只是实参的一份临时拷贝,对形参的临时修改并不会影响我们的实参

void Init(ListNode** pphead)//初始化,采用传参的形式
{
	//注意一下操作都是基于 phead是哨兵位来进行的 他只占一个位置,并不存储任何有效数据
	assert(pphead);
	*pphead = (ListNode*)malloc(sizeof(ListNode));
	if (*pphead == NULL)
	{
		perror("malloc fail\n");
		return 1;//既然开辟失败,没必要执行下面代码
	}

	(*pphead)->data = -1;//注意优先级顺序
	(*pphead)->next = (*pphead)->pre = NULL;//构建一个双向循环链表
}
2.销毁

说白了,其实就是一个结点一个结点进行删除

这自然就需要遍历了

我们在删除之前需要先找到删除结点后一个结点

关键是我们实参到底是指针还是指针地址

 让要删除的结点为 del = phead->next;

那么要删除的结点下一个结点为 del->next

我们不妨试一下~~~

实参为指针

void Destroy(ListNode* phead)//链表销毁;想一下,传一级指针还是二级?  在这里我们传入一级指针,为了保持接口一致性
{
	//销毁我们是一个一个进行删除,自然就需要遍历
	assert(phead);
	ListNode* del = phead->next;
	while (del != phead)
	{
		ListNode* next = del->next;
		free(del);
		/*del = NULL;*/    //  ?
		del = next;
	}
	//来到这说明,此时只有一个哨兵位
	free(phead)
    phead = NULL;

}

 注意当我们只是简单调用销毁函数的时候,代码并不是我们所设想的一样

正常来说,我们的plist的值应该为NULL

 

为了解决这个问题我们就需要在传一级指针的时候,手动把plist 置为空 

 实参为指针地址

 代码如下,但是问题又来了

void LTDestroy(ListNode** pphead) 
{
		assert(pphead && *pphead);
		ListNode* cur = (*pphead)->next;
		while ( cur!=*pphead )
		{
			ListNode* next = cur->next;
			free(cur);
			cur = next;
		}
		free(*pphead);
		*pphead = NULL;
	}

 对于这个问题容本up主先买个关子,欲知后事如何 且听下回分解

为了保持接口一致性,我们这里就使用一级指针来接收

3.尾插

顾名思义,是在尾结点后面进行插入,注意这里在哨兵位的前面进行插入也是可以滴~~~

接下来就是改变指针的指向

1)首先既然是尾插就需要为要插进来的数据开辟结点

这里涉及到创建结点的函数

2)其次是处理指针指向

3)先处理node的pre ,next

4)处理 原来尾结点,哨兵位

 尾插草图如下:

 结点创建函数的代码如下:

ListNode* ListBuyNode(x)//创建结点
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	//来到这,说明空间开辟成功
	node->data = x;
	node->next = node->pre = NULL;
	return node;
}

 

 

 尾插完整代码:

void PushBack(ListNode* phead,DataType x)//尾插
{
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//先处理node 的前驱,后继
	node->pre = phead->pre; //  phead->pre->next原来尾结点的后继,node->pre = phead->pre->next这里出现了野指针的问题 phead->pre->next没有具体指向
	node->next = phead;
	//接下来在处理 原来尾结点,哨兵位
	/*,这样写是错的,以下2句话不可以颠倒
	已经把node这个结点给覆盖掉了,在执行65行代码时,node 是未知的,因为这个结点已经被覆盖了,phead->pre->next此时他的指向也就是未知的,调试时不会报错,但是遍历读取他就会报错了,就像你在打印函数里进行打印时,出现野指针
	phead->pre = node;
	phead->pre->next = node;*/

	phead->pre->next = node;
	phead->pre = node;//别忘了,每尾插进来的结点最终都是一个新的尾结点
}

4.头插

首先是在哨兵位是后面进行的,每插入进来应该结点,此节点就会成为一个新的头节点

同上,需要先创建一个结点

其次找到原来头节点 phead->next 

 头插草图如下:

头插完整代码如下:

void PushFront(ListNode* phead,DataType x)//头插
{
	//头插是在哨兵位的后面进行插入
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理node 的后继,前驱
	node->pre = phead;
	node->next = phead->next;
	//处理phead ,phead->next 
	/*这样写也是对的
	phead->next->pre = node;*/
	phead->next = node;//头节点要进行更新
	phead->next->pre = node;

}

5.尾删

注意我们不能上来就行删除

需要先找到要删除结点 (del)前一个也就是 del->pre

这里我们一定要注意结点的先后顺序

完整代码如下:

void PopBack(ListNode* phead)//尾删
{
	//注意不能直接删除,需要先找到尾结点的前一个结点
	assert(phead);
	//先判断以下是否为空,有2种写法
	/*if (phead->pre == phead)
	{
		return 9;
	}*/
	assert(phead->next != phead);//是否为空
	ListNode* del = phead->pre;//把要删除的结点先保存起来
	//删除之前,先要构成一个新的双向循环链表
	del->pre->next = phead;
	phead->pre = del->pre;
	/*错误的,顺序不能颠倒,因为此时 del->pre已经被覆盖了
	phead->pre = del->pre;//新的尾结点
	del->pre->next = phead;*/

	free(del);
	del = NULL;

}

 

7. 指定位置之后的插入
草图如下:

 

1)要为插入的数据创建结点

2)找到指定位置(pos)之后的结点 (pos->next)和之前的结点(pos->pre

3)  改变指针指向

指定位置之后插入对应完整代码:

void InsertAfter(ListNode* pos, DataType x)//指定位置之后插入
{
	assert(pos);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理 node 的前驱,后继
	node->next = pos->next;
	node->pre = pos;
	//处理 pos 和 pos->next的pre
	pos->next = node;
	pos->next->pre = node;

}
8. 双向链表的指定位置的删除

对应草图如下:

 直接进行删除是不行滴~~~

先要找到指定位置(pos)之后的结点 (pos->next)和之前的结点(pos->pre

其次改变指针走向

void Earse(ListNode* pos)//指定位置删除
{
	assert(pos);
	//需要找到pos之前的一个结点和之后的一个结点
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}
9. 双向链表的按值查找

 这里自然就需要一个一个进行遍历了

按值查找对应完整代码:

ListNode* Find(ListNode* phead, DataType x)//在链表中按值查找,若是找到则返回对应的结点
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来循环遍历的指针
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;//直接返回对应结点
		}
		pcur = pcur->next;
	}
	printf("没有找到\n");
}

 10链表打印

方法同上,对链表进行遍历

void Print(ListNode* phead)//链表打印
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来 遍历的指针,注意他初始值是 phead->next
	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;//记得更新
	}
	printf("\n");
}

 


List.c对应完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

//void Init(ListNode** pphead)//初始化,采用传参的形式
//{
//	//注意一下操作都是基于 phead是哨兵位来进行的 他只占一个位置,并不存储任何有效数据
//	assert(pphead);
//	*pphead = (ListNode*)malloc(sizeof(ListNode));
//	if (*pphead == NULL)
//	{
//		perror("malloc fail\n");
//		return 1;//既然开辟失败,没必要执行下面代码
//	}
//
//	(*pphead)->data = -1;//注意优先级顺序
//	(*pphead)->next = (*pphead)->pre = NULL;//构建一个双向循环链表
//}

ListNode* Init()//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
{
	//自己需要创建一个哨兵位
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	assert(phead);//可能开辟失败
	phead->data = -1;
	phead->pre = phead->next = phead;//记住这里要让他变成双向循环链表
	return phead;
}
ListNode* ListBuyNode(x)//创建结点
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail\n");
		return;
	}
	//来到这,说明空间开辟成功
	node->data = x;
	node->next = node->pre = NULL;
	return node;
}
void Print(ListNode* phead)//链表打印
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来 遍历的指针,注意他初始值是 phead->next
	while (pcur != phead)
	{
		printf("%d-> ", pcur->data);
		pcur = pcur->next;//记得更新
	}
	printf("\n");
}

void PushBack(ListNode* phead,DataType x)//尾插
{
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//先处理node 的前驱,后继
	node->pre = phead->pre; //  phead->pre->next原来尾结点的后继,node->pre = phead->pre->next这里出现了野指针的问题 phead->pre->next没有具体指向
	node->next = phead;
	//接下来在处理 原来尾结点,哨兵位
	/*,这样写是错的,以下2句话不可以颠倒
	已经把node这个结点给覆盖掉了,在执行65行代码时,node 是未知的,因为这个结点已经被覆盖了,phead->pre->next此时他的指向也就是未知的,调试时不会报错,但是遍历读取他就会报错了,就像你在打印函数里进行打印时,出现野指针
	phead->pre = node;
	phead->pre->next = node;*/

	phead->pre->next = node;
	phead->pre = node;//别忘了,每尾插进来的结点最终都是一个新的尾结点
}
void PushFront(ListNode* phead,DataType x)//头插
{
	//头插是在哨兵位的后面进行插入
	assert(phead);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理node 的后继,前驱
	node->pre = phead;
	node->next = phead->next;
	//处理phead ,phead->next 
	/*这样写也是对的
	phead->next->pre = node;*/
	phead->next = node;//头节点要进行更新
	phead->next->pre = node;

}
void PopBack(ListNode* phead)//尾删
{
	//注意不能直接删除,需要先找到尾结点的前一个结点
	assert(phead);
	//先判断以下是否为空,有2种写法
	/*if (phead->pre == phead)
	{
		return 9;
	}*/
	assert(phead->next != phead);//是否为空
	ListNode* del = phead->pre;//把要删除的结点先保存起来
	//删除之前,先要构成一个新的双向循环链表
	del->pre->next = phead;
	phead->pre = del->pre;
	/*错误的,顺序不能颠倒,因为此时 del->pre已经被覆盖了
	phead->pre = del->pre;//新的尾结点
	del->pre->next = phead;*/

	free(del);
	del = NULL;

}
void PopFront(ListNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);//确保不为空
	ListNode* del = phead->next;
	//以下2句没有先后顺序之分
	del->next->pre = phead;

	phead->next = del->next;
	free(del);
	del = NULL;

}
void InsertAfter(ListNode* pos, DataType x)//指定位置之后插入
{
	assert(pos);
	//为插入的数据开辟空间
	ListNode* node = ListBuyNode(x);
	//处理 node 的前驱,后继
	node->next = pos->next;
	node->pre = pos;
	//处理 pos 和 pos->next的pre
	pos->next = node;
	pos->next->pre = node;

}
void Earse(ListNode* pos)//指定位置删除
{
	assert(pos);
	//需要找到pos之前的一个结点和之后的一个结点
	pos->next->pre = pos->pre;
	pos->pre->next = pos->next;
	free(pos);
	pos = NULL;
}
ListNode* Find(ListNode* phead, DataType x)//在链表中按值查找,若是找到则返回对应的结点
{
	assert(phead);
	ListNode* pcur = phead->next;//定义一个用来循环遍历的指针
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;//直接返回对应结点
		}
		pcur = pcur->next;
	}
	printf("没有找到\n");
}
void Destroy(ListNode* phead)//链表销毁;想一下,传一级指针还是二级?  在这里我们传入一级指针,为了保持接口一致性
{
	//销毁我们是一个一个进行删除,自然就需要遍历
	assert(phead);
	ListNode* del = phead->next;
	while (del != phead)
	{
		ListNode* next = del->next;
		free(del);
		/*del = NULL;*/    //  ?
		del = next;
	}
	//来到这说明,此时只有一个哨兵位
	free(phead);
	phead = NULL;

}
void LTDestroy(ListNode** pphead) 
{
		assert(pphead && *pphead);
		ListNode* cur = (*pphead)->next;
		while ( cur!=*pphead )
		{
			ListNode* next = cur->next;
			free(cur);
			cur = next;
		}
		free(*pphead);
		*pphead = NULL;
	}

 List.h对应完整代码

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>

#include<stdlib.h>
//双向链表的实现
typedef int DataType;
typedef struct ListNode {

	DataType data;//数据域
	struct ListNode* pre;//前驱域
	struct ListNode*next;//后继域
}ListNode;

//接口函数的声明
//void Init(ListNode** pphead);//初始化,采用传参的形式
ListNode* Init();//初始化,采用不传参的形式,但是需把哨兵位这个结点返回
void Print(ListNode* phead);//链表打印
void PushBack(ListNode* phead,DataType x);//尾插,这里传入一级指针即可,因为返回的头节点我们不需要进行更改
void PushFront(ListNode* phead, DataType x);//头插
void PopBack(ListNode* phead);//尾删
void PopFront(ListNode* phead);//头删
void InsertAfter(ListNode* pos,DataType x);//指定位置之后插入
void Earse(ListNode* pos);//指定位置删除
ListNode* Find(ListNode*phead,DataType x);//按值查找,若是找到则返回对应的结点
void Destroy(ListNode* phead);//链表销毁;想一下,传一级指针还是二级?为了保持接口一致性我们传入一级指针


ok,以上就是我要为大家进行share的一些基本内容,都来到这里了,要是感觉我写的还不错的话,各位大佬烦劳点个赞,互关以下呗~~~

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

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

相关文章

python项目之数学函数绘图软件(django)

项目简介 管理员用户&#xff1a; &#xff08;1&#xff09;个人信息管理&#xff1a;管理员用户可以通过此功能对自己的密码进行维护。 &#xff08;2&#xff09;用户信息管理&#xff1a;管理员用户通过此功能可以维护系统内注册用户的信息&#xff0c;比如可以对用户的姓…

NSS [鹤城杯 2021]EasyP

NSS [鹤城杯 2021]EasyP 直接给了源码 <?php include utils.php;if (isset($_POST[guess])) {$guess (string) $_POST[guess];if ($guess $secret) {$message Congratulations! The flag is: . $flag;} else {$message Wrong. Try Again;} }if (preg_match(/utils\.p…

CrossOver 23.6.0 虚拟机新功能介绍

CrossOver 23.6.0 Mac 此应用程序允许您运行为 Microsoft Windows 编写的程序&#xff0c;而无需实际安装操作系统。 CrossOver 23.6.0 Mac 包括一个 Windows 程序库&#xff0c;用于它可以运行的 Windows 程序。 您会发现非常流行的应用程序&#xff0c;例如 Microsoft Word…

【JavaEE】HTTP协议

HTTP协议 HTTP是什么?HTTP 协议格式HTTP 请求格式HTTP响应格式协议格式总结 HTTP 请求 (Request)认识 URLURL 基本格式 关于 URL encode认识 "方法" (method)1. GET 方法2. POST 方法 认识请求 "报头" (header) HTTP 响应详解认识 "状态码" (st…

JDK项目分析的经验分享

基本类型的包装类(Character放在最后) String、StringBuffer、StringBuilder、StringJoiner、StringTokenizer(补充正则表达式的知识) CharacterIterator、StringCharacterIterator、CharsetProvider、CharsetEncoder、CharsetDecoder(较难) java.util.function下的函数表…

word公式编辑器能计算吗 word怎么添加公式编辑器

word作为常用的办公软件&#xff0c;常与公式编辑器配合使用来写论文。但该如何在word中使用公式编辑器呢&#xff1f;本文将介绍word公式编辑器能计算吗&#xff0c;word怎么添加公式编辑器的相关内容。 一、word公式编辑器能计算吗 对于word公式编辑器能计算吗这个问题&am…

遇到的题目

第一个线程打印10次a ,第二个线程打印10次吧&#xff0c;第三个线程打印10次c&#xff0c;三个线程交替打印abc public class PrintABC {private static final Object lock new Object();private static int count 0;public static void main(String[] args) {Thread threadA…

nodejs+vue+elementui+express酒店管理系统

登录&#xff1a;运行系统后&#xff0c;进行登录&#xff0c;可使用本系统。 客房预定&#xff1a;此界面先通过条件查询客房信息&#xff0c;然后进行客房预定。对预定的客房还可以取消和支付操作。 信息查询&#xff1a;可查询所有的公告信息&#xff0c;点击公告名称&#…

【黑马程序员】mysql进阶再进阶篇笔记

64. 进阶-锁-介绍(Av765670802,P121) 为了应对不同场景 全局锁-所有表 表计锁 一张表 行级锁 一行数据 65. 进阶-锁-全局锁-介绍(Av765670802,P122) 66. 进阶-锁-全局锁-一致性数据备份(Av765670802,P123) 67. 进阶-锁-表级锁-表锁(Av765670802,P124) 读锁、写锁 68. 进阶…

List 3.5 详解原码、反码、补码

前言 欢迎来到我的博客&#xff0c;我是雨空集&#xff08;全网同名&#xff09;&#xff0c;无论你是无意中发现我&#xff0c;还是有意搜索而来&#xff0c;我都感到荣幸。这里是一个分享知识、交流想法的平台&#xff0c;我希望我的博客能给你带来帮助和启发。如果你喜欢我…

redis 常用方法

、进入redis redis-cli -p 6409 -h 192.168.0.100 -a q9pCeAEMAWEL 2、查询keys keys activity_mobile_* 3、赋值、查值、删除 set mykey 1 get mykey del mykey 4、批量删除 [milanredis-50-240 ~]$ redis-cli -p 6409 -h 192.168.0.100 -a q9pCeAEMAWEL keys abc* | xa…

小米机械键盘 TKL 开启预售:紧凑 87 键布局,到手 229 元

10 月 25 日消息&#xff0c;小米目前在电商平台上架了“小米机械键盘 TKL”&#xff0c;采用 87 键设计&#xff0c;支持蓝牙、有线、2.4G 连接&#xff0c;到手价为 229 元&#xff0c;最晚 11 月 3 日发货。 ▲ 图源 小米 该键盘采用简洁的黑色设计&#xff0c;紧凑 87 键布…

密码学与网络安全:量子计算的威胁与解决方案

第一章&#xff1a;引言 在当今数字化世界中&#xff0c;网络安全一直是一个备受关注的话题。密码学作为网络安全的基石&#xff0c;扮演着至关重要的角色。然而&#xff0c;随着科学技术的不断进步&#xff0c;特别是量子计算的崛起&#xff0c;传统密码学的基础受到了严重威…

IT行业变成了夕阳行业

IT技术发展背景及历程 从2010年左右开始&#xff0c;大众创新&#xff0c;万众创业变成了一个经常看到的词语&#xff0c;在创业潮的带动下&#xff0c;同时刚好赶上了互联网的高速发展&#xff0c;一大批互联网创业公司应运而生&#xff0c;在这样的背景下&#xff0c;IT行业…

JAVASE--继承

在面向对象编程中&#xff0c;继承是一种重要的概念&#xff0c;它允许一个类继承另一个类的特征和行为。在实现继承时&#xff0c;可以使用以下步骤&#xff1a; 1. 创建一个新的子类&#xff0c;它将继承父类的特征和行为。2. 在子类的定义中使用关键字“extends”来指定它的…

c# .net6 在线条码打印基于

条码打印基于:BarTender、ORM EF架构 UI展示&#xff1a; 主页代码&#xff1a; using NPOI.OpenXmlFormats.Spreadsheet; using ServerSide.Models; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawi…

Android官方ShapeableImageView描边/圆形/圆角图,xml布局实现

Android官方ShapeableImageView描边/圆形/圆角图&#xff0c;xml布局实现 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.android.…

【PyQt学习篇 · ②】:QObject - 神奇的对象管理工具

文章目录 QObject介绍Object的继承结构测试QObject对象名称和属性QObject对象名称和属性的操作应用场景 QObject父子对象QObject父子对象的操作 QObject的信号与槽QObject的信号与槽的操作 QObject介绍 在PyQt中&#xff0c;QObject是Qt框架的核心对象之一。QObject是一个基类…

【C++】STL容器适配器入门:【堆】【栈】【队列】(16)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.容器适配器的概念二.为什么stack和q…

Ubuntu Studio 23.10发布

导读Ubuntu Studio 是 Ubuntu 的多媒体社区版。该项目的 23.10 版本重点改进了 PipeWire 支持和音频配置。 PipeWire 已获得大量改进&#xff0c;包括针对专业音频和消费音频的修复。现在&#xff0c;JACK 兼容性可实时运行&#xff0c;一些 FireWire 功能也已实现。 我们还在…