数据结构【有头双向链表】

news2024/9/17 7:21:42

目录

实现双向链表

双向链表数据

创建双向链表

初始化双向链表创建(哨兵位)

尾插

打印双向链表

头插

布尔类型

尾删

头删

查询

指定位置后插入

指定位置删除数据

销毁

顺序表和链表的分析

代码

list.h

list.c

test.c


注意:这⾥的“带头”跟前⾯我们说的“头结点”是两个概念,实际前⾯的在单链表阶段称呼不严 谨,但是为了同学们更好的理解就直接称为单链表的头结点。

带头链表⾥的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这⾥“放哨 的”。

实现双向链表

创建3个文件,list.h头文件,list.c存放函数的文件,test.c测试文件


双向链表数据

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

typedef int data;
typedef struct list
{
	data arr;//存放的数据
	struct list* prev; //节点头(上一个)
	struct list* next; //节点尾(下一个)
}SL;

创建双向链表

双向链表是:带头,双向,循环。

tab->arr存放x数值,双向链表是带循环的,所以节点头和节点尾要指向自己,

tab->next节点尾和tab->prev节点头指向自己。

然后返回当前节点。

//创建双向链表
SL* cjsxlb(data x)
{
	SL* tab = (SL*)malloc(sizeof(SL));
	if (tab == NULL)
	{
		perror("malloc");
		exit(1);
	}
	tab->arr = x;
	tab->next = tab->prev = tab;
	return tab;
}

初始化双向链表创建(哨兵位)

双向链表要带头,所以我们需要创建一个哨兵位head这个哨兵位不能修改。

//初始化
void csh(SL** r);//二级指针的方法
//SL* csh();//一级指针方法


    SL* add = NULL;//二级指针
	csh(&add);

	//SL* add = csh();//一级指针

二级指针:传地址过来可以直接修改到实参。

一级指针:需要add接收创建的哨兵位,然后返回。

//初始化
void csh(SL** r)//二级指针
{
	//创建链表
	*r = cjsxlb(-1);

}

//初始化
//SL* csh()//一级指针
//{
//	//创建链表
//	SL*add = cjsxlb(-1);
//	return add;
//}

尾插

尾插用一级指针就行了,我们不用修改哨兵位。

//尾插
void weic(SL* r,data x);


tab是新的双向链表空间

第一步:tab节点头 ,指向前一个节点。

第二步:tab节点尾,指向哨兵位节点。

第三步:tab前一个节点的节点尾指向tab。

第四步:哨兵位节点头指向tab。

//尾插
void weic(SL* r, data x)
{
	assert(r);
	SL* tab = cjsxlb(x);
	//先让新节点的头连接前一个节点
	tab->prev = r->prev;
	//新节点的尾连接哨兵位
	tab->next = r;
	//在让新节点的前一个节点连接新的节点头
	r->prev->next = tab;
	//让哨兵位的头指向新的节点
	r->prev = tab;
}

尾插的1,2,3,4

//尾插
	weic(add, 1);
	weic(add, 2);
	weic(add, 3);
	weic(add, 4);
	day(add);

打印双向链表

//打印
void day(SL* r);


打印哨兵位没什么用,所以我们从哨兵位下一个节点开始打印

把下一个节点给add, 循环不等于哨兵位,打印每个节点存放的数据。

//打印
void day(SL* r)
{
	SL* add = r->next;
	while (add != r)
	{
		printf("%d->", add->arr);
		add = add->next;
	}
	printf("\n");

}

头插

//头插
void toc(SL* r,data x);


第一步:tab的节点尾指向,哨兵位的下一个节点。

第二步:tab节点头指向哨兵位。

第三步:哨兵位下一个节点的节点头指向tab.

第四步:哨兵位的尾指向tab.

//头插
void toc(SL* r, data x)
{
	assert(r);
	SL* tab = cjsxlb(x);
	//让新节点的尾指向哨兵位下一个节点
	tab->next = r->next;
	//新节点头指向哨兵位
	tab->prev = r;

	//哨兵位下一个节点头指向新节点
	r->next->prev = tab;
	//哨兵位位指向新节点
	r->next = tab;

}

头插了99

	//头插
	toc(&add, 99);

布尔类型

C语⾔原来并没有为布尔值单独设置⼀个类型,⽽是使⽤整数 0 表⽰假,⾮零值表⽰真。 在 C99 中也引⼊了 布尔类型 ,是专⻔表⽰真假的。

_Bool

布尔类型的使⽤得包含头⽂件<stdbool.h>

布尔类型变量的取值是: true 或者 false 

#define bool _Bool
#define false 0
#define true 1

尾删

我们不能删除哨兵位,所以我们需要布尔类型来判断,当前节点是不是只有一个哨兵位,只有哨兵位不能删除。

//bool
bool list(SL* r);
//尾删除
void weisc(SL* r);


布尔类型,判断哨兵位的尾指向的是自己就返回真。

//布尔类型
bool list(SL* r)
{
	assert(r);
	//哨兵位指向的下一个节点等于哨兵位返回真
	return r->next == r;
}

布尔类型判断接收,(!逻辑非)把真变假,把假变真。

第一步:把最后一个节点给add。

第二步:把add前一个节点给tab。

第三步:tab的尾指向哨兵位。

第四步:哨兵位的头指向tab。

第五步:释放add空间。

//尾删除
void weisc(SL* r)
{
	assert(r);
	//布尔类型判断
	assert(!list(r));
	//把最后一个节点给add
	SL* add = r->prev;
	//把最后一个节点的前一个节点给tab
	SL* tab = add->prev;
	//tab尾指向哨兵位
	tab->next = r;
	//哨兵位头指向tab
	r->prev = tab;
	//释放add
	free(add);
	add = NULL;
}

我们可以看到把4删除了。

//尾删除
	weisc(&add);

头删

头删不是删哨兵位,是删除哨兵位的下一个节点。

//头删除
void tosc(SL* r);


布尔类型判断接收,(!逻辑非)把真变假,把假变真。

第一步:把哨兵位下一个节点给add。

第二步:把add下一个节点的头指向哨兵位。

第三步:哨兵位的尾指向add下一个节点。

第四步:释放add空间。

//头删除
void tosc(SL* r)
{
	assert(r);
	//布尔类型判断
	assert(!list(r));
	//哨兵位的下一个节点
	SL* add = r->next;

	//add下一个节点的头指向哨兵位
	add->next->prev = r;
	//哨兵位的尾指向add的下一个节点
	r->next = add->next;
	free(add);
	add = NULL;

}

把1删除了

    //头删除
	tosc(&add);

查询

//查找
SL* cz(SL* r, data x);

从哨兵位下一个节点开始循环查找,找到了返回当前空间就好了。

//查找
SL* cz(SL* r, data x)
{
	assert(r);
	//从哨兵位下一个节点开始
	SL* add = r->next;
	while (add != r)
	{
		if (add->arr == x)
		{
			return add;
		}
		add = add->next;
	}
	return NULL;

}

SL* tab = cz(add, 1);
	/*if (tab == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}*/

指定位置后插入

这原理和头插一样

//指定位置后插入
void zhidcr(SL* tab, data x);


第一步:add的节点尾指向,tab的下一个节点。

第二步:add节点头指向tab。

第三步:tab下一个节点的节点头指向add.

第四步:tab的尾指向add.

//指定位置后插入
void zhidcr(SL* tab, data x)
{
	SL* add = cjsxlb(x);
	//新节点的尾指向tab的下一个节点
	add->next = tab->next;
	//新节点的头指向tab
	add->prev = tab;
	//tab下一个节点的头指向add
	tab->next->prev = add;
	//tab节点的尾指向add
	tab->next = add;

}

我们可以看到在1后面插入了一个99。

//指定位置后插入
	zhidcr(tab, 99);
指定位置删除数据

这个的原理和头删除一样,把哨兵位当做tab的上一个节点就可以了。


//指定位置删除
void zhidsc(SL* tab);


先把tab给add

第一步:把add上一个节点的尾指向add的下一个节点。

第二步:把add的下一个节点的头指向add的上一个节点。

第三步:释放add或tab空间,都是指向一块空间的,然后置为NULL。

//指定位置删除
void zhidsc(SL* tab)
{
	assert(tab);
	SL* add = tab;
	//把add上一个节点的尾指向add的下一个节点
	add->prev->next = add->next;
	//把add的下一个节点的头指向add的上一个节点
	add->next->prev = add->prev;
	//释放add
	free(tab);
	tab = NULL;
}

我们可以看到把3删除了

//指定位置删除
	zhidsc(tab);

销毁

销毁我们需要把哨兵位后面的全部销毁完,再销毁哨兵位。

//销毁
//void ziaoh(SL** r);//二级指针

//销毁
void xiaoh(SL* r);//一级指针

第一步:先把哨兵位下一个节点给add。

第二步:循环销毁哨兵位后面的节点,

tab保存下一个节点,释放add空间,把tab空间给add。

第三步:释放哨兵位,把哨兵位置为NULL,在把add置为NULL。

//销毁
void xiaoh(SL* r)//一级指针
{
	//从哨兵位下一个节点开始销毁
	SL* add = r->next;
	while (add != r)
	{
		//tab保存下一个节点
		SL* tab = add->next;
		//释放
		free(add);
		//下一个节点赋值给add
		add = tab;
	}
	//释放哨兵位
	free(r);
	r = NULL;
	//把add置为空
	add = NULL;
}

//销毁
void xiaoh(SL** r)//二级指针
{
	//从哨兵位下一个节点开始销毁
	SL* add = (*r)->next;
	while (add != *r)
	{
		//tab保存下一个节点
		SL* tab = add->next;
		//释放
		free(add);
		//下一个节点赋值给add
		add = tab;
	}
	//释放哨兵位
	free(*r);
	r = NULL;
	//把add置为空
	add = NULL;
}

二级指针可以传地址过去可以修改到实参,销毁数据相当于销毁了实参。

一级指针不能修改到实参,所以我们销毁完,需要把实参置为NULL。


	//xiaoh(&add);//二级指针

	xiaoh(add);//一级指针
	add = NULL;

我们可以看到销毁成功了。


顺序表和链表的分析

不同点顺序表链表(单链表)
存储空间上物理上⼀定连续逻辑上连续,但物理上不⼀定连续
随机访问⽀持O(1)不⽀持:O(N)
任意位置插⼊或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插⼊动态顺序表,空间不够时需要扩 容和空间浪费没有容量的概念,按需申请释放,不存在 空间浪费
应⽤场景元素⾼效存储+频繁访问任意位置⾼效插⼊和删除

代码

list.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

typedef int data;
typedef struct list
{
	data arr;//存放的数据
	struct list* prev; //节点头(上一个)
	struct list* next; //节点尾(下一个)
}SL;
//初始化
void csh(SL** r);//二级指针的方法
//SL* csh();//一级指针方法
//尾插
void weic(SL* r,data x);
//打印
void day(SL* r);
//头插
void toc(SL* r,data x);
//bool
bool list(SL* r);
//尾删除
void weisc(SL* r);
//头删除
void tosc(SL* r);
//查找
SL* cz(SL* r, data x);
//指定位置后插入
void zhidcr(SL* tab, data x);
//指定位置删除
void zhidsc(SL* tab);
//销毁
//void ziaoh(SL** r);//二级指针
//销毁
void xiaoh(SL* r);//一级指针

list.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"

//创建双向链表
SL* cjsxlb(data x)
{
	SL* tab = (SL*)malloc(sizeof(SL));
	if (tab == NULL)
	{
		perror("malloc");
		exit(1);
	}
	tab->arr = x;
	tab->next = tab->prev = tab;
	return tab;
}


//初始化
void csh(SL** r)//二级指针
{
	//创建链表
	*r = cjsxlb(-1);

}

//初始化
//SL* csh()//一级指针
//{
//	//创建链表
//	SL*add = cjsxlb(-1);
//	return add;
//}



//打印
void day(SL* r)
{
	SL* add = r->next;
	while (add != r)
	{
		printf("%d->", add->arr);
		add = add->next;
	}
	printf("\n");

}


//尾插
void weic(SL* r, data x)
{
	assert(r);
	SL* tab = cjsxlb(x);
	//先让新节点的头连接前一个节点
	tab->prev = r->prev;
	//新节点的尾连接哨兵位
	tab->next = r;
	//在让新节点的前一个节点连接新的节点头
	r->prev->next = tab;
	//让哨兵位的头指向新的节点
	r->prev = tab;
}


//头插
void toc(SL* r, data x)
{
	assert(r);
	SL* tab = cjsxlb(x);
	//让新节点的尾指向哨兵位下一个节点
	tab->next = r->next;
	//新节点头指向哨兵位
	tab->prev = r;

	//哨兵位下一个节点头指向新节点
	r->next->prev = tab;
	//哨兵位位指向新节点
	r->next = tab;

}

//布尔类型
bool list(SL* r)
{
	assert(r);
	//哨兵位指向的下一个节点等于哨兵位返回真
	return r->next == r;
}

//尾删除
void weisc(SL* r)
{
	assert(r);
	//布尔类型判断
	assert(!list(r));
	//把最后一个节点给add
	SL* add = r->prev;
	//把最后一个节点的前一个节点给tab
	SL* tab = add->prev;
	//tab尾指向哨兵位
	tab->next = r;
	//哨兵位头指向tab
	r->prev = tab;
	//释放add
	free(add);
	add = NULL;
}


//头删除
void tosc(SL* r)
{
	assert(r);
	//布尔类型判断
	assert(!list(r));
	//哨兵位的下一个节点
	SL* add = r->next;

	//add下一个节点的头指向哨兵位
	add->next->prev = r;
	//哨兵位的尾指向add的下一个节点
	r->next = add->next;
	free(add);
	add = NULL;

}



//查找
SL* cz(SL* r, data x)
{
	assert(r);
	//从哨兵位下一个节点开始
	SL* add = r->next;
	while (add != r)
	{
		if (add->arr == x)
		{
			return add;
		}
		add = add->next;
	}
	return NULL;

}


//指定位置后插入
void zhidcr(SL* tab, data x)
{
	SL* add = cjsxlb(x);
	//新节点的尾指向tab的下一个节点
	add->next = tab->next;
	//新节点的头指向tab
	add->prev = tab;
	//tab下一个节点的头指向add
	tab->next->prev = add;
	//tab节点的尾指向add
	tab->next = add;

}

//指定位置删除
void zhidsc(SL* tab)
{
	assert(tab);
	SL* add = tab;
	//把add上一个节点的尾指向add的下一个节点
	add->prev->next = add->next;
	//把add的下一个节点的头指向add的上一个节点
	add->next->prev = add->prev;
	//释放add
	free(tab);
	tab = NULL;
}



销毁
//void xiaoh(SL** r)//二级指针
//{
//	//从哨兵位下一个节点开始销毁
//	SL* add = (*r)->next;
//	while (add != *r)
//	{
//		//tab保存下一个节点
//		SL* tab = add->next;
//		//释放
//		free(add);
//		//下一个节点赋值给add
//		add = tab;
//	}
//	//释放哨兵位
//	free(*r);
//	r = NULL;
//	//把add置为空
//	add = NULL;
//}

//销毁
void xiaoh(SL* r)//一级指针
{
	//从哨兵位下一个节点开始销毁
	SL* add = r->next;
	while (add != r)
	{
		//tab保存下一个节点
		SL* tab = add->next;
		//释放
		free(add);
		//下一个节点赋值给add
		add = tab;
	}
	//释放哨兵位
	free(r);
	r = NULL;
	//把add置为空
	add = NULL;
}

test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"list.h"

void p()
{
	SL* add = NULL;//二级指针
	csh(&add);

	//SL* add = csh();//一级指针
	
	//尾插
	weic(add, 1);
	weic(add, 2);
	weic(add, 3);
	weic(add, 4);
	day(add);

	头插
	//toc(&add, 99);
	尾删除
	//weisc(&add);
	头删除
	//tosc(&add);

	SL* tab = cz(add, 3);
	/*if (tab == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}*/
	//指定位置后插入
	//zhidcr(tab, 99);
	//day(add);

	//指定位置删除
	zhidsc(tab);
	day(add);

	//xiaoh(&add);//二级指针

	xiaoh(add);//一级指针
	add = NULL;

}

int main()
{
	p();
	return 0;

}

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

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

相关文章

用Python打造精彩动画与视频,2.2 使用Jupyter Notebook进行编程

2.2 使用Jupyter Notebook进行编程 Jupyter Notebook是一款广泛应用于数据科学、教学和研究的开源工具。它提供了一个交互式的编程环境&#xff0c;支持代码、文本、公式和可视化内容的集成显示&#xff0c;非常适合Python编程尤其是数据分析与可视化任务。 1. 什么是Jupyter…

2026考研数学武忠祥课程视频百度网盘资源+PDF讲义(永久更新)

虽然每年大家推荐的最多的是张宇和汤家凤&#xff0c;但是我强烈推荐武忠祥老师&#xff01; 2026考研数学武忠祥课程领取&#xff1a;2026武忠祥课程&#xff08;考研数学全程&#xff09;基础强化 武忠祥老师真宝藏老师&#xff0c;他讲课不像张宇老师那样段子频出&#xf…

模拟实现c++中的string

c内置string库的相关函数&#xff1a;string - C Reference 目录 一string类构造&#xff0c;拷贝构造和析构&#xff1a; 二string内正向迭代器实现&#xff1a; 三赋值运算符重载实现&#xff1a; 四reserve&#xff0c;empty&#xff0c;clear实现&#xff1a; 五push_b…

PHP与SEO,应用curl库获取百度下拉关键词案例!

编程语言从来都是工具&#xff0c;编程逻辑思维才是最重要的&#xff0c;在限定的规则内&#xff0c;实现自己的想法&#xff0c;正如人生一样&#xff01; 不管是python还是php只要掌握了基础语法规则&#xff0c;明确了实现过程&#xff0c;都能达到想要实现的结果&#xff0…

FFmpeg实战 - 解复用解码

文章目录 前置知识音视频基础概念解复用、解码的流程分析FFMPEG有8个常用库 常见音视频格式的介绍aac格式介绍h264格式介绍flv格式介绍mp4格式介绍 FFmpeg解码解封装实战数据包和数据帧&#xff08;AVPacket/AVFrame&#xff09;AVPacket/AVFrame的引用计数问题API介绍注意事项…

P4139 上帝与集合的正确用法

无限的2 用扩展欧拉定理处理式子 X>phi(p),上面的数 语言描述一下 我们从上面处理&#xff0c;处理到大于phi(p),用定理 我们接着处理 之后我们就可以接着处理Y 即递归phi(p) 确定递归终点phi(p)1 return 0 剩余值Z,Z%phi(1)phi(1)0; // Problem: P4139 上帝与集合…

银行贷款信用评分不足?大数据帮你找回失去的“分”

在这个信息爆炸的时代&#xff0c;无论是个人还是企业&#xff0c;数据都成为了衡量信用和评估风险的重要依据。贷款、融资、求职甚至是日常消费&#xff0c;都可能因为一份好的数据报告而变得更加顺畅。那么&#xff0c;如何高效地查询自己的大数据&#xff0c;面对评分不足时…

UDP通信 单播,广播,组播

UDP通信实现 #include <sys/types.h> #include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); - 参数&#xff1a; struct sockaddr *src_addr, socklen_t *addrlen…

十日Python项目——第四日(用户中心—收货地址)

#前言&#xff1a; 在最近十天我会用Python做一个购物类项目&#xff0c;会用到DjangoMysqlRedisVue等。 今天是第四天&#xff0c;主要负责撰写用户中心部分的收货地址部分。若是有不懂大家可以先阅读我的前三篇博客以能够顺承。 若是大家基础有不懂的&#xff0c;小编前面…

C语言:字符串函数、内存函数剖析

字符串函数、内存函数剖析 一、字符串函数&#xff08;一&#xff09;求字符串长度1、strlen&#xff08;1&#xff09;库函数实现&#xff08;2&#xff09;自定义实现 &#xff08;二&#xff09;长度不受限制的字符串函数1、strcpy&#xff08;1&#xff09;库函数实现&…

宠物猫用空气净化器真的有用吗?值得买的猫用空气净化器牌子排名

作为一名6年资深铲屎官&#xff0c;每天铲猫砂盆的工作无疑是一项挑战。家中不仅弥漫着难以忍受的气味&#xff0c;而且家里的小孩和老人偶尔会因为过敏性鼻炎或结膜炎等问题感到不适。换毛季节尤其头疼&#xff0c;浮毛无处不在&#xff1a;沙发、外套、坐垫&#xff0c;甚至连…

学习008-02-04-08 Localize UI Elements(本地化UI元素)

Localize UI Elements&#xff08;本地化UI元素&#xff09; This lesson explains how to localize an XAF application. It describes how to translate UI elements into German and create a multi-language application. 本课介绍如何本地化XAF应用程序。它描述了如何将U…

【C语言】在限制定条件下数据移动

C语言 在限制定条件下数据移动 给定一个数组 nums&#xff0c;编写一个函数将所有0移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意&#xff0c;必须在不复制数组的情况下原地对数组进行操作。 作业题 解决思路及代码 要将数组中的所有 0 移动到数组的末…

数学中的连分式、无穷连根式、平方根

连分式 连分式&#xff08;continued fraction&#xff09;由和与倒数的多层嵌套构成&#xff0c;可以是有限的&#xff0c;也可以是无限的。 表达式&#xff1a;或 import mathdef fraction_to_continued_fraction(numerator, denominator, max_terms):"""计算…

WordPress原创插件:搜索引擎抓取首图seo图片

WordPress原创插件&#xff1a;搜索引擎抓取首图seo图片 插件设置 插件将在网站头部添加适当的meta标签&#xff0c;以便百度等搜索引擎抓取指定的固定图像。 插件下载 https://download.csdn.net/download/huayula/89596527

[Meachines] [Easy] Friendzone LFI+Python-OS库污染权限提升

信息收集 IP AddressOpening Ports10.10.10.123TCP:21,22,53,80,139,443,445 $ nmap -p- 10.10.10.123 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 3.0.3 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Lin…

vue项目上线打包后出现的问题

1、出现空白页 1.1 打包路径&#xff1a; module.exports {publicPath:./, //修改为绝对路径 } 修改完打包路径后build可以展示页面 1.2 路由模式&#xff1a; 项目上线要求是history模式&#xff0c;需要后端做重定向 前端自测可以使用hash模式 2、代理和环境变量 问题…

信创系统上的数据加密和防泄露该如何对应?

随着信息技术的快速发展和数字化转型的深入推进&#xff0c;关于信创加密和信创防泄露的信息安全问题日益凸显。特别是在国家战略层面&#xff0c;推动自主可控的信息技术体系建设成为重中之重。深信达信创沙盒作为一款基于国产操作系统&#xff08;如麒麟、统信等&#xff09;…

Hugo 部署与自动更新(Git)

文章目录 Nginx部署Hugonginx.confhugo.conf Hugo自动更新Hugo自动更新流程添加访问令牌添加web hookrust实现自动更新接口 Nginx部署Hugo nginx.conf user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;even…

大模型系统学习路线

随着技术的进步&#xff0c;大模型如OpenAI的GPT-4和Sora、Google的BERT和Gemini等已经展现出了惊人的能力-从理解和生成自然语言到创造逼真的图像及视频。所以掌握大模型的知识和技能变得越来越重要。 下面是学习大模型的一些建议&#xff0c;供大家参考。 必备基础知识 **…