二级指针的作用

c语言中指针的作用不言而喻,指针是c语言的一大特色。理解指针的并不是特别难,但是理解二级指针却并不是那么简单。下面就开发中常见的二级指针的场景总结一下。

一, 类似二维数组的用法

在hash表算法中一般会用二级指针来存储元素。说存储也不是很准确,因为指针并没有存储对象,只是存储了对象的地址。hash表为什么会用到二级指针呢?因为hash表会存在冲突的问题,每个槽中要有分支,来存储冲突的元素,以下是hash表示的示意图:


可以看到每个槽可能会有多个元素,所以必须用二维数组来存储元素,由于二维数组的大小必须提前,用二级指针可以动态指定大小。

下面给出了一段简单的hash表算法的代码:

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#define UINT unsigned int
#define SIZE_T unsigned int

struct TITEM
{
    char* Key;
    void* Data;
    struct TITEM* pPrev;
    struct TITEM* pNext;
};

class HashMap
{
public:
    HashMap(int nSize = 83);
    ~HashMap();

    void Resize(int nSize = 83);
    void* Find(char* key, bool optimize = true) const;
    bool Insert(char* key, void* pData);
    void* Set(char* key, void* pData);
    bool Remove(char* key);
    void RemoveAll();
    int GetSize() const;
    char* GetAt(int iIndex) const;
    char* operator[] (int nIndex) const;

protected:
    TITEM** m_aT;
    int m_nBuckets;
    int m_nCount;
};


static UINT HashKey(char* Key)
{
    UINT i = 0;
    SIZE_T len = strlen(Key);
    while( len-- > 0 ) i = (i << 5) + i + Key[len];
    return i;
}

static UINT HashKey(const char* Key)
{
    return HashKey((char*)Key);
};

HashMap::HashMap(int nSize) : m_nCount(0)
{
    if( nSize < 16 ) nSize = 16;
    m_nBuckets = nSize;
    m_aT = new TITEM*[nSize];
    memset(m_aT, 0, nSize * sizeof(TITEM*));
}

HashMap::~HashMap()
{
    if( m_aT ) {
        int len = m_nBuckets;
        while( len-- ) {
            TITEM* pItem = m_aT[len];
            while( pItem ) {
                TITEM* pKill = pItem;
                pItem = pItem->pNext;
                delete pKill;
            }
        }
        delete [] m_aT;
        m_aT = NULL;
    }
}

void HashMap::RemoveAll()
{
    this->Resize(m_nBuckets);
}

void HashMap::Resize(int nSize)
{
    if( m_aT ) {
        int len = m_nBuckets;
        while( len-- ) {
            TITEM* pItem = m_aT[len];
            while( pItem ) {
                TITEM* pKill = pItem;
                pItem = pItem->pNext;
                delete pKill;
            }
        }
        delete [] m_aT;
        m_aT = NULL;
    }

    if( nSize < 0 ) nSize = 0;
    if( nSize > 0 ) {
        m_aT = new TITEM*[nSize];
        memset(m_aT, 0, nSize * sizeof(TITEM*));
    } 
    m_nBuckets = nSize;
    m_nCount = 0;
}

void* HashMap::Find(char* key, bool optimize) const
{
    if( m_nBuckets == 0 || GetSize() == 0 ) return NULL;

    UINT slot = HashKey(key) % m_nBuckets;
    for( TITEM* pItem = m_aT[slot]; pItem; pItem = pItem->pNext ) {
        if( pItem->Key == key ) {
            if (optimize && pItem != m_aT[slot]) {
                if (pItem->pNext) {
                    pItem->pNext->pPrev = pItem->pPrev;
                }
                pItem->pPrev->pNext = pItem->pNext;
                pItem->pPrev = NULL;
                pItem->pNext = m_aT[slot];
                pItem->pNext->pPrev = pItem;
                m_aT[slot] = pItem;
            }
            return pItem->Data;
        }        
    }

    return NULL;
}

bool HashMap::Insert(char* key, void* pData)
{
    if( m_nBuckets == 0 ) return false;
    if( Find(key) ) return false;

    // Add first in bucket
    UINT slot = HashKey(key) % m_nBuckets;
    TITEM* pItem = new TITEM;
    pItem->Key = key;
    pItem->Data = pData;
    pItem->pPrev = NULL;
    pItem->pNext = m_aT[slot];
    if (pItem->pNext)
        pItem->pNext->pPrev = pItem;
    m_aT[slot] = pItem;
    m_nCount++;
    return true;
}

void* HashMap::Set(char* key, void* pData)
{
    if( m_nBuckets == 0 ) return pData;

    if (GetSize()>0) {
        UINT slot = HashKey(key) % m_nBuckets;
        // Modify existing item
        for( TITEM* pItem = m_aT[slot]; pItem; pItem = pItem->pNext ) {
            if( pItem->Key == key ) {
                void* pOldData = pItem->Data;
                pItem->Data = pData;
                return pOldData;
            }
        }
    }

    Insert(key, pData);
    return NULL;
}

bool HashMap::Remove(char* key)
{
    if( m_nBuckets == 0 || GetSize() == 0 ) return false;

    UINT slot = HashKey(key) % m_nBuckets;
    TITEM** ppItem = &m_aT[slot];
    while( *ppItem ) {
        if( (*ppItem)->Key == key ) {
            TITEM* pKill = *ppItem;
            *ppItem = (*ppItem)->pNext;
            if (*ppItem)
                (*ppItem)->pPrev = pKill->pPrev;
            delete pKill;
            m_nCount--;
            return true;
        }
        ppItem = &((*ppItem)->pNext);
    }

    return false;
}

int HashMap::GetSize() const
{
#if 0//def _DEBUG
    int nCount = 0;
    int len = m_nBuckets;
    while( len-- ) {
        for( const TITEM* pItem = m_aT[len]; pItem; pItem = pItem->pNext ) nCount++;
    }
    ASSERT(m_nCount==nCount);
#endif
    return m_nCount;
}

char* HashMap::GetAt(int iIndex) const
{
    if( m_nBuckets == 0 || GetSize() == 0 ) return false;

    int pos = 0;
    int len = m_nBuckets;
    while( len-- ) {
        for( TITEM* pItem = m_aT[len]; pItem; pItem = pItem->pNext ) {
            if( pos++ == iIndex ) {
                return pItem->Key;
            }
        }
    }

    return NULL;
}

char* HashMap::operator[] (int nIndex) const
{
    return GetAt(nIndex);
}


int main()  {
    HashMap map_;
    map_.Insert("hello", (void*)"world");
    char* str = (char*)map_.Find("hello");
    printf("str is %s\n", str);
}

二, 特殊用法,改变链表的指向

对于链表,常用的用法有插入和删除算法。通常插入,删除算法的套路是声明两个指针变量,分别指向目标节点和前一节点,然后改变节点指向。这种算法思路清晰明了,编码也简单,考虑边缘的情况(例如插入,删除在头部)也不是什么难事。

有种更巧妙的算法,直接在链表节点上修改节点指向。但是这种算法不太直接,理解起来不是很容易。例如插入元素,原本元素和将要插入的元素如下表:


要插入的元素介于a,b之间,按照一般的算法,我们会用两个变量来保存这两个值。首先让d指向b的地址,然后让a指向d的地址,变为如下:

扫描二维码关注公众号,回复: 1796499 查看本文章


其实一个变量就可以搞定,因为他们是有联系的,b的地址是a变量的一个值。一个变量即可表示b的地址,也可以指示a的值,他就是二级数组。以下三行代码就可以表示这一过程:

Node** pNode = &pNodeA;        //指向nodeA地址的地址,可能为0x334

pNodeD->next = *pNodeB;         //pNode->next = 0x320

*pNode = pNodeD                       //改变了A的指向

删除节点也是同样的道理。下面给出完整算法:

Node* insert(Node* pH, Node* newData)  {
    Node** link = &pH;
    while(*link && (*link)->data < newData->data)  {
        link = &(*link)->next;
    }
    newData->next = *link;
    *link = newData;
    return pH;
}
Node* delNode(Node* pH, int data)  {
    Node** link = &pH;
    while(*link && (*link)->data != data)  {
        link = &(*link)->next;
    }
    if (*link)  {       //确保删除不存在的元素时不为空
        *link = (*link)->next;   
    }
    return pH;
}

和上一种删除的算法对比一下:

Node* delNode(Node* pH, int data)  {
    Node* pCur = pH;
    Node* pPre = pCur;
    while(pCur && pCur->data != data)  {
        pPre = pCur;
        pCur = pCur->next;
    }
    if (pH == pCur)  {      //删除的是头节点
        pH = pCur->next;
    }
    if (pCur)  {            //确保删除不存在的元素时不为空
        pPre->next = pCur->next;
    }
    return pH;
}





参看:

https://coolshell.cn/articles/8990.html

https://github.com/duilib/duilib/blob/master/DuiLib/UIlib.cpp






猜你喜欢

转载自blog.csdn.net/zxm342698145/article/details/80805475
今日推荐