(一)摘要
本期博客内容继续深入C语言指针部分,针对一些特殊的指针类型进行解释举例,还有数据结构无头非循环单链表的实现,然后还会夹杂一些杂项题目,希望大家喜欢。
上篇博客我们讲到了整型指针、字符指针、数组指针、指针数组的一些简单用法,具体可以看下上一篇博客,这里我们再对于指针数组和数组指针进行进一步的了解。
(二)数组指针与指针数组的理解
下面我们来看一段代码例子吧,这样方便理解:
#include <stdio.h>
int main()
{
int arr1[5];//整型数组,数组元素为五个int型
int* arr2[5];//指针数组,数组元素为五个int*的指针
int(*arr3)[5];//数组指针,一个名为arr3的指针,指向的是一个五个元素的整型数组
int(*arr4[5])[5];//看完上面的介绍,那这个应该如何去分析呢?
}
第一个整形数组大家都理解就不多说了,指针数组是在原来的数组基础上数组名前面加个“*”,但说是这么说,其实*是和前面的int绑一起的,为什么这么说呢,那就要讲到一个小技巧了,这个小技巧是关于如何去判断一个数组元素类型的,比如:int arr[5],我们在看的时候可以把数组名去掉就像这样:int [5],然后这里的“int”代表数组元素的类型,而“[5]”则表示的是数组的元素的个数,同理int* arr[5],去掉数组名后变成int* [5],可以看到数组的元素为“int*”这里表示整型指针,而数组元素为五个,合起来就是一个元素为五个整型指针的数组,也就是指针数组,这也解释了上面为什么*号是与int结合而不是和数组名结合,而如果在*arr上加一个()那表示的意思就完全不一样了,因为()是优先级最高的符号,所以系统判断会优先考虑圆括号里面的,这里如果加了圆括号后圆括号里的就是一个指针就是这样int (*arr) [5],然后再根据上述的取名法(我自己取的)将*arr去掉后变成int [5],这里表示指针所指的对象为一个数组,数组的元素类型为整型且个数为五,这就是数组指针,指针数组与数组指针很容易让人产生混淆,不知道哪个是哪个,这个时候可以看是否有圆括号,如果有圆括号就是指针,没有圆括号只有方括号的就是数组,通俗一点讲就是这样。
现在知道如何判断数组指针和指针数组了,那上述代码的最后一行该怎么判断呢?
我来画图给大家分析一下:
可能会有些抽象,但如果一步一步拆开理解就简单了。
(三)数组传参的问题理解(与指针相关)
首先我们先看下面一段代码:
#include <stdio.h>
void test(int *arr1)
{
for (int i = 0; i < 2; ++i)
{
printf("%p\n", (arr1 + i));
}
}
void test2(int arr1[])
{
for (int i = 0; i < 2; ++i)
{
printf("%p\n", (arr1 + i));
}
}
void test3(int **arr2)
{
printf("%p\n", arr2);
}
int main()
{
int arr1[2]={0};
test(arr1);
for (int i = 0; i < 2; ++i)
{
printf("%p\n", (arr1 + i));
}
int* arr2[5]={0};
printf("%p\n", arr2);
test3(arr2);
}
这里我们定义了三个test函数,然后将数组,指针数组等作为形参传进去,首先是arr1[2]这个数组,我们将它的数组名传给test与test3函数,众所周知,数组名是数组首元素的地址,所以传数组名相当于传地址,所以我们需要用一个地址或者指针来接受它传过来的地址,这里特殊就特殊在我们用int arr1[]来接收,你们会不会纳闷为什么用个数组来接收地址,注意此数组非彼数组,这里我们要除去int和[],只看arr1,这里起作用的只有它,并且它在这里是作为一个地址或者说是指针来用的,别问为什么,问就是语法规定,我也是问了学长好半天才明白,然而int arr1[]这种接收方法跟int *arr1一模一样。
可以看到当你将数组名arr1传入时,你打印两者元素地址时,两者的地址相同,可见这两个代表的意思一样,为什么呢,因为int *arr1就是相当于定义一个指针,别学到这忘记了本,我们定义指针就是数据类型 *p,而这里的p就是arr1,所以我们相当于用一个指针接受数组名,这当然是没问题的,因为你传的是地址嘛。
再就是arr2数组名的传入,函数该用什么去接收这个数组名呢,注意哈这里的arr2[5]是一个指针数组,千万别想当然的认为你传的不是一个地址嘛,我也像上面的数组arr1一样用int *arr1一样用int *arr2这个指针来接收,如果这样想就错了,还是语法错误语法不允许你这样,因为arr2[5]是一个指针数组,那数组里的元素就是指针,如果你只定义一个指针来接收,这个一级指针只是指向这个数组的一个指针元素,然而数组里的int*指针它还记录了一个整型的地址,语法上必须让你这个接收的这个指针指向这个整型就必须指到底,所以要用二级指针来指,没错这是语法上这么要求的,我也只能这么解释,不能说多了,不然会头晕。
具体可以看看这个图,可能会有点抽象,这些毕竟也是我自己个人的理解加上学长的指导得出的结论,所以看的话有点绕,主打一个不要想太多就行。
可以看到这两种情况打印出来的地址都是相同的所以这两个是一样的,再一个就是网课上的用int ****arr2来接收(这里的*号个数大于等于2)也跟上述的两种情况相同,还是那句话别盲目的跟着网课,就拿我看的网课为例,我看的网课老师用的VS2013编译器编译多个*是没有问题的,但我用VS2022编译就不行,语法吧允许,所以有些东西生僻的就不用乱用,要用也要在了解充分后再用,不然就会出现一些不必要的麻烦。
(四)无头非循环单链表的创建
5.1 单链表的定义
typedef int SLCDatatype;//定义数据类型
typedef struct SListNode //SListNode这个东西要定义两遍,上面的这个是给这个结构体用的因为里面有个这个结构体类型的结点
{//确定结点的类型
SLCDatatype Data;//定义结点的数据域类型
struct SListNode* next;//定义结点的指针域类型
}SListNode;
这里给int重命名一下是为了说明这是一个一个数据,不仅仅局限于int型,我们首先定义一下结点的类型,结点SListNode为一个结构体类型,里面存有数据域Data,以及指针域*next。
5.2 单链表的创建
SListNode* CreateSListNode(SLCDatatype x)
{//创建结点,给结点的数据域赋值,然后指针域置空(因为没有开始只有一个结点)
SListNode* p = (SListNode*)malloc(sizeof(SListNode));//为结点动态申请相应空间
if (p == NULL)
{//判断是否从堆中成功申请一个动态空间
printf("从堆中索要一个动态空间失败\n");
exit(-1);
//若没有申请到,则直接终止程序进行
}
//申请成功后,对结点的数据域进行赋值,指针域置空(当只有一个结点时)
p->Data = x;
p->next = NULL;
return p;
}
首先为结点开辟一个内存空间,然后在判断这个空间是否申请成功,注意这里的堆,对于动态申请的内存空间都存放于这个堆中(堆区 (heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,专门用于管理动态内存空间),如果申请不成功,直接就结束程序,结点空间申请失败,如果申请成功那就给结点的数据域赋值,再让指针域为空。
5.3 单链表的尾部插入结点
void SListPushBack(SListNode** pplist, SLCDatatype x)//因为形参要想改变实参要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int *a,同理如果传入的是指针,就要在指针前加个*变成二级指针才可以改变一级指针的方向
{//尾部插入结点
SListNode* createnode = CreateSListNode(x);//创建一个你想要插入的新结点
if (*pplist == NULL)
{//判断传入的单链表是否为一个空表,若为空表,直接让*pplist=*createnode即可
*pplist = createnode;
return;
}
//如果传入单链表非空
SListNode* TailList = *pplist;//定义一个指针使它指向链表此时的头
while (TailList->next)
{//遍历链表,让这个指针指向链表的最后一个结点
TailList = TailList->next;
}
TailList->next = createnode;//让最后一个结点的指针域存放新创建结点的地址
}
注意这里的函数的传参,因为我们要通过形参的指针改变实参的指针方向所以要用二级指针来进行这一操作,打个比方一个整型变量int a,形参要想改变实参需要要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int *a,同理如果传入的是指针,就要在指针前加个*变成二级指针才可以改变一级指针的方向。解释完传参的问题我们在进入下一步,首先我们先用上面定义创建结点的函数创造一个新结点,因为传入的是链表的头指针,所以我们先对它进行判断,判断此时传入的单链表是不是一个空表,如果是那直接让头指针指向这个新结点就行,如果不是我们则需定义一个尾指针TailList让它指向单链表的最后一个结点,那么怎么让它指向最后一个结点呢,这里我们就要用一个循环来实现,首先我们需要让这个尾指针指向第一个结点,因为最后一个结点的指针域为空,所以就以此为循环条件,结点的指针域非空就让TailList这个指针指向下一个结点,直到指针域为空跳出循环,找到最后一个结点的位置后让最后一个结点的指针域存放新结点的地址,把它们连起来,这样尾插就完成了。
5.4 单链表的头部插入结点
void SListPushFront(SListNode** pplist, SLCDatatype x)//因为形参要想改变实参要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int *a,同理如果传入的是指针,就要在指针前加个*变成二级指针才可以改变一级指针的方向
{//头部插入结点
SListNode* createnode = CreateSListNode(x);//创建一个你想要插入的新结点
createnode->next = *pplist;//让新结点的指针域存放传入链表第一个结点的地址,也就是让新结点的next指针指向传入链表的第一个结点
*pplist = createnode;//让pplist指针重新指向新链表的头部
}
首先和尾插一样要先创建一个新的结点才能插,因为传进来的是头指针,头指针头指针肯定只能指向第一个结点,所以我们先让新结点的指针域存放之前第一个结点的地址,让这个新结点变成第一个结点,现在这个新结点变成头了,没有王冠头指针*pplist还不行,所以最后要将*pplist指向这个结点,到此前插完成。
5.5 单链表在任意结点位置之后插入结点
void SListInsertAfter(SListNode** pos, SLCDatatype x)
{//在任意结点位置之后插入一个结点
assert(*pos);//判断所传的结点是否为空,若为空则终止程序,不可能在一个空结点后面插入一个结点吧,对不对,你前面都是空的你搁哪插。
SListNode* createnode = CreateSListNode(x);//创建一个你想要插入的新结点
createnode->next = (*pos)->next;//让新结点的指针域存放插入结点后的结点的地址,通俗一点说就是让新结点的尾巴指向插入结点后面的结点,注意这里为什么要给*pos加个括号,因为这里的->大于*,而我们这里要用的是pos结点里的next指针,所以要先对pos进行解引用打开它再用->
(*pos)->next = createnode;//再让原来的结点的指针域存放新结点的地址,通俗一点说就是让插入的结点的头和之前的结点尾相连
}
插入前老样子,我得判断你传进来的是不是空,你想想如果为空你怎么插在空气后面插一个结点那让谁搞条链子指向你插入的结点呢,你说对吧,所以这里我们就用assert()函数来判断,判断完后若非空,创建你想插入的结点后,让新结点的指针域存放插入结点后的结点的地址,通俗一点说就是让新结点的尾巴指向插入结点后面的结点,注意这里为什么要给*pos加个括号,因为这里的->大于*,而我们这里要用的是pos结点里的next指针,所以要先对pos进行解引用打开它再用->,之后再让原来的结点的指针域存放新结点的地址,通俗一点说就是让插入的结点的头和之前的结点尾相连。
5.6 单链表的尾删法
void SListPopBack(SListNode** pplist)//因为形参要想改变实参要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int* a, 同理如果传入的是指针,就要在指针前加个* 变成二级指针才可以改变一级指针的方向
{//在链表的尾部删除一个结点,尾删法
assert(*pplist);//传入的结点不能为空,不可能在一个空结点后面删除一个结点吧,因为啥也没有呀对吧
if (( * pplist)->next == NULL)
{//如果传入的链表只有一个结点的话,直接释放这个结点的指针
free(*pplist);
*pplist = NULL;//将指针置空,防止它成为野指针
return;
}
SListNode* TaliNode = *pplist;//记录最后一个结点的位置
SListNode* TempNode = *pplist;//记录最后一个结点前一个结点的位置
while (TaliNode->next)//链表的遍历,找结点位置
{
TempNode = TaliNode;
TaliNode = TaliNode->next;
}
free(TaliNode);//释放最后一个结点
TaliNode = NULL;//让最后一个结点的指针置空,防止野指针的产生
TempNode->next = NULL;//此时该指针指向的位置为最后一个结点,指针域应该为空
}
开始我们依然要判断传入的头指针是否为空,为空就是没有结点,那删啥对吧,当链表非空后再判断这个链表是否只有一个结点,如果是则直接释放头指针即可,释放完后别忘了将指针置空,防止其变为野指针,如果不是一个结点,那就定义两个指针一个是记录最后一个结点的位置TailNode指针,另一个是记录最后一个结点前一个结点位置的TempNode指针,之后通过一个遍历让它们找到自己对应所指的位置结点,然后释放最后一个结点,将TailNode这个指针置空防止野指针产生,此时TemoNode指针所指向的结点为最后一个结点,让该结点的指针域为空,然后删除完成。
5.7 单链表的头删法
void SListpopFront(SListNode** pplist)
{//头部删除链表的结点,头删法
assert(*pplist);//老样子,判断传入的结点是否为空
SListNode* TempNode = *pplist;//记录第一个结点的位置
*pplist = (*pplist)->next;//因为要删除前面一个结点,所以头指针要指向第一个结点后面的一个结点
free(TempNode);//将第一个结点释放
TempNode = NULL;//将指针置空,防止野指针产生
}
首先依旧是判断传入的头指针是否为空,若非空则定义一个结点让其记录第一个结点的位置,因为要删除前面一个结点,所以头指针要指向第一个结点后面的一个结点 ,之后再将TempNode指向的结点释放掉,将其置空即可。
5.8 单链表任意位置结点的删除
void SListpop(SListNode** Del,SListNode**pplist)//*Del为指向想要删除结点的指针,*pplist为头指针
{//任意位置删除一个结点
assert(*Del);
assert(*pplist);//防止传入的结点为空
SListNode* TempNode = *pplist;//记录头指针的位置
while ((TempNode)->next != *Del)//将TempNode指针进行遍历,直到指向想要删的结点之前也就是pos结点之前
{
TempNode = TempNode->next;
}
TempNode->next = (*Del)->next;//让TempNode结点的指针域指向所删结点的后一个结点的位置
free(*Del);//释放所要删除的结点
*Del = NULL;//将指针置空,放在它成为野指针
}
首先我们传入我们要删结点的位置和头结点的位置就是它们所对应的指针,然后判空,若非空则让一个TempNode指针记录头指针的位置,再让其进行遍历,直到该指针指向的结点的下一个结点是我们要删的结点为止,跳出循环,让TempNode指向的结点的指针域指向所删结点的后一个结点的位置,再释放想要删除结点对应的指针,再置空即可。
5.9 单链表的遍历
void SListPrint(SListNode* plist)
{//链表的遍历
while (plist)//传入一个头指针,从头开始遍历,直到所有的结点遍历完毕
{
printf("%d——>", plist->Data);//打印出当前结点数据域中所存的数据用——>这个连接,看成一条链子
plist = plist->next;
}
printf("NULL\n");//链表的最后为空
}
通过一个循环遍历单链表将其里面的数据打印出来。
5.10 单链表数据的查找
SListNode* SListFind(SListNode* plist, SLCDatatype x)//传入头指针和你想要查找的数据
{//链表中的数据查找
SListNode* find = plist;//用一个指针指向第一个结点
int i=0;
while (find)
{
if (find->Data == x)
{
printf("数据所在位置为:%d\n",i);
return find;
}
find = find->next;
++i;
}
printf("没有找到相匹配的结点数据");
return NULL;
}
我们定义一个find指针来查找我们想要的数据,通过一个循环遍历单链表,在通过if()结构判断所对应的结点数据域是否等于我们要找的(注意这里的查找针对于不重复数据的查找,对于重复数据的查找还有待实现,也希望大家多多建议)如果找到则返回该结点的位置,没有则一直遍历,遍历完还没找到则就是没有相匹配的数据,返回一个空指针。
5.11 单链表的销毁
void DestroyList(SListNode **plist)//因为最后头指针指向的结点也会被释放,所以最后要将头结点置空,但你在函数里面置空不能影响到外面所以这又提到了形参改变实参,所以需要用二级指针
{//链表的销毁
assert(plist);//判断传入结点是否为空
SListNode* Del = *plist;//指向销毁结点的指针
SListNode* temp = *plist;//这个temp是为了防止让你用*plist进行遍历,正常确实可以拿这个遍历但现在我们用的是*plist,如果用这个遍历会直接影响到外部的链表,所以不采用,故用一个temp指针来进行遍历
while (Del)
{
temp = temp->next;
free(Del);
Del = temp;
}
*plist = NULL;//销毁完毕,将头指针置空,防止变成野指针
}
首先判断传入结点是否为空 ,再定义一个Del指针指向要销毁的结点,再定义一个temp指针用来充当头指针的替代品(这个temp指针是为了防止让你用*plist进行遍历,正常确实可以拿这个遍历但现在我们用的是*plist,如果用这个遍历会直接影响到外部的链表,所以不采用,故用一个temp指针来进行遍历),之后再用循环一个一个释放结点进行单链表的销毁,最后销毁完毕将头指针置空,防止其成为野指针。
5.12 主函数部分
int main()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPrint(plist);
SListPushBack(&plist, 2);
SListPrint(plist);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushBack(&plist, 4);
SListPrint(plist);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPushBack(&plist, 6);
SListPrint(plist);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPushBack(&plist, 7);
SListPrint(plist);
SListPushFront(&plist, 2);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPrint(plist);
SListNode* temp = SListFind(plist, 5);
SListpop(&temp,&plist);
SListPrint(plist);
temp= SListFind(plist, 2);
SListpop(&temp, &plist);
SListPrint(plist);
temp = SListFind(plist, 4);
SListpop(&temp, &plist);
SListPrint(plist);
DestroyList(&plist);
SListPrint(plist);
return 0;
}
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLCDatatype;//定义数据类型
typedef struct SListNode //SListNode这个东西要定义两遍,上面的这个是给这个结构体用的因为里面有个这个结构体类型的结点
{//确定结点的类型
SLCDatatype Data;//定义结点的数据域类型
struct SListNode* next;//定义结点的指针域类型
}SListNode; //下面的这个SListNode是给后面的一些函数用的,少了哪个都不行
SListNode* CreateSListNode(SLCDatatype x)
{//创建结点,给结点的数据域赋值,然后指针域置空(因为没有开始只有一个结点)
SListNode* p = (SListNode*)malloc(sizeof(SListNode));//为结点动态申请相应空间
if (p == NULL)
{//判断是否从堆中成功申请一个动态空间
printf("从堆中索要一个动态空间失败\n");
exit(-1);
//若没有申请到,则直接终止程序进行
}
//申请成功后,对结点的数据域进行赋值,指针域置空(当只有一个结点时)
p->Data = x;
p->next = NULL;
return p;
}
void SListPushBack(SListNode** pplist, SLCDatatype x)//因为形参要想改变实参要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int *a,同理如果传入的是指针,就要在指针前加个*变成二级指针才可以改变一级指针的方向
{//尾部插入结点
SListNode* createnode = CreateSListNode(x);//创建一个你想要插入的新结点
if (*pplist == NULL)
{//判断传入的单链表是否为一个空表,若为空表,直接让*pplist=*createnode即可
*pplist = createnode;
return;
}
//如果传入单链表非空
SListNode* TailList = *pplist;//定义一个指针使它指向链表此时的头
while (TailList->next)
{//遍历链表,让这个指针指向链表的最后一个结点
TailList = TailList->next;
}
TailList->next = createnode;//让最后一个结点的指针域存放新创建结点的地址
}
void SListPushFront(SListNode** pplist, SLCDatatype x)//因为形参要想改变实参要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int *a,同理如果传入的是指针,就要在指针前加个*变成二级指针才可以改变一级指针的方向
{//头部插入结点
SListNode* createnode = CreateSListNode(x);//创建一个你想要插入的新结点
createnode->next = *pplist;//让新结点的指针域存放传入链表第一个结点的地址,也就是让新结点的next指针指向传入链表的第一个结点
*pplist = createnode;//让pplist指针重新指向新链表的头部
}
void SListInsertAfter(SListNode** pos, SLCDatatype x)
{//在任意结点位置之后插入一个结点
assert(*pos);//判断所传的结点是否为空,若为空则终止程序,不可能在一个空结点后面插入一个结点吧,对不对,你前面都是空的你搁哪插。
SListNode* createnode = CreateSListNode(x);//创建一个你想要插入的新结点
createnode->next = (*pos)->next;//让新结点的指针域存放插入结点后的结点的地址,通俗一点说就是让新结点的尾巴指向插入结点后面的结点,注意这里为什么要给*pos加个括号,因为这里的->大于*,而我们这里要用的是pos结点里的next指针,所以要先对pos进行解引用打开它再用->
(*pos)->next = createnode;//再让原来的结点的指针域存放新结点的地址,通俗一点说就是让插入的结点的头和之前的结点尾相连
}
void SListPopBack(SListNode** pplist)//因为形参要想改变实参要传址,正常如果你传一个整型变量a,要想改变函数里面变量的同时函数外的也改变就必须在传入的时候定义为int* a, 同理如果传入的是指针,就要在指针前加个* 变成二级指针才可以改变一级指针的方向
{//在链表的尾部删除一个结点,尾删法
assert(*pplist);//传入的结点不能为空,不可能在一个空结点后面删除一个结点吧,因为啥也没有呀对吧
if (( * pplist)->next == NULL)
{//如果传入的链表只有一个结点的话,直接释放这个结点的指针
free(*pplist);
*pplist = NULL;//将指针置空,防止它成为野指针
return;
}
SListNode* TaliNode = *pplist;//记录最后一个结点的位置
SListNode* TempNode = *pplist;//记录最后一个结点前一个结点的位置
while (TaliNode->next)//链表的遍历,找结点位置
{
TempNode = TaliNode;
TaliNode = TaliNode->next;
}
free(TaliNode);//释放最后一个结点
TaliNode = NULL;//让最后一个结点的指针置空,防止野指针的产生
TempNode->next = NULL;//此时该指针指向的位置为最后一个结点,指针域应该为空
}
void SListpopFront(SListNode** pplist)
{//头部删除链表的结点,头删法
assert(*pplist);//老样子,判断传入的结点是否为空
SListNode* TempNode = *pplist;//记录第一个结点的位置
*pplist = (*pplist)->next;//因为要删除前面一个结点,所以头指针一个指向第一个结点后面的一个结点
free(TempNode);//将第一个结点释放
TempNode = NULL;//将指针置空,防止野指针产生
}
void SListpop(SListNode** Del,SListNode**pplist)//*Del为指向想要删除结点的指针,*pplist为头指针
{//任意位置删除一个结点
assert(*Del);
assert(*pplist);//防止传入的结点为空
SListNode* TempNode = *pplist;//记录头指针的位置
while ((TempNode)->next != *Del)//将TempNode指针进行遍历,直到指向想要删的结点之前也就是pos结点之前
{
TempNode = TempNode->next;
}
TempNode->next = (*Del)->next;//让TempNode结点的指针域指向所删结点的后一个结点的位置
free(*Del);//释放所要删除的结点
*Del = NULL;//将指针置空,放在它成为野指针
}
void SListPrint(SListNode* plist)
{//链表的遍历
while (plist)//传入一个头指针,从头开始遍历,直到所有的结点遍历完毕
{
printf("%d——>", plist->Data);//打印出当前结点数据域中所存的数据用——>这个连接,看成一条链子
plist = plist->next;
}
printf("NULL\n");//链表的最后为空
}
SListNode* SListFind(SListNode* plist, SLCDatatype x)//传入头指针和你想要查找的数据
{//链表中的数据查找
SListNode* find = plist;//用一个指针指向第一个结点
int i=0;
while (find)
{
if (find->Data == x)
return find;
find = find->next;
}
printf("没有找到相匹配的结点数据");
return NULL;
}
void DestroyList(SListNode **plist)//因为最后头指针指向的结点也会被释放,所以最后要将头结点置空,但你在函数里面置空不能影响到外面所以这又提到了形参改变实参,所以需要用二级指针
{//链表的销毁
assert(plist);//判断传入结点是否为空
SListNode* Del = *plist;//指向销毁结点的指针
SListNode* temp = *plist;//这个temp是为了防止让你用*plist进行遍历,正常确实可以拿这个遍历但现在我们用的是*plist,如果用这个遍历会直接影响到外部的链表,所以不采用,故用一个temp指针来进行遍历
while (Del)
{
temp = temp->next;
free(Del);
Del = temp;
}
*plist = NULL;//销毁完毕,将头指针置空,防止变成野指针
}
int main()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPrint(plist);
SListPushBack(&plist, 2);
SListPrint(plist);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushBack(&plist, 4);
SListPrint(plist);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPopBack(&plist);
SListPrint(plist);
SListPushBack(&plist, 5);
SListPrint(plist);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPushBack(&plist, 6);
SListPrint(plist);
SListPushFront(&plist, 3);
SListPrint(plist);
SListPushBack(&plist, 7);
SListPrint(plist);
SListPushFront(&plist, 2);
SListPrint(plist);
SListPushFront(&plist, 1);
SListPrint(plist);
SListNode* temp = SListFind(plist, 5);
SListpop(&temp,&plist);
SListPrint(plist);
temp= SListFind(plist, 2);
SListpop(&temp, &plist);
SListPrint(plist);
temp = SListFind(plist, 4);
SListpop(&temp, &plist);
SListPrint(plist);
DestroyList(&plist);
SListPrint(plist);
return 0;
}
运行结果
(五)杂项
两数之和leetcode题自己写的代码解析
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i, temp, target,j,b,n,*num;
printf("请输入数组元素的个数\n");
scanf_s("%d", &temp);
num = (int*)malloc(sizeof(int) * temp);
printf("请输入对应数组元素个数的元素\n");
for (i = 0; i < temp; i++)
{
scanf_s("%d", &num[i]);
}
printf("请输入目标值\n");
scanf_s("%d", &target);
for (i = 0; i < temp; i++)
{
j = target - num[i];
for (n = i+1; n < temp; n++)//找到第二个元素的下标
{
if (j == num[n])
{
printf("[%d %d] ", i,n);
return 0;
}
}
}
return 1;
}
本次博客就到这里了,如果有错误还请各位批评指正,我们下次再见。