C语言数据结构----双向链表
概括:主要说明双向链表的基本概念和具体操作以及源代码。
一、基本概念
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语言数据结构----双向链表的更多相关文章
- C++语言实现双向链表
这篇文章是关于利用C++模板的方式实现的双向链表以及双向链表的基本操作,在之前的博文C语言实现双向链表中,已经给大家分析了双向链表的结构,并以图示的方式给大家解释了双向链表的基本操作.本篇文章利用C+ ...
- C语言实现双向链表
目前我们所学到的链表,无论是动态链表还是静态链表,表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表(或单链表). 虽然使用单链表能 100% 解决逻辑关系为 & ...
- 用C语言把双向链表中的两个结点交换位置,考虑各种边界问题。
用C语言把双向链表中的两个结点交换位置,考虑各种边界问题. [参考] http://blog.csdn.net/silangquan/article/details/18051675
- (js描述的)数据结构[双向链表](5)
(js描述的)数据结构[双向链表](5) 一.单向链表的缺点 1.只能按顺序查找,即从上一个到下一个,不能反过来. 二.双向链表的优点 1.可以双向查找 三.双向链表的缺点 1.结构较单向链表复杂. ...
- 数据结构 双向链表 C语言实现
dlist.h #ifndef __dList_H #define __dlist_H typedef int Item; typedef struct Node *PNode; typedef PN ...
- c语言数据结构复习
1)线性表 //顺序存储下线性表的操作实现 #include <stdio.h> #include <stdlib.h> typedef int ElemType; /*线性表 ...
- 读谭浩强C语言数据结构有感(1)
1.什么是数据结构? 数据结构,就是我们计算机内部的运算,编程语言的基础工作模式吧,个人总结的 = = !! 数据:说简单一点,就是计算机二进制机器码,然后通过一些复杂的操作,变为复杂的语言. 数据元 ...
- C语言数据结构----栈与递归
本节主要说程序中的栈函数栈的关系以及栈和递归算法的关系. 一.函数调用时的栈 1.程序调用时的栈是也就是平时所说的函数栈是数据结构的一种应用,函数调用栈一般是从搞地质向低地址增长的,栈顶为内存的低地址 ...
- 第二章 R语言数据结构
R语言存储数据的结构包括:标量.向量.矩阵.数组.数据框和列表:可以处理的数据类型包括:数值型.字符型.逻辑型.复数型和原生型. 数据结构 向量 向量是用来存储数值型.字符型或逻辑型数据的一维数组.单 ...
随机推荐
- 常用ajax请求
JQuery版本的ajax请求:(包括处理WebService中xml字符串) $.ajax({ type: "POST", async: true, url: "&qu ...
- Intellij idea workflow 工作流插件安装
idea提供支持的工作插件名字叫actiBPM,可以在idea中在线安装,但往往会连接不成功安装失败,所以这里提供了硬盘安装的方式: 首先是要去官网下载actiBPM插件,下载地址: http://p ...
- github--新手使用错误分析
先上去github 或者任意托管的网站.注册账号,新建仓库, 在本地运行Xcode 新建工程,新建工程的时候 勾上本地 的仓库,然后 在本地的项目根目录执行下边的命令: git remote add ...
- mysql远程登录权限
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'WITH GRANT OPTION; FLUSH PRIVILEGES;
- 高级复制实验配置添加复制节点操作时报错:ORA-23308: object GP.T does not exist or is invalid
出错原因: 使用高级复制时,在源端启动复制支持,执行语句:REPADMIN@bys1>execute dbms_repcat.generate_replication_support('gp', ...
- Genymotion配置及使用教程(最新最完整版附各部分下载地址)
Genymotion配置及使用教程(最新最完整版附各部分下载地址) FROM:http://blog.csdn.net/beiminglei/article/details/13776013 早都听说 ...
- HDU 3613 Best Reward(扩展KMP)
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=3613 [题目大意] 一个字符串的价值定义为,当它是一个回文串的时候,价值为每个字符的价值的和,如果 ...
- Zombie.js Insanely fast, headless full-stack testing using Node.js
(92) Is there a better crawler than Scrapy? - Quora Is there a better crawler than Scrapy?Edit Insan ...
- 基于视觉信息的网页分块算法(VIPS) - yysdsyl的专栏 - 博客频道 - CSDN.NET
基于视觉信息的网页分块算法(VIPS) - yysdsyl的专栏 - 博客频道 - CSDN.NET 于视觉信息的网页分块算法(VIPS) 2012-07-29 15:22 1233人阅读 评论(1) ...
- QT与JavaScript互调 - 虹的日志 - 网易博客
QT与JavaScript互调 - 虹的日志 - 网易博客 QT与JavaScript互调 2012-05-29 21:43:14| 分类: 技术 | 标签:qt javascript w ...