【数据结构-初阶】详解线性表(3)---双链表

【数据结构-初阶】详解线性表(3)---双链表

🎈主页传送门:良木生香

🔥个人专栏:《C语言》 《数据结构-初阶》 《程序设计》

🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离


目录

1、双链表的概念

2、双链表的基本实现

2.1、双向链表节点的创建

2.2、双向链表的初始化

2.3、双向链表长度的计算

2.4、双向链表的插入操作:

2.4.1、头部插入:

2.4.2、尾部插入:

2.4.3、查找pos位置的元素:

2.4.4、pos位置插入:

2.4.4.1、pos位置之前插入:

2.4.4.2、pos位置之后插入:

2.5、双向链表的删除操作:

2.5.1、头部删除:

2.5.2、尾部删除:

2.5.3、pos位置删除:

2.6、双向链表的元素查找:

2.7、双向链表的修改:

2.8、双向链表的打印:

2.9、双向链表的销毁:

3、代码总和:


上期回顾:在上一篇文章中,我们了解了链表是由一个一个节点通过指针连接在一起而组成的,每个节点是由数据域和指针域组成的.基于这个基础,我们实现了单链表(单向不带头结点不循环链表)的增删查改四个基本操作.

那么今天我们来实现一下另外一个链表---双向带头结点循环链表(双链表)

1、双链表的概念

双链表,根据类比单链表得出,双链表是由两个指针的,因为单链表只有一个指针嘛,嘻嘻。确实,双链表的结构与单链表极其的相似,双链表同样由数据域和指针域组成,只不过在指针域中,双链表有两个指针,一个指针是指向有一个节点,一个指针是指向前一个节点,如下图:

因为今天要讲的是双向带头结点循环链表,所以整个链表的就应该为下图所示:

头结点相当于哨兵位,不会因为链表的操作而改变,有了这些前备知识之后,我们就可以开始对双链表进行实现了~~~

2、双链表的基本实现

2.1、双向链表节点的创建

双向链表节点的创建与单链表的相似,都是先向内存申请一个节点的空间,然后对立面的元素先进行初始化,但因为双向链表里面有两个指针,所以在指针的指向上会有所不同

下面是图示:

在初始化指针指向时候,要将两个指针指向自己,下面是具体的代码:

DListNode* Buy_Node(Elemtype data) { DListNode* newNode = (DListNode*)malloc(sizeof(DListNode)); if (newNode == NULL) { printf("新节点创建失败...\n"); return NULL; } newNode->next = newNode; newNode->data = data; newNode->front = newNode; return newNode; } 

2.2、双向链表的初始化

今天我们要讲的是带头节点的双向链表,那么就不用像不带头结点的单链表那样子对第一个节点初始化,所以我们只用对哨兵位进行初始化,即创建一个空节点,随便给节点进行赋值,在这里我将哨兵位赋值上-1,代码如下:

DListNode* Init_DListNode() { DListNode* phead = Buy_Node(-1); //现在是创建头节点 if (phead == NULL) { printf("头节点创建失败...\n"); return NULL; } return phead; } 

2.3、双向链表长度的计算

计算双向链表的长度,只用遍历一遍链表并用计数器技术即可,下面是代码

int Get_DListlength(DListNode* phead) { DListNode* pcur = phead; int count = 0; if (pcur->next == phead) { return 0; } while (pcur->next != phead) { pcur = pcur->next; count++; } return count; } 

我们将用于遍历的指针放在头节点(哨兵位)之后,即正式节点的位置,这样就不会将哨兵位计算进去

2.4、双向链表的插入操作:

与之前的链表一样,插入都可以分为头插,尾插,pos位置插入

2.4.1、头部插入:

对于头部插入,我们是将新节点插入到哨兵位与第一个正式节点之间的,下面图是示意图:

下面是具体实现的代码:

void Push_Front(DListNode* phead, Elemtype data) { assert(phead); //先创建新节点 DListNode* newNode = Buy_Node(data); if (newNode == NULL) { printf("新的头插节点创建失败...\n"); return; } //phead newNode phead->next newNode->next = phead->next; newNode->front = phead; phead->next->front = newNode; phead->next = newNode; }
在写代码的时候,要注意先将新节点的next与front指针与旧节点进行连接,再断开旧节点之间的连接

2.4.2、尾部插入:

尾部插入就是在双向链表中的最后一个节点与头结点之间插入新的节点,在单链表中,我们要遍历整个链表才能找到尾结点,但是在循环链表中就不用这么麻烦,因为哨兵位与尾结点是相连接的,我们可以通过哨兵位的front指针直接找到尾结点,这样一来就显得非常简单了,其实图示可以参考头部插入的图示,只用将哨兵位改成尾结点,第一个正式节点改成哨兵位即可,现在我们直接上代码:

void Push_Back(DListNode* phead, Elemtype data) { assert(phead); DListNode* newNode = Buy_Node(data); if (newNode == NULL) { printf("尾插节点创建失败...\n"); return; } //这时候就要创建尾巴节点,用于指向最后一个节点 DListNode* ptail = phead->front; //ptail newNode phead // newNode->front = ptail; newNode->next = phead; ptail->next = newNode; phead->front = newNode; }
要注意的依旧是,要优先处理新节点的两个指针与旧节点的关系,再将旧节点的相关指针指向新节点

2.4.3、查找pos位置的元素:

在进行写一个操作之前,我们要先做一个铺垫,先用一个函数来查找pos位置上的元素,看看pos位置上是否存在元素.这里我们用遍历的方法进行查找:

DListNode* Search_elem_for_pos(DListNode* phead, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return NULL; } DListNode* pcur = phead->next; for (int i = 1; i < pos; i++) { if (pcur->next == phead) { printf("pos位置没有元素...\n"); return NULL; } pcur = pcur->next; } return pcur; } 
这里我们将返回的值设计为指针类型,也就是如果找到了,可以直接获得这个元素的指针,以便后续操作的开展

2.4.4、pos位置插入:

2.4.4.1、pos位置之前插入:

在进行pos位置的相关操作之前,我们都是要进行一遍老步骤:判断pos值,判断pos位置是不是存在元素,如果存在元素,那就么pos位置之前插入就相当于头插,示意图与头插几乎一样,所以我们直接上代码:

void Push_pos_Front(DListNode* phead, Elemtype data, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } if (pos == 1) { Push_Front(phead, data); return; } DListNode* ppos = Search_elem_for_pos(phead, pos); if (ppos == NULL) { //Push_Front(phead, data); return; } //ppos->front newNode ppos else { DListNode* newNode = Buy_Node(data); if (newNode == NULL) return; newNode->next = ppos; newNode->front = ppos->front; ppos->front->next = newNode; ppos->front = newNode; } } 
要注意的是,如果pos值为1,那么就相当于头插,直接调用头插的函数即可

2.4.4.2、pos位置之后插入:

步骤与pos位置之前插入相同,如果pos位置存在元素,那么示意图就与尾尾插相似,那就直接上代码吧~~~~

void Push_pos_Back(DListNode* phead, Elemtype data, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } if (pos == 1) { Push_Back(phead, data); return; } DListNode* newNode = Buy_Node(data); if (newNode == NULL) { return; } DListNode* ppos = Search_elem_for_pos(phead, pos); if (ppos == NULL) { //Push_Back(phead, data); return; } //ppos newNode ppos->next newNode->next = ppos->next; newNode->front = ppos; ppos->next->front = newNode; ppos->next = newNode; }
如果pos值为1,那么就相当于尾插,像pos位置之前一样,直接调用尾插函数即可.

2.5、双向链表的删除操作:

删除操作与插入操作也一样,依旧是分头删,尾删,pos位置删除

2.5.1、头部删除:

头部删除指的是删除第一个正式节点,让哨兵位与第二个正式节点相连接,下面是是示意图:

在了解了示意图之后,我们直接上代码:

void Pop_Front(DListNode* phead) { assert(phead); int len = Get_DListlength(phead); if (len == 0) { printf("链表为空,无法删除...\n"); return; } //phead phead->next phead->next->next DListNode* delet = phead->next; phead->next = phead->next->next; phead->next->next->front = phead; free(delet); delet = NULL; }
要注意的是,在删除时对于指针的操作与增加时有所不同,在删除时是要先将不删除的两个节点先相连接,再free掉要删除的节点,最后要记得将delet指针置为NULL哦~~~

2.5.2、尾部删除:

与头部删除相类似,依旧是处理被删除节点与不删除节点之间的关系,我们直接上代码:

void Pop_Back(DListNode* phead) { assert(phead); int len = Get_DListlength(phead); if (len == 0) { printf("链表为空,无法删除...\n"); return; } DListNode* delet = phead->front; //delet->front delet phead; delet->front->next = phead; phead->front = delet->front; free(delet); delet = NULL; }
再三提醒,再free掉delet指针的内容后,要将delet指针置为NULL才行,不然会成为野指针

2.5.3、pos位置删除:

对链表而言,就没有pos之前或者之后删除了,直接就是指定的pos位置删除,删除的方式与头删尾删一样,无非就是为位置的不同罢了,话不多说,我们直接上代码:

void Pop_Pos(DListNode* phead, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } if (pos == 1) { Pop_Front(phead); return; } if (pos == len) { Pop_Back(phead); return; } //pos->front pos pos->next DListNode* delet = Search_elem_for_pos(phead, pos); if (delet == NULL) { return; } delet->front->next = delet->next; delet->next->front = delet->front; free(delet); delet = NULL; }
要注意的是,pos系列的操作一定要记得对pos值进行判断,以及特殊情况的处理,像pos==1或者pos==尾结点,这就要直接调用头删或者尾删了

2.6、双向链表的元素查找:

想要查找链表里面的元素,就要先求出当前链表的长度,看看长度是否为0,然后再用第一个正式节点开始遍历,查找链表中是否存在这个元素,有的话就输出"找到了",没有就输出"没找到",下面上代码:

void Search_elem(DListNode* phead, Elemtype data) { assert(phead); int len = Get_DListlength(phead); if (len == 0) { printf("当前链表为0,没有元素可以查找...\n"); return; } DListNode* pcur = phead->next; int pos = 1; while (pcur != phead) { if (pcur->data == data) { printf("找到了!!!\n"); return; } pcur = pcur->next; pos++; } printf("找不到...\n"); return; }

2.7、双向链表的修改:

想要修改某个位置的元素的值,就要先输入想修改的位置,记忆修改之后的数值,所以参数藜麦那就要含有pos以及data两个参数,然后再判断pos位置是否存在元素,不存在则无法进行修改:

void Change_elem(DListNode* phead, Elemtype data, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } DListNode* ppos = Search_elem_for_pos(phead, pos); if (ppos == NULL) { printf("想要修改的元素不存在\n"); return; } ppos->data = data; }

如果存在,直接修改就行.

2.8、双向链表的打印:

打印就更加简单了,直接遍历打印:

//现在是打印双向带头结点循环链表 void my_printf(DListNode* phead) { DListNode* pcur = phead->next; if (pcur == phead) { printf("当前链表内容为空...\n"); return; } while (pcur != phead) { printf("%d ", pcur->data); pcur = pcur->next; } } 

2.9、双向链表的销毁:

在做完一系列的操作之后,肯定是要对链表进行销毁的,我们就采用与打印相同的方法---遍历,逐个节点逐个节点进行销毁:

//现在是销毁链表 void Destory_list(DListNode* phead) { DListNode* pcur = phead->next; while (pcur != phead) { DListNode* delet = pcur; pcur = pcur->next; free(delet); delet = NULL; } free(phead); phead = NULL; } 

要注意的是,销毁完所有的节点之后,要记得将遍历的指针和哨兵位置为NULL.

以上就是关于带头循环双向链表基本操作的详解了,下面是所有代码的综合,有兴趣的朋友们可以看看~~~~~~

3、代码总和:

话不多说,直接上代码:

#define _CRT_SECURE_NO_WARNINGS 520 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<windows.h> #include<assert.h> //现在说的双向链表指的是双向带头循环链表 typedef int Elemtype; //现在定义双链表结构体 typedef struct DListNode { Elemtype data; //数据域 struct DListNode* front; struct DListNode* next; //指针域 }DListNode; //现在实现双向链表的基本操作: DListNode* Init_DListNode(DListNode* phead); int Get_DListlength(DListNode* phead); void Push_Front(DListNode* phead, Elemtype data); void Push_Back(DListNode* phead, Elemtype data); void Push_pos_Front(DListNode* phead, Elemtype data, int pos); void Push_pos_Back(DListNode* phead, Elemtype data, int pos); void Pop_Front(DListNode* phead); void Pop_Back(DListNode* phead); void Pop_Pos(DListNode* phead, int pos); DListNode* Search_elem_for_pos(DListNode* phead, int pos); void Search_elem(DListNode* phead, Elemtype data); void Change_elem(DListNode* phead, Elemtype data, int pos); //计算链表长度 int Get_DListlength(DListNode* phead) { DListNode* pcur = phead; int count = 0; if (pcur->next == phead) { return 0; } while (pcur->next != phead) { pcur = pcur->next; count++; } return count; } DListNode* Buy_Node(Elemtype data) { DListNode* newNode = (DListNode*)malloc(sizeof(DListNode)); if (newNode == NULL) { printf("新节点创建失败...\n"); return NULL; } newNode->next = newNode; newNode->data = data; newNode->front = newNode; return newNode; } DListNode* Init_DListNode() { DListNode* phead = Buy_Node(-1); //现在是创建头节点 if (phead == NULL) { printf("头节点创建失败...\n"); return NULL; } return phead; } void Push_Front(DListNode* phead, Elemtype data) { assert(phead); //先创建新节点 DListNode* newNode = Buy_Node(data); if (newNode == NULL) { printf("新的头插节点创建失败...\n"); return; } //phead newNode phead->next newNode->next = phead->next; newNode->front = phead; phead->next->front = newNode; phead->next = newNode; } void Push_Back(DListNode* phead, Elemtype data) { assert(phead); DListNode* newNode = Buy_Node(data); if (newNode == NULL) { printf("尾插节点创建失败...\n"); return; } //这时候就要创建尾巴节点,用于指向最后一个节点 DListNode* ptail = phead->front; //ptail newNode phead // newNode->front = ptail; newNode->next = phead; ptail->next = newNode; phead->front = newNode; } DListNode* Search_elem_for_pos(DListNode* phead, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return NULL; } DListNode* pcur = phead->next; for (int i = 1; i < pos; i++) { if (pcur->next == phead) { printf("pos位置没有元素...\n"); return NULL; } pcur = pcur->next; } return pcur; } void Push_pos_Front(DListNode* phead, Elemtype data, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } if (pos == 1) { Push_Front(phead, data); return; } DListNode* ppos = Search_elem_for_pos(phead, pos); if (ppos == NULL) { //Push_Front(phead, data); return; } //ppos->front newNode ppos else { DListNode* newNode = Buy_Node(data); if (newNode == NULL) return; newNode->next = ppos; newNode->front = ppos->front; ppos->front->next = newNode; ppos->front = newNode; } } void Push_pos_Back(DListNode* phead, Elemtype data, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } if (pos == 1) { Push_Back(phead, data); return; } DListNode* newNode = Buy_Node(data); if (newNode == NULL) { return; } DListNode* ppos = Search_elem_for_pos(phead, pos); if (ppos == NULL) { //Push_Back(phead, data); return; } //ppos newNode ppos->next newNode->next = ppos->next; newNode->front = ppos; ppos->next->front = newNode; ppos->next = newNode; } void Pop_Front(DListNode* phead) { assert(phead); int len = Get_DListlength(phead); if (len == 0) { printf("链表为空,无法删除...\n"); return; } //phead phead->next phead->next->next DListNode* delet = phead->next; phead->next = phead->next->next; phead->next->next->front = phead; free(delet); delet = NULL; } void Pop_Back(DListNode* phead) { assert(phead); int len = Get_DListlength(phead); if (len == 0) { printf("链表为空,无法删除...\n"); return; } DListNode* delet = phead->front; //delet->front delet phead; delet->front->next = phead; phead->front = delet->front; free(delet); delet = NULL; } void Pop_Pos(DListNode* phead, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } if (pos == 1) { Pop_Front(phead); return; } if (pos == len) { Pop_Back(phead); return; } //pos->front pos pos->next DListNode* delet = Search_elem_for_pos(phead, pos); if (delet == NULL) { return; } delet->front->next = delet->next; delet->next->front = delet->front; free(delet); delet = NULL; } void Change_elem(DListNode* phead, Elemtype data, int pos) { assert(phead); int len = Get_DListlength(phead); if (pos<1 || pos>len) { printf("pos值不合法...\n"); return; } DListNode* ppos = Search_elem_for_pos(phead, pos); if (ppos == NULL) { printf("想要修改的元素不存在\n"); return; } ppos->data = data; } //现在是打印菜单 void printf_menu() { printf("================================================================\n"); printf(" ***带头节点双向循环链表*** \n"); printf("插入:\n"); printf("1.头插 2.尾插 3.Pos位置之前插入 4.pos位置之后插入\n"); printf("删除:\n"); printf("5.头删 6.尾删 7.pos位置删除\n"); printf("其他:\n"); printf("8.查找 9.修改\n"); printf("================================================================\n"); printf("\n"); } void Search_elem(DListNode* phead, Elemtype data) { assert(phead); int len = Get_DListlength(phead); if (len == 0) { printf("当前链表为0,没有元素可以查找...\n"); return; } DListNode* pcur = phead->next; int pos = 1; while (pcur != phead) { if (pcur->data == data) { printf("找到了!!!\n"); return; } pcur = pcur->next; pos++; } printf("找不到...\n"); return; } //现在是打印双向带头结点循环链表 void my_printf(DListNode* phead) { DListNode* pcur = phead->next; if (pcur == phead) { printf("当前链表内容为空...\n"); return; } while (pcur != phead) { printf("%d ", pcur->data); pcur = pcur->next; } } //现在是销毁链表 void Destory_list(DListNode* phead) { DListNode* pcur = phead->next; while (pcur != phead) { DListNode* delet = pcur; pcur = pcur->next; free(delet); delet = NULL; } free(phead); phead = NULL; } int main() { //DListNode head; DListNode* phead = Init_DListNode(); int choose = 0; do { system("cls"); printf_menu(); printf("当前的链表为:\n"); my_printf(phead); printf("\n"); printf("请输入你的选择(按-1结束程序):\n"); scanf("%d", &choose); switch (choose) { case 1: { printf("请输入你想输入元素的个数:\n"); int num = 0; Elemtype data = 0; scanf("%d", &num); printf("请输入你想输入的元素:\n"); for (int i = 0; i < num; i++) { scanf("%d", &data); Push_Front(phead, data); } Sleep(1000); printf("插入成功!!!\n"); Sleep(2000); break; } case 2: { printf("请输入你想输入元素的个数:\n"); int num = 0; Elemtype data = 0; scanf("%d", &num); printf("请输入你想输入的元素:\n"); for (int i = 0; i < num; i++) { scanf("%d", &data); Push_Back(phead, data); } Sleep(1000); printf("插入成功!!!\n"); Sleep(2000); break; } case 3: { printf("请输入pos:\n"); int pos = 0; scanf("%d", &pos); Elemtype data = 0; printf("请输入你想输入的元素:\n"); scanf("%d", &data); Push_pos_Front(phead, data, pos); Sleep(1000); printf("插入成功!!!\n"); Sleep(2000); break; } case 4: { printf("请输入pos:\n"); int pos = 0; scanf("%d", &pos); Elemtype data = 0; printf("请输入你想输入的元素:\n"); scanf("%d", &data); Push_pos_Back(phead, data, pos); Sleep(1000); printf("插入成功!!!\n"); Sleep(2000); break; } case 5: { Pop_Front(phead); printf("删除成功!\n"); Sleep(2000); break; } case 6: { Pop_Back(phead); printf("删除成功!\n"); Sleep(2000); break; } case 7: { printf("请输入你想删除的位置:\n"); int pos = 0; scanf("%d", &pos); Pop_Pos(phead, pos); printf("删除成功!\n"); Sleep(2000); break; } case 8: { printf("请输入你想查找的元素:\n"); Elemtype data = 0; scanf("%d", &data); Search_elem(phead, data); Sleep(2000); break; } case 9: { printf("请输入你向修改之后的元素:\n"); Elemtype data = 0; scanf("%d", &data); printf("请输入你想修改的位置:\n"); int pos = 0; scanf("%d", &pos); Change_elem(phead, data, pos); printf("修改成功!\n"); Sleep(2000); break; } case -1: { printf("正在退出程序...\n"); Sleep(2000); printf("退出成功!!!\n"); Sleep(2000); break; } } } while (choose != -1); Destory_list(phead); return 0; }

以上就是我对单链表所有内容的分享了,感谢大佬们的阅读~~~

文章是自己写的哈,有啥描述不对的、不恰当的地方,恳请大佬指正,看到后会第一时间修改,感谢您的阅读。

Read more

《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——构造正/倒排索引(中篇)》

《C/C+++ Boost 轻量级搜索引擎实战:架构流程、技术栈与工程落地指南——构造正/倒排索引(中篇)》

前引:这是一个聚焦基础搜索引擎核心工作流的实操项目,基于 C/C++ 技术生态落地:从全网爬虫抓取网页资源,到服务器端完成 “去标签 - 数据清洗 - 索引构建” 的预处理,再通过 HTTP 服务接收客户端请求、检索索引并拼接结果页返回 —— 完整覆盖了轻量级搜索引擎的端到端逻辑。项目采用 C++11、STL、Boost 等核心技术栈,搭配 CentOS 7 云服务器 + GCC 编译环境(或 VS 系列开发工具)部署,既适配后端工程的性能需求,也能通过可选的前端技术(HTML5/JS 等)优化用户交互,是理解搜索引擎底层原理与 C++ 工程实践的典型案例 目录 【一】Jieba分词工具 【二】正/倒排索引结构设计

By Ne0inhk
【C++指南】类和对象(十):const成员函数

【C++指南】类和对象(十):const成员函数

💓 博客主页:倔强的石头的ZEEKLOG主页             📝Gitee主页:倔强的石头的gitee主页             ⏩ 文章专栏:《C++指南》                                   期待您的关注 目录 引言 一、const成员函数的定义与语法 1. 基本语法 2. 底层原理 二、const成员函数的作用与约束 1. 保障数据安全 2. 与const对象的关系 三、特殊场景与进阶技巧 1. mutable关键字 2. 函数重载 3. 权限传递规则 四、最佳实践与常见误区 1. 编码规范建议 2. 易错点分析 五、总结 引言 在C++中,const成员函数是面向对象编程中保障数据安全性的重要机制。它通过限制函数对类成员的修改权限,提升代码的健壮性和可维护性。 本文将结合代码示例,从语法、原理到实际应用场景,全面解析const成员函数的核心要点。   一、const成员函数的定义与语法

By Ne0inhk

多态(C++)

一、(1)编译时(静态)  函数重载,函数模板 (2)运行时(动态)    重点 二、多态的定义及实现 1.构成条件 多态是一个继承关系下的类对象,去调用同一函数,产生不同的行为 1.1必须重要条件 a.必须是基类(父类)的指针或引用调用函数 b.被调用的函数必须是虚函数 其中基类的virtual可以不写,但是不建议,而且这也是考试的埋坑处 1.2虚函数的重写/覆盖:基类中有一个跟派生类完全相同的虚函数(即返回值类型,函数名字,参数列表(指类型int,char...))也称三同 接下来我们看一道知道答案想给面试官背后来个板砖的题 这里按照常规思路,调用test(),而父子类func构成多态(与参数的值无关,类型相同即可), 那么大部分人都会觉得是A->1,这就调到的出题人的坑里,这就考验大家在平时对于细节的掌握,其实构成多态是形成如下的函数

By Ne0inhk