概括:主要说明双向链表的基本概念和具体操作以及源代码。

一、基本概念

1.有了单链表以后我们可以把内存中小块的空间联系在一起,并且把每一个小块都存储上我们想要存储的数值。但是单链表只有一个next,我们每一次都要从头开始遍历整个链表,这样的话如果我们对单链表进行逆序访问那么将是一项很耗时的操作。

2.双向链表解决了上面的问题,我们不单对每一个数据节点都设置一个next,同时还有一个pre指针,这样我们可以完成对链表的双向查找。

3.双向链表的结构示意图如下所示:

二、把单链表更改成为双向链表

1.首先更改链表中的header,链表中的header对应的是一个结构体,如下所示:

struct _tag_DLinkListNode
{
DLinkListNode* next;
DLinkListNode* pre;
};

在这里相对于单链表增加了一个pre指针,作前驱。

2.在create函数中初始化将header.pre以及clear函数中的header.next都设置为NULL。

ret->header.pre = NULL;

3.其他地方的操作还要注意在插入一个节点和删除一个节点函数中的改变。

三、双向链表的具体操作

1.双向链表的创建

DLinkList* DLinkList_Create()
{
TDLinkList* ret = (TDLinkList*)malloc(sizeof (TDLinkList));
if (ret != NULL)
{
ret->length = 0;
ret->slider = NULL;
ret->header.next = NULL;
ret->header.pre = NULL;
} return ret;
}

这里的TDLinkList* ret = (TDLinkList*)malloc(sizeof (TDLinkList));是为链表头获取空间,链表中其他数据节点的空间是在主函数中定义的,也就是插入链表时候由 结构体进行定义。

2.双向链表的清除、销毁、以及获取长度

这一部分要注意返回值的定义和返回值的类型。

3.双向链表的插入操作

在链表插入函数中,一共有四点需要注意。

(1)普通的插入方式,不是头插法也不是尾插法,只是单纯的把一个数据节点插入到链表中。基本操作示意图如下:

这样我们基本上使用四步操作就可以解决:

current->next = node;
node->pre = current;
next->pre = node;
node->pre = current;

(2)如果插入的节点是采用头插法,而且链表中已经存在其他元素,那么我们要进行判定,因为这个时候不可以再单纯的令current = node->pre,这个时候的node是第一个元素,所以它的pre应该指向NULL。

代码实现:

if (current == (DLinkListNode*)slist)
{
node->pre = NULL;
}

(3)当我们插入的节点是链表中的第0个位置,并且链表中没有其他元素,也就是链表的长度为0的时候,这个时候我们插入的元素的node->pre指向的current是头结点,显然不应该这样,也应该令node->pre = NULL,虽然老唐的判定是slist->length = 0但是我自己的判定current == (DLinkListNode*)slist已经包含了老唐的情况。

这种情况的代码实现:

if (current == (DLinkListNode*)slist)
{
node->pre = NULL;
}

(4)插入节点的第三种特殊情况:当我们从链表的尾部插入元素,这个时候current是倒数第一个节点,next是NULL,我们要插入的节点node在插入完成以后,node->next应该指向空。

这种情况下的代码实现:

if (next != NULL)
{
// node->next = next;
next->pre = node;
}

(5)对然第二种情况已经实现了slist->length == 0的情况,但是假如我们的slist->length == 0的时候,我们的游标并没有指向当前插入的元素,为了解决这个问题,代码实现如下:

if (slist->length == 0)
{
slist->slider = node;
}

插入函数的整体实现如下所示:

int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos)
{
TDLinkList *slist = (TDLinkList*)list;
int ret = (slist != NULL);
ret = ret && (pos >= 0);
ret = ret && (node != NULL);
int i = 0;
if (ret)
{
DLinkListNode*current = (DLinkListNode*)slist;
DLinkListNode*next = NULL;
for (i = 0; (i < pos) && (current->next != NULL); i++)
{
current = current->next;
}
next = current->next; current->next = node; node->next = next;
if (next != NULL)
{
// node->next = next;
next->pre = node;
} node->pre = current;
if (current == (DLinkListNode*)slist)
{
node->pre = NULL;
} if (slist->length == 0)
{
slist->slider = node;
} slist->length++;
}
return ret;
}

4.双向链表的删除元素操作

删除操作除了常规的操作以外也存在一些特殊的情况。

(1)常规的删除链表中的一个元素,也就是在这个双向链表中的不是第一个元素,也不是最后元素,而且这个时候的双向链表已经有了一定的长度。如下图所示:

具体的代码实现如下:

current->next = next;
next->node = current;

(2)删除链表中的元素的第二种情况,我们要删除链表中的第0个元素,这个时候我们头结点的next被赋值为next(第1个节点),但是这个时候第一个节点应该变为第0个节点,而第0个节点指向的为header,所以这个时候next->pre = NULL。


代码实现:

current->next = next;
next->pre= NULL;

同时这种情况下要对next节点是否是为NULL 进行判定。

(3)删除了第0个元素以后,链表中不再有其他元素,也就是我们删除的元素是链表中的唯一节点,这个时候我们只需要将current->next = next,而并不需要进行next->pre =current,因为这个时候根本就不存在next->pre的情况了。

这里不再需要next->pre = current,具体的代码实现如下:

if (next != NULL)

我们这种情况下next = = NULL,所以我们不再指向if下面的代码。

(4)如果删除的链表中的最后一个节点,如果这个节点为空,那么只执行current->next = next,这个判定if (next != NULL)已经完成,不再执行有关next->pre的操作。

具体代码实现如下:

if (next != NULL)

删除函数的具体实现如下:

(5)如果我们要删除的节点恰好是游标现在所指向的元素,那么我们需要将游标指向next。

具体代码实现如下:

if (slist->slider == ret)
{
slist->slider = next;
}

删除函数的具体实现代码如下:

DLinkListNode* DLinkList_Delete(DLinkList* list, int pos)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode * ret = NULL;
int i = 0; if ((slist != NULL) && (pos >=0) && (pos < slist->length))
{
DLinkListNode* current = (DLinkListNode*)(slist);
DLinkListNode*next = NULL;
for (i = 0; i < pos; i++)
{
current = current->next;
}
ret = current->next; next->pre = current;*/
next = ret->next;
current->next = next; if (next != NULL)
{
next->pre = current;
if (current == (DLinkListNode*)slist)
{
current->next = NULL;
next->pre = NULL;
} } if (slist->slider == ret)
{
slist->slider = next;
} slist->length--; }
return ret;
}

四、测试程序以及游标

1.测试程序

(1)我们的插入方式如下:

 DLinkList_Insert(list, (DLinkListNode*)&v1, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v2, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v3, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v4, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v5, DLinkList_Length(list));

我们采用的尾插法,就是每一次插入一个元素都是从链表的尾部插入。

(2)我们在操作游标之前,不必要对游标进行复位,如果不对游标进行复位,那么我们采用尾插法将会把元素的游标挤到第一个位置,那么我们就可以正常操作游标了。

(3)如果我们采用头插法插入元素,插入方式如下:

DLinkList_Insert(list, (DLinkListNode*)&v1, 0);
DLinkList_Insert(list, (DLinkListNode*)&v2, 0);
DLinkList_Insert(list, (DLinkListNode*)&v3, 0);
DLinkList_Insert(list, (DLinkListNode*)&v4, 0);
DLinkList_Insert(list, (DLinkListNode*)&v5, 0);

(4)在我们进行游标操作之前,我们要对游标进行复位,因为头插法将会把游标顺序的挤到最后一个位置,这个时候如果我们朦胧的将游标再向后移动一个将会导致程序的崩溃,但是这个时候向前移动并不会出错。

五、源代码

1.双向链表实现.c文件

#include <stdio.h>
#include <stdlib.h>
#include "1.h" /************************************************************************
*这个结构体里定义的是链表头的信息,我们的链表操作和链表遍历都离不开链表头
************************************************************************/
typedef struct student
{
DLinkListNode header;
DLinkListNode *slider; //游标
int length;
}TDLinkList; /***********************************************************************************************
*函数名: DLinkList_Create
*参数:void
*返回值:DLinkList*类型,是一个void*类型,然后再由接收函数进行强制类型转换
*功能:创建链表,并返回链表头
***********************************************************************************************/
DLinkList* DLinkList_Create()
{
/*
为链表头获得空间,链表中其他数据节点的空间是在主函数中定义的,也就是插入链表时候由
结构体进行定义。
*/
TDLinkList* ret = (TDLinkList*)malloc(sizeof (TDLinkList));
if (ret != NULL)
{
ret->length = 0;
ret->slider = NULL;
ret->header.next = NULL;
ret->header.pre = NULL;
} return ret;
}
/***********************************************************************************************
*函数名: DLinkList_Destroy
*参数:DLinkList* list 传进来的是链表头
*返回值:void
*功能:销毁链表头
***********************************************************************************************/
void DLinkList_Destroy(DLinkList* list)
{
free(list);
}
/***********************************************************************************************
*函数名: DLinkList_Clear
*参数:DLinkList* list 传进来的是链表头
*返回值:void
*功能:清空链表,并把链表头信息清空
***********************************************************************************************/
void DLinkList_Clear(DLinkList* list)
{
TDLinkList *slist = (TDLinkList*)list;
if (slist != NULL)
{
slist->length = 0;
slist->header.next = NULL;
slist->header.pre = NULL;
slist->slider = NULL;
}
}
/***********************************************************************************************
*函数名: DLinkList_Length
*参数:DLinkList* list 传进来的是链表头
*返回值:int类型的整数
*功能:获得链表长度,并将链表的长度返回
***********************************************************************************************/
int DLinkList_Length(DLinkList* list)
{
/*首先给返回值赋初值,如果函数的返回值为-1,则证明链表并不存在*/
int ret = -1;
TDLinkList *slist = (TDLinkList*)list;
if (slist != NULL)
{
ret = slist->length;
}
return ret;
}
/***********************************************************************************************
*函数名: DLinkList_Insert
*参数:DLinkList* list 传进来的是链表头 DLinkListNode* node 要插入的数据节点,其实是我们
*实际要插入的数据节点的指针 int pos 要插入链表中的位置(注意这个是从0开始算起的)
*返回值:int类型的整数
*功能:如果插入元素成功返回1,否则返回其他。
***********************************************************************************************/
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos)
{
TDLinkList *slist = (TDLinkList*)list;
int ret = (slist != NULL);
ret = ret && (pos >= 0);
ret = ret && (node != NULL);
int i = 0;
if (ret)
{
DLinkListNode*current = (DLinkListNode*)slist;
DLinkListNode*next = NULL;
for (i = 0; (i < pos) && (current->next != NULL); i++)
{
current = current->next;
}
next = current->next; current->next = node; node->next = next;
if (next != NULL)
{
// node->next = next;
next->pre = node;
} node->pre = current;
if (current == (DLinkListNode*)slist)
{
node->pre = NULL;
} if (slist->length == 0)
{
slist->slider = node;
} slist->length++;
}
return ret;
} /***********************************************************************************************
*函数名: DLinkList_Get
*参数:DLinkList* list 传进来的是链表头 int pos 要插入链表中的位置(注意这个是从0开始算起的)
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的链表指针和位置,可以获得这个位置上的数据节点信息。
***********************************************************************************************/
DLinkListNode* DLinkList_Get(DLinkList* list, int pos)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode* ret = NULL;
int i = 0;
if ((slist != NULL)&& (pos >= 0) && (pos < slist->length))
{
DLinkListNode*current = (DLinkListNode*)slist;
//DLinkListNode*next = NULL;
for (i = 0; i < pos; i++)
{
current = current->next;
}
/*current永远都是我们要找的节点的前一个节点*/
ret = current->next;
}
return ret;
}
/***********************************************************************************************
*函数名: DLinkList_Delete
*参数:DLinkList* list 传进来的是链表头 int pos 要插入链表中的位置(注意这个是从0开始算起的)
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的链表指针和位置,可以获取删除指定位置上的元素,并对指定位置上的元素进行删除。
***********************************************************************************************/
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode * ret = NULL;
int i = 0; if ((slist != NULL) && (pos >=0) && (pos < slist->length))
{
DLinkListNode* current = (DLinkListNode*)(slist);
DLinkListNode*next = NULL;
for (i = 0; i < pos; i++)
{
current = current->next;
}
ret = current->next; next->pre = current;*/
next = ret->next;
current->next = next; if (next != NULL)
{
next->pre = current;
if (current == (DLinkListNode*)slist)
{
current->next = NULL;
next->pre = NULL;
} } if (slist->slider == ret)
{
slist->slider = next;
} slist->length--; }
return ret;
}
/***********************************************************************************************
*函数名: DLinkList_DeleteNode
*参数:DLinkList* list 传进来的是链表头 int pos 要插入链表中的位置(注意这个是从0开始算起的)
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的链表指针和位置,通过游标指向我们要删除的元素,然后调用DLinkList_Delete函数
进行删除。
***********************************************************************************************/
DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode * ret = NULL;
int i = 0;
if (slist != NULL)
{
DLinkListNode* current = (DLinkListNode*)(slist);
for (i = 0; i < slist->length; i++)
{
if (current->next == node)
{
ret = current->next;
break;
}
current = current->next;
} if (current != NULL)
{
DLinkList_Delete (list, i);
}
}
return ret;
} /***********************************************************************************************
*函数名: DLinkList_Reset
*参数:DLinkList* list 传进来的是链表头
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的链表指针将游标重新指向头结点所指向的下一个元素的位置,也就是所谓的游标复位。
进行删除。
***********************************************************************************************/
DLinkListNode* DLinkList_Reset(DLinkList* list)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode* ret = NULL;
if (slist != NULL)
{
slist->slider = slist->header.next;
ret = slist->slider;
}
return ret;
}
/***********************************************************************************************
*函数名: DLinkList_Current
*参数:DLinkList* list 传进来的是链表头
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的指针,找到游标当前指向的元素,并将这个当前元素返回。
***********************************************************************************************/
DLinkListNode* DLinkList_Current(DLinkList* list)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode* ret = NULL;
if (slist != NULL)
{
ret = slist->slider;
}
return ret;
} /***********************************************************************************************
*函数名: DLinkList_Next
*参数:DLinkList* list 传进来的是链表头
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的指针,找到游标指向前一个元素,并将这个前一个元素返回。
***********************************************************************************************/
DLinkListNode* DLinkList_Next(DLinkList* list)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode* ret = NULL; if( (slist != NULL) && (slist->slider != NULL) )
{
ret = slist->slider;
slist->slider = ret->next;
} return ret;
}
/***********************************************************************************************
*函数名: DLinkList_Pre
*参数:DLinkList* list 传进来的是链表头
*返回值:DLinkListNode*类型 也就是返回的是一个链表的节点结构体指针
*功能:通过传进来的指针,找到游标指向前一个元素,并将这个前一个元素返回。
***********************************************************************************************/
DLinkListNode* DLinkList_Pre(DLinkList* list)
{
TDLinkList* slist = (TDLinkList*)list;
DLinkListNode* ret = NULL;
if (slist != NULL && slist->slider != NULL)
{
slist->slider = slist->slider->pre;
ret = slist->slider;
}
return ret;
}

2.双向链表的头文件

#ifndef _1_H_
#define _1_H_ typedef void DLinkList;
typedef struct _tag_DLinkListNode DLinkListNode;
/*这个结构体是聊表头的一个成员*/
struct _tag_DLinkListNode
{
DLinkListNode* next;
DLinkListNode* pre;
}; DLinkList* DLinkList_Create(); void DLinkList_Destroy(DLinkList* list); void DLinkList_Clear(DLinkList* list); int DLinkList_Length(DLinkList* list); int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos); DLinkListNode* DLinkList_Get(DLinkList* list, int pos); DLinkListNode* DLinkList_Delete(DLinkList* list, int pos); DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node); DLinkListNode* DLinkList_Reset(DLinkList* list); DLinkListNode* DLinkList_Current(DLinkList* list); DLinkListNode* DLinkList_Next(DLinkList* list); DLinkListNode* DLinkList_Pre(DLinkList* list); #endif

3.测试程序

#include <stdio.h>
#include <stdlib.h>
#include "1.h"
/* run this program using the console pauser or add your own getch, system("pause") or input loop */ struct Value
{
DLinkListNode header;
int v;
}; int main(int argc, char *argv[])
{
int i = 0;
DLinkList* list = DLinkList_Create();
struct Value* pv = NULL;
struct Value v1;
struct Value v2;
struct Value v3;
struct Value v4;
struct Value v5; v1.v = 1;
v2.v = 2;
v3.v = 3;
v4.v = 4;
v5.v = 5; DLinkList_Insert(list, (DLinkListNode*)&v1, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v2, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v3, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v4, DLinkList_Length(list));
DLinkList_Insert(list, (DLinkListNode*)&v5, DLinkList_Length(list)); DLinkList_Insert(list, (DLinkListNode*)&v1, 0);
DLinkList_Insert(list, (DLinkListNode*)&v2, 0);
DLinkList_Insert(list, (DLinkListNode*)&v3, 0);
DLinkList_Insert(list, (DLinkListNode*)&v4, 0);
DLinkList_Insert(list, (DLinkListNode*)&v5, 0); for(i=0; i<DLinkList_Length(list); i++)
{
pv = (struct Value*)DLinkList_Get(list, i); printf("插入的元素为:%d\n", pv->v);
} printf("\n"); //DLinkList_Delete(list, 0);
//DLinkList_Delete (list)*/
for(i=0; i<DLinkList_Length(list); i++)
{
pv = (struct Value*)DLinkList_Next(list);
printf("%d\n", pv->v);
} printf("\n"); DLinkList_Reset(list);
DLinkList_Next(list); pv = (struct Value*)DLinkList_Current(list); printf("%d\n", pv->v); DLinkList_DeleteNode(list, (DLinkListNode*)pv); pv = (struct Value*)DLinkList_Current(list); printf("%d\n", pv->v); DLinkList_Pre(list); pv = (struct Value*)DLinkList_Current(list); printf("%d\n", pv->v); printf("Length: %d\n", DLinkList_Length(list)); DLinkList_Destroy(list); return 0;
}

C语言数据结构----双向链表的更多相关文章

  1. C++语言实现双向链表

    这篇文章是关于利用C++模板的方式实现的双向链表以及双向链表的基本操作,在之前的博文C语言实现双向链表中,已经给大家分析了双向链表的结构,并以图示的方式给大家解释了双向链表的基本操作.本篇文章利用C+ ...

  2. C语言实现双向链表

    目前我们所学到的链表,无论是动态链表还是静态链表,表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表(或单链表). 虽然使用单链表能 100% 解决逻辑关系为 & ...

  3. 用C语言把双向链表中的两个结点交换位置,考虑各种边界问题。

    用C语言把双向链表中的两个结点交换位置,考虑各种边界问题. [参考] http://blog.csdn.net/silangquan/article/details/18051675

  4. (js描述的)数据结构[双向链表](5)

    (js描述的)数据结构[双向链表](5) 一.单向链表的缺点 1.只能按顺序查找,即从上一个到下一个,不能反过来. 二.双向链表的优点 1.可以双向查找 三.双向链表的缺点 1.结构较单向链表复杂. ...

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

    dlist.h #ifndef __dList_H #define __dlist_H typedef int Item; typedef struct Node *PNode; typedef PN ...

  6. c语言数据结构复习

    1)线性表 //顺序存储下线性表的操作实现 #include <stdio.h> #include <stdlib.h> typedef int ElemType; /*线性表 ...

  7. 读谭浩强C语言数据结构有感(1)

    1.什么是数据结构? 数据结构,就是我们计算机内部的运算,编程语言的基础工作模式吧,个人总结的 = = !! 数据:说简单一点,就是计算机二进制机器码,然后通过一些复杂的操作,变为复杂的语言. 数据元 ...

  8. C语言数据结构----栈与递归

    本节主要说程序中的栈函数栈的关系以及栈和递归算法的关系. 一.函数调用时的栈 1.程序调用时的栈是也就是平时所说的函数栈是数据结构的一种应用,函数调用栈一般是从搞地质向低地址增长的,栈顶为内存的低地址 ...

  9. 第二章 R语言数据结构

    R语言存储数据的结构包括:标量.向量.矩阵.数组.数据框和列表:可以处理的数据类型包括:数值型.字符型.逻辑型.复数型和原生型. 数据结构 向量 向量是用来存储数值型.字符型或逻辑型数据的一维数组.单 ...

随机推荐

  1. Ubuntu的快捷键

    正如大家都知道的那样,Ubuntu的终端的Terminal能快捷的操作该linux系统,减少鼠标的使用.(vim党,尽量避免使用鼠标) 在Ubuntu中,终端的快捷键为(大小写无关的): Ctrl + ...

  2. leetcode Roman to Integer python

    class Solution(object): def romanToInt(self, s): """ :type s: str :rtype: int "& ...

  3. Linux命令之修改主机名

    ubuntu永久修改主机名 1.查看主机名 在Ubuntu系统中,快速查看主机名有多种方法: 其一,打开一个GNOME终端窗口,在命令提示符中可以看到主机名,主机名通常位于“@”符号后: 其二,在终端 ...

  4. Scala学习之延迟绑定

    package com.swust.example object TraitDemo2 extends App{ //抽象类 abstract class Writer { def writeMess ...

  5. (总结)Nginx/LVS/HAProxy负载均衡软件的优缺点详解

    PS:Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件,本人都在多个项目中实施过,参考了一些资料,结合自己的一些使用经验,总结一下. 一般对负载均衡的使用是随着网站规模的提升根据不 ...

  6. Groovy中那些神奇注解之ToString

    继续上一篇:Groovy中那些神奇注解之Memoized 这篇就讲讲@groovy.transform.ToString这个注解,这注解太熟悉了,熟悉到让人一看就知道是干吗的,不就是把Bean转在St ...

  7. setFocus一定要写在setLayout设置的后面,否则不起作用——使用setFocusPolicy为控件设置不同的焦点策略:Tab焦点,Click焦点,Wheel焦点和没有焦点

    QLineEdit* pEditor = new QLineEdit(m_strText);    pEditor->resize(.......);    pEditor->move(. ...

  8. Android SDK与API版本的对应关系

    看教程.开发Android程序等很多地方,需要设置Android SDK的版本,而其要我们写的却是API版本的数字, 为了方便查看 Android SDK与API版本的对应关系 我在SDK Manag ...

  9. sharepoint 2013 文档库 资源管理器打开报错 在文件资源管理器中打开此位置时遇到问题,将此网站添加到受信任站点列表,然后重试。

    我们在使用sharepoint 2013的文档库或者资源库的时候,经常会需要用到使用“资源管理器”来管理文档,但是有时候,点击“使用资源管理器打开”,会提示如下错误: 在文件资源管理器中打开此位置时遇 ...

  10. mvc 防止客服端多次提交

    但凡web开发中都会有户多次点击了提交按钮导致多次提交的情况,一般的集中做法 1.通过js在用户点击的时候将按钮disabled掉,但是这样并不是很可靠(我就可以跳过这个,用一个for循环 我直接自己 ...