C语言结构体与链表,单向链表构造,链表遍历,链表元素查找,链表元素删除

C语言的结构体

C语言中的结构体非常常用和非常非常重要,也是链表的基础,真正把结构体掌握好了,链表也就很容易掌握了。

在C语言中,申明一个结构体,很简单,如下代码所示:

struct TEST_S
{
    char name[20];
    int age;
    char gender;
};

这就申明了一个TEST_S的结构体,有三个成员字段,name,age和gender,在使用结构体的时候也非常方便,简单,如下代码所示:

struct TEST_S test;
test.age=20;
strcpy(test.name,"SimpleSoft");
test.gender='M';

那么这个结构体在内存中占用多大空间呢?可以参考这篇文章

C语言结构体-大小,对齐,填充,使用及其他https://blog.csdn.net/zhanghaiyang9999/article/details/110957728

但是,我们在使用这个结构体的时候,每次都需要在TEST_S的前面加上struct关键字,否则就会出错,编译通不过,例如:

TEST_S test

在编译的时候就会提示如下错误:

error C2065: “TEST_S”: 未声明的标识符

为了使用上的方便,即在使用的时候不需要每次都加上struct关键字,则需要在什么结构体的时候使用typedef关键字,如下所示:

typedef struct _TEST_S_
{
	char name[20];
	int age;
	char gender;
}TEST_S;

这样,我们就可以直接使用TEST_S了。

链表

链表是什么?如果是第一次接触链表的话,可以假设链表就像一列火车,每列火车的每节车厢首尾相连,如下图所示。

链表有头有尾,可以从头到尾走遍整个火车车厢一样,关键点是什么呢?两节车厢之间是由某个东西连接起来的。

那么关键点就来了,在C语言中,怎么来表示链表这个数据结构呢?仍然以火车车厢为例,如图所示:

  

从图中可以看出来,车厢和车厢之间需要一个东西连接起来,而且是有顺序的(也就是有方向的),1号车厢在2号车厢的前面,2号车厢在3号车厢的前面,依次类推。

而且还有一个特点,每节车厢包含两部分内容,一个是车厢本身,一个是连接前一个车厢的挂钩。

因此,在计算机中,比如C语言中的链表其实也和一列火车类似,火车是由若干节车厢组成的,链表也是由由若干个元素组成的,每个元素也需要包含两部分类容,一个内容就是数据,一个内容是指向前一个元素的“挂钩”。

因为一个数据元素至少要存储两个内容,一个真正的数据,一个“挂钩”,因此,很自然的就可以用结构体来表示链表的一个数据元素,因为结构体天然地支持多个字段。就像前面的例子中的结构体TEST_S一样,里面有name,age,gender等多个

数据信息,我们如果要用TEST_S来表示链表中的元素的话,还需要一个字段,即指向前一个元素或者后一个元素的“挂钩”类型,如下所示:

typedef struct _TEST_S_
{
	char name[20];
	int age;
	char gender;
	挂钩 guagou;
}TEST_S;

那么当我们有TEST_S这样的结构体的时候,就可以用来形成链表了,形成的链表就是这样的:

那么挂钩应该是个什么样的数据类型呢?其实很容易想到,它一定是个指针,因为我们要指向下一个元素,但是是什么类型的指针呢?也就是说我们要确定这个指针的类型,要确定这个挂钩类型的指针,也并不是很麻烦,我们的元素类型是什么,那么这个指针类型就是什么。因为这个指针要指向这个元素,因此这个指针的类型和元素的类型必须一致。

那么问题就来了,因为我们知道元素的类型是struct _TEST_S_,那么指针类型一定就是struct _TEST_S_ *,所以这个指针也是结构体的指针,所以,为了能够让结构体struct _TEST_S_能够成为链表的元素,我们也必须把挂钩类型申明为struct _TEST_S_类型,则新的struct _TEST_S_申明为:

typedef struct _TEST_S_
{
	char name[20];
	int age;
	char gender;
	struct _TEST_S_* guagou;
}TEST_S;

为了显得更专业一些,我们通常把变量guagou申明为next或者prev,表示指向下一个元素或者前一个元素,如果我们需要一个元素的挂钩既要指向前一个元素,又要指向下一个元素,则我们就需要两个挂钩,比如双向链表就需要两个挂钩,那么这个结构体的申明形式就会如下所示:

typedef struct _TEST_S_
{
	char name[20];
	int age;
	char gender;
	struct _TEST_S_* prev;
	struct _TEST_S_* next;
}TEST_S;

构造链表

链表的构造过程其实就像连接一列火车的过程,先构造第一节车厢,然后再构造第二列车厢,并把第二列车厢挂到第一列车厢后面,依次类推,就构造了一整列火车。

如果这列火车构造完成后,只能从最后一节往前走,或者只能从第一节车厢往后走,那么这样的列车是单向的,如果构造这样的链表的话,就是单向链表,这列先描述单向链表。

构造单向链表

我们先申明一个单向链表中用到的结构体,如下所示:

typedef struct _TEST_S_
{
	char name[20];
	int age;
	char gender;
	struct _TEST_S_* next;
}TEST_S;

我们始终以构造列车作为例子,先构造一节车厢,我们构造链表也一样,先构造一个链表元素,即我们为结构体定义一个变量并分配内存:

        TEST_S *first = (TEST_S*)malloc(sizeof(TEST_S));
	first->age=20;
	strcpy(first->name,"SimpleSoft");
	first->gender='M';
	first->next = NULL;

因为这是我们构造的第一节车厢,挂钩无处所指,所以设置first->next=NULL,然后再构造第二节车厢,因为第二节车厢后面还没有东西,第二节车厢的挂钩依然无所指,但是我们要把第一节车厢和第二节车厢连接起来,所以把第一节车厢的挂钩指向第二节车厢(可能跟实际的列车构造稍有不同),代码如下:

	//create the first object
        TEST_S *second = NULL;
	TEST_S *first = (TEST_S*)malloc(sizeof(TEST_S));
	first->age=20;
	strcpy(first->name,"SimpleSoft");
	first->gender='M';
	first->next = NULL;
	//create the scond object
	second = (TEST_S*)malloc(sizeof(TEST_S));
	second->age=22;
	strcpy(second->name,"Tom");
	second->gender='M';
	second->next = NULL;
	//
	first->next = second;

如果我们再构造第三节,第四节车厢,也是这种模式,只是我们重复拷贝代码而已,为了简单的演示这个过程,我们再创建一个车厢,再拷贝一次代码,代码如下:

        TEST_S *first = NULL;
	TEST_S *second = NULL;
	TEST_S *third = NULL;
	//create the first object
	first = (TEST_S*)malloc(sizeof(TEST_S));
	first->age=20;
	strcpy(first->name,"SimpleSoft");
	first->gender='M';
	first->next = NULL;
	//create the scond object

	second = (TEST_S*)malloc(sizeof(TEST_S));
	second->age=22;
	strcpy(second->name,"Tom");
	second->gender='M';
	second->next = NULL;
	//
	first->next = second;
	//create the third object
	third = (TEST_S*)malloc(sizeof(TEST_S));
	third->age=20;
	strcpy(third->name,"Lily");
	third->gender='F';
	third->next = NULL;

	second->next=third;

构造完这三阶“车厢”后,我们就构造了一个拥有三节车厢的小列车,同样,我们也就构造了一个有三个元素的单向链表。这个链表的样子如下:

为了简单一点,我们把这个结构体简化一下,只保留一个数据字段,然后用循环创建具有4个元素的链表,如下:

typedef struct _TEST_S_
{
	int data;
	struct _TEST_S_* next;
}TEST_S;

通常单向链表需要一个头元素,这个头元素的作用是在操作这个链表的时候,可以从“头”开始,比如遍历,查找,删除元素等,这个“头”元素往往不存储实际数据,仅仅用来指向链表的有用的第一个元素,最后构造的具有4个元素的链表如下:

则构造这个链表的代码如下:

        int i=1;
	TEST_S *temp = NULL;
	TEST_S *head = (TEST_S*)malloc(sizeof(TEST_S));
	head->data = 0;
	head->next = NULL;
	temp = head;
	for(i=1;i<=3;i++)
	{
		TEST_S* element = (TEST_S*)malloc(sizeof(TEST_S));
		element->data = i;
		element->next = NULL;
		temp->next = element;
		temp = element;
	}

因为我们把每次新构建的链表元素作为最后一个元素,所以这里使用一个临时变量temp用来存储上一次构建的链表元素。

上面的这种构造链表的方式是把新构建的元素放到链表的末尾,我们也可以把新构建的元素放到链表的开头,有兴趣的朋友可以自己试一试。

遍历链表

链表的遍历是比较简单的,就是从头到尾挨个遍历一遍,一直到最后一个链表的元素为止。

也就是说用一个循环就可以了,那么结束条件是什么呢?因为最后一个元素的“挂钩”指向NULL,所以只要判断元素的next是否为空就可以,如果next为空,则退出循环,遍历代码如下:

void visit_list(TEST_S *list_head)
{
	TEST_S *temp = list_head->next;
	while(temp)
	{
		printf("the value =%d\n",temp->data);
		temp = temp->next;
	}
}

这个遍历代码是基于前面创建链表的代码的,遍历时,我们仍然需要一个temp临时变量指向链表头元素head,然后一直遍历下去,并打印元素的data成员的值,运行结果如下:

 查找元素

查找链表元素的过程其实就是遍历链表的过程,在遍历的时候,依次比较元素是否与要查找的元素匹配,如果匹配,则找到,退出循环,否则一直遍历结束。代码如下:

int find_value(TEST_S *list_head,int match_value)
{
	TEST_S *temp = list_head->next;

	while(temp)
	{
		if(match_value == temp->data)
		{
			printf("found the value %d\n",match_value);
			return 0;
		}
		temp = temp->next;
	}
	return 1;
}

这就是在链表中查找元素的函数,如果找到匹配的元素,则返回0,否则返回1.

因此,如果要在链表中查找是否有值为2的元素,则调用函数为:

find_value(head,2);

删除元素

删除链表的元素比查找元素要多做一步,就是把找到的匹配的元素从链表中删除,比如如下所示的链表:

 如果要把元素值为2的链表元素删除,则删除后的效果如下图所示:

元素值为2的链表元素从链表中被删除,同时链表元素1的next指针就直接执行元素3了。

整个过程,就是前面查找的过程,就是如果找到了匹配的元素,就删除对应的元素,代码如下:

int delete_value(TEST_S *list_head,int match_value)
{	
	TEST_S *prev = list_head;
	TEST_S *temp = list_head->next;
	while(temp)
	{		
		if(temp->data == match_value)
		{
			prev->next = temp->next;
			free(temp);
			return 0;
		}
		prev = temp;
		temp = temp->next;
	}
	return 1;
}

比如我们要删除元素2,则调用:

delete_value(head,2);

再次调用visit_list(head),则元素2就被删除了。如下所示:

        printf("删除前链表元素为:\n");
	visit_list(head);
	printf("删除后链表元素为:\n");
	delete_value(head,2);

指向效果如图:

目前介绍的都是单向链表的相关操作,下一篇文章将介绍双向链表,循环链表等操作,本质上是差不太多的,只要理解 了单向链表,就没有问题了。

 

下一篇 C/C++语言链表的排序与查找定位

https://blog.csdn.net/zhanghaiyang9999/article/details/114972065

 

 

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Simple-Soft

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值