引言
List
Public Member Functions |
|
bool | operator== (const base_list &rhs) const |
void | empty () |
base_list (const base_list &tmp) | |
base_list (const base_list &rhs, MEM_ROOT *mem_root) | |
base_list (bool error) | |
bool | push_back (void *info) |
bool | push_back (void *info, MEM_ROOT *mem_root) |
bool | push_front (void *info) |
void | remove (list_node **prev) |
void | concat (base_list *list) |
void * | pop (void) |
void | disjoin (base_list *list) |
void | prepand (base_list *list) |
void | sort (Node_cmp_func cmp, void *arg) |
Sort the list. | |
void | swap (base_list &rhs) |
list_node * | last_node () |
list_node * | first_node () |
void * | head () |
void ** | head_ref () |
bool | is_empty () const |
list_node * | last_ref () |
這不僅是一個鏈表還可以用作是stack。這裏主要想說一下push_back函數,這裏充分體現了二維指針的強大,MySQL中的這種技巧隨處可見。在介紹push_back實現之前先說一下list_node的結構
struct list_node :public Sql_alloc
{
list_node *next;
void *info;
list_node(void *info_par,list_node *next_par)
:next(next_par),info(info_par)
{}
list_node() /* For end_of_list */
{
info= 0;
next= this;
}
};
可以看到list_node的next指針聲明在了第一個位置,這是一個相當重要的技巧,MySQL中的其他鏈表節點基本也都是將next放在第一個位置,原因就在push_back的實現中。在來看一個base_list的結構
class base_list :public Sql_alloc
{
protected:
list_node *first,**last;
public:
uint elements;
...
...
};
這也是一個比較通常的封裝,但是不平常的是last是一個二維指針,當我讀到這段代碼的時候一直疑惑,爲什麼用二維指針呢?答案還是push_back的實現中,push_back代碼如下
inline bool push_back(void *info)
{
if (((*last)=new list_node(info, &end_of_list)))
{
last= &(*last)->next;
elements++;
return 0;
}
return 1;
}
在sql_list.h中有一extern變量 爲list_node end_of_list,他的特點是end_of_list的next就是它本身,這個節點作爲base_list的尾節點,初始化bast_list時,first指向它,*last指向它。小Daemon
struct node{
struct node *next;
int a;
};
int main()
{
node node1,node2;
node1.a=1;
node2.a=2;
node2.next=NULL;
node1.next=&node2; node *p = &node1;
node **q = (node **)p;
return 1;
}
調試一下在我的機器上運行如下:----------------------------------------------------------
0x7fffffffd9e0 | node1的地址{next = 0x7fffffffd9f0, a = 1}
---------------------------------------------------------
0x7fffffffd9f0 | node2的地址{next = 0x0, a = 2}
---------------------------------------------------------
所以這裏p = 0x7fffffffd9e0,十分好理解。我的機器是64位機器所以sizeof(node) = 16,其中指針8,int 4,剩下的4個浪費掉。所以node1 node2之間剛好差兩個字節,16位。此時node **q = (node **)p; 會將p原本指向一個16位的結構體struct node強制轉化爲一個指向struct node*的8位的二維指針q的值是0x7fffffffd9e0,*q的值爲0x7fffffffd9f0,爲node2的地址,node.next的值。如果在聲明node時候將next與a順序調換,是得不到這個效果的。
抽離的base_list實現
#include<stdio.h>
struct list_node
{
list_node *next;
void *info;
list_node(void *info_par,list_node *next_par)
:next(next_par),info(info_par)
{}
list_node() /* For end_of_list */
{
info= 0;
next= this;
}
};
list_node end_of_list;
class base_list
{
public:
list_node *first,**last;
unsigned int elements;
void empty() { elements=0; first= &end_of_list; last=&first;}
base_list() { empty(); }
bool push_back(void *info)
{
if (((*last)=new list_node(info, &end_of_list)))
{
last= &(*last)->next;
elements++;
return 0;
}
return 1;
}
};
int main()
{
int a=1,b=2,c=3,d=4;
base_list list;
list.push_back((void*)(&a));
list.push_back((void*)(&b));
list.push_back((void*)(&c));
list.push_back((void*)(&d));
return 0;
}
first指向鏈表的第一個元素,*last指向最後一個元素,last指向last->next待添加到鏈表末尾的那個元素調試一下在我的機器上運行如下:
-----------------------------------------------------------------------------
0x601050 | end_of_list的地址{next = 0x601050 <end_of_list>, info = 0x0},first *last的值
----------------------------------------------------------------------------
0x7fffffffd9f0 | 0x601050 ,last的值
---------------------------------------------------------------------------
push_back(a)之後的執行(*last)=new list_node(info, &end_of_list) 申請一個節點next指向end_of_list
*last的值爲0x601050所以first 的值也會更新爲這個新申請的節點
*last以及first被更新爲0x602010,他們的next值爲ox601050
last= &(*last)->next;
(*last)->next指向end_of_list值爲0x601050
&(*last)->next的值到底是什麼呢?即哪一個地址存儲的是0x601050這個值
答案是0x602010
與上面的解釋一樣,(list_node*)0x602010是first *last, (list_node**)0x602010是一個指向list_node*的指針,剛好這個地址的前八位爲first->next
即(*last)->next 0x601050 所以last=0x602010此時的*last = 0x601050 即指向了最後一個元素
-----------------------------------------------------------------------------
0x601050 | end_of_list的地址{next = 0x601050 <end_of_list>, info = 0x0}
----------------------------------------------------------------------------
0x602010 | {next = 0x601050 <end_of_list>, info = &a}, first指向這個
---------------------------------------------------------------------------
0x602010 | last等於這個地址值,*last = 0x601050 所以要清楚對於*last的修改就是讀first->next的修改,繼續往下看
---------------------------------------------------------------------------
push_back(b)之後-----------------------------------------------------------------------------
0x601050 | end_of_list的地址{next = 0x601050 <end_of_list>, info = 0x0}
----------------------------------------------------------------------------
0x602010 | {next = 0x602030 , info = &a}, first指向這個
---------------------------------------------------------------------------
0x602030 | {next = 0x601050 <end_of_list>, info = &b}
---------------------------------------------------------------------------
0x602030 | last等於這個地址值,*last = 0x601050 所以要清楚對於*last的修改就是讀first->next的修改,繼續往下看
---------------------------------------------------------------------------
接下來的過程重複這個過程完成鏈表的建立。base_list::remove的實現
void remove(list_node **prev)
{
list_node *node=(*prev)->next;
if (!--elements)
last= &first;
else if (last == &(*prev)->next)
last= prev;
delete *prev;
*prev=node;
}
可以看到由於list_node結構的特殊,next位於第一個位置,所以對一個list_node *p 直接取地址&p 則會直接得到p的前一個節點,所以在remove中只傳一個參數就可以了,prev就是待刪除節點的前一個節點,*prev就是要刪除的節點。