单链表中二级指针的使用以及单链表的增删查改操作

news2024/12/24 22:10:52

前言:在链表的构建中,链表的初始化和销毁为何需要使用一个二级指针,而不是只需要传递一个指针就可以了,其问题的关键就在于c语言的参数传递的方式是值传递
那么,这篇文章就来聊一聊在链表的初始化中一级指针的传递和二级指针的区别,并总结给出单链表的C语言的增删查改操作详细代码.

开整开整:

 

目录

 

1.一级指针和二级指针的区别

函数栈帧的创建和销毁以及不同类型的变量存储区

关于NULL,你了解多少?

2.链表中的相关指针操作

一级指针+头结点初始化 = 二级指针

初始化头结点的两种方式:

1.自定义头结点初始化函数

2.手写结构体构造函数

3.单链表的基本功能实现

3.1带头节点的单链表

3.2不带头节点的链表

4.金句频道


1.一级指针和二级指针的区别

一级指针这里不再进行阐述,我们重点是来说说二级指针的原理及应用场景,我们现在试想以下的场景:

其实在大多数场景下用不到二级指针,但是,作为链表,也只是需要在特殊情况下才需要使用二级指针,

预备知识:

函数栈帧的创建和销毁以及不同类型的变量存储区

       我们都知道,函数在调用时会创建栈帧,在栈帧中实现函数的相关操作,比如创建临时变量和开辟空间等操作,但是这里需要注意的是,空间开辟是在堆区,堆区不会受函数栈帧的影响,也就是说函数栈帧内部开辟的空间,是直接在堆区上开辟的,并不会随着函数栈帧的销毁而销毁,有关函数栈帧的问题,可以参考函数栈帧的创建与销毁,而函数内部创建的变量不一样,这些变量都会随着函数调用的结束,函数栈帧的销毁而销毁,并且在函数传值调用时,形参变量只是复制了一份实参变量,并不会真的拿实参变量来操作,这也就有了传值调用和传址调用的区别,我们的地址是唯一的,当我们采用传址调用时,通过地址想当于我们直接找到了实参变量,也就相当于间接操作实参。

关于NULL,你了解多少?

       NULL一般表示无效地址,我们所说的置空就是将指针指向NULL,对NULL的解引用操作是不合法的,NULL其地址值为0,而由于任何进程的0地址开始存储的都是系统关键地址,比如进程的退出,堆栈维护,键盘处理等系统控制程序的地址。因此0地址是不允许用户代码中直接读写访问的(hacking除外),如果某指针被赋予NULL,之后该指针被用来操作对象或内存,要么在编译时报错,要么运行时程序崩溃。

       指针被赋值为NULL的意义在于,将NULL作为唯一无效指针的标志,明确规定指针值要么为NULL要么为其他有效地址,方便后续代码判断该指针的有效性,以便代码不会访问无效地址.

所以,我们尝试通过NULL进行传参时一定要注意,我们需要注意此时函数内部的操作是否是合法的,并且,如果我们想要操作以NULL传入的变量时,在栈区上进行的操作都会随着函数栈帧的销毁而失效,其原因可以解释为是对NULL指针的传入是无效的,我们知道传入一个地址可以修改该地址上的值,但是对于传入的NULL指针来说,不管函数内部的局部变量如何操作,在这个函数结束后,都会回到传入NULL指针的状态,下面,我们给出有关二级指针的操作示例:

#include<bits/stdc++.h>
void func1(int* pp)
{
	pp = (int*)malloc(sizeof(int)*10);//分配了空间,相当于有了可以辨识的唯一地址,但是由于pp传入时拷贝的是NULL指针,是无效指针,所以pp的操作不会影响到实参p
	*pp = 1;
	printf("%d\n", *pp);
}
void func2(int* p)
{
	*p += 4;//由于p此时的地址是有效地址,所以我们相当于在操作a
	int* q = p;
	*q += 4;//同理,这相当于指针的传递,但是需要在实参指针有效的情况下,这里只是指出不要产生实参是什么类型,形参就要用它的指针的固化概念(当然二级指针也是对的)
}
void func3(int** p)
{
	printf("%p\n", p);
	printf("%p\n", *p);
	*p = (int*)malloc(sizeof(int));
	printf("%p\n", *p);
	**p = 3;
	
}
int main()
{
	int* p = NULL;
	//这里注意&p一定不是NULL,p相当于是个指针变量,开了变量p就要有有效的地址,只是这个地址上存储的是无效的地址罢了
	func1(p);
	printf("%p\n", p);
	func3(&p);
	printf("%p\n", p);
	printf("%d\n", *p);
	printf("%d\n", *p);
	int a = 3;
	int* pp = &a;
	func2(pp);
	printf("%d\n", *pp);
	

	return 0;
}

我们有如下的运行结果:

下面是二级指针和一级指针的示意图:

 

 可见,我们操作NULL指针的唯一的途径,就是传NULL指针的指针,这些我们会在接下来的链表操作中用到,希望读者理解。

2.链表中的相关指针操作

通过上述的分析,我们将其应用到链表中,来思考以下几个问题:

1.如何初始化链表才能避免对NULL指针的操作,正常的操作链表?

2.链表添加头结点和不添加头结点有什么不同之处?

3.有没有方法可以在使用一级指针的情况下能够正常操作链表?

首先,我们在前面说,对操作NULL指针的唯一方法就是传二级指针,那么我们在链表中,也需要传二级指针,所以我们在设计参数时,要传入的就变成了结构体的二级指针,对二级指针变量解引用才是合法的操作,具体可看下面的示例:

void LinkedListDestroy(SingleNodeList **head){//7释放链表

         SingleNodeList *p=*head,*q;

         while(p!=NULL){

                   q=p;

                   p=p->next;

                   free(q);

         }

         *head=NULL;

}

是不是所有的操作都需要二级指针?

并不是这样的,在链表的操作中,有很多不会改变链表状态的函数,如打印输出函数,寻找特定值得函数,这些函数并不会操作和改变链表结构,所以并不需要二级指针,当然,在前面,我们的例子中也看出,有些操作直接采用一级指针的传递就可以进行,当然,这需要一个很重要的前提:实参一定不是NULL,实参一定不是NULL,实参一定不是NULL,这个前提很重要,将在后面展开。

头结点的意义在哪?

我们在链表函数的设计时,经常需要考虑链表是不是为空,而头结点存在的意义就是为了消除这个考虑因素,通过在链表头部增加一个虚拟节点(实际存在,但是我们并不会使用)来使链表的操作更加统一和简便,这样,我们在空链表的时候,就不会在出现对NULL指针的错误解引用操作了,因为此时我们的链表至少存在一个头结点了。

一级指针+头结点初始化 = 二级指针

从前面的例子中我们不难看出,二级指针是适用于实参是NULL的情况与实参是正常有效指针的情况下的统一操作,那么我们就可以直接通过设置头结点来将链表初始化为不为NULL的指针,这样就不会在出现,对NULL指针的误解引用了,哼哼,终于不用写两个*了~~~

 哎~~等等,先回来,我再问一句,那头结点怎么开辟呢?(对不起,二级指针,我错了,没了你我怎么初始化啊,回来吧)

庆幸的是,我们这里只需要在初始化头结点的时候用二级指针就好了,其他的情况下,直接用头结点指针操作就好了。


初始化头结点的两种方式:

1.自定义头结点初始化函数

// 初始化链表法,创建空头结点
void InitSList(SLTNode** head)
{
    *head = (SLTNode*)malloc(sizeof(SLTNode));
    (* head)->data = -1;
    (* head)->next = NULL;
    
}

你初始化数据域为-1,那万一我链表中的值就是-1怎么办?

      你这问题问的好(其实是我自己想说),我们的真正的数据域要想和头节点的数据域区分的话还是比较简单的,其一,我们在操作时可以使指向链表的指针直接从第二个结构体开始遍历,直接跳过头结点,其二,如果某些操作下必须从头结点开始遍历,那也可以直接在结构体上再加一个标记,将头结点标记一下,一般还是不需要这样操作的。

2.手写结构体构造函数

会c++的朋友你们有福了,我们只需要手写一个结构体内部的构造函数来实现初始化头结点的功能,如下:

头结点初始化函数直接两行结束:

//构造函数法创建头结点
SLTNode* head = NULL;
//采用构造函数实现方式,初始化头结点为-1和NULL
head=new SLTNode;

3.单链表的基本功能实现

3.1带头节点的单链表

//SList.h

#pragma once  //防止头文件重复包含

//头文件的包含
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//符号和结构的声明
typedef int SLTDataType;  //数据类型重命名
typedef struct SListNode   //链表的一个节点
{
	SLTDataType data;
	struct SListNode* next;  //存放下一个节点的地址
	
	struct SListNode(SLTDataType d=-1, SListNode* p = NULL)//构造函数法创建头结点(C++)
	{
		data = d;
		next = p;
	}
}SLTNode;//如果这里可以写SLTNode,*SLT;那么所有的二级指针都可以替换成SLT*

//函数的声明
// 初始化链表,创建空头结点
void InitSList(SLTNode** head);
//创建新建节点
SLTNode* BuySLTNode(SLTDataType x);
//在头部插入数据
void SListPushFront(SLTNode* pphead, SLTDataType x);
//销毁链表
void SListDestory(SLTNode* pphead);
//在尾部插入数据
void SListPushBack(SLTNode* pphead, SLTDataType x);
//查找指定数据
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
//在pos之前插入数据
void SListInsert(SLTNode* pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入数据
void SListInsertAfter(SLTNode* pphead, SLTNode* pos, SLTDataType x);
//打印链表
void SListPrint(SLTNode* phead);
//在头部删除数据
void SListPopFront(SLTNode* pphead);
//在尾部删除数据
void SListPopBack(SLTNode* pphead);
//删除pos位置处的数据
void SListErase(SLTNode* pphead, SLTNode* pos);
//删除pos位置后的数据
void SListEraseAfter(SLTNode* pphead, SLTNode* pos);
//修改pos位置处的函数
void SListModify(SLTNode* phead, SLTNode* pos, SLTDataType x);

//SList.cpp

#include "Slist.h"

// 初始化链表法,创建空头结点
void InitSList(SLTNode** head)
{
    *head = (SLTNode*)malloc(sizeof(SLTNode));
    (* head)->data = -1;
    (* head)->next = NULL;
    
}
//创建新建节点
SLTNode* BuySLTNode(SLTDataType x)
{
    //手动创建
   // SLTNode* newnode= (SLTNode*)malloc(sizeof(SLTNode));
    //newnode->data = x;
    //newnode->next = NULL;
    

    //构造函数创建 
    SLTNode* newnode = new SLTNode(x);//其他未传入的变量都初始化为默认值

    return newnode;
}

//在头部插入数据
void SListPushFront(SLTNode* pphead, SLTDataType x)
{
    SLTNode* p = pphead;
    SLTNode* q = p->next;
    p->next = BuySLTNode(x);
    p->next->next = q;//链接

}

//销毁链表
void SListDestory(SLTNode* pphead)
{
    //注意,这里我们认为销毁链表不会销毁头结点,所以我们要从第二个也即有效数据节点开始
    SLTNode* p = pphead->next;
    if (!p)
        return;
    else
    {
        while (p)
        {
            SLTNode* temp = p->next;//先记住要销毁节点的下一个节点,避免链接断开导致无法删除
            free(p);
            p = NULL;
            p = temp;
        }
    }
    
}
//在尾部插入数据
void SListPushBack(SLTNode* pphead, SLTDataType x)
{
    SLTNode* p = pphead;
    while (p->next)//这里要注意不能写while(p)这样会导致插入的数据链接不到链表上,原因是在函数内部变量的东西都会随着函数的结束而销毁,创建的空间随之也会内存泄漏,具体可以画物理图看看
    {
        p = p->next;
    }
    p->next = BuySLTNode(x);
     
}
//查找指定数据
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
    SLTNode* p = phead->next;
    while (p)
    {
        if (p->data == x)
            return p;
        p = p->next;
    }
   //如果查找不到,则返回NULL
    return NULL;
}
//在pos之前插入数据
void SListInsert(SLTNode* pphead, SLTNode* pos, SLTDataType x)
{
    SLTNode* p = pphead->next;
    while (p->next!=pos&&p)//找到pos前面的节点
    {
        p = p->next;
    }
    //如果找不到pos,返回NULL
    if (p == NULL)
        return;
    else
    {
        p->next = BuySLTNode(x);
        p->next->next = pos;
    }
    
}
//在pos之后插入数据
void SListInsertAfter(SLTNode* pphead, SLTNode* pos, SLTDataType x)
{
    SLTNode* p = pphead->next;
    while (p != pos&&p)//找到pos节点处
    {
        p = p->next;
    }
    if (p == NULL)//没找到pos
        return;
    else
    {
        SLTNode* q = p->next;//保存pos后面的节点
        p->next = BuySLTNode(x);
        p->next->next = q;
    }
}
//打印链表
void SListPrint(SLTNode* phead)
{
    SLTNode* p = phead->next;//我们创建的头结点不需要打印
    while (p->next)//这里的目的只是为了好看,中间可以加个箭头
    {
        printf("%d->", p->data);
        p = p->next;
    }
    printf("%d", p->data);
    printf("->NULL\n");
}
//在头部删除数据
void SListPopFront(SLTNode* pphead)
{
    SLTNode* p = pphead->next;
    if (!p)
    {
        printf("当前链表为空\n");
        return;
    }
    else
    {
        //防止内存泄漏,我们要将第一个数据free掉
        SLTNode* q = p->next;
        free(p);
        p = NULL;
        pphead->next = q;
    }
}
//在尾部删除数据
void SListPopBack(SLTNode* pphead)
{
    SLTNode* p = pphead->next;
    if (!p)
    {
        printf("当前链表为空\n");
        return;
    }
    else if (p->next == NULL)//只有一个节点(不算头结点)
    {
        free(p);
        p = NULL;
    }
    else //两个及以上节点数
    {
        while (p->next->next)//这里我们要在删除最后一个节点的同时将原倒数第二个节点的next置空,所以我们需要一次向后看两个节点
        {
            p = p->next;
        }
        free(p->next);
        p->next = NULL;
    }

}
//删除pos位置处的数据
void SListErase(SLTNode* pphead, SLTNode* pos)
{
    SLTNode* p = pphead->next;
    while (p->next!= pos&&p)
    {
        p = p->next;
    }
    if (!p)
    {
        printf("找不到节点pos\n");
        return;
    }
    else
    {
        SLTNode* q = p->next->next;//记住pos后面的节点
        free(p->next);
        p->next = q;
    }
}
//删除pos位置后的数据
void SListEraseAfter(SLTNode* pphead, SLTNode* pos)
{
    SLTNode* p = pphead->next;
    while (p!= pos && p)
    {
        p = p->next;
    }
    if (!p)
    {
        printf("找不到节点pos\n");
        return;
    }
    else
    {
        SLTNode* q = p->next->next;//记住pos后面的后面的数据
        free(p->next);
        p->next = q;
    }
}
//修改pos位置处的函数
void SListModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
    SLTNode* p = phead->next;
    while (p != pos && p)
    {
        p = p->next;
    }
    if (!p)
    {
        printf("找不到节点pos\n");
        return;
    }
    {
        p->data = x;
    }
}

//测试代码,样例均为合法样例,其他样例均为测试,如果发现bug还望指出,感谢

#include <bits/stdc++.h>
#include "Slist.h"

//测试代码
int main()
{
	//构造函数法创建头结点
	SLTNode* head = NULL;
	//采用构造函数实现方式,初始化头结点为-1和NULL
	head=new SLTNode;
	//手动修改为头结点,因为我们的构造函数后序可能还会用到
	 
	//采用自写初始化函数的方式创建头几点,注意NULL指针初始时不能解引用,所以我们只能用二级指针
	//InitSList(&head);
	SListPushFront(head, 1);
	SListPushFront(head, 2);
	SListPushBack(head, 3);
	SListPushBack(head, 4);
	SLTNode* p = SListFind(head, 3);
	if (p)
		printf("%d\n", p->next->data);
	SListInsert(head, p, 5);
	SListInsertAfter(head, p, 6);
	SListInsertAfter(head, p, 6);
	SListPopFront(head);
	SListPopBack(head);
	SListModify(head,p, 4);
	SListEraseAfter(head, p);
	SListErase(head, p);
	
	SListPrint(head);
	
	
	return 0;
}

3.2不带头节点的链表

终究还是逃不过二级指针......,咳咳,开个玩笑,二级指针很好理解的,现在早早学会了就不怕了,那不是更好嘛?这里我就偷个懒,只给出声明和实现啦,嘿嘿

// slist.h
typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x

void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值

void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SList* plist);
// slist.c
#include "SList.h"


SListNode* BuySListNode(SLTDateType x)
{
	SListNode* node = (SListNode*)malloc(sizeof(SListNode));
	node->data = x;
	node->next = NULL;

	return node;
}

void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	//while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

void SListPushBack(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}
}

void SListPopBack(SListNode** pplist)
{
	SListNode* prev = NULL;
	SListNode* tail = *pplist;
	// 1.空、只有一个节点
	// 2.两个及以上的节点
	if (tail == NULL || tail->next == NULL)
	{
		free(tail);
		*pplist = NULL;
	}
	else
	{
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;
	}
}


void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);

	// 1.空
	// 2.非空
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		newnode->next = *pplist;
		*pplist = newnode;
	}
}

void SListPopFront(SListNode** pplist)
{
	// 1.空
	// 2.一个
	// 3.两个及以上
	SListNode* first = *pplist;
	if (first == NULL)
	{
		return;
	}
	else if (first->next == NULL)
	{
		free(first);
		*pplist = NULL;
	}
	else
	{
		SListNode* next = first->next;
		free(first);
		*pplist = next;
	}
}

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}

	return NULL;
}

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* next = pos->next;
	// pos newnode next
	SListNode* newnode = BuySListNode(x);
	pos->next = newnode;
	newnode->next = next;
}

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	// pos next nextnext
	SListNode* next = pos->next;

	if (next != NULL)
	{
		SListNode* nextnext = next->next;
		free(next);
		pos->next = nextnext;
	}
}

4.金句频道

      初中,你哭了,整个班级都围过来问你怎么了。高中,你难过,几个死党摸摸,告诉你还有我们没关系。大学,没人管你怎么样。工作了,人家只会觉得你演技很棒。其实,成长就是逼着你一个人去坚强。只有当你变强大,你才不害怕孤单,当你变好,你才会遇到更好的。

 

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

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

相关文章

AutoCAD使用技巧

AutoCAD使用技巧 环境说明AutoCAD 导入EXCELAutoCAD移动对象到原点 环境说明 本文基于AutoCAD 2021版本。 AutoCAD 导入EXCEL 如果菜单栏隐藏点击显示菜单栏&#xff1a; 在excel中选中copy内容赋值&#xff0c;AutoCAD中选择&#xff1a;编辑-选择性粘贴-作为AutoCAD图元…

必用WhatsApp营销的4个理由

WhatsApp是世界上最受欢迎的消息传递应用程序。每天有1万新用户加入WhatsApp。各种规模的公司都利用该平台与世界各地的客户进行有效的沟通&#xff0c;这要归功于其广泛的覆盖范围、用户友好的设计和安全的端到端加密。因此&#xff0c;WhatsApp聊天机器人迅速普及。 1.为您的…

Kotlin的出现无疑是为了超越Java而存在

Kotlin的出现无疑是为了超越Java而存在。在Google I/O 2017中&#xff0c;Google 宣布 Kotlin 成为 Android 官方开发语言&#xff0c;背景就是Oracle告Google侵权使用java。众所周知&#xff0c;Java的跨平台的开发语言&#xff0c;得益于虚拟机。我比较关注Kotlin用于Android…

Python 使用pipreqs命令生成 `requirements.txt`报错

Python 使用pipreqs命令生成 requirements.txt报错&#xff1a;Fatal error in launcher: Unable to create process using ‘“E:\Anaconda\python.exe” “D:\Anaconda\Scripts\pipreqs.exe” ./ --encodingutf-8’: ??? 问题描述—Python 使用pipreqs命令生成 requireme…

47.对齐网格项目和轨道

首先我们先将模块1关闭&#xff0c;模块2使用CSS网格 display: none;.container--2 {/* STARTER */font-family: sans-serif;background-color: black;font-size: 40px;margin: 100px;width: 1000px;height: 600px;/* CSS GRID */display: grid;}然后我们常间一个三列两行的网…

STM32开发(十六)STM32F103 片内资源 —— 实时时钟RTC 详解

文章目录 一、基础知识点二、开发环境三、STM32CubeMX相关配置四、Vscode代码讲解五、结果演示串口显示乱码解决方案 一、基础知识点 本实验通过stm32片内资源RTC实现实时时钟&#xff0c;通过数码管显示时间。设定闹钟&#xff0c;实现准点报时。 数码管相关知识点&#xff1…

maven总结

maven maven1.构建依赖2.依赖冲突的规则3.可选依赖和排除依赖可选依赖排除依赖 4.依赖范围5.项目构建生命周期6.插件7.模块聚合8.模块继承9.属性&#xff08;类似常量&#xff09;&#xff08;1&#xff09; 自定义属性&#xff08;2&#xff09;直接调用内置属性 10.多个环境配…

PingPingPing

拿到题的时候我是没有丝毫的思路&#xff0c;可能是没有做过太多命令注入的题目&#xff0c;所以反应不过来 还是查看的别人的wp&#xff0c;得知这是一道有关命令注入的题目 通过页面提示传入ip并且利用管道符查看所有文件 但是当我们查看flag.php的时候发现页面过滤掉了空格 …

诊断DTC故障码两种形式的转换:符号转数字

诊断DTC code数据由3个字节组成:HighByte + MiddleByte + LowByte。有两种表现形式: 数字:三个字节值用16进制表示,比如0xC07304符号:这种形式更直观地用字符描述该DTC故障所属的系统,故障类型等信息我们重点讲一下符号的形式: 字符形式的DTC故障码,由7个字符组成,代…

Chatbox - 一款适用于 GPT-4 / GPT-3.5 (OpenAI API) 的桌面应用程序

简介 给大家推荐一款适用于 GPT-4 / GPT-3.5 (OpenAI API) 的桌面应用程 ChatBox&#xff0c;开源的 ChatGPT API (OpenAI API) 跨平台桌面客户端&#xff0c;Prompt 的调试与管理工具&#xff0c;也可以用作 ChatGPT Plus 平替。 下载 ► chatBox 下载安装 ⇲ 为什么不直接…

3D目标检测--PointPillars论文和OpenPCDet代码解读

文章目录 1. 论文动机2. PointPillars概述3. PointPillars模型3.1 Pillar Feature Net3.1.1 Pillar Feature Net模块理论解析3.1.2 Pillar Feature Net模块代码解析 3.2 Backbone&#xff08;2D CNN&#xff09;3.2.1 Backbone&#xff08;2D CNN&#xff09;模块理论解析3.2.2…

LeetCode 312. Burst Balloons【区间DP】困难

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

校验规则引擎

目录 一 架构设计图 二 表设计及数据展示 三 顶层接口 四 压测结果 五 其他规则引擎比较 适用场景&#xff1a;校验场景以及使用该思想进行可视化配置化开发&#xff08;可大幅提高开发效率&#xff0c;长期维护简单&#xff09; 例如&#xff1a;履约系统下单中的校验&…

C++(初识结束)

目录 4缺省参数 4.1缺省参数的概念 4.2缺省参数分类 4.3缺省参数的应用 5.函数重载 5.1函数重载概念 5.2c支持函数重载的原理-名字修饰&#xff08;name Mangling&#xff09; 6.引用 6.1引用概念 6.2引用特性 6.3常引用 6.4使用场景 6.4.1做参数 6.4.2做返…

2023年4月-近期看书

复习书记 用于读书 文章目录 复习书记一、(2001)控制工程基础二、(3001)交通管理与控制三、(1001)英语 一、(2001)控制工程基础 学习这本书的前6章节。 参看视频链接&#xff1a; https://www.bilibili.com/video/BV1Sb411q7jU?p8&spm_id_frompageDriver&vd_source…

构建完善的帮助中心:降低企业客户服务成本,提高客户满意度

客户服务是企业成功的关键之一&#xff0c;一家公司的成功不仅仅取决于产品或服务的质量&#xff0c;还在于能否为客户提供优质的客户服务。随着现代科技的发展&#xff0c;企业需要面对越来越多的客户服务需求&#xff0c;不良的客户服务将对企业的声誉和业务产生严重的影响。…

马斯克爆料Twitter裁了八成员工;OpenAI CEO:GPT-5根本不存在;小鹏被曝年终奖打0.5折 | AI一周资讯

来源: AI前线 微信号&#xff1a;ai-front 整理 | 凌敏 微软宣布开源 Deep Speed Chat&#xff1b;消息称软银旗下 Arm 启动赴美 IPO&#xff1b;国家网信办出台生成式 AI 管理办法&#xff1b;前理想 AI 芯片一号位骄旸加入三星&#xff0c;负责组建 GPU 团队…… 资 讯 Op…

KHS灌装机西门子触摸屏维修6AV6545-6DB10-0BS0

KHS设备维修范围包括&#xff1a;KHS无菌灌装设备&#xff0c;KHS包装设备&#xff0c;PET瓶装机&#xff0c; KHS码垛设备&#xff0c; KHS灌装机。 科埃斯KHS灌装机西门子P350工控机常见故障现象 1、工控机开机有显示&#xff0c;但是屏幕很暗&#xff0c;用调亮度功能键调…

熵、贝叶斯、极大似然

熵及熵在机器学习中的作用 熵 reference&#xff1a;https://blog.csdn.net/tsyccnh/article/details/79163834 香农给熵的定义&#xff1a;无损编码事件信息的最小平均编码长度 直观理解熵的定义&#xff1a;表示某一件事的不确定性 I ( x 0 ) − l o g ( p ( x 0 ) ) I(…

【Java版oj】day37数据库连接池、mkdir

目录 一、数据库连接池 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、mkdir &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、数据库连接…