双向链表--C语言实现数据结构

news2025/1/23 23:16:46

本期带大家一起用C语言实现双向链表🌈🌈🌈

文章目录

    • 一、链表的概念🌎
    • 二、链表中数据元素的构成🌎 🌍
    • 三、链表的结构🌎 🌍 🌏
    • 四、 双向带哨兵位循环链表的实现🌎 🌍 🌏 🌎
      • 一、定义双向链表的结构体✅
      • 二、接口的实现✅✅
        • 1.双向链表的创建以及初始化
        • 2、尾插
        • 3、头插
        • 4、检查链表当中是否只有哨兵位
        • 5、尾删
        • 6、头删
        • 7、查找链表当中元素
        • 8、在pos位置之前插入数据
        • 9、删除pos位置的数据
        • 10、打印双向链表
        • 11、销毁双向链表
        • 12、双向链表的测试
    • 五、源代码💡💡💡
        • 1、LIst.h
        • 2、List.c
        • 3、test.c
    • 六、感谢与交流✅

一、链表的概念🌎

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的;简单来说,线性表的链式存储结构生成的表,称作“链表”。

二、链表中数据元素的构成🌎 🌍

每个元素本身由两部分组成:

1、本身的信息,称为“数据域”

2、指向直接后继的指针,称为“指针域”

三、链表的结构🌎 🌍 🌏

1、带哨兵位或者不带哨兵位 🦴

在这里插入图片描述

2、循环或者不循环 🦴 🦴

在这里插入图片描述

3、单向或者双向 🦴 🦴 🦴
在这里插入图片描述

四、 双向带哨兵位循环链表的实现🌎 🌍 🌏 🌎

在这里插入图片描述

这里先建立三个文件:
1️⃣ :SeqList.h文件,用于函数声明
2️⃣ :SeqList.c文件,用于函数的定义
3️⃣ :Test.c文件,用于测试函数
建立三个文件的目的: 将顺序表作为一个项目来进行书写,方便我们的学习与观察。

一、定义双向链表的结构体✅

双向链表的结构为
存储的有效数据data
指向后一个数据地址的指针next
指向前一个数据指针的数据prev


typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;

	struct ListNode* next;

	struct ListNode* prev;
}LTNode;

这里我们使用typedef对我们所存储的数据,以及双向链表结构体重命名,方便我们后续修改 🍊 🍋 🍒

二、接口的实现✅✅

1.双向链表的创建以及初始化

LTNode* CreatNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = CreatNode(-1);

	phead->next = phead;
	phead->prev = phead;
}

在这里插入图片描述

创建哨兵位节点

2、尾插

对链表进行尾插操作,添加数据到链表的尾部🌱 🌿 ☘️

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

	LTNode* newnode = CreatNode(x);

	LTNode* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;

}

首先我们需要创建一个新的节点,然后将我们的双向链表和我们创建的新节点进行链接起来🌱 🌿
在这里插入图片描述

3、头插

对链表进行头插操作,添加数据到链表的头部
但注意的是:不是添加到哨兵位的前面🚀 🛸
而是添加到哨兵位下一个节点的前面

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

	LTNode* node = CreatNode(x);
	node->next = phead->next;
	phead->next->prev = node;
	phead->next = node;
	node->prev = phead;
}

在这里插入图片描述

4、检查链表当中是否只有哨兵位

bool LTEmpty(LTNode* phead)
{
	return phead == phead->next;
}

5、尾删

对于 双向链表 的尾删,只要找到尾结点的前一个节点改变它和哨兵位的连接关系即可🚀 🛸
如果要找到尾结点的前一个节点,那么我只需要通过
哨兵位 的 prev 找到 尾,在通过 尾 的 prev 就可以找到 尾结点的前一个节点。然后调整这个节点和哨兵位的链接关系,然后 释放尾结点 就可以了

但是要注意,当链表只有哨兵位的时候不能进行删除操作!!!✈️

如图所示

在这里插入图片描述

void LTPopBack(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	
	LTNode* tail = phead->prev;

	LTNode* tailPrev = tail->prev;
	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;
}

上面说过,如果当链表只有哨兵位的时候不能进行删除操作!!!
所以我们通过了一个函数来检查当前链表是否只有一个哨兵位✈️ ✈️

bool LTEmpty(LTNode* phead)
{
	return phead == phead->next;
}

6、头删

对于头删来说,我需要删除 哨兵位的 next 节点 ,我们
需要连接哨兵位和哨兵位next的next,然后释放哨兵位的next🌟

但是同样需要注意的是:链表当中只有哨兵位的时候我们不能对其删除!!!

如图所示:🌟
在这里插入图片描述

void LTPopFront(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}

	LTNode* next = phead->next->next;

	phead->next = next;
	phead->next->prev = phead;
}

7、查找链表当中元素

当我们需要查找链表当中是否存在某个元素的时候,我们需要链表,看是否存在该个元素🌟🌟

但是带头双向循环链表当中不存在NULL

所以停止遍历的时候我们的终止条件为!=·phead

如果找到,返回该节点的地址;如果找不到返回 NULL

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

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

8、在pos位置之前插入数据

在 pos 位置之前插入,那么 通过 pos 的 prev 找到 pos 位置的上一个节点 posPrev ,然后改变 posPrev 和 新节点 newnode 之间的链接和 newnode 和 pos 之间的链接

需要同LTFind一起使用

void LTInsert(LTNode* pos, LTDataType x)
{

	LTNode* node = CreatNode(x);

	LTNode* posPrev = pos->prev;
	posPrev->next = node;
	node->next = pos;
	node->prev = posPrev;
	pos->prev = node;
}

代码实现逻辑图如下所示
在这里插入图片描述
那么很好,有了这个接口之后,可以把它 复用 于 尾插 和 头插。

对于 尾插 来说, pos 位置就是 phead ,因为 phead 的前面就是

链表的尾,在 phead 位置前插入,就是尾插:

void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);
	LTInsert(phead, x);
}

对于 头插 来说, pos 位置就是 phead->next

为第一个节点的前面,在 phead->next 位置前插入,就是头插:


void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTInsert(phead->next, x);
}

9、删除pos位置的数据

在 pos 位置删除, 只要找到 pos 的前一个节点 posPrev

然后找到 pos 的后一个节点 posNext ,然后将这两个节点的 prev 和 next 建立正确的链接关系。然后释放 pos 节点,pos 节点置空。🌟🌟🌟

void LTErase(LTNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

同样的,这个接口也能复用于 尾删 和 头删 。

对于 尾删 来说,, pos 位置就是 phead->prev

为链表的尾,删除 phead->prev 位置,就是尾删:

为了避免空指针异常,我们需要进行判断

void LTPopBack(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->prev);
}

对于 头删 来说, pos 位置就是 phead->next

为链表的头,删除 phead->next 位置,就是头删:

为了避免空指针异常,我们需要进行判断

void LTPopFront(LTNode* phead)
{
	assert(phead);
	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->next);
}

10、打印双向链表

打印整个链表,就只需要遍历链表,控制好循环的停止条件:
循环终止条件设置为!=phead

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("哨兵位->");
	while (cur != phead)
	{
		printf("%d ", cur->data);

		cur = cur->next;
	}
	printf("\n");
}

11、销毁双向链表

我需要把 哨兵位 ,链表的节点 全部删除,那么我就要使用循环来删除。

循环的结束条件为 != phead。在销毁的过程中,每次记住我当前节点的下一个节点,以便迭代🍻🍻

但是由于我们的 形参是一级指针

所以不能够将哨兵位正常销毁,我们需要在主函数当中手动将哨兵位置为NULL

void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		LTNode* next = cur->next;

		free(cur);
		cur = next;
	}
	free(cur);
}

12、双向链表的测试

在这里插入图片描述

五、源代码💡💡💡

1、LIst.h

#pragma once


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

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;

	struct ListNode* next;

	struct ListNode* prev;
}LTNode;


//初始化
LTNode* LTInit();

//打印链表
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead,LTDataType x);

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

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

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

//查找
LTNode* LTFind(LTNode* phead,LTDataType x);
//pos之前插入
void LTInsert(LTNode* pos, LTDataType x);

//删除pos位置的值
void LTErase(LTNode* pos);
//销毁
void LTDestroy(LTNode* phead);

2、List.c

#define _CRT_SECURE_NO_WARNINGS


#include"List.h"


LTNode* CreatNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = CreatNode(-1);

	phead->next = phead;
	phead->prev = phead;
}

bool LTEmpty(LTNode* phead)
{
	return phead == phead->next;
}
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("哨兵位->");
	while (cur != phead)
	{
		printf("%d ", cur->data);

		cur = cur->next;
	}
	printf("\n");
}



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

	//LTNode* newnode = CreatNode(x);

	//LTNode* tail = phead->prev;

	//tail->next = newnode;
	//newnode->prev = tail;
	//phead->prev = newnode;
	//newnode->next = phead;

	LTInsert(phead, x);
}


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

	//LTNode* node = CreatNode(x);
	//node->next = phead->next;
	//phead->next->prev = node;
	//phead->next = node;
	//node->prev = phead;


	LTInsert(phead->next, x);
}


void LTPopBack(LTNode* phead)
{
	assert(phead);
	/*if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	
	LTNode* tail = phead->prev;

	LTNode* tailPrev = tail->prev;
	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;*/

	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->prev);
}


void LTPopFront(LTNode* phead)
{
	assert(phead);
	/*if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}

	LTNode* next = phead->next->next;

	phead->next = next;
	phead->next->prev = phead;*/

	if (LTEmpty(phead))
	{
		printf("链表为空\n");
		return;
	}
	LTErase(phead->next);
}


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

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

void LTInsert(LTNode* pos, LTDataType x)
{

	LTNode* node = CreatNode(x);

	LTNode* posPrev = pos->prev;
	posPrev->next = node;
	node->next = pos;
	node->prev = posPrev;
	pos->prev = node;
}

void LTErase(LTNode* pos)
{
	assert(pos);

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;

	while (cur != phead)
	{
		LTNode* next = cur->next;

		free(cur);
		cur = next;
	}
	free(cur);
}

3、test.c

#define _CRT_SECURE_NO_WARNINGS

#include"List.h"

void test1()
{
	printf("尾插:");
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");
}


void test2()
{
	printf("头插:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTPrint(plist);
	LTDestroy(plist);
	plist = NULL;
	printf("\n");
}

void test3()
{
	printf("尾删:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	 LTPopBack(plist);
	 LTPopBack(plist);
	 LTPopBack(plist);
	 LTPopBack(plist);

	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");
}

void test4()
{
	printf("头删:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);

	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	

	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");

}

void test5()
{
	printf("pos之前插入");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTNode* pos = LTFind(plist,5);
	
	if (pos != NULL)
	{
		LTInsert(pos, 9);

	}

	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");

}

void test6()
{
	printf("删除pos的值:");
	LTNode* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTNode* pos = LTFind(plist, 3);
	if (pos != NULL)
	{
		LTErase(pos);
	}
	LTPrint(plist);
	LTDestroy(plist);
	printf("\n");

}
int main()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	return 0;
}

六、感谢与交流✅

🌹🌹🌹如果大家通过本篇博客收获了,对顺序表有了新的了解的话
那么希望支持一下哦如果还有不明白的,疑惑的话,或者什么比较好的建议的话,可以发到评论区,
我们一起解决,共同进步 ❗️❗️❗️
最后谢谢大家❗️❗️❗️💯💯💯

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

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

相关文章

ROS2 中 使用奥比中光 Orbbec Astra Pro 深度相机

本文将以 Ubuntu 20.04 和 ROS2 foxy 环境为例&#xff0c;详细介绍如何在 ROS2 中使用奥比中光 Orbbec Astra Pro 深度相机。在这一篇文章中&#xff0c;你会学到如何创建工作空间&#xff0c;使用 usb_cam 功能包&#xff0c;编译安装使用 ros_astra_camera 等。 文章目录 1.…

年薪50万的程序员和一般的中学教师相比,被亲戚看不起

我是一名程序员&#xff0c;已经工作五年&#xff0c;年薪大概有50万左右。然后&#xff0c;亲戚家的孩子是博士生&#xff0c;在一所中学教书&#xff0c;自己一年的工资可以抵达五六年的薪资&#xff0c;不过还是被亲戚给鄙视了。 很多人都持有不同的观点。我自己是一名程序…

vue-事件修饰符+键盘事件

事件修饰符 1、prevent&#xff1a; 阻止默认事件&#xff08;或在方法中使用e.preventDefault()&#xff09; <a hrefhttps://blog.csdn.net/weixin_52993364?typeblog click.preventshowInfo>点我</a> 说明&#xff1a;这样点击后就不会发生地址的跳转 2、s…

Linux查找指令 时间查看

date 我们在windows中想要看一下时间&#xff0c;我们可以直接在显示器上看到&#xff0c;但是如果我们用的是linux远程登录软件我们像查看一下时间&#xff0c;我们应该怎么做&#xff1f; 我们直接输入date&#xff0c;我们就可以看到当前的时间&#xff0c;不过这个是系统按…

蚁群算法ACS处理旅行商问题TSP【Java实现】

1. 介绍 蚁群算法是一种群体智能算法&#xff0c;模拟了蚂蚁寻找食物时的行为&#xff0c;通过蚂蚁之间的信息交流和合作&#xff0c;最终实现全局最优解的寻找【是否找得到和迭代次数有关】。 蚁群算法的基本思想是将搜索空间看作一个由节点组成的图&#xff0c;每个节点代表…

Linux awk [-v] {print} 命令

AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 语法&#xff1a;语法&#xff1a;awk 条件1 {动作 1} 条件 2 {动作 2} … 文件名 awk是处理文本文件的语言&#xff0c;所以要传入文本数据供其处理&#xff08;文件逐行读入&#xff09;&#xff0c;…

合宙Air780e C-SDK开发

Air78e简介 AirXXXE系列模组&#xff0c;是合宙通信基于移芯EC618平台设计研发的新款4G Cat.1模组。 Air780e的资料点击这里打开。 Air78e开发板简介 一代 IPEX 天线连接器&#xff08;选配&#xff09;4G 弹簧天线一个下载/调试串口&#xff0c;两个通用串口IO 口默认电平…

电子电气架构——车辆电子电气架构的网络安全汇总

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本文主要分享关于车辆电子电气架构的网络安全常见隐患和对应现阶段解决办法。 背景信息 不知道是否还记得《速度与激情8》中黑客Cipher通过网络侵入车辆,…

PyTorch2.0向后兼容性和加速效果浅探

前言 在PyTorch2022开发者大会上&#xff0c;PyTorch团队发布了一个新特性——torch.compile&#xff0c;将PyTorch的性能推向了新的高度&#xff0c;称这个新版本为PyTorch2.0。torch.compile的引入不影响之前的功能&#xff0c;其是一个完全附加和可选的功能&#xff0c;因此…

Linux系统调用函数(300多个)

前言&#xff1a;这里只是给出中文描述&#xff0c;方便浏览熟悉&#xff0c;具体情况建议去具体环境&#xff08;Linux系统&#xff09;下执行 1&#xff09;man 2 systemcalls &#xff08;查看所有系统调用函数&#xff09;&#xff1b;2&#xff09;man 2 open &#xff08…

Codeforces Round 872 (Div. 2)

Problem - D2 - Codeforces 思路&#xff1a; 我们设good点到所有k点的距离和为dis。 假设good点不止一个&#xff0c;那么我们good点的dis应该都是相等的&#xff08;废话&#xff09;。设当前点u是good点&#xff0c;如果他往儿子v移动&#xff0c;儿子有w个点属于k&#…

Maven 项目模板学习

目录 Maven 项目模板 什么是 archetype&#xff1f; 使用项目模板 Maven 将询问原型的版本 创建的项目 创建 pom.xml Maven 项目文档 Maven 快照(SNAPSHOT) 什么是快照? 项目快照 vs 版本 app-ui 项目的 pom.xml 文件 Maven 快照(SNAPSHOT)的出现是因为为了如果pom有…

OpenPCDet系列 | 4.4 DataProcessor点云数据处理模块解析

文章目录 DataProcessor模块解析1. mask_points_and_boxes_outside_range2. shuffle_points3. transform_points_to_voxels DataProcessor模块解析 在对batch_data的处理中&#xff0c;经过了point_feature_encoder模块处理后&#xff0c;就轮到了进行data_processor处理。在d…

django路由(多应用配置)

一、配置全局路由 在应用下&#xff0c;定义视图函数views.py from django.http import HttpResponse from django.shortcuts import render# Create your views here.def get_order(request):return HttpResponse("orders应用下的路由") 在项目的urls路由配置中&…

Qt事件传递及相关的性能问题

在使用Qt时&#xff0c;我们都知道能通过mousePressEvent&#xff0c;eventFilter等虚函数的重写来处理事件&#xff0c;那么当我们向一个界面发送事件&#xff0c;控件和它的父控件之间的事件传递过程是什么样的呢&#xff1f; 本文将以下图所示界面为例&#xff0c;结合源码介…

【sentinel】热点规则详解及源码分析

何为热点&#xff1f;热点即经常访问的数据。很多时候我们希望统计某些热点数据中访问频次最高的Top K数据&#xff0c;并对其访问进行限制。 比如&#xff1a; 商品ID为参数&#xff0c;统计一段时间内最常购买的商品ID并进行限制用户ID为参数&#xff0c;针对一段时间内频繁…

【linux】init进程的详解

文章目录 概述init进程完成从内核态向用户态的转变&#xff08;1&#xff09;一个进程先后两种状态&#xff08;2&#xff09;init进程在内核态下的工作内容&#xff08;3&#xff09;init进程在用户态下的工作内容&#xff08;4&#xff09;init进程如何从内核态跳跃到用户态 …

springboot+vue高校社团管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的高校社团管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

Linux快速安装Erlang和RabbitMQ单机版

环境 CentOS7Xshell6XFtp6Erlang 21.3RabbitMQ 3.8.4 安装方式 同一个软件有很多种安装方式&#xff0c;在Linux系统有几种常见的软件安装方式&#xff1a; 源码编译安装&#xff1a;一般需要解压&#xff0c;然后使用make、make install等命令RPM&#xff08;RedHat Packa…

从物业管理到IT互联网精英,月薪11k的她几经辗转,终得偿所愿!

所谓“男怕入错行”&#xff0c;其实对女生来说也是一样&#xff0c;不同行业对人生的改变太大&#xff0c;想要找到满意的工作&#xff0c;就要不断去尝试。 西安的学员小文&#xff0c;大学毕业后从事的本专业&#xff08;物业管理&#xff09;工作&#xff0c;但不是很喜欢…