数据结构之链表(2),双向链表

news2024/11/15 4:11:08

目录

前言

一、链表的分类详细

二、双向链表

三、双向链表的实现

四、List.c文件的完整代码

五、使用演示

总结



前言

        接着上一篇单链表来详细说说链表中什么是带头和不带头,“哨兵位”是什么,什么是单向什么是双向,什么是循环和不循环。然后实现完整的双向链表代码。


❤️感谢支持,点赞关注不迷路❤️


一、链表的分类详细

上一篇已经说过,链表总共分为8中结构,常用的就是单链表双链表

这里的单链表全称:不带头单向不循环链表双链表全称:带头双向循环链表

以下是上一篇中提到的8中结构:

注意:因为常用的只有单链表和双链表,将这两种链表结构掌握之后就能够自行实现其他结构的链表。


1.带头和不带:

带头与不带头的区别就是:带头的链表有一个头结点(head),也叫哨兵位。

哨兵位:带头链表的头结点,不存储任何有效元素,仅用于占位,代表链表的头部,功能类似于“放哨”,因此叫哨兵位。

注意:上一篇中的单链表是不带头的,因此不存在头结点的说法,所以上一篇中说的头结点仅仅是为了方便称呼第一个节点的一种不规范叫法,只有带头链表才有头结点的说法。尤其是在链表的分类中。


2.单向和双向

区别:

  1. 结构上:单向链表只有一个指向下一个节点的指针。双向链表有一个指向前一个节点的指针和一个指向后一个节点的指针。
  2. 功能上:单向链表只能单向访问下一个节点。双向链表可以既可以访问下一个节点也可以访问上一个节点。

3.循环和不循环

区别:不循环链表的尾结点next指针会指向空指针。循环链表的尾结点next指针不为空,可以无限循环的访问下一个节点。

注意:循环链表的尾结点的next指针可以指向链表的第一个节点,或者中间的任一节点,甚至尾结点。这都叫循环链表。


二、双向链表

双向链表全称:带头双向循环链表。

结构图如下:

双向链表的结构声明如下:

typedef int LTDataType;
//定义双链表结构
typedef struct ListNode
{
    LTDataType data;//存储数据
    struct ListNode* next;//指向下一个节点
    struct ListNode* prev;//指向上一个节点
}LTNode;

同样,双向链表的实现也分为三个文件:

  1. List.h:双向链表的头文件,包含各种库函数头文件、链表结构、函数的声明。
  2. List.c:用于实现各种接口(函数)。
  3. test.c:用于测试。


三、双向链表的实现

1.List.h文件

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

typedef int LTDataType;
//定义双链表结构
typedef struct ListNode
{
	LTDataType data;//存储数据
	struct ListNode* next;//指向下一个节点
	struct ListNode* prev;//指向上一个节点
}LTNode;

//初始化
//第一种:传二级指针
//void LTInit(LTNode** pphead);
//第二种:不传参
LTNode* LTInit();

//打印
void LTPrint(LTNode* phead);

//尾插
void LTPushBack(LTNode* phead, LTDataType x);

//头插
void LTPushFront(LTNode* phead, LTDataType x);

//判空
bool LTEmpty(LTNode* phead);

//尾删
void LTPopBack(LTNode* phead);

//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);

//删除指定位置的节点
void LTErase(LTNode* pos);

//销毁
//第一种:传二级指针
//void LTDesTroy(LTNode** pphead);
//第二种:传一级指针,缺点是需手动将哨兵位置空
void LTDesTroy(LTNode* phead);


2.List.c文件

1.LTBuyNode函数

//申请新节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;

	//前后指针全部指向自己,自循环
	newnode->next = newnode->prev = newnode;

	return newnode;
}

解析:

  1. 功能:用于申请新节点。
  2. 参数:新节点的需数据x
  3. 使用malloc函数申请一个节点空间,判空后将其数据域赋值x。注意,新节点的两个指针都需要指向自己,这样才是一个双向循环链表。
  4. 因为该函数只为其他函数服务,因此可以只在 List.c 文件中定义,无需在 List.h 中声明。


2.LTInit函数

//初始化
//第一种:传二级指针
//void LTInit(LTNode** pphead)
//{
//	assert(pphead);
//
//	//创建一个头结点(哨兵位)
//	*pphead = LTBuyNode(-1);
//}

//第二种:不传参
LTNode* LTInit()
{
	return LTBuyNode(-1); 
}

解析:

  1. 该函数有两个版本,第二种为优化后的版本,为的是保持接口的一致性,因为双向链表的其它功能函数都只是传一级指针。为了避免混淆,优化为不传参版本。
  2. 功能:初始化为哨兵位(头结点)。
  3. 第一种:需要传参版本,传二级指针,因为只有二级指针才能修改一级指针的指向。因此这里使用二级指针进行初始化赋值。-1可以改为任何值,因为哨兵位存储的数据为无效数据。
  4. 第二种:不传参版本,直接创建并返回哨兵位的地址。
  5. 使用上的区别:第一种需要自己定义一个双向链表类型的空指针,然后使用调用该函数进行传参初始化,注意传参时需&。第二种自己创建一个双向链表类型的空指针并调用该函数进行赋值。


3.LTPrint函数

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

解析:

  1. 功能:打印链表的所有节点数据。
  2. 参数:链表的头结点(哨兵位)。
  3. 首先断言头结点不能为空,创建一个 pcur 指针指向第一个有效节点,
  4. 然后进行循环遍历时,因为该链表是循环链表,因此循环一遍的判定是 pcur 指向的节点不是头结点。循环体里就是打印数据然后让pcur走向下一个节点。打印完后换行即可。


4.LTPushBack函数

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);

	//首先修改新节点的指针指向
	newnode->next = phead;//next指针指向哨兵位
	newnode->prev = phead->prev;//prev指针指向尾节点

	//再修改旧尾节点和哨兵位
	phead->prev->next = newnode;//旧尾节点的next指针指向新尾节点
	phead->prev = newnode;//哨兵位后指针指向新尾节点
}

解析:

  1. 功能:在链表的尾部插入一个节点
  2. 参数:头结点(哨兵位)和需要插入的节点数据
  3. 首先断言头结点不为空,申请新节点,然后就需要改变新节点和受影响节点的指针指向。这里需要注意修改的前后顺序,
  4. 先修改新节点的指针指向,新节点的 next 指针应该指向头结点,因为是循环链表,原尾结点可以通过头结点找到,即 phead->prev 指向的就是原来的尾结点,将新节点的 prev 指针指向原尾结点。
  5. 修改完新节点就需要修改头结点和原尾结点的指针指向,原尾结点依旧可以通过头结点的 prev 指针找到,将其 next 指针指向新节点。最后就是头结点的 prev 指针需要指向新节点。


5.LTPushFront函数

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);

	//首先还是先修改新节点的指向
	newnode->next = phead->next;
	newnode->prev = phead;

	//然后修改旧头结点和哨兵位
	phead->next->prev = newnode;
	phead->next = newnode;
}

解析:

  1. 功能:在第一个有效节点之前插入数据,也就是头结点(哨兵位)之后
  2. 参数:头结点(哨兵位)和需插入的节点数据、
  3. 首先头插为什么不是在头结点前面插入数据,很简单,头结点的 prev 指针就是指向尾结点的,所以如果在头结点之前插入数据,其实就是尾插。
  4. 实现过程,开头还是断言和申请新节点,然后先修改新节点的前后指针指向,原第一个有效节点可以通过头结点的 next 指针找到。修改完新节点后就修改原第一节点的prve指针,使其指向新节点,最后修改头结点的 next 指针也指向新节点。


6.LTEmpty函数

//判空
//只有哨兵位节点说明链表为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

解析:

  1. 功能:判断链表是不是空链表,也就是判断链表是不是只有头结点(哨兵位)
  2. 参数:头结点(哨兵位)
  3. 该函数主要为后续删除链表节点时判断链表是否为空链表,实现简单,断言头结点后,直接判断头结点的下一个节点是不是还是头结点即可。


7.LTPopBack函数

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	//先保存尾结点和倒数第二个节点
	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

	//再修改倒数第二节点和哨兵位节点指向
	prev->next = phead;
	phead->prev = prev;

	//释放尾结点
	free(del);
	del = NULL;
}

解析:

  1. 功能:删除尾结点
  2. 参数:头结点(哨兵位)
  3. 首先断言判断头结点是否为空指针以及链表是否为空链表,然后创建 del 指针保存尾结点,创建 prev 指针保存倒数第二个节点,避免修改完受影响节点的前后指针指向后找不到尾结点,
  4. 然后修改倒数第二个节点的 next 指针指向头结点,以及头结点的 prev 指针指向倒数第二个节点。最后释放尾结点(del),最后释放尾结点 del 即可。


8.LTPopFront函数

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	//还是先保存第一个有效节点
	LTNode* del = phead->next;
	del->next->prev = phead;//修改第二个有效节点指向
	phead->next = del->next;//修该哨兵位指向

	//释放
	free(del);
	del = NULL;
}

解析:

  1. 功能:删除第一个有效节点,也就是头结点(哨兵位)的下一个节点
  2. 参数:头结点(哨兵位)
  3. 还是先断言判断头结点和链表是否为空,创建 del 指针保存第一个有效节点,然后先通过 del指针找到并修改第二个有效节点的 prev 指针,使其指向头结点,再修改头结点的 next 指针指向第二个节点。最后就可以直接释放第一个节点 del 即可。

9.LTFind函数

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* pcur = phead->next;
	//循环遍历
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}

	return NULL;
}

解析:

  1. 功能:根据指定数据查找对应的节点,然后返回该节点的地址,主要是配合后面两个函数使用
  2. 有了上面函数实现的经验,查找函数实现简单,循环遍历,循环链表循环一遍的标志是下一节点为头结点,找到就返回节点的地址,找不到返回空指针。


10.LTInsert函数

//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);

	//先修改新节点朝向
	newnode->next = pos->next;
	newnode->prev = pos;

	//在修改受影响的两个节点朝向,注意顺序
	pos->next->prev = newnode;
	pos->next = newnode;
}

解析:

  1. 功能:在指定位置之后插入数据,这个指定位置需要通过 LTFind 函数指定。
  2. 双向链表插入数据其实都差不多,先修改新节点的前后指针,再修改受影响节点的指针。这里不再赘述。


11.LTErase函数

//删除指定位置的节点
void LTErase(LTNode* pos)
{
	assert(pos);

	//先修改pos的前后节点
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

解析:

  1. 功能:删除指定位置的节点
  2. 这个实现更加简单,通过 pos 指针先修改前后节点的指针指向,然后就可以直接释放掉自己。


11.LTDesTroy函数

//销毁
//第一种:传二级指针
//void LTDesTroy(LTNode** pphead)
//{
//	assert(pphead && *pphead);
//
//	LTNode* pcur = (*pphead)->next;
//	while (pcur != *pphead)
//	{
//		LTNode* Next = pcur->next;
//		free(pcur);
//		pcur = Next;
//	}
//
//	free(*pphead);
//	*pphead = NULL;
//	pcur = NULL;
//}

//第二种:传一级指针,缺点是需手动将哨兵位置空
void LTDesTroy(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}

	free(phead);
	phead = pcur = NULL;
}

解析:

  1. 销毁函数有两种,第二种为优化后的版本,同样也是为了接口的一致性,避免使用时混淆,因此传一级指针合适,但是带来的缺点就是需要手动将哨兵位置空。
  2. 两个版本的函数实现方式大致相同,循环遍历,在释放当前节点之前要先使用 Next 指针保存下一节点的地址。
  3. 第一个版本缺点是需要传二级指针,但是不需要手动将哨兵位置空,函数内部就可以实现置空,第二版本优点就是同其他接口一样,只需要传一级指针,缺点就是在调用完该函数后需要手动将哨兵位置空。


四、List.c文件的完整代码

#include "List.h"

//申请新节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;

	//前后指针全部指向自己,自循环
	newnode->next = newnode->prev = newnode;

	return newnode;
}

//初始化
//第一种:传二级指针
//void LTInit(LTNode** pphead)
//{
//	assert(pphead);
//
//	//创建一个头结点(哨兵位)
//	*pphead = LTBuyNode(-1);
//}

//第二种:不传参
LTNode* LTInit()
{
	return LTBuyNode(-1);
}


//打印
void LTPrint(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);

	//首先修改新节点的指针指向
	newnode->next = phead;//next指针指向哨兵位
	newnode->prev = phead->prev;//prev指针指向尾节点

	//再修改旧尾节点和哨兵位
	phead->prev->next = newnode;//旧尾节点的next指针指向新尾节点
	phead->prev = newnode;//哨兵位后指针指向新尾节点
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* newnode = LTBuyNode(x);

	//首先还是先修改新节点的指向
	newnode->next = phead->next;
	newnode->prev = phead;

	//然后修改旧头结点和哨兵位
	phead->next->prev = newnode;
	phead->next = newnode;
}

//判空
//只有哨兵位节点说明链表为空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	//先保存尾结点和倒数第二个节点
	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

	//再修改倒数第二节点和哨兵位节点指向
	prev->next = phead;
	phead->prev = prev;

	//释放尾结点
	free(del);
	del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	//还是先保存第一个有效节点
	LTNode* del = phead->next;
	del->next->prev = phead;//修改第二个有效节点指向
	phead->next = del->next;//修该哨兵位指向

	//释放
	free(del);
	del = NULL;
}

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* pcur = phead->next;
	//循环遍历
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}

	return NULL;
}

//在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* newnode = LTBuyNode(x);

	//先修改新节点朝向
	newnode->next = pos->next;
	newnode->prev = pos;

	//在修改受影响的两个节点朝向,注意顺序
	pos->next->prev = newnode;
	pos->next = newnode;
}

//删除指定位置的节点
void LTErase(LTNode* pos)
{
	assert(pos);

	//先修改pos的前后节点
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;

	free(pos);
	pos = NULL;
}

//销毁
//第一种:传二级指针
//void LTDesTroy(LTNode** pphead)
//{
//	assert(pphead && *pphead);
//
//	LTNode* pcur = (*pphead)->next;
//	while (pcur != *pphead)
//	{
//		LTNode* Next = pcur->next;
//		free(pcur);
//		pcur = Next;
//	}
//
//	free(*pphead);
//	*pphead = NULL;
//	pcur = NULL;
//}

//第二种:传一级指针,缺点是需手动将哨兵位置空
void LTDesTroy(LTNode* phead)
{
	assert(phead);

	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}

	free(phead);
	phead = pcur = NULL;
}


五、使用演示

使用测试文件 test.c 进行演示:

#include "List.h"

void ListTest1()
{
	//创建哨兵位(头结点)
	LTNode* plist = LTInit();

	//尾插4个节点
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	printf("尾插:");
	LTPrint(plist);

	//头插4个节点
	LTPushFront(plist, 11);
	LTPushFront(plist, 12);
	LTPushFront(plist, 13);
	LTPushFront(plist, 14);
	printf("头插:");
	LTPrint(plist);

	//在1后面插入一个数据
	LTInsert(LTFind(plist, 1), 66);
	printf("在1后面插入66:");
	LTPrint(plist);

	//尾删
	LTPopBack(plist);
	printf("尾删:");
	LTPrint(plist);

	//头删
	LTPopFront(plist);
	printf("头删:");
	LTPrint(plist);

	//删除11
	LTErase(LTFind(plist, 11));
	printf("删除11:");
	LTPrint(plist);

	//销毁
	LTDesTroy(plist);
	plist = NULL;//需要手动置空
	printf("链表已销毁\n");

}

int main()
{
	ListTest1();

	return 0;
}

运行结果:


总结

        以上就是本文的全部内容,感谢支持。

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

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

相关文章

U盘恢复数据工具:让数据失而复得的魔法

优盘里数据丢失无疑会给我们的工作和生活带来诸多不便。幸运的是&#xff0c;优盘数据恢复软件应运而生&#xff0c;它们如同数据的守护者&#xff0c;为我们提供了找回丢失数据的希望。这次我们就一同来探讨u盘恢复数据有什么方法吧。 1.福昕恢复数据 链接直达&#xff1a;h…

AutoSar 通信服务架构,CAN通信诊断详解

文章目录 Com&#xff08;通信服务模块&#xff09;PDU的定义和结构PDU的分类IPDU Mux 模块PDU R 模块&#xff08;路由&#xff09;Bus TP 模块BUS InterfaceCanIf模块LinIf模块 发送数据示例&#xff08;CAN报文&#xff09;接收数据示例&#xff08;CAN报文&#xff09;通信…

监控告警功能详细介绍及操作演示:运维团队的智能保障

在当今这个信息化高速发展的时代&#xff0c;运维团队面临着前所未有的挑战。为了确保系统的稳定性和高效运维&#xff0c;监控告警功能成为了运维团队不可或缺的得力助手。本文将详细介绍我们的监控告警功能&#xff0c;并结合实际操作页面进行演示&#xff0c;帮助运维团队更…

Docker入门指南:快速学习Docker的基本操作

为什么需要Docker 有时我们在本地开发好程序并成功运行之后&#xff0c;却在服务器上运行不起来&#xff0c;通过观察日志通常会发现&#xff0c;哦原来是这个库没安装&#xff0c;于是我们就需要先安装需要用到的库&#xff0c;然后再启动服务你可能还会发现用到的数据库信息…

《Linux从小白到高手》理论篇(六):Linux软件安装一篇通

List item 本篇介绍Linux软件安装相关的操作命令&#xff0c;看完本文&#xff0c;有关Linux软件安装相关操作的常用命令你就掌握了99%了。 Linux软件安装 RPM RPM软件的安装、删除、更新只有root权限才能使用&#xff1b;查询功能任何用户都可以操作&#xff1b;如果普通用…

真正的Open AI ——LLaMA颠覆开源大模型

1. LLaMA 简介 LLaMA&#xff08;Large Language Model Meta AI&#xff09;是由Meta&#xff08;原Facebook&#xff09;推出的一个大型语言模型系列&#xff0c;旨在通过更小的模型规模和更少的计算资源&#xff0c;实现与其他主流语言模型&#xff08;如GPT&#xff09;相媲…

spring简短注入

新建bean 创建set方法 jpackage com.dependency.spring6.bean;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class User {private static final Logger LOGGER LoggerFactory.getLogger(User.class);private String username;private String password;pr…

RPA跨流程复用元素技巧|实在RPA研究

为什么要跨流程复用元素 在 RPA 操作中&#xff0c;元素至关重要&#xff0c;因为自动化的本质就是模拟人类对元素的操作。基本上&#xff0c;每个流程都会包含若干个元素。对于同时维护多个流程的用户而言&#xff0c;相似的流程包&#xff0c;甚至是同一个元素。例如电商用户…

Solidworks斜接法兰快速绘制钣金箱体

Solidworks斜接法兰快速绘制钣金箱体 Chapter1 Solidworks斜接法兰快速绘制钣金箱体 Chapter1 Solidworks斜接法兰快速绘制钣金箱体 0.5mm间距为钣金焊接的预留焊缝。

Linux云计算 |【第四阶段】RDBMS1-DAY6

主要内容&#xff1a; MySQL索引&#xff08;索引分类、创建索引&#xff09;、用户及授权&#xff08;创建用户并授权、查看授权、撤销授权、授权库mysql&#xff09;、root密码恢复、备份、使用mysqldump进行逻辑备份、Percona 一、MySQL索引 1、基本概念 MySQL 索引(Inde…

给虚拟机安装操作系统以及无密码SSH登录

安装完虚拟化软件VMware Workstation Pro 17之后&#xff0c;我们下载了Ubuntu光盘映像文件&#xff0c;上次说演示desktop版的安装&#xff0c;但是考虑到后面要部署数据库&#xff0c;所以为了方便起见还是下载sever服务器版。 文件还挺大&#xff0c;在等待下载完成这会我们…

基于SpringBoot的休闲娱乐代理售票系统设计与实现

1.1研究背景 21世纪&#xff0c;我国早在上世纪就已普及互联网信息&#xff0c;互联网对人们生活中带来了无限的便利。像大部分的企事业单位都有自己的系统&#xff0c;由从今传统的管理模式向互联网发展&#xff0c;如今开发自己的系统是理所当然的。那么开发休闲娱乐代理售票…

C++那些事之内存优化

C那些事之内存优化 通常程序运行时内存是一个比较大的问题&#xff0c;如何减少内存占用和提升访问速度是至关重要。为了解决这些问题&#xff0c;C20 引入了 no_unique_address 特性&#xff0c;并结合空基类优化&#xff08;EBO, Empty Base Optimization&#xff09;&#x…

33 指针与数组:数组名与指针的关系、使用指针遍历数组、数组指针、指针数组、字符指针

目录​​​​​​​ 1 数组名与指针的关系 1.1 数组名 1.2 对数组名取地址 1.3 数组名与指针的区别 1.3.1 类型不同 1.3.2 sizeof 操作符的行为不同 1.3.3 & 操作符的行为不同 1.3.4 自增自减运算的行为不同 1.3.5 可变性不同 2 使用指针遍历数组 2.1 使用 *(nu…

智能网联汽车飞速发展,安全危机竟如影随形,如何破局?

随着人工智能、5G通信、大数据等技术的飞速发展&#xff0c;智能网联汽车正在成为全球汽车行业的焦点。特别是我国智能网联汽车市场规模近年来呈现快速增长态势&#xff0c;彰显了行业蓬勃发展的活力与潜力。然而&#xff0c;车联网技术的广泛应用也带来了一系列网络安全问题&a…

Mybatis知识

1. 基础知识 mybatis是基于java的持久层框架&#xff0c;它内部封装了jdbc&#xff0c;使开发者只需要关注sql语句本身&#xff0c;而不需要花费精力去处理加载驱动&#xff0c;创建连接&#xff0c;创建statement等繁杂的过程。 通过xml或者注解的方式将要执行的各种sta…

序列化方式五——ProtoStuff

介绍 Protostuff是一个基于Java的高效序列化库&#xff0c;它使用Protocol Buffers&#xff08;简称protobuf&#xff09;协议&#xff0c;为Java对象提供高效、灵活且易用的序列化和反序列化方法。Protostuff的主要优势在于其高性能和简单的使用方式&#xff0c;相对于其他序…

C#多线程数据同步的几种方式(不同的锁)

无锁 多个关联数据无法完整获取修改 internal class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");ThreadPool.QueueUserWorkItem(Thread1);ThreadPool.QueueUserWorkItem(Thread2);ThreadPool.QueueUserWorkItem(Thread3);Console…

SpringBoot框架在墙绘产品展示中的运用

4 系统设计 墙绘产品展示交易平台的设计方案比如功能框架的设计&#xff0c;比如数据库的设计的好坏也就决定了该系统在开发层面是否高效&#xff0c;以及在系统维护层面是否容易维护和升级&#xff0c;因为在系统实现阶段是需要考虑用户的所有需求&#xff0c;要是在设计阶段没…

linux查看进程所在的目录

在Linux系统中&#xff0c;查看一个进程所在的目录&#xff08;即该进程的可执行文件所在的目录&#xff09;可以通过多种方式实现。 以下是查找进程的可执行文件路径的方法&#xff1a; 使用ls -l /proc/[pid]/cwd 你可以通过查看/proc/[pid]/cwd的符号链接来找到进程的可执…