从0开始创建单链表

news2024/11/25 1:11:25

前言

       这次我来为大家讲解链表,首先我们来理解一下什么是单链表,我们可以将单链表想象成火车

每一节车厢装着货物和连接下一个车厢的链子,单链表也是如此,它是将一个又一个的数据封装到节点上,节点里不仅包含着数据,还又指向下一个节点的指针。

因此单链表的结构体表示如下:

typedef int SLTDataType;

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SListNode;

所以单链表的特点是:

在物理结构上不一定是线性的
在逻辑结构上是线性的

我们通常会将单链表抽象成如下图所示: 这样会方便我们接下来的思考和代码的实现。

单链表代码实现

打印

我们先来写打印代码,这个代码也方便我们后期的测试。

打印单链表,我们需要遍历单链表的所有数据,这个函数的形参传一级指针就可以了,因为打印并不需要改变头指针。

void SListPrint(SListNode* phead)
{
	SListNode* ptail = phead;
	while (ptail)
	{
		printf("%d->", ptail->data);
		ptail = ptail->next;
	}
	printf("NULL\n");
}

这里定义一个变量ptail,是为了不想改变phead,说不定哪天要在这个函数再次使用phead,所以我定义了一个ptail来进行遍历,以便后面想使用phead的时候,可以快速增加代码

创建新节点

鉴于插入数据都需要创建一个新节点,为了方便后面的头插,尾插等各种插入,于是我们来封装一个函数来创建新节点:

SListNode* CreatNewnode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

尾插

尾插需要在单链表的尾部插入一个新的数据,画图理解一下:

需要我们找到单链表原先的尾节点,将尾节点的next指向newnode,于是我们很快就会写出如下的代码:

void SListPushBack(SListNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);

	SListNode* newnode = CreatNewnode(x);

	//找尾节点
	SListNode* pcur = *pphead;
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	pcur->next = newnode;
}

由于上面的代码是基于链表不为空这一种情况实现的,这时候我们还要思考,如果链表为空的时候,上面的代码还能实现吗?

我们来走一下代码,如果链表为空,pcur==NULL,pcur->next一定会报错,对空指针是不能解引用的,所以上面的代码无法处理链表为空的情况,我们就需要单独进行处理!!!

这个尾插代码应该是这样的:

void SListPushBack(SListNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);

	SListNode* newnode = CreatNewnode(x);

	//如果链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SListNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

所以当链表为空的时候,我们需要改变头指针的指向,所以传二级指针!!!

头插

头插需要我们在单链表的最前面插入一个数据,我们画图来理解一下:

头插很显然要改变头指针,所以形参要影响实参需要传二级指针。

这时候是先newnode指向原先的第一个节点,再改变phead?还是先改变phead再将newnode 指向原先的第一个节点?

在不创建另外一个变量的时候,我们要找到原先第一个节点只能通过phead->next,如果先改变了phead 的话,我们就找不到原先的第一个节点了。
所以需要将新节点的next指向原先的第一个节点,然后我们将头指针改变,指向新节点。

void SListPushFront(SListNode** pphead, SLTDataType x)//头插
{
	assert(pphead);

	SListNode* newnode = CreatNewnode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

这个时候,由于上面的代码还是基于链表不为空的条件下进行的,所以我们还要考虑链表为空的情况,走一下代码,*pphead == NULL,newnode->next = NULL,*phead = newnode ,很显然这个代码也能处理好链表为空的情况,于是我们不需要任何的改动。

尾删

尾删指删除单链表最后一个节点。画图理解一下:

这时候我们需要找到尾节点和尾节点的前一个节点,将尾节点释放掉,改变现在的尾节点的next指向,置为NULL。

所以我们需要两个临时变量,一个来遍历链表找到尾节点,一个来保存上一个节点!!!

这时我们来写代码:

void SListPopBack(SListNode** pphead)//尾删
{
	assert(pphead && *pphead);//不能传NULL,链表也不能为空

	//找尾节点
	SListNode* pcur = *pphead;
	SListNode* prev = *pphead;

	while (pcur->next)
	{
            prev = pcur;
            pcur = pcur->next;
	}
	free(pcur);
	pcur = NULL;
	prev->next = NULL;

}

这里我们需要断言一下,就是链表不能为空,链表为空,删什么?

上面的代码是基于链表至少又两个节点的情况下,如果链表只有一个节点呢?也就是头指针指向的就是你要尾删的节点,走一下代码:*pphead == NULL,pcur = prev =NULL,此时while(pcur->next),对空指针解引用,直接报错,既然如此,我们就写多一点代码来专门处理只有一个节点的情况。

void SListPopBack(SListNode** pphead)//尾删
{
	assert(pphead && *pphead);//不能传NULL,链表也不能为空
	//链表只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找尾节点
		SListNode* pcur = *pphead;
		SListNode* prev = *pphead;

		while (pcur->next)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		free(pcur);
		pcur = NULL;
		prev->next = NULL;
	}
}

由于可能会出现只有一个节点的情况,删除的话也需要改变头指针,所以传二级指针。

头删

头删指删除第一个节点。画图理解一下:

我们需要改变头指针的指向,所以必须传二级指针!!!

void SListPopFront(SListNode** pphead)//头删
{
	assert(pphead && *pphead);//不能传NULL,链表也不能为空

	SListNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

老规矩,如果链表只有一个节点,走一下代码:SListNode->next = (*pphead) -> next =NULL; 释放掉第一个节点,*pphead = next = NULL,刚好可以处理这种情况,代码不需要修改。

查找

写查找代码也是方便我们后续的指定位置的插入删除作准备。

这个代码和简单,只需要遍历单链表。

SListNode* SListFind(SListNode* phead, SLTDataType x)
{
	SListNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

删除指定节点

我们画图理解一下:

我们需要改变两个个节点,pos前一个节点的next指向改成pos后一个节点。

void SListPopPos(SListNode** pphead, SListNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

	SListNode* pcur = *pphead;
	while (pcur->next != pos)
	{
            pcur = pcur->next;
	}
	pcur->next = pos->next;

	free(pos);
	pos = NULL;
		
}

如果pos节点就是第一个节点(即pphead == pos),我们来走一下代码,在while循环中pcur->next 不会找到pos,因为pcur = *phead = pos,所以循环最后会导致pcur走到NULL,然后发生对空指针的解引用,所以我们需要单独处理这一种情况

void SListPopPos(SListNode** pphead, SListNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if (pos == *pphead)
	{
		//调用头删
		SListPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		pcur->next = pos->next;

		free(pos);
		pos = NULL;
	}
}

由于pos 就是头指针,所以pos的删除就是头删,我们可以调用我们之前写好的头删函数,无论你要不要调用头删函数,都要对头指针进行修改,所以要传二级指针(头指针的地址)。而pos不需要传二级指针是因为一般情况下我们不会再次使用pos,所以我们不需要改变pos的指向,也就不用传pos 的地址过去。

删除指定位置之前的数据

画图理解:

我们需要找到pos前两个节点,释放pos 前一个节点,改变pos前前节点的next 的指向,变为指向pos这个节点。

void SListPopPosFront(SListNode** pphead, SListNode* pos)
{
	assert(pphead && pos); 
	assert(pos != *pphead);
	
	SListNode* pcur = *pphead;
	SListNode* prev = *pphead;
	while (pcur->next != pos)
	{
            prev = pcur;
            pcur = pcur->next;
	}
	prev->next = pos;
	free(pcur);
	pcur = NULL;
		
}

我们要考虑一下如果pos前面只有一个节点的情况下,我们来走一下代码:当prev->next = pos(pphead = pos),释放pcur(也就是释放了pphead)。这时候头指针没了,你找不到后面的数据了!!!

所以我们单独处理一下这种情况。

void SListPopPosFront(SListNode** pphead, SListNode* pos)
{
	assert(pphead && pos); 
	assert(pos != *pphead);
	
	if ((*pphead)->next == pos)
	{
		//头删
		SListPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		SListNode* prev = *pphead;
		while (pcur->next != pos)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		prev->next = pos;
		free(pcur);
		pcur = NULL;
	}
}

头删,需要改变头指针的指向,所以要用二级指针来接收头指针的地址。

删除指定位置之后的数据

画图理解:

由于我们可以通过pos 就可以找到pos后面的节点,所以我们直接传pos就可以了。

我们需要找到pos后后一个节点,然后改变pos 的next 指向指向后后一个节点,为了方便书写代码,我们可以定义一个临时变量del来保存要删除的节点。

这里要注意,指定位置之后必须要有一个节点,否则删什么?所以这里断言一下。

void SListPopPosAfter(SListNode* pos)
{
	assert(pos && pos->next);
	SListNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

我们来考虑一下,如果要删除的节点刚好是尾节点,我们来走一下代码:pos->next = del->next = NULL;free(del),没有问题,那就不用单独处理。

指定位置前插入

画图理解一下:

我们需要改变前一个节点,为了找到pos 的前一个结点,我们需要传入头指针对链表进行遍历。

void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);

	SListNode* newnode = CreatNewnode(x);
	SListNode* pcur = *pphead;
	while (pcur->next != pos)
	{
		pcur = pcur->next;
	}
	newnode->next = pos;
	pcur->next = newnode;
}

如果pos就是第一个节点,上面的代码中while循环就无法找到pos前一个节点,所以我们要单独处理一下这种情况,这时可以调用一下我们写过的头插函数。

头插,需要改变头指针,所以传二级指针。

void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);

	if (*pphead == pos)
	{
		//头插
		SListPushFront(pphead, x);
	}
	else
	{
		SListNode* newnode = CreatNewnode(x);
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		newnode->next = pos;
		pcur->next = newnode;
	}
}

指定位置之后插入

画图理解一下:

这个代码我们可以通过pos找到pos后一个节点,所以不需要传入头指针。

void SListPushPosAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);

	SListNode* newnode = CreatNewnode(x);
	SListNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

我们来考虑一下pos就是尾节点的情况,能不能走得通,走一下代码,next = pos->next = NULL,pos->next = newnode,newnode->next = next = NULL,可以,没有任何问题,就不需要改代码了。

销毁链表

销毁链表需要一个一个节点依次删除,所以要遍历链表。

void SListDestroy(SListNode** pphead)
{
	SListNode* pcur = *pphead;
	while (pcur)
	{
		SListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

测试

每写完一个函数,我们就需要进行调试测试来看代码有没有问题,这时一个好的习惯,希望各位也能这样做。

一下是我自己的测试代码:我放在了test.c文件里(就是测试文件)

#include "SList.h"

void test1()
{
    SListNode* plist = NULL;

    //测试尾插
    //SListPushBack(&plist, 1);
    //SListPrint(plist);

    //SListPushBack(&plist, 2);
    //SListPrint(plist);

    //SListPushBack(&plist, 3);
    //SListPrint(plist);

    //测试头插
    SListPushFront(&plist, 10);
    SListPrint(plist);

    SListPushFront(&plist, 20);
    SListPrint(plist);

    SListPushFront(&plist, 30);
    SListPrint(plist);

    //测试尾删
  /*  SListPopBack(&plist);
    SListPrint(plist);

    SListPopBack(&plist);
    SListPrint(plist);

    SListPopBack(&plist);
    SListPrint(plist);*/

    //测试头删
    //SListPopFront(&plist);
    //SListPrint(plist);

    //SListPopFront(&plist);
    //SListPrint(plist);

    //SListPopFront(&plist);
    //SListPrint(plist);

    //测试查找
 /*   SListNode* find = NULL;
    find = SListFind(plist, 30);*/
 /*   if (find == NULL)
    {
        printf("找不到\n");
    }
    else
    {
        printf("找到了!%d\n", find->data);
    }*/
    //测试删除pos节点
    /*SListPopPos(&plist, find);
    SListPrint(plist);*/

    //删除指定位置之前的数据
    //SListPopPosFront(&plist, find);
    //SListPrint(plist);

    //删除指定位置之后的数据
   /* SListPopPosAfter(find);
    SListPrint(plist);*/

    //指定位置前插入
 /*   SListPushPosFront(&plist, find, 100);
    SListPrint(plist);*/

    //指定位置之后插入
  /*  SListPushPosAfter(find, 100);
    SListPrint(plist);*/

    SListDestroy(&plist);
    SListPrint(plist);
}

int main()
{
    test1();
    return 0;
}

分装函数

SList.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;

typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SListNode;

//打印
void SListPrint(SListNode* phead);

//插入
void SListPushBack(SListNode** pphead, SLTDataType x);//尾插
void SListPushFront(SListNode** pphead, SLTDataType x);//头插

//删除
void SListPopBack(SListNode** pphead);//尾删
void SListPopFront(SListNode** pphead);//头删

//查找
SListNode* SListFind(SListNode* phead, SLTDataType x);

//删除pos节点
void SListPopPos(SListNode** pphead, SListNode* pos);

//删除指定位置之前的数据
void SListPopPosFront(SListNode** pphead, SListNode* pos);

//删除指定位置之后的数据
void SListPopPosAfter(SListNode* pos);

//指定位置前插入
void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x);

//指定位置之后插入
void SListPushPosAfter(SListNode* pos, SLTDataType x);

//销毁链表
void SListDestroy(SListNode** pphead);

SList.c

#include "SList.h"

//创建新节点
SListNode* CreatNewnode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//打印
void SListPrint(SListNode* phead)
{
	SListNode* ptail = phead;
	while (ptail)
	{
		printf("%d->", ptail->data);
		ptail = ptail->next;
	}
	printf("NULL\n");
}

//插入
void SListPushBack(SListNode** pphead, SLTDataType x)//尾插
{
	assert(pphead);

	SListNode* newnode = CreatNewnode(x);

	//如果 *pphead==NULL
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SListNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

void SListPushFront(SListNode** pphead, SLTDataType x)//头插
{
	assert(pphead);

	SListNode* newnode = CreatNewnode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

void SListPopBack(SListNode** pphead)//尾删
{
	assert(pphead && *pphead);//不能传NULL,链表也不能为空
	//链表只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找尾节点
		SListNode* pcur = *pphead;
		SListNode* prev = *pphead;

		while (pcur->next)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		free(pcur);
		pcur = NULL;
		prev->next = NULL;
	}
}

void SListPopFront(SListNode** pphead)//头删
{
	assert(pphead && *pphead);//不能传NULL,链表也不能为空

	SListNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

//查找
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
	SListNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//删除pos节点
void SListPopPos(SListNode** pphead, SListNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if (pos == *pphead)
	{
		//调用头删
		SListPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		pcur->next = pos->next;

		free(pos);
		pos = NULL;
	}
}

//删除指定位置之前的数据
void SListPopPosFront(SListNode** pphead, SListNode* pos)
{
	assert(pphead && pos); 
	assert(pos != *pphead);
	
	if ((*pphead)->next == pos)
	{
		//头删
		SListPopFront(pphead);
	}
	else
	{
		SListNode* pcur = *pphead;
		SListNode* prev = *pphead;
		while (pcur->next != pos)
		{
			prev = pcur;
			pcur = pcur->next;
		}
		prev->next = pos;
		free(pcur);
		pcur = NULL;
	}
}

//删除指定位置之后的数据
void SListPopPosAfter(SListNode* pos)
{
	assert(pos && pos->next);
	SListNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

//指定位置前插入
void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);

	if (*pphead == pos)
	{
		//头插
		SListPushFront(pphead, x);
	}
	else
	{
		SListNode* newnode = CreatNewnode(x);
		SListNode* pcur = *pphead;
		while (pcur->next != pos)
		{
			pcur = pcur->next;
		}
		newnode->next = pos;
		pcur->next = newnode;
	}
}

//指定位置之后插入
void SListPushPosAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);

	SListNode* newnode = CreatNewnode(x);
	SListNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

//销毁链表
void SListDestroy(SListNode** pphead)
{
	SListNode* pcur = *pphead;
	while (pcur)
	{
		SListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

单链表完结撒花!!!

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

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

相关文章

【c语言】结构体的访问

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…

力扣HOT100 - 41. 缺失的第一个正数

解题思路&#xff1a; 原地哈希 就相当于&#xff0c;让每个数字n都回到下标为n-1的家里。 而那些没有回到家里的就成了孤魂野鬼流浪在外&#xff0c;他们要么是根本就没有自己的家&#xff08;数字小于等于0或者大于nums.size()&#xff09;&#xff0c;要么是自己的家被别…

51-37 由浅入深理解 Stable Diffusion 3

2024年3月5日&#xff0c;Stability AI公开Stable Diffusion 3论文&#xff0c;Scaling Rectified Flow Transformers for High-Resolution Image Synthesis。公司像往常一样承诺后续将开源代码&#xff0c;开源之光&#xff01;&#xff01;&#xff01; 在LDW潜在扩散模型论文…

学习51单片机必备:从电子基础到编程技巧全解析

学习51单片机需要掌握一系列的基础知识和技能&#xff0c;以下是一些主要的学习内容&#xff1a; 电子基础知识 了解基本的电子元件和电路原理是学习单片机的基础。这有助于理解单片机如何与外围设备交互以及如何设计电路。 数字逻辑 理解数字逻辑和布尔代数&#xff0c;对于编…

第十三届蓝桥杯真题:x进制减法,数组切分,gcd,青蛙过河

目录 x进制减法 数组切分 gcd 青蛙过河 x进制减法 其实就是一道观察规律的题。你发现如果a这个位置上的数x&#xff0c;b这个位置上的数是y&#xff0c;那么此位置至少是max(x,y)1进制。一定要把位置找对啊 #include <bits/stdc.h> using namespace std; typedef l…

如何恢复 iPhone 删除的照片?

照片是iPhone空间不足的主要原因&#xff0c;因此许多用户选择删除一些重复或不喜欢的图片来释放设备。然而&#xff0c;人们在清洁过程中不小心遗漏了自己喜欢的照片的情况很常见。如果你找不到这些珍贵的照片&#xff0c;你一定很难过。其实&#xff0c;您不必担心这个问题&a…

echarts 多环形图

环形图效果&#xff1a; option {"angleAxis": {"max": 1,"show": false,"splitLine": {"show": false},"axisLabel": {"show": false},"axisTick": {"show": false}},"ra…

Idea显示无法自动装配。找不到‘ xxx’类型的Bean

虽然只标红&#xff0c;不报错&#xff0c;但是看着非常别扭&#xff01; 原因&#xff1a; 当我们在使用Autowired注解的时候&#xff0c;默认requiredtrue,表示注入的时候bean必须存在&#xff0c;否则注入失败。 解决方案一&#xff1a; 在自动转配的注解后面添加(require…

低频电磁仿真 | 新能源汽车性能提升的利器

永磁同步电机 新能源汽车的心脏 近年来&#xff0c;全球变暖的趋势日益加剧&#xff0c;极端天气事件层出不穷&#xff0c;这些现象都反映出当前气候形势的严峻性。为了应对这一全球性挑战&#xff0c;各国纷纷采取行动&#xff0c;制定了一系列降碳、减碳的措施。中国在2020年…

2024年淘宝天猫京东618超级红包领取入口

2024年淘宝天猫京东618超级红包领取入口 1、打开「词令」&#xff0c;输入词令关键词直达口令「超红88」&#xff1b; 2、搜索直达2024年淘宝天猫、京东618超级红包领取入口&#xff1b;

新天龙八部3永恒经典之江山策仿官方_源码架设教程

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 一. 效果演示 新天龙…

解锁电气数据新价值:SolidWorks Electrical助力企业转型

在信息化、数字化的时代&#xff0c;电气数据库已成为企业不可或缺的核心资产。它以其独特的功能和优势&#xff0c;助力企业在激烈的市场竞争中脱颖而出&#xff0c;实现数字化转型的跨越式发展。 SolidWorks Electrical电气数据库具备强大的数据整合能力。它能够将企业内部各…

揭秘大前端开发方向的新机遇!

众所周知&#xff0c;华为开发者大会2023&#xff0c;宣布不再兼容安卓&#xff0c;同时宣布了“鸿飞计划”&#xff0c;欲与iOS、安卓在市场三分天下&#xff0c;这对中国国产操作系统而言&#xff0c;具有划时代的意义。 鸿蒙应用开发的兴起&发展 鸿蒙操作系统是华为自…

【解决】安装模块时报错:ERROR: *.whl is not a valid wheel filename.

其实错误信息已经告诉你了&#xff0c;就是你的文件名有问题。在你下载whl文件时一定要注意原文件的文件名&#xff0c;不要改动文件名。 以我安装pandas模块为例吧。 在我下载whl文件时&#xff0c;因为网速太慢&#xff0c;我就下载了多次&#xff0c;导致文件名变成了这个…

SpringBoot 中的日志原来是这么工作的

在有些场景&#xff0c;能通过调整日志的打印策略来提升我们的系统吞吐量,你知道吗&#xff1f; 我们以Springboot集成Log4j2为例&#xff0c;详细说明Springboot框架下Log4j2是如何工作的&#xff0c;你可能会担心&#xff0c;如果是使用Logback日志框架该怎么办呢&#xff1…

01_QT编译报错:Cannot find file:问题解决

QT编译报错&#xff1a;Cannot find file:问题解决 报错原因&#xff1a;创建路径存在中文字符&#xff0c;将文件路径改为英文字符即可

睿贝外贸ERP appPatchDownLoad 任意文件读取漏洞复现

0x01 产品简介 睿贝外贸ERP软件专门为中小型外贸企业打造的一款界面简洁、易用、操作人性,几乎实现零培训, 彻底颠覆其他软件漫长的实施周期。主要功能包括邮件管理、客户管理、邮营销、产品、供应商,报价、订单、出运、单证、财务、利润分析等一体化管理,以及Excel格式单…

防错设计及原理

目录 1、防错的作用 2、防错的原理 2.1断根原理 2.2保险原理 2.3自动原理 2.4相符原理 2.5顺序原理 2.6隔离原理 2.7层别原理 2.8复制原理 2.9警告原理 2.10缓和原理 防错法&#xff08;Poka-Yoke&#xff09;&#xff0c;又称愚巧法、防呆法&#xff0c;是一种在作…

用python写的文本水印隐藏工具

看见一个文本水印项目&#xff1a;text_blind_watermark https://github.com/guofei9987/text_blind_watermark 假设这种场景是不是合适使用&#xff1a; 小米su7发布之前一周&#xff0c;各大博主已经已经拿到参数配置和价格表了&#xff0c;保密政策不允许博主提前发布&…

Java基础入门--第十一章--JDBC(Java Database Connection)Java数据库连接

JDBC 11.1 什么是JDBC11.1.1 JDBC概述11.1.2 JDBC驱动程序 11.2 JDBC的常用API11.3 JDBC编程11.3.1 JDBC 编程步骤11.3.2 实现第一个JDBC程序 我的MySQL的root密码: root 11.1 什么是JDBC 11.1.1 JDBC概述 JDBC的全称是Java数据库连接&#xff08;Java Database Connectivit…